前言
本篇博文主要讲述的是基于Android原生MediaCodec通过Camera2 API进行图像数据采集并编码为H.264的实现过程,如果对此感兴趣的不妨驻足观看,也欢迎大家大家对本文中描述不当或者不正确的地方进行指正。如果对于Camera2预览还不熟悉的可以观看博主上一篇博文:Android 基于Camera2 API进行摄像机图像预览。
MediaCodec简介
MediaCodec 主要是用于对音视频进行编解码。它通常与 MediaExtractor、MediaMuxer、Surface 和 AudioTrack 等组件一起使用。MediaCodec 支持硬件加速,可以利用设备的硬件资源来提高编解码的性能。
1、MediaCodec 编解码流程
MediaCodec 采用异步方式处理数据,使用一组输入输出缓冲区(ByteBuffer)。编解码流程大致如下:
-
请求一个空的输入缓冲区,填充满数据后传递给 MediaCodec 处理。
-
MediaCodec 处理完数据后,将结果输出至一个空的输出缓冲区中。
-
从 MediaCodec 获取输出缓冲区的数据,消耗掉里面的数据后,释放回编解码器。
具体流程可以参考下图:
2、MediaCodec 生命周期
从上图可以看出MediaCodec 的生命周期包括三种状态:Stopped、Executing、Released。
-
Stopped,分三种子状态:
-
Configured,MediaCodec实例创建后,调用configure方法后就进入了Configured状态
-
Uninitialized,MediaCodec实例被创建后,在调用configure方法前都处于该状态;
-
Error,MediaCodec遇到错误时进入该状态,通常可能是队列操作返回错误或异常导致的;
-
-
Executing,分三种状态
-
Flushed,在调用start()方法后MediaCodec立即进入Flushed子状态,此时MediaCodec会拥有所有的缓存。可以在Executing状态的任何时候通过调用flush()方法返回到Flushed子状态;
-
Running,一旦第一个输入缓存(input buffer)被移出队列,MediaCodec就转入Running子状态,这种状态占据了MediaCodec的大部分生命周期。通过调用stop()方法转移到Uninitialized状态;
-
End of Stream,将一个带有end-of-stream标记的输入buffer入队列时,MediaCodec将转入End-of-Stream子状态。在这种状态下,MediaCodec不再接收之后的输入buffer,但它仍然产生输出buffer直到end-of-stream标记输出
-
-
Released,当使用完MediaCodec后,必须调用release()方法释放其资源。调用 release()方法进入最终的Released状态;
编码实现
老规矩,依然需要对MediaCodec进行封装,在封装之前我们需要先设计下对应的接口,而根据上面了解到的MediaCodec生命周期中的几个状态我们需要对其进行记录,方便对外获取,因此接口设计如下:
interface ICodec {
enum class State{
IDLE,
START,
STOP,
CLOSE
}
fun start()
fun stop()
fun close()
fun getState():State
}
interface IVideoEncoder: ICodec
这里的close对应的是MediaCodec的release接口。ICodec设计思想是考虑到后续会扩展出不止VideocEncoder还会有Decoder因此只包含了最原始的几个接口设计,针对Encoder和Decoder区别性的接口可以基于ICodec进行扩展继续添加,因为IVideoEncoder暂时没有需要额外添加的接口所以只是单纯的继承自ICodec,后面有需要再添加即可。
接着让我们再编写一个VideoCodec类并实现IVideoEncoder接口,开始我们的编码实现。
class VideoEncoder(private var params:VideoEncParams = VideoEncParams()):IVideoEncoder {
private var state = ICodec.State.IDLE
private var enccoder:MediaCodec? = null
private var inputSurface:Surface? = null
private var encodeThread:Thread? = null
private var callback:EncoderCallback? = null
fun setEncoderCallback(callback:EncoderCallback){
this.callback = callback
}
interface EncoderCallback{
fun onCallback(data:ByteArray,frameFlags:Int)
}
VideoEncoder实现了IVideoEncoder接口并且构造时需要传入VideoEncParams,VideoEncParams我们等下再看,inputSurface是编码输入源,encodeThread用于异步进行编码,callback则是对外提供编码后数据的回调,回调并不仅仅只包含编码后的帧数据,还包含有一个Int类型的frameFlags,这个frameFlags可以理解为编码这一帧的类型,例如SPS帧或者关键帧等,现在我们回过头来看下传入的VideoEncParams:
class Vi