一、MediaCodec介绍
MediaCodec是Android音视频中相当重要的一个API。MediaCodec类可以用于使用一些基本的多媒体编解码器(音视频编解码组件),它是Android基本的多媒体支持基础架构的一部分通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack 一起使用。
广义而言,编解码器处理输入数据以生成输出数据。 它异步处理数据并使用一组输入和输出缓冲区。 在简单的层面上,您请求(或接收)一个空输入缓冲区,填充数据并将其发送到编解码器进行处理。 编解码器使用数据并将其转换为其空的输出缓冲区之一。 最后,您请求(或接收)一个填充的输出缓冲区,消耗其内容并将其释放回编解码器。
一个编解码器可以处理输入的数据来产生输出的数据,编解码器使用一组输入和输出缓冲器来异步处理数据。你可以创建一个空的输入缓冲区,填充数据后发送到编解码器进行处理。编解码器使用输入的数据进行转换,然后输出到一个空的输出缓冲区。最后你获取到输出缓冲区的数据,消耗掉里面的数据,释放回编解码器。如果后续还有数据需要继续处理,编解码器就会重复这些操作。
上图是一张MediaCodec编码器的数据流程图,1)对于提供数据的Client来说,首先是向Codec申请一组空buffers(图中的inputbuffers),然后将数据放入buffers中,最后将装满数据的buffers还给Codec;2)对于消耗数据的Client来说,先向Codec申请一组带处理好的数据的buffers(图中的outputbuffers),然后消费buffers中的数据,最后将buffers还给Codec。
MediaCodec的编解方式
MediaCodec可以通过同步和异步两种方式进行编解码。同步编解码是指在主线程中进行解码,这种方式简单易懂,但是会导致主线程阻塞,影响用户体验。而异步编解码则是在子线程中进行编解码,不会阻塞主线程,可以提高应用的响应速度和流畅度。在异步编解码中,需要开辟两个线程,分别用于视频和音频的编解码,通过线程间的通信来实现音视频的同步播放。
在Android 5.0 之后,google 建议使用异步解码的方式去使用 MediaCodec。
MediaCodec生命周期
MediaCodec 有三种状态,分别是执行(Executing)、停止(Stopped)和释放(Released),其中执行和停止分别有三个子状态,执行的三个字状态分别是 Flushed、Running 和 Stream-of-Stream,停止的三个子状态分别是 Uninitialized、Configured 和 Error,MediaCodec 生命周期示意图如下:
上图是Codec的生命周期示意图,从图中可以看出:
1)当创建编解码器的时候处于未初始化状态。首先你需要调用configure(…)方法让它处于Configured状态,然后调用start()方法让其处于Executing状态。在Executing状态下,你就可以使用上面提到的缓冲区来处理数据。
2)Executing的状态下也分为三种子状态:Flushed, Running、End-of-Stream。在start() 调用后,编解码器处于Flushed状态,这个状态下它保存着所有的缓冲区。一旦第一个输入buffer出现了,编解码器就会自动运行到Running的状态。当带有end-of-stream标志的buffer进去后,编解码器会进入End-of-Stream状态,这种状态下编解码器不在接受输入buffer,但是仍然在产生输出的buffer。此时你可以调用flush()方法,将编解码器重置于Flushed状态。
3)调用stop()将编解码器返回到未初始化状态,然后可以重新配置。 完成使用编解码器后,您必须通过调用release()来释放它。
4)在极少数情况下,编解码器可能会遇到错误并转到错误状态。 这是使用来自排队操作的无效返回值或有时通过异常来传达的。 调用reset()使编解码器再次可用。 您可以从任何状态调用它来将编解码器移回未初始化状态。 否则,调用 release()动到终端释放状态。
MediaCodec本身并不具备Codec能力,通过调动底层编解码组件获得了Codec的能力,底层的编解码组件包括ACodec(采用 OMX 框架的解码器实现)和CCodec(采用 Codec 2 框架的解码器实现),如下是MediaCodec与ACodec组件的关系图:
MediaCodec 使用异步消息处理机制(AMessage/ALooper/AHandler)
编解码器支持的数据类型
编解码器对三种数据进行操作:压缩数据,原始音频数据和原始视频数据。 所有三种数据都可以使用ByteBuffers进行处理,但您应该使用Surface作为原始视频数据以提高编解码器的性能。 Surface使用本地视频缓冲区而不映射或将它们复制到ByteBuffers; 因此,它更有效率。 使用Surface时通常无法访问原始视频数据,但可以使用ImageReader类来访问不安全的已解码(原始)视频帧。 这可能仍然比使用ByteBuffers更高效,因为某些本地缓冲区可能映射到direct ByteBuffers中。 当使用ByteBuffer的模式,您可以使用访问原始视频帧Image