// started from https://github.com/google/grafika/blob/f3c8c3dee60153f471312e21acac8b3a3cddd7dc/src/com/android/grafika/VideoEncoderCore.java package io.cine.android.streaming; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaFormat; import android.os.Build; import android.os.Bundle; import android.util.Log; import java.nio.ByteBuffer; import static io.cine.android.streaming.Utilities.supportsAdaptiveStreaming; /** * @hide */ public abstract class AndroidEncoder { private final static String TAG = "AndroidEncoder"; private final static boolean VERBOSE = false; protected Muxer mMuxer; protected MediaCodec mEncoder; protected MediaCodec.BufferInfo mBufferInfo; protected int mTrackIndex; protected volatile boolean mForceEos = false; public AndroidEncoder(Muxer muxer) { mMuxer = muxer; } /** * This method should be called before the last input packet is queued * Some devices don't honor MediaCodec#signalEndOfInputStream * e.g: Google Glass */ public void signalEndOfStream() { mForceEos = true; } public void release() { if (mMuxer != null) mMuxer.onEncoderReleased(mTrackIndex); if (mEncoder != null) { mEncoder.stop(); mEncoder.release(); mEncoder = null; if (VERBOSE) Log.i(TAG, "Released encoder"); } } @TargetApi(Build.VERSION_CODES.KITKAT) public void adjustBitrate(int targetBitrate) { if (supportsAdaptiveStreaming() && mEncoder != null) { Bundle bitrate = new Bundle(); bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, targetBitrate); mEncoder.setParameters(bitrate); } else if (!supportsAdaptiveStreaming()) { Log.w(TAG, "Ignoring adjustVideoBitrate call. This functionality is only available on Android API 19+"); } } public void drainEncoder(boolean endOfStream) { if (endOfStream && VERBOSE) { if (isSurfaceInputEncoder()) { Log.i(TAG, "final video drain"); } else { Log.i(TAG, "final audio drain"); } } synchronized (mMuxer) { final int TIMEOUT_USEC = 1000; if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ") track: " + mTrackIndex); if (endOfStream) { if (VERBOSE) Log.d(TAG, "sending EOS to encoder for track " + mTrackIndex); // When all target devices honor MediaCodec#signalEndOfInputStream, return to this method // if(isSurfaceInputEncoder()){ // if (VERBOSE) Log.i(TAG, "signalEndOfInputStream for track " + mTrackIndex); // mEncoder.signalEndOfInputStream(); // // Note: This method isn't honored on certain devices including Google Glass // } } 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) { break; // out of while } else { if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS"); } } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { // not expected for an encoder encoderOutputBuffers = mEncoder.getOutputBuffers(); } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // should happen before receiving buffers, and should only happen once MediaFormat newFormat = mEncoder.getOutputFormat(); if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat); // now that we have the Magic Goodies, start the muxer mTrackIndex = mMuxer.addTrack(newFormat); Log.d(TAG, "ADDED TRACK INDEX: " + mTrackIndex + " " + this.getClass().getName()); // Muxer is responsible for starting/stopping itself // based on knowledge of expected # tracks } else if (encoderStatus < 0) { Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); // let's ignore it } else { ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; if (encodedData == null) { throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); } if (mBufferInfo.size >= 0) { // Allow zero length buffer for purpose of sending 0 size video EOS Flag // adjust the ByteBuffer values to match BufferInfo (not needed?) encodedData.position(mBufferInfo.offset); encodedData.limit(mBufferInfo.offset + mBufferInfo.size); if (mForceEos) { mBufferInfo.flags = mBufferInfo.flags | MediaCodec.BUFFER_FLAG_END_OF_STREAM; Log.i(TAG, "Forcing EOS"); } // It is the muxer's responsibility to release encodedData mMuxer.writeSampleData(mEncoder, mTrackIndex, encoderStatus, encodedData, mBufferInfo); if (VERBOSE) { Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer, \t ts=" + mBufferInfo.presentationTimeUs + "track " + mTrackIndex); } } if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { if (!endOfStream) { Log.w(TAG, "reached end of stream unexpectedly"); } else { if (VERBOSE) Log.d(TAG, "end of stream reached for track " + mTrackIndex); } break; // out of while } } } if (endOfStream && VERBOSE) { if (isSurfaceInputEncoder()) { Log.i(TAG, "final video drain complete"); } else { Log.i(TAG, "final audio drain complete"); } } } } protected abstract boolean isSurfaceInputEncoder(); }