一文搞懂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> &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的代表。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!

推薦閱讀: