一文搞懂Codec2解碼組件

1 前言

在本篇中,我們將關註Codec 2.0以下幾個問題:

1.從頂而下,一個解碼組件是如何創建的

2.組件的接口有哪些,分別是什麼含義

3.組件是如何運行的,輸入與輸出的數據流是怎樣的

2 組件的創建

CCodec在allocate中,通過CreateComponentByName創建瞭具體的解碼組件。

//android/frameworks/av/media/codec2/sfplguin/CCodec.cpp
void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
    ...
    AString componentName = codecInfo->getCodecName();
    std::shared_ptr<Codec2Client> client;

    // set up preferred component store to access vendor store parameters
    //從CCodec調用到component是通過HAL層服務的,默認谷歌的原生服務為 
    //android.hardware.media.c2@IComponentStore/software,默認廠商的服務為 	
    //android.hardware.media.c2@IComponentStore/default,在android小機shell中通過lshal|grep media可以查詢 
    //到正在運行的codec2服務,如果廠商已支持codec2,則可以查詢到default服務。如果CCodec中能夠創建到default 
    //服務,則可以將該服務設置為Preferred Codec2 ComponentStore,也就是將其作為目標組件。
    client = Codec2Client::CreateFromService("default");
    if (client) {
        ALOGI("setting up '%s' as default (vendor) store", client->getServiceName().c_str());
        SetPreferredCodec2ComponentStore(
                std::make_shared<Codec2ClientInterfaceWrapper>(client));
    }
	//創建具體的解碼組件或者編碼組件,譬如c2.android.avc.decoder
	//所有omx與codec2的編解碼組件支持列表可以在libstagefright/data目錄下的xml中查詢得到,它們的加載與
	//排序情況可以在libstagefright/MediaCodecList.cpp中追蹤
    std::shared_ptr<Codec2Client::Component> comp =
            Codec2Client::CreateComponentByName(
            componentName.c_str(),
            mClientListener,
            &client);
    ...
    ALOGI("Created component [%s]", componentName.c_str());
    mChannel->setComponent(comp);
    auto setAllocated = [this, comp, client] {
        Mutexed<State>::Locked state(mState);
        if (state->get() != ALLOCATING) {
            state->set(RELEASED);
            return UNKNOWN_ERROR;
        }
        state->set(ALLOCATED);
        state->comp = comp;
        mClient = client;
        return OK;
    };
    ...

    // initialize config here in case setParameters is called prior to configure
    Mutexed<Config>::Locked config(mConfig);
    status_t err = config->initialize(mClient, comp);
    ...
    config->queryConfiguration(comp);

    mCallback->onComponentAllocated(componentName.c_str());
}

繼續追蹤Codec2Client::CreateComponentByName接口。

//android/frameworks/av/media/codec2/hidl/client/client.cpp
std::shared_ptr<Codec2Client::Component>
        Codec2Client::CreateComponentByName(
        const char* componentName,
        const std::shared_ptr<Listener>& listener,
        std::shared_ptr<Codec2Client>* owner,
        size_t numberOfAttempts) {
    std::string key{"create:"};
    key.append(componentName);
    std::shared_ptr<Component> component;
    c2_status_t status = ForAllServices(
            key,
            numberOfAttempts,
            [owner, &component, componentName, &listener](
                    const std::shared_ptr<Codec2Client> &client)
                        -> c2_status_t {
                //調用Codec2Client類的createComponent接口,獲取component
                c2_status_t status = client->createComponent(componentName,
                                                             listener,
                                                             &component);
                ...
                return status;
            });
    ...
    return component;
}

追蹤Codec2Client類的createComponent接口。

\\av\media\codec2\hidl\client\client.cpp
c2_status_t Codec2Client::createComponent(
        const C2String& name,
        const std::shared_ptr<Codec2Client::Listener>& listener,
        std::shared_ptr<Codec2Client::Component>* const component) {

    c2_status_t status;
    sp<Component::HidlListener> hidlListener = new Component::HidlListener{};
    hidlListener->base = listener;
    //這裡的mBase是什麼?這裡調用的是IComponentStore的createComponent接口
    Return<void> transStatus = mBase->createComponent(
            name,
            hidlListener,
            ClientManager::getInstance(),
            [&status, component, hidlListener](
                    Status s,
                    const sp<IComponent>& c) {
                status = static_cast<c2_status_t>(s);
                if (status != C2_OK) {
                    return;
                }
                *component = std::make_shared<Codec2Client::Component>(c);
                hidlListener->component = *component;
            });
    ...
    return status;
}

我們先看一下IComponentStore的createComponent接口。

\\av\media\codec2\hidl\1.0\utils\include\codec2\hidl\1.0\ComponentStore.h
struct ComponentStore : public IComponentStore {
    ComponentStore(const std::shared_ptr<C2ComponentStore>& store);
    virtual ~ComponentStore() = default;
    // Methods from ::android::hardware::media::c2::V1_0::IComponentStore.
    virtual Return<void> createComponent(
            const hidl_string& name,
            const sp<IComponentListener>& listener,
            const sp<IClientManager>& pool,
            createComponent_cb _hidl_cb) override;
    virtual Return<void> createInterface(
            const hidl_string& name,
            createInterface_cb _hidl_cb) override;、
    ...
}

該接口的實現為:

\\av\media\codec2\hidl\1.0\utils\ComponentStore.cpp
// Methods from ::android::hardware::media::c2::V1_0::IComponentStore
Return<void> ComponentStore::createComponent(
        const hidl_string& name,
        const sp<IComponentListener>& listener,
        const sp<IClientManager>& pool,
        createComponent_cb _hidl_cb) {

    sp<Component> component;
    std::shared_ptr<C2Component> c2component;
    //C2PlatformComponentStore的createComponent調用
    //調用C2PlatformComponentStore的createComponent接口,返回的是一個C2Component對象
    //譬如,這個對象可以是C2SoftAvcDec Component對象,也可以是VendorHwAvcDec Component對象
    Status status = static_cast<Status>(
            mStore->createComponent(name, &c2component));

    if (status == Status::OK) {
        onInterfaceLoaded(c2component->intf());
        //把前面創建的C2SoftAvcDec“裝載”到Component類中,Client調用Component
        //Component內部會調用到C2SoftAvcDec
        //Component相當於對原生編解碼組件/廠商編解碼組件的統一封裝
        component = new Component(c2component, listener, this, pool);
        if (!component) {
            status = Status::CORRUPTED;
        } else {
            reportComponentBirth(component.get());
            if (component->status() != C2_OK) {
                status = static_cast<Status>(component->status());
            } else {
                component->initListener(component);
                if (component->status() != C2_OK) {
                    status = static_cast<Status>(component->status());
                }
            }
        }
    }
    _hidl_cb(status, component);
    return Void();
}

關於C2PlatformComponentStore的createComponent調用,它的實現在C2Store.cpp中,它繼承於C2ComponentStore類,有幾個重要成員對象,ComponentModule,ComponentLoader,有幾個重要的接口,listComponents(),createComponent(),createInterface()。ComponentLoader包含ComponentModule對象,而ComponentModule主要提供兩個接口,createComponent()與createInterface(),內部也包含著C2ComponentFactory成員以及它的創建與銷毀接口,分別是C2ComponentFactory::CreateCodec2FactoryFunc,C2ComponentFactory::DestroyCodec2FactoryFunc。

    \\av\media\codec2\vndk\C2Store.cpp
    class C2PlatformComponentStore : public C2ComponentStore {
    public:
        virtual std::vector<std::shared_ptr<const C2Component::Traits>> listComponents() override;
        ...
        virtual c2_status_t createInterface(
                C2String name, std::shared_ptr<C2ComponentInterface> *const interface) override;
        virtual c2_status_t createComponent(
                C2String name, std::shared_ptr<C2Component> *const component) override;
        virtual ~C2PlatformComponentStore() override = default;
    
    private:
    
        /**
         * An object encapsulating a loaded component module.
         */
        struct ComponentModule : public C2ComponentFactory,
                public std::enable_shared_from_this<ComponentModule> {
            virtual c2_status_t createComponent(
                    c2_node_id_t id, std::shared_ptr<C2Component> *component,
                    ComponentDeleter deleter = std::default_delete<C2Component>()) override;
            virtual c2_status_t createInterface(
                    c2_node_id_t id, std::shared_ptr<C2ComponentInterface> *interface,
                    InterfaceDeleter deleter = std::default_delete<C2ComponentInterface>()) override;
     		...
        protected:
    		...
            void *mLibHandle; ///< loaded library handle
            C2ComponentFactory::CreateCodec2FactoryFunc createFactory; ///< loaded create function
            C2ComponentFactory::DestroyCodec2FactoryFunc destroyFactory; ///< loaded destroy function
            C2ComponentFactory *mComponentFactory; ///< loaded/created component factory
        };
    
        /**
         * An object encapsulating a loadable component module.
         */
        struct ComponentLoader {
            /**
             * Load the component module.
             *
             * This method simply returns the component module if it is already currently loaded, or
             * attempts to load it if it is not.
             */
            c2_status_t fetchModule(std::shared_ptr<ComponentModule> *module) {
                c2_status_t res = C2_OK;
                std::lock_guard<std::mutex> lock(mMutex);
                std::shared_ptr<ComponentModule> localModule = mModule.lock();
                if (localModule == nullptr) {
                    localModule = std::make_shared<ComponentModule>();
                    res = localModule->init(mLibPath);
                    if (res == C2_OK) {
                        mModule = localModule;
                    }
                }
                *module = localModule;
                return res;
            }
    
            /**
             * Creates a component loader for a specific library path (or name).
             */
            ComponentLoader(std::string libPath)
                : mLibPath(libPath) {}
    
        private:
            std::weak_ptr<ComponentModule> mModule; ///< weak reference to the loaded module
        };
    
        struct Interface : public C2InterfaceHelper {
    	...
        };
    
        /**
         * Retrieves the component module for a component.
         */
        c2_status_t findComponent(C2String name, std::shared_ptr<ComponentModule> *module);
    
        /**
         * Loads each component module and discover its contents.
         */
        void visitComponents();
        std::map<C2String, ComponentLoader> mComponents; ///< path -> component module
        std::map<C2String, C2String> mComponentNameToPath; ///< name -> path
        std::vector<std::shared_ptr<const C2Component::Traits>> mComponentList;
    	...
    };

C2PlatformComponentStore::createComponent調用findComponent(name, &module)找到擁有component的ComponentModule,再通過module->createComponent(0, component)調用,找到相應的component。

    \\av\media\codec2\vndk\C2Store.cpp
    c2_status_t C2PlatformComponentStore::createComponent(
            C2String name, std::shared_ptr<C2Component> *const component) {
        // This method SHALL return within 100ms.
        component->reset();
        std::shared_ptr<ComponentModule> module;
        c2_status_t res = findComponent(name, &module);
        if (res == C2_OK) {
            // TODO: get a unique node ID
            res = module->createComponent(0, component);
        }
        return res;
    }

findComponent(name, &module)有兩步,先通過visitComponents()列舉出所有可用的components,再調用ComponentLoader的fetchModule(),找到擁有component的ComponentModule。module可以看作是組件,加載某個module,也就是加載對應的組件,module提供的 createComponent()接口就是用來創建具體component的,譬如C2SoftAvcDec。

    \\av\media\codec2\vndk\C2Store.cpp
    c2_status_t C2PlatformComponentStore::findComponent(
            C2String name, std::shared_ptr<ComponentModule> *module) {
        (*module).reset();
        visitComponents();
        auto pos = mComponentNameToPath.find(name);
        if (pos != mComponentNameToPath.end()) {
            return mComponents.at(pos->second).fetchModule(module);
        }
        return C2_NOT_FOUND;
    }

visitComponents()訪問mComponents對象(這是一個map對象,將path與component module映射關聯,這一映射工作在C2PlatformComponentStore初始化時進行),遍歷所有的mComponents,即pathAndLoader對象,如果一個對象的loader能夠加載成功,則添加到mComponentNameToPath對象中。

    \\av\media\codec2\vndk\C2Store.cpp
    void C2PlatformComponentStore::visitComponents() {
        std::lock_guard<std::mutex> lock(mMutex);
        if (mVisited) {
            return;
        }
        //參考定義 std::map<C2String, ComponentLoader> mComponents; ///< path -> component module
        for (auto &pathAndLoader : mComponents) {
            const C2String &path = pathAndLoader.first;
            ComponentLoader &loader = pathAndLoader.second;
            std::shared_ptr<ComponentModule> module;
            if (loader.fetchModule(&module) == C2_OK) {
                std::shared_ptr<const C2Component::Traits> traits = module->getTraits();
                if (traits) {
                    mComponentList.push_back(traits);
                    mComponentNameToPath.emplace(traits->name, path);
                    for (const C2String &alias : traits->aliases) {
                        mComponentNameToPath.emplace(alias, path);
                    }
                }
            }
        }
        mVisited = true;
    }

loader.fetchModule(&module)這個函數定義在ComponentLoader類中,在這裡再貼一次代碼。

    \\av\media\codec2\vndk\C2Store.cpp   
    c2_status_t fetchModule(std::shared_ptr<ComponentModule> *module) {
        c2_status_t res = C2_OK;
        std::lock_guard<std::mutex> lock(mMutex);
        std::shared_ptr<ComponentModule> localModule = mModule.lock();
        if (localModule == nullptr) {
            localModule = std::make_shared<ComponentModule>();
            res = localModule->init(mLibPath);
            if (res == C2_OK) {
                mModule = localModule;
            }
        }
        *module = localModule;
        return res;
    }

對於module,會調用初始化函數,初始化成功就算是fetch到瞭。初始化作瞭什麼工作,參見C2PlatformComponentStore::ComponentModule::init函數,也就是對編解碼庫dlopen成功,可獲得相應的函數地址,譬如,C2SoftAvcDec.cpp中的C2ComponentFactory* CreateCodec2Factory()與void DestroyCodec2Factory()。當然還有其他,不面面俱道瞭。

    \\av\media\codec2\vndk\C2Store.cpp   
    c2_status_t C2PlatformComponentStore::ComponentModule::init(
            std::string libPath) {
        ALOGV("in %s", __func__);
        ALOGV("loading dll");
        mLibHandle = dlopen(libPath.c_str(), RTLD_NOW|RTLD_NODELETE);
        createFactory =
            (C2ComponentFactory::CreateCodec2FactoryFunc)dlsym(mLibHandle, "CreateCodec2Factory");
        LOG_ALWAYS_FATAL_IF(createFactory == nullptr,
                "createFactory is null in %s", libPath.c_str());
        destroyFactory =
            (C2ComponentFactory::DestroyCodec2FactoryFunc)dlsym(mLibHandle, "DestroyCodec2Factory");
        LOG_ALWAYS_FATAL_IF(destroyFactory == nullptr,
                "destroyFactory is null in %s", libPath.c_str());
        mComponentFactory = createFactory();
    	...
        std::shared_ptr<C2ComponentInterface> intf;
        c2_status_t res = createInterface(0, &intf);
    	...
        return mInit;
    }

那麼問題來瞭,為什麼谷歌對它自己的codec2插件組C2PlatformComponentStore設計得這麼復雜,能不能簡化一點。

3 組件接口

在codec2/components目錄下,有base, avc, aom, hevc, aac等文件夾,base目錄下是SimpleC2Component.cpp與SimpleC2Interface.cpp以及對應的頭文件,avc目錄下是C2SoftAvcDec.cpp,C2SoftAvcEnc.cpp以及對應的頭文件,其他編解碼器文件夾亦同樣道理。C2SoftAvcDec,C2SoftHevcDec等編解碼器類都是繼承於SimpleC2Component類的,也就是說,SimpleC2Component是components的頂層類,它對接瞭component類的接口,實現瞭編解碼器的公共流程部分,C2SoftAvcDec,C2SoftHevcDec等子類繼承SimpleC2Component的一些接口,實現各自的編解碼操作。

SimpleC2Component實現的component的接口如下:

    \\av\media\codec2\components\base\include\SimpleC2Component.h
    // C2Component
    // From C2Component
    //設置回調
    virtual c2_status_t setListener_vb(
    	const std::shared_ptr<Listener> &listener, c2_blocking_t mayBlock) override;
    //送數據到component,數據打包成某種對象,叫C2Work,這個對象很關鍵,它包含input與output
    virtual c2_status_t queue_nb(std::list<std::unique_ptr<C2Work>>* const items) override;
    //暫時沒有多大用處,不管它
    virtual c2_status_t announce_nb(const std::vector<C2WorkOutline> &items) override;
    //跳播使用,將當前數據沖刷掉
    virtual c2_status_t flush_sm(
    	flush_mode_t mode, std::list<std::unique_ptr<C2Work>>* const flushedWork) override;
    //渲染可用的幀
    virtual c2_status_t drain_nb(drain_mode_t mode) override;
    virtual c2_status_t start() override;
    virtual c2_status_t stop() override;
    virtual c2_status_t reset() override;
    virtual c2_status_t release() override;
    virtual std::shared_ptr<C2ComponentInterface> intf() override;

而C2SoftAvcDec,C2SoftHevcDec等子類繼承SimpleC2Component的接口如下:

    \\av\media\codec2\components\base\include\SimpleC2Component.h
    virtual c2_status_t onInit() = 0;
    virtual c2_status_t onStop() = 0;
    virtual void onReset() = 0;
    virtual void onRelease() = 0;
    virtual c2_status_t onFlush_sm() = 0;
    //最重要的處理函數,處理的對象是C2Work,它包含著輸入輸出,交互配置方面的類。
    virtual void process(
        const std::unique_ptr<C2Work> &work,
        const std::shared_ptr<C2BlockPool> &pool) = 0;
    virtual c2_status_t drain(
        uint32_t drainMode,
        const std::shared_ptr<C2BlockPool> &pool) = 0;

4 組件運行原理

SimpleC2Component有一個成員對象WorkHandler,這個類繼承於AHandler,也就是說,SimpleC2Component內部運行一個線程,來自上層的接口調用,都可以發送消息到onMessageReceived中排隊處理,譬如初始化、停止、重置、釋放以及數據處理等工作,都在隊列中排隊處理,相應的處理都是調用到子類的實現,譬如,onInit(),onStop(),onReset(),onRelease(),以及processQueue()。

我們可以看一下onMessageReceived的實現。

\\av\media\codec2\components\base\SimpleC2Component.cpp
void SimpleC2Component::WorkHandler::onMessageReceived(const sp<AMessage> &msg) {
    std::shared_ptr<SimpleC2Component> thiz = mThiz.lock();
	...
    switch (msg->what()) {
        case kWhatProcess: {
            if (mRunning) {
                if (thiz->processQueue()) {
                    (new AMessage(kWhatProcess, this))->post();
                }
            } else {
                ALOGV("Ignore process message as we're not running");
            }
            break;
        }
        case kWhatInit: {
            int32_t err = thiz->onInit();
            Reply(msg, &err);
            [[fallthrough]];
        }
        case kWhatStart: {
            mRunning = true;
            break;
        }
        case kWhatStop: {
            int32_t err = thiz->onStop();
            Reply(msg, &err);
            break;
        }
        case kWhatReset: {
            thiz->onReset();
            mRunning = false;
            Reply(msg);
            break;
        }
        case kWhatRelease: {
            thiz->onRelease();
            mRunning = false;
            Reply(msg);
            break;
        }
        default: {
            ALOGD("Unrecognized msg: %d", msg->what());
            break;
        }
    }
}

我們看一下AVC解碼器內部是如何處理輸入與輸出數據的,在這個process中,處理完輸入,解碼,處理輸出,在處理output buffer時,process的思路是這樣的:從內存池申請一個GraphicBlock,對應地設置給解碼器Buffer地址以供解碼輸出,如果解碼後有幀輸出,則將當前的GraphicBlock轉換為C2Buffer對象,返回給上層。類似於FFMPEG,你給它一個output frame,它就將解碼圖片填充到frame,你取走顯示。可以推斷,軟解碼器內部應該也有申請一個隊列的buffer,這個隊列維護著解碼所需要的參考圖像。

\\av\media\codec2\components\avc\C2SoftAvcDec.cpp
//省略瞭部分不影響理解主要流程的代碼
void C2SoftAvcDec::process(
        const std::unique_ptr<C2Work> &work,
        const std::shared_ptr<C2BlockPool> &pool) {
    // Initialize output work
    work->result = C2_OK;
    work->workletsProcessed = 0u;
    work->worklets.front()->output.flags = work->input.flags;
    size_t inOffset = 0u;
    size_t inSize = 0u;
    uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
    C2ReadView rView = mDummyReadView;
    if (!work->input.buffers.empty()) {
    	//為瞭得到輸入數據,層層訪問,真正放數據的地址在rView.data()[]中
    	//把work這個對象用思維導圖畫出來,我們可以更容易的理解work,到底擁有哪些成員,如何訪問
        rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
        inSize = rView.capacity();
        ...
    }
    bool eos = ((work->input.flags & C2FrameData::FLAG_END_OF_STREAM) != 0);
    bool hasPicture = false;

    ALOGV("in buffer attr. size %zu timestamp %d frameindex %d, flags %x",
          inSize, (int)work->input.ordinal.timestamp.peeku(),
          (int)work->input.ordinal.frameIndex.peeku(), work->input.flags);
    size_t inPos = 0;
    while (inPos < inSize) {
    	//ensureDecoderState會從內存池中fetch一個GraphicBlock
    	//實質上也就是調用Gralloc接口取得一個output buffer
        if (C2_OK != ensureDecoderState(pool)) {
            mSignalledError = true;
            work->workletsProcessed = 1u;
            work->result = C2_CORRUPTED;
            return;
        }

        ivd_video_decode_ip_t s_decode_ip;
        ivd_video_decode_op_t s_decode_op;
        {
        	//mOutBlock即是上述fetch到的output buffer,通過map映射可以得到一個wView,類似於rView
        	//wView.data()[]指向out buffer的真正地址
        	//wView.data()[C2PlanarLayout::PLANE_Y]就是要存在Y變量的地址
        	//wView.data()[C2PlanarLayout::PLANE_U]就是要存在U變量的地址
            C2GraphicView wView = mOutBlock->map().get();
            ...
            //setDecodeArgs所作的主要工作是,告訴解碼器,輸入數據的地址是什麼,輸出地址包括Y/U/V
            //分量的地址是什麼,輸入數據的長度是多少
            if (!setDecodeArgs(&s_decode_ip, &s_decode_op, &rView, &wView,
                               inOffset + inPos, inSize - inPos, workIndex)) {
                mSignalledError = true;
                work->workletsProcessed = 1u;
                work->result = C2_CORRUPTED;
                return;
            }

            if (false == mHeaderDecoded) {
                /* Decode header and get dimensions */
                setParams(mStride, IVD_DECODE_HEADER);
            }
            //解碼器庫是用瞭第三方的,已經被谷歌收購
            (void) ivdec_api_function(mDecHandle, &s_decode_ip, &s_decode_op);
        }
        if (s_decode_op.i4_reorder_depth >= 0 && mOutputDelay != s_decode_op.i4_reorder_depth) {
        	//目前不清楚把這個重排序長度告訴上層有什麼作用,TODO
            mOutputDelay = s_decode_op.i4_reorder_depth;
            ALOGV("New Output delay %d ", mOutputDelay);

            C2PortActualDelayTuning::output outputDelay(mOutputDelay);
            std::vector<std::unique_ptr<C2SettingResult>> failures;
            c2_status_t err =
                mIntf->config({&outputDelay}, C2_MAY_BLOCK, &failures);
            if (err == OK) {
                work->worklets.front()->output.configUpdate.push_back(
                    C2Param::Copy(outputDelay));
            } 
            continue;
        }
        if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) {
            if (mHeaderDecoded == false) {
                mHeaderDecoded = true;
                setParams(ALIGN64(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME);
            }
            if (s_decode_op.u4_pic_wd != mWidth || s_decode_op.u4_pic_ht != mHeight) {
                mWidth = s_decode_op.u4_pic_wd;
                mHeight = s_decode_op.u4_pic_ht;
                CHECK_EQ(0u, s_decode_op.u4_output_present);

                C2StreamPictureSizeInfo::output size(0u, mWidth, mHeight);
                std::vector<std::unique_ptr<C2SettingResult>> failures;
                c2_status_t err = mIntf->config({&size}, C2_MAY_BLOCK, &failures);
                if (err == OK) {
                    work->worklets.front()->output.configUpdate.push_back(
                        C2Param::Copy(size));
                } 
                continue;
            }
        }
        (void)getVuiParams();
        hasPicture |= (1 == s_decode_op.u4_frame_decoded_flag);
        if (s_decode_op.u4_output_present) {
        	//通過createGraphicBuffer調用,將mOutBlock"轉換"成C2Buffer對象
        	//把C2Buffer添加到work對象的輸出隊列中
        	//通過listener->onWorkDone_nb回調,可以將work返回到CCodec層
        	//以上是這個函數以及其內部調用的主要實現內容,內部調用的finish()函數屬於SimpleC2Component
            finishWork(s_decode_op.u4_ts, work);
        }
        inPos += s_decode_op.u4_num_bytes_consumed;
    }
    if (eos) {
        drainInternal(DRAIN_COMPONENT_WITH_EOS, pool, work);
        mSignalledOutputEos = true;
    } else if (!hasPicture) {
        fillEmptyWork(work);
    }

    work->input.buffers.clear();
}

在Component中,輸入與輸出對象都封裝在work對象中,甚至上下層的配置交互對象也包括在work對象中,與OMX是不一樣的,OMX的數據對象是BufferHeader,輸入是一個Input BufferHeader,輸出是一個Output BufferHeader,對象中包括buffer地址,分配的buffer大小,有效數據長度,有效數據長度的偏移量,buffer標志等。 那麼,work對象也應該會包括類似的成員。

我們來看兩張思維導圖,全局觀察work對象。

在這裡插入圖片描述

在這裡插入圖片描述

C2SoftAvcDec::process中有一句代碼,從work中訪問rView。

rView = work->input.buffers[0]->data().linearBlocks().front().map().get();

從上述兩圖中,我們可以追蹤這一條訪問線路,訪問C2Work對象的成員C2FrameData,繼續訪問C2FrameData對外的成員vector linearBlocks(),C2ConstLinearBlock有一個方法C2Acquirable map(),這個映射方法返回一個C2ReadView對象,這個C2ReadView對象有一個data()[]數組,指向瞭Y/U/V的向量地址,也就是真正存放解碼數據的內存地址。而Input與Output都是以C2FrameData來描述,Output並非像Input一樣,直接作為C2Work的成員,而是作為C2Work->worklets的成員。worklet是一個list類型,C2SoftAvcDec在存放output buffer的時候,總是存放在第一個worklets的output中,參見思維導圖,output是C2FrameData類型,它擁有一個C2Buffer容器,C2SoftAvcDec總是將新的output buffer丟進容器中,它可以一次丟很多個output buffer,然後一次性通過work回送到上層,上層可以一次性從work中取到多個output buffer去作渲染。C2WorkOrdinalStruct ordinal包括著buffer的pts與frameIndex信息。這裡有個疑問待解決,為什麼output buffer總是存放在第一個worklets的output中,worklets作為一個隊列對象,有什麼其他的意義?

上面我們分析瞭兩個點,一個是模塊的消息處理機制,另一個是如何送數據到解碼器再取出幀數據回送到上層,接下來看第三點,CCodec每次送多少輸入數據下來,component每次處理多少數據,回送輸出數據給CCodec作渲染在哪些地方。

上層是調用SimpleC2Component::queue_nb接口送數據下來的。

\\av\media\codec2\components\base\SimpleC2Component.cpp
c2_status_t SimpleC2Component::queue_nb(std::list<std::unique_ptr<C2Work>> * const items) {
    {
        Mutexed<ExecState>::Locked state(mExecState);
        if (state->mState != RUNNING) {
            return C2_BAD_STATE;
        }
    }
    bool queueWasEmpty = false;
    {
        Mutexed<WorkQueue>::Locked queue(mWorkQueue);
        queueWasEmpty = queue->empty();
        while (!items->empty()) {
            queue->push_back(std::move(items->front()));
            items->pop_front();
        }
    }
    if (queueWasEmpty) {
        (new AMessage(WorkHandler::kWhatProcess, mHandler))->post();
    }
    return C2_OK;
}

觀察上面的代碼,入參是一個列表對象,也就是說,每次送多個work,一個work可以包括一個C2Buffer容器,碼流都是放在容器的第一個元素,雖然一個容器可以放多個C2Buffer,但它就隻放瞭一個C2Buffer。我們可以從下面的代碼中發現,每一次的process,都隻從work中取一個C2Buffer。

 \\av\media\codec2\components\avc\C2SoftAvcDec.cpp
 void C2SoftAvcDec::process(
         const std::unique_ptr<C2Work> &work,
         const std::shared_ptr<C2BlockPool> &pool) {
 	...
     uint32_t workIndex = work->input.ordinal.frameIndex.peeku() & 0xFFFFFFFF;
     C2ReadView rView = mDummyReadView;
     if (!work->input.buffers.empty()) {
     	//關註buffers[0]
         rView = work->input.buffers[0]->data().linearBlocks().front().map().get();
         inSize = rView.capacity();
     }
 }

SimpleC2Component::processQueue()每次隻處理一個work,處理完就把work回送上去。

\\av\media\codec2\components\base\SimpleC2Component.cpp
bool SimpleC2Component::processQueue() {
	....
    ALOGV("start processing frame #%" PRIu64, work->input.ordinal.frameIndex.peeku());
    //處理work
    process(work, mOutputBlockPool);
    ALOGV("processed frame #%" PRIu64, work->input.ordinal.frameIndex.peeku());
    Mutexed<WorkQueue>::Locked queue(mWorkQueue);
    if (work->workletsProcessed != 0u) {
        queue.unlock();
        Mutexed<ExecState>::Locked state(mExecState);
        ALOGV("returning this work");
        std::shared_ptr<C2Component::Listener> listener = state->mListener;
        state.unlock();
        //回送work
        listener->onWorkDone_nb(shared_from_this(), vec(work));
    }
    ...
}

在沒有新送下來的work需要處理的時候,processQueue()會調用drain接口作“渲染”操作,它會看解碼器是否有幀數據生成,有的話,就填充到work中回送到上層。

\\av\media\codec2\components\base\SimpleC2Component.cpp
bool SimpleC2Component::processQueue() {
	....
    if (!work) {
        c2_status_t err = drain(drainMode, mOutputBlockPool);
        if (err != C2_OK) {
            Mutexed<ExecState>::Locked state(mExecState);
            std::shared_ptr<C2Component::Listener> listener = state->mListener;
            state.unlock();
            listener->onError_nb(shared_from_this(), err);
        }
        return hasQueuedWork;
    }
    ...
}

另一個渲染的地方是在process()中,解碼完發現有幀數據的時候,就調用finishWork()將work回送。

\\av\media\codec2\components\avc\C2SoftAvcDec.cpp
void C2SoftAvcDec::process(
        const std::unique_ptr<C2Work> &work,
        const std::shared_ptr<C2BlockPool> &pool) {
	...
        if (s_decode_op.u4_output_present) {
            finishWork(s_decode_op.u4_ts, work);
        }
    ...
}

5 小結

Component內部的邏輯還是比較好理解的,重點在於它是如何申請buffer的,如何將buffer“送”給解碼器,解碼完後是如何取得buffer並返回上層,難點在於work對象層層封裝,當你要訪問實際內存地址時,如何訪問,如果要取得內存的handle,又要如何訪問,這一點通過將work對象一層一層的“繪制”出來,就好懂得多。接下來問題來瞭,在OMX中,上下層的交互配置是通過setParamerter/getParamerter等接口進行的,那麼在Codec2中是如何進行的?Codec2中到底有沒有像OMX一樣的BufferCountActual設計?Codec2在調用nativewindow的setMaxDequeuedBufferCount時是如何確定maxDequeueBufferCount的?GraphicBuffer的生命周期是如何控制的?

到此這篇關於一文搞懂Codec2解碼組件的文章就介紹到這瞭,更多相關Codec2解碼組件內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: