Codec2框架解析 一文搞懂Codec2框架解析
Kayson12345 人气:21 前言–Codec2.0是什么
在Android Q之前,Android的两套多媒体框架分别为MediaPlayer与MediaCodec,后者只负责解码与渲染工作,解封装工作由MediaExtractor代劳,MediaCodec经由ACodec层调用第三方编解码标准接口OpenMAX IL,实现硬件编解码。芯片厂商只需要支持上Khronos 制定的OpenMAX接口,就可以实现MediaCodec的硬件编解码。谷歌在Android Q上推出了Codec2.0,指在于取代ACodec与OpenMAX,它可以看作是一套新的对接MediaCodec的中间件,往上对接MediaCodec Native层,往下提供新的API标准供编解码使用,相当于ACodec 2.0。
2 Codec2.0框架
Codec2.0的代码目录位于/frameworks/av/media/codec2。目录结构如下:
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,如下所示:
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> ¶ms) 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的代表。Interface与Component都是经由ComponentStore创建而来,ComponentStore可以看作是对接Codec2Client的组件,该组件可以由不同的插件实现,原生实现的是C2PlatformComponentStore,厂商可以通过实现自己的Store插件对接到ComponentStore,则完成了硬件编解码在Codec 2.0的对接。
3 流程解析
CCodec类的对象关系如下图所示:
Codec2Client的成员Component通过C2PlatformComponent而创建,C2ComponentStore是接口类。而在ClientListener这条通路上,是一条回调通路,从底往上回调,分别经过SimpleC2Component、Component::Listener、HidlListener以及ClientListener,到达CCodec,再回调到MediaCodec。
3.1 初始化流程
CCodec的初始化接口为initiateAllocateComponent,调用到内部函数allocate,allocate做了许多工作,首先是调用到Codec2Client的接口CreateFromService,尝试创建了一个服务名为default的Codec2Client客户端(服务名为default的Codec2Client是厂商的Codec2Client),否则则创建服务名为software的Codec2Client,这是谷歌的原生Codec2Client,即,基于C2PlatformComponentStore的codec 2插件。如果能够创建default Codec2Client,则会调用SetPreferredCodec2ComponentStore,将厂商的ComponentStore设置为默认的codec 2插件。这样子,codec2.0就不会走谷歌原生的软编解码器,而会走芯片厂商提供的编解码器,通常是硬编硬解。
3.2 启动流程
mChannel是MCodecBufferChannel类,它的start接口实现稍微复杂,主要是获取AllocatorStore,再为input buffer与output buffer创建BlockPool,完成之后通过CCodec::mCallback回调告诉MediaCodec。接下来,初始化input buffer,开始调用queue接口送数据进编解码组件,原生组件为SimpleC2Component,具体可以送到C2SoftAvcDec,也可以送到C2SoftHevcDec,等等。
3.3 Input Buffer的回调
当input buffer数据被消耗以后,onInputBuffersReleased通过IPC被调用,HidlListener继而开始回调onInputBufferDone,Codec2Client是个接口类,实现类为CCodec::ClientListener,因而回调到了CCodec::ClientListener,往后通过CCodec,CCodecBufferChannel,CCodecBufferChannel在完成onInputBufferReleased与expireComponentBuffer之后,调用feedInputBufferAvailable继续送空闲的Input Buffer给编解码组件。
//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(); }
onInputBuffersReleased究竟是怎么被触发的,目前仍未追踪到,在client.h中,有一段对Input Buffer管理的描述,说明了onInputBuffersReleased是一个IPC call。如下所示:
* InputBufferManager holds a collection of records representing tracked buffers * and their callback listeners. Conceptually, one record is a triple (listener, * frameIndex, bufferIndex) where * * - (frameIndex, bufferIndex) is a pair of indices used to identify the buffer. * - listener is of type IComponentListener. Its onInputBuffersReleased() * function will be called after the associated buffer dies. The argument of * onInputBuffersReleased() is a list of InputBuffer objects, each of which * has the following members: * * uint64_t frameIndex * uint32_t arrayIndex * * When a tracked buffer associated to the triple (listener, frameIndex, * bufferIndex) goes out of scope, listener->onInputBuffersReleased() will be * called with an InputBuffer object whose members are set as follows: * * inputBuffer.frameIndex = frameIndex * inputBuffer.arrayIndex = bufferIndex
3.4 Output Buffer的回调
这一条路就有点长了,难点在于Codec2Client::Listener与IComponentListener是接口类,分别由CCodec::ClientListener与Codec2Client::Component::HidlListener实现,这会让不熟悉C++的人一时半会摸不着头脑。从这一条通路可以看出不同模块的层次,HidleListener连接沟通了SimpleC2Component与Codec2Client,而Codec2Client是CCodec所调用的对象,CCodec将Buffer的管理都将由CodecBufferChannel打理,而CodecBufferChannel直接反馈于MediaCodec。
我们来看一下这条回调路上几个类的关系。譬如,Component::Listener回调的时候,调用的是IComponentListener的接口,而IComponentListener实际由Codec2Client::Component::HidlListener继承实现,所以,实际上是调用到了HidlListener,故而用实线表示,虚函数的调用用虚线表示。
4 总结
在CCodec的几个接口中,初始化、启动、参数与配置交互、回调交互是比较复杂的流程,对于参数与配置交互,在OMX中是采用SetParameter、SetConfig、GetParameter、GetConfig来实现的,而在Codec2中,由ComponentInterface、C2Param一起完成,这块留作下次研究。我们从顶至下,先明确顶层CCodec的接口,通过几个接口的流程追踪,梳理出各个类的关系,也了解了数据的回调流向,如此一来,后续分析代码就有了框架层的认识,不会陷入细节绕得团团转。
加载全部内容