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变化时,必须重新指向新的buffer
outputBuffers=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 超时的时候会到这个case
Log.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();
}
}
//处理完成 释放buffer
mMediaCodec.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;
}
@Override
public 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);
}
}
@Override
public 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);
}
@Override
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
}
}