MedaiCodec简介
MediaCodec是Android中提供的音视频编/解码工具。它主要是完成上层接口的封装,供给开发者使用,编解码功能实际是在native底层服务中完成的
MediaCodec工作流程
包括两个缓冲区队列
一个输入缓冲区队列,包含一组输入缓冲区(格式ByteBuffer);
一个输出缓冲区队列,包含一组输出缓冲区(格式ByteBuffer);
使用中,需要不断重复以下过程:
1.把原始数据放入输入缓冲区队列中一个空缓冲区上;
2.编/解码器从输入缓冲队列中获取缓冲区数据,进行编码处理,结果存放到输出缓冲区上一个空缓冲区上,处理完毕后,释放该输入缓冲区,它会被重新放回输入缓冲区队列,以便下次重复使用;
3.对输出缓冲区上的数据做自己需要的业务处理处理,处理完毕后,释放该输出缓冲区,它会被重新放回输出缓冲区队列,以便下次重复使用。
当对视频帧进行编/解码时,一般会用编码器创建一个输入Surface或为编码器设置一个输出Surface,在这里Surface充当着数据缓冲区的角色。使用Surface可以提高编/解码器的能,Surface直接使用native视频数据缓存,没有映射或复制它们到ByteBuffers,这种方式会更加高效。
状态机,三类状态:
1.停止态(Stopped):
1.1 未初始化状态(Uninitialized)
1.2 配置状态(Configured)
1.3 错误状态(Error)
2.执行态(Executing):
2.1 刷新状态(Flushed)
2.2 运行状态( Running)
2.3 流结束状态(End-of-Stream)
3.释放态(Released)
使用流程:
1.创建编/解码器,此时处于未初始化状态(Uninitialized);
2.调用configure(…)方法对编解码器进行配置,使编解码器转入配置状态(Configured);
3.调用start()方法,使其转入执行刷新状态(Flushed);
4.此时编解码器已经拥有其输入/输出缓存,当第一个输入缓存区被移出队列,编解码器转入运行状态( Running);
5.在运行状态( Running)中,编解码器不断对输入缓冲区中数据做编解码操作,结果存到输出缓冲区;
6.当一个带有end-of-stream标记的输入缓存入队列时,编解码器将转入流结束状态(End-of-Stream)。在这种状态下,编解码器不再接收新的输入缓存,但它仍然产生输出缓存。直到
当输入缓存中所有数据都被处理完,带有end-of-stream标记的数据帧到达输出缓存后,转入释放态(Released)。
7.当我们处理完输出数据后,在此状态下可以调用release()进行相关资源的释放。
状态转化其它相关要点:
1.在执行状态(Executing)下的任何时候,通过调用flush()方法使编解码器重新返回到刷新子状态(Flushed);
2.通过调用stop()方法使编解码器返回到未初始化状态(Uninitialized),此时这个编解码器可以再次重新配置;
3.编解码器遇到错误时会进入错误状态(Error),此时调用reset()方法使编解码器再次可用。
4.任何状态下调用reset()方法使编解码器返回到未初始化状态(Uninitialized)
MediaCodec相关API介绍:
createDecoderByType:获取解码器对象
createEncoderBytype:获取编码器对象
configure:对编解码器进行配置,使编解码器转入配置状态
start:使编码器转入执行刷新状态
stop:结束并返回到未初始化状态
release:释放实例资源
createInputSurface:创建输入缓冲Surface
setOutputSurface:设置输出缓冲Surface
getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组
queueInputBuffer:输入流入队列
dequeueInputBuffer:从输入流队列中取数据进行编码操作
getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组
dequeueOutputBuffer:从输出队列中取出编码操作之后的数据
releaseOutputBuffer:处理完成,释放ByteBuffer数据
MediaCodec的两种工作方式
1.同步方式:数据输入和输出依次进行。
要等待上一次数据输入后,才能数据输出;
等待上一次数据输出后,才能再次进行数据输入。
2.异步方式:数据输入和数据输出操作顺序相互独立。
是底层服务来判断何时输入/输出可以进行,然后进行相应回调,
开发者在回调中进行数据输入/输出处理。
同步方式
while (!Thread.interrupted()){//只要线程不中断if (!isEOF){//返回有效的buffer 索引,如果没有相关的Buffer可用,就返回-1//传入的timeoutUs为0表示立即返回//如果数据的buffer可用,将无限期等待timeUs的单位是纳秒//从 输入流中取数据进行编解码int index =mMediaCodec.dequeueInputBuffer(10000);if (index >= 0){ByteBuffer byteBuffer=inputBuffers[index];Log.d("lpf","bytebuffer is "+byteBuffer);int sampleSize=mMediaExtractor.readSampleData(byteBuffer,0);Log.d("lpf","sampleSize is "+sampleSize);if (sampleSize < 0){Log.d("lpf","inputBuffer is BUFFER_FLAG_END_OF_STREAMING");//输入流入队列mMediaCodec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);isEOF=true;}else{mMediaCodec.queueInputBuffer(index,0,sampleSize,mMediaExtractor.getSampleTime(),0);mMediaExtractor.advance(); //下一帧数据}}}//从输出流中取出编解码后的数据int outIndex=mMediaCodec.dequeueOutputBuffer(info,100000);switch (outIndex){case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED://当buffer变化时,必须重新指向新的bufferoutputBuffers=mMediaCodec.getOutputBuffers();break;case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED://当Buffer的封装格式发生变化的时候,需重新指向新的buffer格式Log.d("lpf","output buffer changed");break;case MediaCodec.INFO_TRY_AGAIN_LATER://dequeueOutputBuffer 超时的时候会到这个caseLog.d("lpf","dequeueOutputBuffer timeout");break;default:ByteBuffer buffer=outputBuffers[outIndex];//由于配置的时候 将Surface 传进去了 所以解码的时候 将数据直接交给了Surface进行显示了//使用简单的时钟的方式保持视频的fps(每秒显示的帧数),不然视频会播放的比较快Log.d("lpf","解码之后的 buffer数据="+buffer);while (info.presentationTimeUs/1000>System.currentTimeMillis()-startMs){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}//处理完成 释放buffermMediaCodec.releaseOutputBuffer(outIndex,true);break;}if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){Log.d("lpf","outputBuffer BUFFER_FLAG_END_OF_STREAM");break;}}
异步方式
public class VideoDecodeThread extends Thread {
Handler handler ;
HandlerThread mHandlerThread;
mHandlerThread = new HandlerThread("video");
mHandlerThread.start();
handler = new Handler(getLooper());
mMediaCodec.setCallback(mCallback,handler); //在子线程中执行回调
mCallback.setMediaExtractor(mMediaExtractor);
//调用Start 如果没有异常信息,表示成功构建组件
mMediaCodec.start();
public class MyMediaCallback extends MediaCodec.Callback {MediaExtractor mMediaExtractor ;public void setMediaExtractor(MediaExtractor mediaExtractor) {Log.e("TAG","setMediaExtractor");mMediaExtractor = mediaExtractor;}@Overridepublic void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {Log.e("TAG","onInputBufferAvailable");ByteBuffer inputBuffer = codec.getInputBuffer(index);int sampleSize=mMediaExtractor.readSampleData(inputBuffer,0);if (sampleSize > 0){codec.queueInputBuffer(index,0,sampleSize,mMediaExtractor.getSampleTime(),0);mMediaExtractor.advance();}else {codec.queueInputBuffer(index,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);}}@Overridepublic void onOutputBufferAvailable(@NonNull MediaCodec codec, int index,@NonNull MediaCodec.BufferInfo info) {Log.e("TAG","onOutputBufferAvailable");try {Thread.sleep(30);} catch (InterruptedException e) {e.printStackTrace();}codec.releaseOutputBuffer(index,true);}@Overridepublic void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {}@Overridepublic void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {}
}