下图是生命周期的说明图:如图可以看到:
- 当创建编解码器的时候处于未初始化状态。首先你需要调用 configure(…)方法让它处于
Configured 状态,然后调用 start()方法让其处于 Executing 状态。在 Executing 状态下,你就
可以使用上面提到的缓冲区来处理数据。 - Executing 的状态下也分为三种子状态:Flushed, Running、End-of-Stream。在 start() 调用
后,编解码器处于 Flushed 状态,这个状态下它保存着所有的缓冲区。一旦第一个输入 buffer
出现了,编解码器就会自动运行到 Running 的状态。当带有 end-of-stream 标志的 buffer 进
去后,编解码器会进入 End-of-Stream 状态,这种状态下编解码器不在接受输入 buffer,但
是仍然在产生输出的 buffer。此时你可以调用 flush()方法,将编解码器重置于 Flushed 状态。 - 调用 stop()将编解码器返回到未初始化状态,然后可以重新配置。 完成使用编解码器后,
您必须通过调用 release()来释放它。 - 在极少数情况下,编解码器可能会遇到错误并转到错误状态。 这是使用来自排队操作的
无效返回值或有时通过异常来传达的。 调用 reset()使编解码器再次可用。 您可以从任何状
态调用它来将编解码器移回未初始化状态。 否则,调用 release()动到终端释放状态。
MediaCodec API 说明
MediaCodec 可以处理具体的视频流,主要有这几个方法:
getInputBuffers:获取需要编码数据的输入流队列,返回的是一个 ByteBuffer 数组
queueInputBuffer:输入流入队列
dequeueInputBuffer:从输入流队列中取数据进行编码操作
getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个 ByteBuffer 数组
dequeueOutputBuffer:从输出队列中取出编码操作之后的数据
releaseOutputBuffer:处理完成,释放 ByteBuffer 数据三、MediaCodec 流控
流控基本概念
流控就是流量控制。为什么要控制,因为条件有限!涉及到了 TCP 和视频编码:
对 TCP 来说就是控制单位时间内发送数据包的数据量,对编码来说就是控制单位时间内输
出数据的数据量。
TCP 的限制条件是网络带宽,流控就是在避免造成或者加剧网络拥塞的前提下,尽可能利用
网络带宽。带宽够、网络好,我们就加快速度发送数据包,出现了延迟增大、丢包之后,就
放慢发包的速度(因为继续高速发包,可能会加剧网络拥塞,反而发得更慢)。
视频编码的限制条件最初是解码器的能力,码率太高就会无法解码,后来随着 codec 的发
展,解码能力不再是瓶颈,限制条件变成了传输带宽/文件大小,我们希望在控制数据量的
前提下,画面质量尽可能高。
一般编码器都可以设置一个目标码率,但编码器的实际输出码率不会完全符合设置,因为在
编码过程中实际可以控制的并不是最终输出的码率,而是编码过程中的一个量化参数
(Quantization Parameter,QP),它和码率并没有固定的关系,而是取决于图像内容。
无论是要发送的 TCP 数据包,还是要编码的图像,都可能出现“尖峰”,也就是短时间内出
现较大的数据量。TCP 面对尖峰,可以选择不为所动(尤其是网络已经拥塞的时候),这没
有太大的问题,但如果视频编码也对尖峰不为所动,那图像质量就会大打折扣了。如果有几
帧数据量特别大,但仍要把码率控制在原来的水平,那势必要损失更多的信息,因此图像失
真就会更严重。
Android 硬编码流控
MediaCodec 流控相关的接口并不多,一是配置时设置目标码率和码率控制模式,二是动态
调整目标码率(Android 19 版本以上)。配置时指定目标码率和码率控制模式:
码率控制模式有三种:
CQ 表示完全不控制码率,尽最大可能保证图像质量;
CBR 表示编码器会尽量把输出码率控制为设定值,即我们前面提到的“不为所动”;
VBR 表示编码器会根据图像内容的复杂度(实际上是帧间变化量的大小)来动态调整输出
码率,图像复杂则码率高,图像简单则码率低;