~安卓Codec2.0的编解码流程

安卓Codec2.0的编解码流程

在Android R以及之前的版本上面都是使用OMX的编解码框架,Android S及之后的视频编解码强制采用Codec2.0框架,Android T及之后的音频编解码也强制采用Codec2.0框架。

Android OpenMAX指南

Codec2.0简介

  1. 在Android Q上,谷歌推出mainline计划,在mainline计划中,所涉及的模块是可通过Google Play商店进行升级的,同时也是不允许厂商在SDK上进行修改的,在该主线方式下,厂商对于mainline模块的修改只能通过向主线提交代码的方式进行,从而mainline模块减小了安卓生态的碎片化。谷歌的Codec 2.0属于mainline模块,厂商无法修改其代码,这样子的话,各家厂商的安卓设备都在多媒体中间件模块保持一致性,如果厂商想要在中间件模块上添加特性或者修复某个BUG,厂商只能向谷歌主线提交代码或者补丁,如果为主线所合并,这将同时让谷歌与其他厂商受益。

  2. 相比于ACodec+OMX的中间件框架,Codec 2.0将具备若干特性:组件连通(component chaining),过滤器(filters),配置可查询(configureation querying)。组件包括解码组件,编码组件,组件连通按目前的信息应该理解为类似OMX的特性,解码组件连接编码组件,或者编码组件连接解码组件,前者的场景包括编码格式转换,后者的场景包括直播推流。对于过滤器与配置可查询的特性,目前没有足够信息了解到其内容。

  3. 在性能上,相比于ACodec+OMX框架,Codec 2.0将具备更多的优势与可能性。因为Codec 2.0的解码组件与编码组件所依赖的Buffer管理机制具备零拷贝特性,避免大块数据的拷贝所引起的性能降低。

Codec2.0框架解析

本文来自于"Codec2入门:框架解析"

Codec2.0框架代码

Codec2.0的代码目录位于/frameworks/av/media/codec2。目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
codec2
|--components #具体编解码组件与组件接口层
| |--base/SimpleC2Component.cpp
| |--base/SimpleC2Interface.cpp
| |--avc/C2SoftAvcDec.cpp
|--core #存在核心的头文件,譬如Buffer定义、Component定义、Config定义、Param定义
|--docs #暂时存放doxygen配置文件与脚本
|--faultinjection
|--hidl #与hidl调用相关的实现
|--client/client.cpp
|--1.0/utils/Component.cpp
|--1.0/utils/ComponentInterface.cpp
|--1.0/utils/ComponentStore.cpp
|--1.0/utils/Configurable.cpp
|--1.0/utils/include/codec2/hidl/1.0/Component.h
|--1.0/utils/include/codec2/hidl/1.0/Configurable.h
|--1.0/utils/include/codec2/hidl/1.0/ComponentInterface.h
|--sfplugin #顶层接口与实现层
| |--CCodec.cpp
| |--CCodec.h
| |--CBufferChannel.cpp
| |--CBufferChannel.h
|--tests
|--vndk #基础的util实现
| |--C2Store.cpp

sfplugin/CCodec.cpp是顶层实现,它提供的接口为MediaCodec Native层所调用,与libstagefright/ACodec接口一致,都继承于CodecBase,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
virtual std::shared_ptr<BufferChannelBase> getBufferChannel() override;
virtual void initiateAllocateComponent(const sp<AMessage> &msg) override;
virtual void initiateConfigureComponent(const sp<AMessage> &msg) override;
virtual void initiateCreateInputSurface() override;
virtual void initiateSetInputSurface(const sp<PersistentSurface> &surface) override;
virtual void initiateStart() override;
virtual void initiateShutdown(bool keepComponentAllocated = false) override;
virtual status_t setSurface(const sp<Surface> &surface) override;

virtual void signalFlush() override; virtual void signalResume() override;
virtual void signalSetParameters(const sp<AMessage> &params) override;
virtual void signalEndOfInputStream() override;
virtual void signalRequestIDRFrame() override;

void onWorkDone(std::list<std::unique_ptr<C2Work>> &workItems);
void onInputBufferDone(uint64_t frameIndex, size_t arrayIndex);

CCodec类中最重要的成员对象包括mChannel、mClient、mClientListener。

  • mChannel是CCodecBufferChannel类,主要负责buffer的传递。
  • mClient是Codec2Client类,提供了Codec 2.0的最精要的接口,它包括了四个子类:Listener、Configurable、Interface以及Component。Client.h头文件对此有一段简要的描述,可翻阅之。
  • Listener用于input buffer、output buffer以及error的回调。
  • Interface提供配置与参数的交互接口,在component与CCodec之间。
  • Component则是具体decoder/encoder component的代表。
  • ComponentStore可以看作是对接Codec2Client的组件,Interface与Component都是经由ComponentStore创建而来,该组件可以由不同的插件实现,原生实现的是C2PlatformComponentStore,厂商可以通过实现自己的Store插件对接到ComponentStore以完成硬件编解码在Codec 2.0的对接。

Codec2.0代码流程

CCodec类的对象关系如下图所示:

CCodec类对象关系图

Codec2Client的成员Component通过C2PlatformComponent而创建,C2ComponentStore是接口类。而在ClientListener这条通路上,是一条回调通路,从底往上回调,分别经过SimpleC2Component、Component::Listener、HidlListener以及ClientListener,到达CCodec,再回调到MediaCodec。

初始化流程

CCodec的初始化接口为initiateAllocateComponent,调用到内部函数allocate,allocate做了许多工作,首先是调用到Codec2Client的接口CreateFromService,尝试创建了一个服务名为default的Codec2Client客户端(芯片厂商提供),否则则创建服务名为software的Codec2Client(谷歌原生软编解码),即基于C2PlatformComponentStore的codec2插件。

如果能够创建default的Codec2Client,则会调用SetPreferredCodec2ComponentStore,将厂商的ComponentStore设置为默认的codec 2插件。这样子,codec2就不会走谷歌原生的软编解码器,而会走芯片厂商提供的编解码器,通常是硬编硬解。

CCodec的初始化流程

启动流程

Input Buffer的回调

当Input Buffer数据被消耗以后,onInputBuffersReleased通过IPC被调用,HidlListener继而开始回调onInputBufferDone,Codec2Client是个接口类,实现类为CCodec::ClientListener,因而回调到了CCodec::ClientListener,往后通过CCodec,CCodecBufferChannel,CCodecBufferChannel在完成onInputBufferReleased与expireComponentBuffer之后,调用feedInputBufferAvailable继续送空闲的Input Buffer给编解码组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//client.cpp
virtual Return<void> onInputBuffersReleased(
const hidl_vec<InputBuffer>& inputBuffers) override {
std::shared_ptr<Listener> listener = base.lock();
if (!listener) {
LOG(DEBUG) << "onInputBuffersReleased -- listener died.";
return Void();
}
for (const InputBuffer& inputBuffer : inputBuffers) {
LOG(VERBOSE) << "onInputBuffersReleased --"
" received death notification of"
" input buffer:"
" frameIndex = " << inputBuffer.frameIndex
<< ", bufferIndex = " << inputBuffer.arrayIndex
<< ".";
listener->onInputBufferDone(
inputBuffer.frameIndex, inputBuffer.arrayIndex);
}
return Void();
}
`CCodec::onInputBufferDone`

onInputBuffersReleased究竟是怎么被触发的,目前仍未追踪到,在client.h中,有一段对Input Buffer管理的描述,说明了onInputBuffersReleased是一个IPC call:

1
2
3
4
5
6
7
8
9
10
11
12
* InputBufferManager持有代表跟踪缓冲区及其回调侦听器的记录集合。
* 从概念上讲,一条记录是一个三元组(listener, frameIndex, bufferIndex),
* 其中(frameIndex, bufferIndex)是一对用于标识缓冲区的索引。
* 侦听器属于IComponentListener类型。它的onInputBuffersReleased()
* 函数将在关联的缓冲区死亡后被调用。onInputBuffersReleased()的参数是
* 一个InputBuffer对象列表,每个对象都有以下成员:
* uint64_t frameIndex
* uint32_t arrayIndex
* 当与三元组(listener, frameIndex, bufferIndex)关联的跟踪缓冲区超出范围时,
* listener->onInputBuffersReleased()将使用InputBuffer对象调用,其成员设置如下:
* inputBuffer.frameIndex = frameIndex
* inputBuffer.arrayIndex = bufferIndex

Output Buffer的回调

这一条路就有点长,难点在于Codec2Client::Listener与IComponentListener是接口类,分别由CCodec::ClientListener与Codec2Client::Component::HidlListener实现,这会让不熟悉C++的人一时半会摸不着头脑。从这一条通路可以看出不同模块的层次,HidleListener连接沟通了SimpleC2Component与Codec2Client,而Codec2Client是CCodec所调用的对象,CCodec将Buffer的管理都将由CodecBufferChannel打理,而CodecBufferChannel直接反馈于MediaCodec。

`SimpleC2Component::onWorkDone`

我们来看一下这条回调路上几个类的关系。譬如,Component::Listener回调的时候,调用的是IComponentListener的接口,而IComponentListener实际由Codec2Client::Component::HidlListener继承实现,所以,实际上是调用到了HidlListener,故而用实线表示,虚函数的调用用虚线表示。

onWorkDone的回调过程

Codec2.0框架总结

在CCodec的几个接口中,初始化、启动、参数与配置交互、回调交互是比较复杂的流程,对于参数与配置交互,在OMX中是采用SetParameter、SetConfig、GetParameter、GetConfig来实现的,而在Codec2中,由ComponentInterface、C2Param一起完成,这块留作下次研究。我们从顶至下,先明确顶层CCodec的接口,通过几个接口的流程追踪,梳理出各个类的关系,也了解了数据的回调流向,如此一来,后续分析代码就有了框架层的认识,不会陷入细节绕得团团转。

Codec2.0框架组件

组件创建

组件接口

组件运行原理

Codec2.0组件小结

Codec2.0类的解析


~安卓Codec2.0的编解码流程
https://blog.siantao.top/技术/计算机/软件/Android/多媒体/编解码/~安卓Codec2.0的编解码流程/
作者
玉水仙楊
发布于
2023年3月1日
许可协议