本文你可以了解到
本文主要简介Android使用硬解码API实现硬解码的流程,包含MediaCodec输入输出缓冲、MediaCodec解码流程、解码代码封装和讲解。
一、简介
MediaCodec 是Android 4.1(api 16)版本引入的编解码接口,同时支持音视频的编码和解码。
一定要好好理解接下来这两幅图,因为后续的代码就是基于这两幅图来编写的。
数据流
首先,来看看MediaCodec的数据流,也是官方Api文档中的,很多文章都会引用。
仔细看一下,MediaCodec将数据分为两部分,分别为input(左边)和output(右边),即输入和输出两个数据缓冲区。
input:是给客户端输入需要解码的数据(解码时)或者需要编码的数据(编码时)。
output:是输出解码好(解码时)或者编码好(编码时)的数据给客户端。
MediaCodec内部使用异步的方式对input和output数据进行处理。MediaCodec将处理好input的数据,填充到output缓冲区,交给客户端渲染或处理
注:客户端处理完数据后,必须手动释放output缓冲区,否则将会导致MediaCodec输出缓冲被占用,无法继续解码。
状态
依然是一副来自官方的状态图
再仔细看看这幅图,整体上分为三个大的状态:Sotpped、Executing、Released。
- Stoped:包含了3个小状态:Error、Uninitialized、Configured。
首先,新建MediaCodec后,会进入Uninitialized状态;
其次,调用configure方法配置参数后,会进入Configured;
- Executing:同样包含3个小状态:Flushed、Running、End of Stream。
再次,调用start方法后,MediaCodec进入Flushed状态;
接着,调用dequeueInputBuffer方法后,进入Running状态;
最后,当解码/编码结束时,进入End of Stream(EOF)状态。
这时,一个视频就处理完成了。
- Released:最后,如果想结束整个数据处理过程,可以调用release方法,释放所有的资源。
那么,Flushed是什么状态呢?
从图中我们可以看到,在Running或者End of Stream状态时,都可以调用flush方法,重新进入Flushed状态。
当我们在解码过程中,进入了End of Stream后,解码器就不再接收输入了,这时候,需要调用flush方法,重新进入接收数据状态。
或者,我们在播放视频过程中,想进行跳播,这时候,我们需要Seek到指定的时间点,这时候,也需要调用flush方法,清除缓冲,否则解码时间戳会混乱。
再次强调一下,一定要好好理解这两幅图,因为后续的代码就是基于这两幅图来编写的。
二、解码流程
MediaCodec有两种工作模式,分别为异步模式和同步模式,这里我们使用同步模式,异步模式可以参考官网例子。
根据官方的数据流图和状态图,画出一个最基础的解码流程如下:
经过初始化和配置以后,进入循环解码流程,不断的输入数据,然后获取解码完数据,最后渲染出来,直到所有数据解码完成(End of Stream)。
三、开始解码
根据上面的流程图,可以发现,无论音频还是视频,解码流程基本是一致的,不同的地方只在于【配置】、【渲染】两个部分。
定义解码器
因此,我们将整个解码流程抽象为一个解码基类:BaseDecoder,为了规范代码和更好的拓展性,我们先定义一个解码器:IDecoder,继承Runnable。
interface IDecoder: Runnable {
/**
* 暂停解码
*/
fun pause()
/**
* 继续解码
*/
fun goOn()
/**
* 停止解码
*/
fun stop()
/**
* 是否正在解码
*/
fun isDecoding(): Boolean
/**
* 是否正在快进
*/
fun isSeeking(): Boolean
/**
* 是否停止解码
*/
fun isStop(): Boolean
/**
* 设置状态监听器
*/
fun setStateListener(l: IDecoderStateListener?)
/**
* 获取视频宽
*/
fun getWidth(): Int
/**
* 获取视频高
*/
fun getHeight(): Int
/**
* 获取视频长度
*/
fun getDuration(): Long
/**
* 获取视频旋转角度
*/
fun getRotationAngle(): Int
/**
* 获取音视频对应的格式参数
*/
fun getMediaFormat(): MediaFormat?
/**
* 获取音视频对应的媒体轨道
*/
fun getTrack(): Int
/**
* 获取解码的文件路径
*/
fun getFilePath(): String
}
定义了解码器的一些基础操作