使用MediaCodec进行硬解码通常有两种模式:同步and异步。
异步是由硬解码器通过回调函数送取数据,由于我没有过这种方式,所以不清楚异步下是否会出现解码器存帧现象。
同步的话是由我选择什么时候送取数据,总体流程就是
1>送数据:
Public int InputData(byte[] data,int Len,int timeStamp)
{
if(data == null || len <= 0)
{
return -1;
}
int inputIndex = -1;
try{
//获取解码器输入buffer的index
//此处形参是MediaCodec输入buffer的等待时间,这里为0
inputIndex = MediaCodec.dequeueInputBuffer(0);
}
catch(Exception e)
{
e.printStackTrace();
return -1;
}
if(inputIndex <0)
{
return -1;
}
//送入解码数据
ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
inputBuffer.clear();
inputBuffer.put(data,0,len);
try{
MediaCodec.queueInnputBuffer(inputIndex,0,len,timeStamp,0);
}
catch(Exception e)
{
e.printStackTrace();
return -1;
}
return 0;
}
2>取数据
public int OutputData()
{
int outputIndex = -1;
BufferInfo bufferinfo = new BufferInfo();
try{
outputIndex = MediaCodec.dequeueOutputBuffer(bufferinfo,0);
}
catch(Exception e)
{
e.printStackTrace();
return -1;
}
if(outputIndex >= 0)
{
//成功获取解码器输出index
bufferinfo.presentationTimeUs;//时间戳
bufferinfo.size;//大小
}
else
{
//若outputIndex为-1,则硬解码器暂时没有解码后数据
retrun -1;
}
return 0;
}
3> 渲染
public int Render(int outputIndex, boolean isRender)
{
if(outputIndex < 0)
{
return -1;
}
try
{
//此处outputIndex是由OutputData获取到的,isRender为true则MediaCodec会将解码后的纹理直接渲染到创建时绑定的Surface上
MediaCodec.releaseOutputBuffer(outputIndex,isRender);
}
catch(Exception e)
{
e.printStackTrack();
return -1;
}
return 0;
}
以上3个步骤可以在同一个线程内进行,也可在不同线程内调用。
但无论是一个线程还是多个线程,都会出现MediaCodec内部存几帧的情况,这样实时流下会增加延时,而文件模式下硬解码器内部存的这几帧数据会无法推出渲染。
我尝试过专门开一个线程去取输出缓存区的index,但是发现某些设备上这几帧还是无法取出,后来看Android开发者手册发现,MediaCodec是有3种状态的,running、flush、end-of-stream,可以通过设置End-of-stream标志位来告诉MediaCodec无数据要解了,这种模式下是无法获取输入缓冲区的index的,但是还是可以继续获取输出缓冲区的index,经过尝试,将MediaCodec设置为end-of-stream模式可以迅速的取出解码器内部存的那几帧。
4>设置End-of-stream
public int SetEndofStream()
{
int inputIndex = -1;
try{
inputIndex = MediaCodec.dequeueInputBuffer(0);
}
catch(Exception e)
{
e.printStackTrace();
return -1;
}
if(inputIndex < 0 )
{
//需要重新获取
return -1;
}
try{
MediaCodec.queueInputBuffer(inputIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
catch(Exception e)
{
e.printStackTrace();
return -1;
}
return 0;
}
SetEndofStream()还可以用于清空硬解码器,如果设置End-of-stream后还需要继续解码只需要调用MediaCodec.flush();把硬解码器设置为等待状态,然后再inputdata就可以继续解码了。