package com.serenegiant.xiaxl.media_encoder; import android.media.MediaCodec; import android.media.MediaFormat; import com.serenegiant.xiaxl.LogUtils; import com.serenegiant.xiaxl.media_muxer.SohuMediaMuxerManager; import java.io.IOException; import java.nio.ByteBuffer; /** * 视频与音频录制的基类 */ public abstract class BaseMediaEncoderRunable implements Runnable { private static final String TAG = BaseMediaEncoderRunable.class.getSimpleName(); // 10[msec] protected static final int TIMEOUT_USEC = 10000; /** * */ public interface MediaEncoderListener { void onPrepared(BaseMediaEncoderRunable encoder); void onStopped(BaseMediaEncoderRunable encoder); } // 同步锁 protected final Object mSync = new Object(); // Flag that indicate this encoder is capturing now. // 是否正在进行录制的状态记录 protected volatile boolean mIsCapturing; // Flag that indicate the frame data will be available soon. // 可用数据帧数量 private int mRequestDrainEncoderCount; // Flag to request stop capturing // 结束录制的标识 protected volatile boolean mRequestStop; // Flag that indicate encoder received EOS(End Of Stream) // 结束录制标识 protected boolean mIsEndOfStream; //Flag the indicate the muxer is running // muxer结束标识 protected boolean mMuxerStarted; //Track Number protected int mTrackIndex; /** * ----------------------------- */ // MediaCodec instance for encoding protected MediaCodec mMediaCodec; // BufferInfo instance for dequeuing private MediaCodec.BufferInfo mBufferInfo; /** * ---------------------------- */ // MediaMuxerWarapper instance protected SohuMediaMuxerManager mSohuMediaMuxerManager; // protected final MediaEncoderListener mMediaEncoderListener; /** * 构造方法 * * @param mediaMuxerManager * @param mediaEncoderListener */ public BaseMediaEncoderRunable(final SohuMediaMuxerManager mediaMuxerManager, final MediaEncoderListener mediaEncoderListener) { LogUtils.d(TAG,"---BaseMediaEncoderRunable---"); if (mediaEncoderListener == null) { throw new NullPointerException("MediaEncoderListener is null"); } if (mediaMuxerManager == null) { throw new NullPointerException("MediaMuxerWrapper is null"); } // this.mSohuMediaMuxerManager = mediaMuxerManager; this.mMediaEncoderListener = mediaEncoderListener; // // this.mSohuMediaMuxerManager.addEncoder(BaseMediaEncoderRunable.this); // LogUtils.d(TAG,"---BaseMediaEncoderRunable synchronized (mSync) before begin---"); synchronized (mSync) { LogUtils.d(TAG,"---BaseMediaEncoderRunable synchronized (mSync) begin---"); // create BufferInfo here for effectiveness(to reduce GC) mBufferInfo = new MediaCodec.BufferInfo(); // wait for starting thread new Thread(this, getClass().getSimpleName()).start(); try { mSync.wait(); } catch (final InterruptedException e) { e.printStackTrace(); } } LogUtils.d(TAG,"---BaseMediaEncoderRunable synchronized (mSync) end---"); } /** * the method to indicate frame data is soon available or already available * * @return return true if encoder is ready to encod. */ public boolean frameAvailableSoon() { LogUtils.d(TAG, "---frameAvailableSoon---"); LogUtils.d(TAG, "---mSync before begin---"); synchronized (mSync) { LogUtils.d(TAG, "---mSync begin---"); if (!mIsCapturing || mRequestStop) { LogUtils.d(TAG, "mIsCapturing: "+mIsCapturing); LogUtils.d(TAG, "mRequestStop: "+mRequestStop); LogUtils.d(TAG, "return false"); return false; } mRequestDrainEncoderCount++; LogUtils.d(TAG, "mRequestDrainEncoderCount: "+mRequestDrainEncoderCount); mSync.notifyAll(); } LogUtils.d(TAG, "---mSync end---"); LogUtils.d(TAG, "return true"); return true; } /** * encoding loop on private thread */ @Override public void run() { LogUtils.d(TAG,"---run---"); LogUtils.d(TAG,"---run synchronized (mSync) before begin---"); // 线程开启 synchronized (mSync) { LogUtils.d(TAG,"---run synchronized (mSync) begin---"); // mRequestStop = false; mRequestDrainEncoderCount = 0; // mSync.notify(); } LogUtils.d(TAG,"---run synchronized (mSync) end---"); // 线程开启 final boolean isRunning = true; boolean localRequestStop; boolean localRequestDrainEncoderFlag; while (isRunning) { // LogUtils.d(TAG,"---run2 synchronized (mSync) before begin---"); synchronized (mSync) { LogUtils.d(TAG,"---run2 synchronized (mSync) begin---"); localRequestStop = mRequestStop; localRequestDrainEncoderFlag = (mRequestDrainEncoderCount > 0); if (localRequestDrainEncoderFlag) { mRequestDrainEncoderCount--; } } LogUtils.d(TAG,"---run2 synchronized (mSync) end---"); // 停止编码时,调用 if (localRequestStop) { drainEncoder(); // request stop recording signalEndOfInputStream(); // process output data again for EOS signale drainEncoder(); // release all related objects release(); break; } // 需要编码 if (localRequestDrainEncoderFlag) { drainEncoder(); } else { // ------线程进入等待状态--------- LogUtils.d(TAG,"---run3 synchronized (mSync) before begin---"); synchronized (mSync) { LogUtils.d(TAG,"---run3 synchronized (mSync) begin---"); try { mSync.wait(); } catch (final InterruptedException e) { e.printStackTrace(); break; } } LogUtils.d(TAG,"---run3 synchronized (mSync) end---"); } } // end of while synchronized (mSync) { mRequestStop = true; mIsCapturing = false; } } /** * 目前在主线程被调用 * * @throws IOException */ public abstract void prepare() throws IOException; /** * 目前主线程调用 */ public void startRecording() { LogUtils.d(TAG,"---startRecording synchronized (mSync) before begin---"); synchronized (mSync) { LogUtils.d(TAG,"---startRecording synchronized (mSync) begin---"); // 正在录制标识 mIsCapturing = true; // 停止标识 置false mRequestStop = false; // mSync.notifyAll(); } LogUtils.d(TAG,"---startRecording synchronized (mSync) end---"); } /** * 停止录制(目前在主线程调用) */ public void stopRecording() { LogUtils.d(TAG,"---stopRecording synchronized (mSync) before begin---"); synchronized (mSync) { LogUtils.d(TAG,"---stopRecording synchronized (mSync) begin---"); if (!mIsCapturing || mRequestStop) { return; } mRequestStop = true; mSync.notifyAll(); } LogUtils.d(TAG,"---stopRecording synchronized (mSync) end---"); } /** * Release all releated objects */ public void release() { // 回调停止 try { mMediaEncoderListener.onStopped(BaseMediaEncoderRunable.this); } catch (final Exception e) { e.printStackTrace(); } // 设置标识 停止 mIsCapturing = false; // ------释放mediacodec-------- if (mMediaCodec != null) { try { mMediaCodec.stop(); mMediaCodec.release(); mMediaCodec = null; } catch (final Exception e) { e.printStackTrace(); } } // ----------释放muxer----------- if (mMuxerStarted) { if (mSohuMediaMuxerManager != null) { try { mSohuMediaMuxerManager.stop(); } catch (final Exception e) { e.printStackTrace(); } } } // mBufferInfo置空 mBufferInfo = null; } /** * 停止录制 */ public void signalEndOfInputStream() { encode(null, 0, getPTSUs()); } /** * Method to set byte array to the MediaCodec encoder * * @param buffer * @param length length of byte array, zero means EOS. * @param presentationTimeUs */ protected void encode(final ByteBuffer buffer, final int length, final long presentationTimeUs) { // if (!mIsCapturing) { return; } // final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers(); // while (mIsCapturing) { final int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_USEC); if (inputBufferIndex >= 0) { final ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); // if (buffer != null) { inputBuffer.put(buffer); } if (length <= 0) { mIsEndOfStream = true; // mMediaCodec.queueInputBuffer( // inputBufferIndex, 0, 0, // presentationTimeUs, // MediaCodec.BUFFER_FLAG_END_OF_STREAM); break; } else { mMediaCodec.queueInputBuffer( // inputBufferIndex, 0, length, presentationTimeUs, 0); } break; } else if (inputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { } } } /** * mEncoder从缓冲区取数据,然后交给mMuxer编码 */ protected void drainEncoder() { if (mMediaCodec == null) { return; } // int count = 0; if (mSohuMediaMuxerManager == null) { return; } //拿到输出缓冲区,用于取到编码后的数据 ByteBuffer[] encoderOutputBuffers = mMediaCodec.getOutputBuffers(); LOOP: while (mIsCapturing) { //拿到输出缓冲区的索引 int encoderStatus = mMediaCodec.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { // no output available yet if (!mIsEndOfStream) { if (++count > 5) { // out of while break LOOP; } } } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { // this shoud not come when encoding //拿到输出缓冲区,用于取到编码后的数据 encoderOutputBuffers = mMediaCodec.getOutputBuffers(); } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // should happen before receiving buffers, and should only happen once if (mMuxerStarted) { throw new RuntimeException("format changed twice"); } // get output format from codec and pass them to muxer final MediaFormat format = mMediaCodec.getOutputFormat(); // mTrackIndex = mSohuMediaMuxerManager.addTrack(format); // mMuxerStarted = true; // if (!mSohuMediaMuxerManager.start()) { // we should wait until muxer is ready synchronized (mSohuMediaMuxerManager) { while (!mSohuMediaMuxerManager.isStarted()) try { mSohuMediaMuxerManager.wait(100); } catch (final InterruptedException e) { break LOOP; } } } } else if (encoderStatus < 0) { // unexpected status } else { //获取解码后的数据 final ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; if (encodedData == null) { // this never should come...may be a MediaCodec internal error throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); } // if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { mBufferInfo.size = 0; } // if (mBufferInfo.size != 0) { // encoded data is ready, clear waiting counter count = 0; if (!mMuxerStarted) { // muxer is not ready...this will prrograming failure. throw new RuntimeException("drain:muxer hasn't started"); } // write encoded data to muxer(need to adjust presentationTimeUs. mBufferInfo.presentationTimeUs = getPTSUs(); // 编码 mSohuMediaMuxerManager.writeSampleData(mTrackIndex, encodedData, mBufferInfo); prevOutputPTSUs = mBufferInfo.presentationTimeUs; } // return buffer to encoder mMediaCodec.releaseOutputBuffer(encoderStatus, false); // if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { // when EOS come. mIsCapturing = false; break; // out of while } } } } /** * previous presentationTimeUs for writing */ private long prevOutputPTSUs = 0; /** * get next encoding presentationTimeUs * * @return */ protected long getPTSUs() { long result = System.nanoTime() / 1000L; if (result < prevOutputPTSUs) result = (prevOutputPTSUs - result) + result; return result; } }