summaryrefslogtreecommitdiff
path: root/android/sdl_android/src/main/java/com/smartdevicelink/encoder
diff options
context:
space:
mode:
authorBilal Alsharifi <bilal.alsharifi@gmail.com>2019-03-04 11:54:25 -0500
committerBilal Alsharifi <bilal.alsharifi@gmail.com>2019-03-04 11:54:25 -0500
commit6aa38653411257300a77a885064dc9ddda4cd35f (patch)
tree72ea420e134ca6ef5d0b937a8656ca657055c6b4 /android/sdl_android/src/main/java/com/smartdevicelink/encoder
parente59b00f4443f7d3e90c86bae562cde3decd95127 (diff)
downloadsdl_android-6aa38653411257300a77a885064dc9ddda4cd35f.tar.gz
Move sdl_android to a subfolder
Diffstat (limited to 'android/sdl_android/src/main/java/com/smartdevicelink/encoder')
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/encoder/EncoderUtils.java107
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java241
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java403
3 files changed, 751 insertions, 0 deletions
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/encoder/EncoderUtils.java b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/EncoderUtils.java
new file mode 100644
index 000000000..362564da8
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/EncoderUtils.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2017, Xevo Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.encoder;
+
+import android.annotation.TargetApi;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public final class EncoderUtils {
+ private final static String TAG = "EncoderUtils";
+
+ /**
+ * Extracts codec-specific data from MediaFormat instance
+ *
+ * Currently, only AVC is supported.
+ *
+ * @param format MediaFormat instance retrieved from MediaCodec
+ * @return byte array containing codec-specific data, or null if an error occurred
+ */
+ public static byte[] getCodecSpecificData(MediaFormat format) {
+ if (format == null) {
+ return null;
+ }
+
+ String name = format.getString(MediaFormat.KEY_MIME);
+ if (name == null) {
+ return null;
+ }
+
+ // same as MediaFormat.MIMETYPE_VIDEO_AVC but it requires API level 21
+ if (name.equals("video/avc")) {
+ return getAVCCodecSpecificData(format);
+ } else {
+ Log.w(TAG, "Retrieving codec-specific data for " + name + " is not supported");
+ return null;
+ }
+ }
+
+ /**
+ * Extracts H.264 codec-specific data (SPS and PPS) from MediaFormat instance
+ *
+ * The codec-specific data is in byte-stream format; 4-byte start codes (0x00 0x00 0x00 0x01)
+ * are added in front of SPS and PPS NAL units.
+ *
+ * @param format MediaFormat instance retrieved from MediaCodec
+ * @return byte array containing codec-specific data, or null if an error occurred
+ */
+ private static byte[] getAVCCodecSpecificData(MediaFormat format) {
+ // For H.264, "csd-0" contains SPS and "csd-1" contains PPS. Refer to the documentation
+ // of MediaCodec.
+ if (!(format.containsKey("csd-0") && format.containsKey("csd-1"))) {
+ Log.w(TAG, "H264 codec specific data not found");
+ return null;
+ }
+
+ ByteBuffer sps = format.getByteBuffer("csd-0");
+ int spsLen = sps.remaining();
+ ByteBuffer pps = format.getByteBuffer("csd-1");
+ int ppsLen = pps.remaining();
+
+ byte[] output = new byte[spsLen + ppsLen];
+ try {
+ sps.get(output, 0, spsLen);
+ pps.get(output, spsLen, ppsLen);
+ } catch (Exception e) {
+ // should not happen
+ Log.w(TAG, "Error while copying H264 codec specific data: " + e);
+ return null;
+ }
+
+ return output;
+ }
+
+ private EncoderUtils() {}
+}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java
new file mode 100644
index 000000000..6c9198cb4
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/SdlEncoder.java
@@ -0,0 +1,241 @@
+package com.smartdevicelink.encoder;
+
+import java.io.IOException;
+import java.io.PipedOutputStream;
+import java.nio.ByteBuffer;
+
+import android.annotation.TargetApi;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.util.Log;
+import android.view.Surface;
+
+import com.smartdevicelink.proxy.interfaces.IVideoStreamListener;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class SdlEncoder {
+
+ private static final String TAG = "SdlEncoder";
+
+ // parameters for the encoder
+ private static final String _MIME_TYPE = "video/avc"; // H.264/AVC video
+ private static final long KEEPALIVE_INTERVAL_MSEC = 100;
+
+ // private static final String MIME_TYPE = "video/mp4v-es"; //MPEG4 video
+ private int frameRate = 30;
+ private int frameInterval = 5;
+ private int frameWidth = 800;
+ private int frameHeight = 480;
+ private int bitrate = 6000000;
+
+ // encoder state
+ private MediaCodec mEncoder;
+ private PipedOutputStream mOutputStream;
+ private IVideoStreamListener mOutputListener;
+ private long mLastEmittedFrameTimestamp;
+
+ // allocate one of these up front so we don't need to do it every time
+ private MediaCodec.BufferInfo mBufferInfo;
+
+ // Codec-specific data (SPS and PPS)
+ private byte[] mH264CodecSpecificData = null;
+
+ public SdlEncoder () {
+ }
+ public void setFrameRate(int iVal){
+ frameRate = iVal;
+ }
+ public void setFrameInterval(int iVal){
+ frameInterval = iVal;
+ }
+ public void setFrameWidth(int iVal){
+ frameWidth = iVal;
+ }
+ public void setFrameHeight(int iVal){
+ frameHeight = iVal;
+ }
+ public void setBitrate(int iVal){
+ bitrate = iVal;
+ }
+ public void setOutputStream(PipedOutputStream mStream){
+ mOutputStream = mStream;
+ }
+ public void setOutputListener(IVideoStreamListener listener) {
+ mOutputListener = listener;
+ }
+ public Surface prepareEncoder () {
+
+ mBufferInfo = new MediaCodec.BufferInfo();
+
+ MediaFormat format = MediaFormat.createVideoFormat(_MIME_TYPE, frameWidth,
+ frameHeight);
+
+ // Set some properties. Failing to specify some of these can cause the
+ // MediaCodec
+ // configure() call to throw an unhelpful exception.
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, frameInterval);
+
+ // Create a MediaCodec encoder, and configure it with our format. Get a
+ // Surface
+ // we can use for input and wrap it with a class that handles the EGL
+ // work.
+ //
+ // If you want to have two EGL contexts -- one for display, one for
+ // recording --
+ // you will likely want to defer instantiation of CodecInputSurface
+ // until after the
+ // "display" EGL context is created, then modify the eglCreateContext
+ // call to
+ // take eglGetCurrentContext() as the share_context argument.
+ try {
+ mEncoder = MediaCodec.createEncoderByType(_MIME_TYPE);
+ } catch (Exception e) {e.printStackTrace();}
+
+ if(mEncoder != null) {
+ mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ return mEncoder.createInputSurface();
+ } else {
+ return null;
+ }
+ }
+
+ public void startEncoder () {
+ if(mEncoder != null) {
+ mEncoder.start();
+ }
+ }
+
+ /**
+ * Releases encoder resources.
+ */
+ public void releaseEncoder() {
+ if (mEncoder != null) {
+ mEncoder.stop();
+ mEncoder.release();
+ mEncoder = null;
+ }
+ if (mOutputStream != null) {
+ try {
+ mOutputStream.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ mOutputStream = null;
+ }
+ mH264CodecSpecificData = null;
+ }
+
+ /**
+ * Extracts all pending data from the encoder
+ * <p>
+ * If endOfStream is not set, this returns when there is no more data to
+ * drain. If it is set, we send EOS to the encoder, and then iterate until
+ * we see EOS on the output. Calling this with endOfStream set should be
+ * done once, right before stopping the muxer.
+ */
+ public void drainEncoder(boolean endOfStream) {
+ final int TIMEOUT_USEC = 10000;
+
+ if(mEncoder == null || (mOutputStream == null && mOutputListener == null)) {
+ return;
+ }
+ if (endOfStream) {
+ mEncoder.signalEndOfInputStream();
+ }
+
+ ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
+ while (true) {
+ int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo,
+ TIMEOUT_USEC);
+ if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (!endOfStream) {
+ trySendVideoKeepalive();
+ break; // out of while
+ }
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ // not expected for an encoder
+ encoderOutputBuffers = mEncoder.getOutputBuffers();
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ if (mH264CodecSpecificData == null) {
+ MediaFormat format = mEncoder.getOutputFormat();
+ mH264CodecSpecificData = EncoderUtils.getCodecSpecificData(format);
+ } else {
+ Log.w(TAG, "Output format change notified more than once, ignoring.");
+ }
+ } else if (encoderStatus < 0) {
+ } else {
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ // If we already retrieve codec specific data via OUTPUT_FORMAT_CHANGED event,
+ // we do not need this data.
+ if (mH264CodecSpecificData != null) {
+ mBufferInfo.size = 0;
+ } else {
+ Log.i(TAG, "H264 codec specific data not retrieved yet.");
+ }
+ }
+
+ if (mBufferInfo.size != 0) {
+ ByteBuffer encoderOutputBuffer = encoderOutputBuffers[encoderStatus];
+ byte[] dataToWrite = null;
+ int dataOffset = 0;
+
+ // append SPS and PPS in front of every IDR NAL unit
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0
+ && mH264CodecSpecificData != null) {
+ dataToWrite = new byte[mH264CodecSpecificData.length + mBufferInfo.size];
+ System.arraycopy(mH264CodecSpecificData, 0,
+ dataToWrite, 0, mH264CodecSpecificData.length);
+ dataOffset = mH264CodecSpecificData.length;
+ } else {
+ dataToWrite = new byte[mBufferInfo.size];
+ }
+
+ try {
+ encoderOutputBuffer.position(mBufferInfo.offset);
+ encoderOutputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
+
+ encoderOutputBuffer.get(dataToWrite, dataOffset, mBufferInfo.size);
+
+ emitFrame(dataToWrite);
+ } catch (Exception e) {}
+ }
+
+ mEncoder.releaseOutputBuffer(encoderStatus, false);
+
+ if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ break; // out of while
+ }
+ }
+ }
+ }
+
+ private void trySendVideoKeepalive() {
+ if (mH264CodecSpecificData == null) {
+ return;
+ }
+
+ try {
+ long timeSinceLastEmitted = System.currentTimeMillis() - mLastEmittedFrameTimestamp;
+ if (timeSinceLastEmitted >= KEEPALIVE_INTERVAL_MSEC) {
+ emitFrame(mH264CodecSpecificData);
+ }
+ } catch (IOException e) {}
+ }
+
+ private void emitFrame(final byte[] dataToWrite) throws IOException {
+ if (mOutputStream != null) {
+ mOutputStream.write(dataToWrite, 0, mBufferInfo.size);
+ } else if (mOutputListener != null) {
+ mOutputListener.sendFrame(
+ dataToWrite, 0, dataToWrite.length, mBufferInfo.presentationTimeUs);
+ }
+ mLastEmittedFrameTimestamp = System.currentTimeMillis();
+ }
+} \ No newline at end of file
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java
new file mode 100644
index 000000000..6b7f2a686
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2017 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.encoder;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+
+import com.smartdevicelink.proxy.interfaces.IVideoStreamListener;
+import com.smartdevicelink.proxy.rpc.ImageResolution;
+import com.smartdevicelink.proxy.rpc.VideoStreamingFormat;
+import com.smartdevicelink.proxy.rpc.enums.VideoStreamingCodec;
+import com.smartdevicelink.streaming.video.VideoStreamingParameters;
+
+import java.nio.ByteBuffer;
+
+@TargetApi(19)
+@SuppressWarnings("NullableProblems")
+public class VirtualDisplayEncoder {
+ private static final String TAG = "VirtualDisplayEncoder";
+ private VideoStreamingParameters streamingParams = new VideoStreamingParameters();
+ private DisplayManager mDisplayManager;
+ private volatile MediaCodec mVideoEncoder = null;
+ private volatile MediaCodec.BufferInfo mVideoBufferInfo = null;
+ private volatile Surface inputSurface = null;
+ private volatile VirtualDisplay virtualDisplay = null;
+ private IVideoStreamListener mOutputListener;
+ private Boolean initPassed = false;
+ private final Object STREAMING_LOCK = new Object();
+
+ // Codec-specific data (SPS and PPS)
+ private byte[] mH264CodecSpecificData = null;
+
+ //For older (<21) OS versions
+ private Thread encoderThread;
+
+
+ /**
+ * Initialization method for VirtualDisplayEncoder object. MUST be called before start() or shutdown()
+ * Will overwrite previously set videoWeight and videoHeight
+ * @param context to create the virtual display
+ * @param outputListener the listener that the video frames will be sent through
+ * @param streamingParams parameters to create the virtual display and encoder
+ * @throws Exception if the API level is <19 or supplied parameters were null
+ */
+ public void init(Context context, IVideoStreamListener outputListener, VideoStreamingParameters streamingParams) throws Exception {
+ if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ Log.e(TAG, "API level of 19 required for VirtualDisplayEncoder");
+ throw new Exception("API level of 19 required");
+ }
+
+ if (context == null || outputListener == null || streamingParams == null || streamingParams.getResolution() == null || streamingParams.getFormat() == null) {
+ Log.e(TAG, "init parameters cannot be null for VirtualDisplayEncoder");
+ throw new Exception("init parameters cannot be null");
+ }
+
+ this.mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+
+ this.streamingParams.update(streamingParams);
+
+ mOutputListener = outputListener;
+
+ initPassed = true;
+ }
+
+ @SuppressWarnings("unused")
+ public VideoStreamingParameters getStreamingParams(){
+ return this.streamingParams;
+ }
+
+ @SuppressWarnings("unused")
+ public void setStreamingParams(int displayDensity, ImageResolution resolution, int frameRate, int bitrate, int interval, VideoStreamingFormat format) {
+ this.streamingParams = new VideoStreamingParameters(displayDensity, frameRate, bitrate, interval, resolution, format);
+ }
+
+ @SuppressWarnings("unused")
+ public void setStreamingParams(VideoStreamingParameters streamingParams) {
+ this.streamingParams = streamingParams;
+ }
+
+ /**
+ * NOTE: Must call init() without error before calling this method.
+ * Prepares the encoder and virtual display.
+ */
+ public void start() throws Exception {
+ if (!initPassed) {
+ Log.e(TAG, "VirtualDisplayEncoder was not properly initialized with the init() method.");
+ return;
+ }
+ if (streamingParams == null || streamingParams.getResolution() == null || streamingParams.getFormat() == null) {
+ return;
+ }
+
+ synchronized (STREAMING_LOCK) {
+
+ try {
+ inputSurface = prepareVideoEncoder();
+
+ // Create a virtual display that will output to our encoder.
+ virtualDisplay = mDisplayManager.createVirtualDisplay(TAG,
+ streamingParams.getResolution().getResolutionWidth(), streamingParams.getResolution().getResolutionHeight(),
+ streamingParams.getDisplayDensity(), inputSurface, DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
+
+ startEncoder();
+
+ } catch (Exception ex) {
+ Log.e(TAG, "Unable to create Virtual Display.");
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ public void shutDown() {
+ if (!initPassed) {
+ Log.e(TAG, "VirtualDisplayEncoder was not properly initialized with the init() method.");
+ return;
+ }
+ try {
+ if (encoderThread != null) {
+ encoderThread.interrupt();
+ encoderThread = null;
+ }
+
+ if (mVideoEncoder != null) {
+ mVideoEncoder.stop();
+ mVideoEncoder.release();
+ mVideoEncoder = null;
+ }
+
+ if (virtualDisplay != null) {
+ virtualDisplay.release();
+ virtualDisplay = null;
+ }
+
+ if (inputSurface != null) {
+ inputSurface.release();
+ inputSurface = null;
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "shutDown() failed");
+ }
+ }
+
+ private Surface prepareVideoEncoder() {
+
+ if (streamingParams == null || streamingParams.getResolution() == null || streamingParams.getFormat() == null) {
+ return null;
+ }
+
+ if (mVideoBufferInfo == null) {
+ mVideoBufferInfo = new MediaCodec.BufferInfo();
+ }
+
+ String videoMimeType = getMimeForFormat(streamingParams.getFormat());
+
+ MediaFormat format = MediaFormat.createVideoFormat(videoMimeType, streamingParams.getResolution().getResolutionWidth(), streamingParams.getResolution().getResolutionHeight());
+
+ // Set some required properties. The media codec may fail if these aren't defined.
+ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, streamingParams.getBitrate());
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, streamingParams.getFrameRate());
+ format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, streamingParams.getInterval()); // seconds between I-frames
+
+
+ // Create a MediaCodec encoder and configure it. Get a Surface we can use for recording into.
+ try {
+ mVideoEncoder = MediaCodec.createEncoderByType(videoMimeType);
+ mVideoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+ Surface surface = mVideoEncoder.createInputSurface(); //prepared
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ mVideoEncoder.setCallback(new MediaCodec.Callback() {
+ @Override
+ public void onInputBufferAvailable(MediaCodec codec, int index) {
+ // nothing to do here
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Override
+ public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+ try {
+ ByteBuffer encodedData = codec.getOutputBuffer(index);
+ if (encodedData != null) {
+ if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ info.size = 0;
+ }
+ if (info.size != 0) {
+ byte[] dataToWrite;// = new byte[info.size];
+ int dataOffset = 0;
+
+ // append SPS and PPS in front of every IDR NAL unit
+ if ((info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0 && mH264CodecSpecificData != null) {
+ dataToWrite = new byte[mH264CodecSpecificData.length + info.size];
+ System.arraycopy(mH264CodecSpecificData, 0, dataToWrite, 0, mH264CodecSpecificData.length);
+ dataOffset = mH264CodecSpecificData.length;
+ } else {
+ dataToWrite = new byte[info.size];
+ }
+
+ encodedData.position(info.offset);
+ encodedData.limit(info.offset + info.size);
+
+ encodedData.get(dataToWrite, dataOffset, info.size);
+ if (mOutputListener != null) {
+ mOutputListener.sendFrame(dataToWrite, 0, dataToWrite.length, info.presentationTimeUs);
+ }
+ }
+
+ codec.releaseOutputBuffer(index, false);
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+ e.printStackTrace();
+ }
+
+ @Override
+ public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+ mH264CodecSpecificData = EncoderUtils.getCodecSpecificData(format);
+ }
+ });
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ //Implied from previous if check that this has to be older than Build.VERSION_CODES.LOLLIPOP
+ encoderThread = new Thread(new EncoderCompat());
+
+ } else {
+ Log.e(TAG, "Unable to start encoder. Android OS version " + Build.VERSION.SDK_INT + "is not supported");
+ }
+
+ return surface;
+
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ return null;
+ }
+
+ private void startEncoder() {
+ if (mVideoEncoder != null) {
+ mVideoEncoder.start();
+ }
+ if (encoderThread != null) {
+ encoderThread.start();
+ }
+ }
+
+ public Display getVirtualDisplay() {
+ return virtualDisplay.getDisplay();
+ }
+
+ private String getMimeForFormat(VideoStreamingFormat format) {
+ if (format != null) {
+ VideoStreamingCodec codec = format.getCodec();
+ if (codec != null) {
+ switch (codec) { //MediaFormat constants are only available in Android 21+
+ case VP8:
+ return "video/x-vnd.on2.vp8"; //MediaFormat.MIMETYPE_VIDEO_VP8
+ case VP9:
+ return "video/x-vnd.on2.vp9"; //MediaFormat.MIMETYPE_VIDEO_VP9
+ case H264: //Fall through
+ default:
+ return "video/avc"; // MediaFormat.MIMETYPE_VIDEO_AVC
+ }
+ }
+ }
+ return null;
+ }
+
+ private class EncoderCompat implements Runnable {
+
+ @Override
+ public void run() {
+ try {
+ drainEncoder(false);
+ } catch (Exception e) {
+ Log.w(TAG, "Error attempting to drain encoder");
+ } finally {
+ mVideoEncoder.release();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ void drainEncoder(boolean endOfStream) {
+ if (mVideoEncoder == null || mOutputListener == null) {
+ return;
+ }
+
+ if (endOfStream) {
+ mVideoEncoder.signalEndOfInputStream();
+ }
+
+ ByteBuffer[] encoderOutputBuffers = mVideoEncoder.getOutputBuffers();
+ Thread currentThread = Thread.currentThread();
+ while (!currentThread.isInterrupted()) {
+ int encoderStatus = mVideoEncoder.dequeueOutputBuffer(mVideoBufferInfo, -1);
+ if(encoderStatus < 0){
+ if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+ // no output available yet
+ if (!endOfStream) {
+ break; // out of while
+ }
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ // not expected for an encoder
+ encoderOutputBuffers = mVideoEncoder.getOutputBuffers();
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ if (mH264CodecSpecificData == null) {
+ MediaFormat format = mVideoEncoder.getOutputFormat();
+ mH264CodecSpecificData = EncoderUtils.getCodecSpecificData(format);
+ } else {
+ Log.w(TAG, "Output format change notified more than once, ignoring.");
+ }
+ }
+ } else {
+ if ((mVideoBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+ // If we already retrieve codec specific data via OUTPUT_FORMAT_CHANGED event,
+ // we do not need this data.
+ if (mH264CodecSpecificData != null) {
+ mVideoBufferInfo.size = 0;
+ } else {
+ Log.i(TAG, "H264 codec specific data not retrieved yet.");
+ }
+ }
+
+ if (mVideoBufferInfo.size != 0) {
+ ByteBuffer encoderOutputBuffer = encoderOutputBuffers[encoderStatus];
+ byte[] dataToWrite;
+ int dataOffset = 0;
+
+ // append SPS and PPS in front of every IDR NAL unit
+ if ((mVideoBufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0 && mH264CodecSpecificData != null) {
+ dataToWrite = new byte[mH264CodecSpecificData.length + mVideoBufferInfo.size];
+ System.arraycopy(mH264CodecSpecificData, 0, dataToWrite, 0, mH264CodecSpecificData.length);
+ dataOffset = mH264CodecSpecificData.length;
+ } else {
+ dataToWrite = new byte[mVideoBufferInfo.size];
+ }
+
+ try {
+ encoderOutputBuffer.position(mVideoBufferInfo.offset);
+ encoderOutputBuffer.limit(mVideoBufferInfo.offset + mVideoBufferInfo.size);
+
+ encoderOutputBuffer.get(dataToWrite, dataOffset, mVideoBufferInfo.size);
+
+ if (mOutputListener != null) {
+ mOutputListener.sendFrame(dataToWrite, 0, dataToWrite.length, mVideoBufferInfo.presentationTimeUs);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ mVideoEncoder.releaseOutputBuffer(encoderStatus, false);
+
+ if ((mVideoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+ break; // out of while
+ }
+ }
+ }
+ }
+ }
+}