争分夺秒的优化--->为了提供极致的视频播放体验 熟悉MediaCodec解码原理的肯定知道, MediaCodec提供了同步模式和异步模式两种模式:
Android L以及Andrioid L以上的版本提供了异步模式, 通过在codec--->configure中设置callback来将数据处理放在异步线程中完成,然后在回调中处理解码出的数据,给开发者设置了回调,你可以在回调中根据自己的需求来处理原始数据.
MediaCodec codec = MediaCodec.createByCodecName(name); MediaFormat mOutputFormat; // member variable codec.setCallback(new MediaCodec.Callback() { @Override void onInputBufferAvailable(MediaCodec mc, int inputBufferId) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId); // fill inputBuffer with valid data … codec.queueInputBuffer(inputBufferId, …); } @Override void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A // bufferFormat is equivalent to mOutputFormat // outputBuffer is ready to be processed or rendered. … codec.releaseOutputBuffer(outputBufferId, …); } @Override void onOutputFormatChanged(MediaCodec mc, MediaFormat format) { // Subsequent data will conform to new format. // Can ignore if using getOutputFormat(outputBufferId) mOutputFormat = format; // option B } @Override void onError(…) { … } }); codec.configure(format, …); mOutputFormat = codec.getOutputFormat(); // option B codec.start(); // wait for processing to complete codec.stop(); codec.release();
codec.start()执行的核心处理都放在异步线程中完成,所以又称为异步模式
同步模式当然就是不另起线程去解码数据, 在执行完codec.start()之后,通过codec实例获取InputBuffer和OuputBuffer来获取数据.
MediaCodec codec = MediaCodec.createByCodecName(name); codec.configure(format, …); MediaFormat outputFormat = codec.getOutputFormat(); // option B codec.start(); for (;;) { int inputBufferId = codec.dequeueInputBuffer(timeoutUs); if (inputBufferId >= 0) { ByteBuffer inputBuffer = codec.getInputBuffer(…); // fill inputBuffer with valid data … codec.queueInputBuffer(inputBufferId, …); } int outputBufferId = codec.dequeueOutputBuffer(…); if (outputBufferId >= 0) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A // bufferFormat is identical to outputFormat // outputBuffer is ready to be processed or rendered. … codec.releaseOutputBuffer(outputBufferId, …); } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // Subsequent data will conform to new format. // Can ignore if using getOutputFormat(outputBufferId) outputFormat = codec.getOutputFormat(); // option B } } codec.stop(); codec.release();
我们为了提升ExoPlayer的渲染性能,核心的思路是两个:
MediaCodecRenderer.java中初始化MediaCodec函数中, 会根据设置的mode来建立同步或者异步模式的codec
codec = MediaCodec.createByCodecName(codecName); if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD && Util.SDK_INT >= 23) { codecAdapter = new AsynchronousMediaCodecAdapter(codec, getTrackType()); } else if (mediaCodecOperationMode == OPERATION_MODE_ASYNCHRONOUS_DEDICATED_THREAD_ASYNCHRONOUS_QUEUEING && Util.SDK_INT >= 23) { codecAdapter = new AsynchronousMediaCodecAdapter( codec, /* enableAsynchronousQueueing= */ true, getTrackType()); } else { codecAdapter = new SynchronousMediaCodecAdapter(codec); }
AsynchronousMediaCodecAdapter.java继承MediaCodec.Callback,实现MediaCodec解码的异步模式,同时定义一个专用的HandlerThread,用来处理解码消息.同时在AsynchronousMediaCodecAdapter.java中定义一个buffer队列来处理codec解码出来的原始数据.
@Override public void onInputBufferAvailable(MediaCodec codec, int index) { synchronized (lock) { mediaCodecAsyncCallback.onInputBufferAvailable(codec, index); } }
@Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { synchronized (lock) { mediaCodecAsyncCallback.onOutputBufferAvailable(codec, index, info); } }
在回调到onInputBufferAvailable函数,将空闲的input buffer存在队列中,然后不断dequeueInputBufferIndex获取对应的input buffer位置,向里面写入原始数据, 开始解码. 解码完成,在onOutputBufferAvailable回调中获取解码完的原始数据,读取完解码之后的原始数据之后,Codec需要将output buffer释放.腾出空间,方便后续的解码数据写入.
从改善前和改善后的流程分析来看, 改善后的流程节省了等待音频解析的时间,可以在异步线程中解出视频流数据立即渲染出来, 提升了渲染性能.