安卓执法仪编码器之同步/异步模式
Posted John0551
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓执法仪编码器之同步/异步模式相关的知识,希望对你有一定的参考价值。
在安卓执法仪开发过程中,我们发现有些设备的编码器性能较低,后来发现MediaCodec库支持异步模式了, 切换到异步模式果然会提高一些编码帧率.
同步模式
MediaCodec 同步模式调用图如下:
创建,配置,启动AAC编码器:
MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, spec.getSample(), spec.getChannel());
format.setInteger(MediaFormat.KEY_BIT_RATE, 16000);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 1920); // 1920 = 16k的采样,60毫秒长度.
Timber.i("audio codec:mediaFormat = " + videoFormat);
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();
获取输入队列并塞入PCM数据:
int inputBufferIndex = mCodec.dequeueInputBuffer(100);
if (inputBufferIndex >= 0)
if (pcmBuf.capacity() != length)
pcmBuf = ByteBuffer.allocate(length * 2).order(ByteOrder.LITTLE_ENDIAN);
pcmBuf.asShortBuffer().put(pcm, 0, length);
pcmBuf.clear();
ByteBuffer inputBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
inputBuffer = mCodec.getInputBuffer(inputBufferIndex);
else
final ByteBuffer[] inputBuffers = mCodec.getInputBuffers();
inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(pcmBuf);
mCodec.queueInputBuffer(inputBufferIndex, 0, length * 2, timestamp / 1000, 0);// , needKeyFrm
inputBufferIndex小于0表示没有可用的输出帧(可能编码缓冲已满).
获取输出的AAC音频帧:
int aacOutputBufferIndex = aacCodec.dequeueOutputBuffer(mBufferInfo, 0);
long timeSpend2 = SystemClock.elapsedRealtime() - begin;
if (timeSpend2 > 200)
Timber.w("aac dequeueOutputBuffer spend too much time....%d ms", timeSpend2);
if (aacOutputBufferIndex >= 0)
try
ByteBuffer outputBuffer;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
outputBuffer = aacCodec.getOutputBuffer(aacOutputBufferIndex);
else
outputBuffer = audioOutputBuffers[aacOutputBufferIndex];//outputBuffer保存的就是H264数据了
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) // sps
mBufferInfo.size = 0;
if (mBufferInfo.size != 0)
outputBuffer.position(mBufferInfo.offset);
outputBuffer.limit(mBufferInfo.offset + mBufferInfo.size);
onAudioEncoded(outputBuffer, mBufferInfo);
finally
aacCodec.releaseOutputBuffer(aacOutputBufferIndex, false);//释放资源
else if (aacOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
MediaFormat outputFormat = aacCodec.getOutputFormat();
ByteBuffer sps = outputFormat.getByteBuffer("csd-0");
int sps_size = sps.remaining();
extra2 = new byte[sps_size];
sps.get(extra2);
if (recordReady)
startMediaMuxer();
aacOutputBufferIndex小于0表示还没编出一帧数据;或者没有待编码的输入帧了.
同步模式的输入输出相互影响,其中一方过慢,都会导致另一方无法取到有效Buffer,CPU无意义地消耗在轮训上面.效率较低
异步模式
工作模式如下:
异步模式下,我们需要先把PCM数据先存到缓冲里面,在编码器可用时再存入编码器.
相比同步模式,异步模式要设置一个回调,该调用发生在create之后,configure之前:
mediaCodec.setCallback(mAudioCallback, mAudioHandler);
其中mAudioCallback
是回调对象,mAudioHandler
是回调函数执行的线程的Handler
.
回调函数如下:
private MediaCodec.Callback mAudioCallback = new MediaCodec.Callback()
@Override
public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int id)
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(id);
inputBuffer.clear();
Pair pair = mInputAudioQueue.poll();
Timber.d("AudioAvailable.queue size=%d",mInputAudioQueue.size());
long presentationTimeUs = 0;
if (pair != null)
ByteBuffer dataSources = (ByteBuffer) pair.second;
presentationTimeUs = (long) pair.first;
inputBuffer.put(dataSources);
mediaCodec.queueInputBuffer(id, 0, inputBuffer.position(), presentationTimeUs / 1000, (mExit && pair == null)? MediaCodec.BUFFER_FLAG_END_OF_STREAM:0);
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int id, @NonNull MediaCodec.BufferInfo bufferInfo)
try
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0)
// 等输出回调中遇到该标志位时,表示缓冲帧已消耗完毕.
int flag = codecFlag.decrementAndGet();
Timber.i("audio codec EOS,flag goto:%d.", flag);
if (flag == 0)
// 当音频,视频的缓冲帧都消耗完毕时,停止录像.
Timber.i("收到音频BUFFER_FLAG_END_OF_STREAM标志,即将停止录像.");
PublishSubject x = recordSubject;
if (x != null) x.onComplete();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
quitSafely();
else
quit();
return;
// Timber.i("MediaCodec onOutputBufferAvailable:"+bufferInfo.size+","+id);
//走到这里的时候,说明数据已经编码成AAC格式了
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(id);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) // sps
bufferInfo.size = 0;
if (bufferInfo.size != 0)
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
onAudioEncoded(outputBuffer, bufferInfo);
finally
mediaCodec.releaseOutputBuffer(id, false);//释放资源
// Timber.i("MediaCodec releaseOutputBuffer:"+id);
@Override
public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e)
Timber.e(e);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
quitSafely();
else quit();
@Override
public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat outputFormat)
int flag = codecFlag.incrementAndGet();
Timber.i("Audio MediaCodec onOutputFormatChanged.flag:%d,format:%s",flag, outputFormat);
//特别注意此处的调用
ByteBuffer sps = outputFormat.getByteBuffer("csd-0");
int sps_size = sps.remaining();
extra2 = new byte[sps_size];
sps.get(extra2);
if (recordReady)
startMediaMuxer();
;
其中,onInputBufferAvailable
表示编码器空闲了,输入端可输入数据了.因此我们从缓冲区取出一个音频PCM帧存入编码器;此外onOutputBufferAvailable
表示编码器编码出一个音频帧了.我们从编码器里面取出帧数据进行muxing
.onError
表示编码器异常,一般不会出现,出错后停止编码器即可.onOutputFormatChanged
表示编码器内部状态更改了,在这里我们取出MetaData
.
经过测试异步模式的优势基本符合预期,其CPU消耗低,且效率要高于同步模式.
异步模式录像停止时,我们先把缓存在编码器内部的帧都消耗掉后,再优雅停止,否则缓存在编码器中的媒体帧被丢失,录像会不完整.停止录像正确操作应该这样:
- 设置EOF标志位,不再输入.
- 等输出回调中遇到该标志位时,表示缓冲帧已消耗完毕.停止编码器
- 等音频,视频编码器都停止时,停止录像.
具体见上面onOutputBufferAvailable
里的注释.
我们得到编码器输出帧后,下面要进行的就是写入录像文件内.这个过程被称为muxing
.
参考资料
以上是关于安卓执法仪编码器之同步/异步模式的主要内容,如果未能解决你的问题,请参考以下文章