一文搞懂Codec2框架解析
1 前言–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的接口,通過幾個接口的流程追蹤,梳理出各個類的關系,也瞭解瞭數據的回調流向,如此一來,後續分析代碼就有瞭框架層的認識,不會陷入細節繞得團團轉。
到此這篇關於一文搞懂Codec2框架解析的文章就介紹到這瞭,更多相關Codec2框架解析內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 一文搞懂Codec2解碼組件
- Android硬件解碼組件MediaCodec使用教程
- Android音視頻開發Media FrameWork框架源碼解析
- C#實現同步模式下的端口映射程序
- Angular 與 Component store實踐示例