返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >一文搞懂Codec2解码组件
  • 712
分享到

一文搞懂Codec2解码组件

2024-04-02 19:04:59 712人浏览 八月长安
摘要

目录1 前言2 组件的创建3 组件接口4 组件运行原理5 小结1 前言 在本篇中,我们将关注Codec 2.0以下几个问题: 1.从顶而下,一个解码组件是如何创建的 2.组件的接口有

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:
    
        
        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
        };
    
        
        struct ComponentLoader {
            
            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;
            }
    
            
            ComponentLoader(std::string libPath)
                : mLibPath(libPath) {}
    
        private:
            std::weak_ptr<ComponentModule> mModule; ///< weak reference to the loaded module
        };
    
        struct Interface : public C2InterfaceHelper {
    	...
        };
    
        
        c2_status_t findComponent(C2String name, std::shared_ptr<ComponentModule> *module);
    
        
        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) {
                
                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解码组件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 一文搞懂Codec2解码组件

本文链接: https://lsjlt.com/news/135175.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 一文搞懂Codec2解码组件
    目录1 前言2 组件的创建3 组件接口4 组件运行原理5 小结1 前言 在本篇中,我们将关注Codec 2.0以下几个问题: 1.从顶而下,一个解码组件是如何创建的 2.组件的接口有...
    99+
    2024-04-02
  • 一文搞懂Codec2框架解析
    目录1 前言–Codec2.0是什么2 Codec2.0框架3 流程解析3.1 初始化流程3.2 启动流程3.3 Input Buffer的回调3.4 Output Buffer的回...
    99+
    2024-04-02
  • 一文搞懂Vue2中的组件通信
    目录vue 组件通信方式1.props传参2.emit,on通信3.$ref,$children实例通信4.$parent通信5.插槽通信(一般不用)6.$attr,$listene...
    99+
    2024-04-02
  • 一文搞懂HBA卡
    HBA卡是一个简称,准确叫法应该是:主机总线适配器(Host Bus Adapter,HBA),也叫做FC-HBA卡(俗称:光纤网卡)、iSCSI-HBA卡(RJ45接口)。这是一个在服务器和存储装置间提供输入/输出(I/O)处理和物理连接...
    99+
    2023-08-31
    服务器
  • 一文搞懂Vue3中的异步组件defineAsyncComponentAPI的用法
    目录前言传递工厂函数作为参数传递对象类型作为参数总结前言 当我们的项目达到一定的规模时,对于某些组件来说,我们并不希望一开始全部加载,而是需要的时候进行加载;这样的做得目的可以很好的...
    99+
    2024-04-02
  • 一文搞懂Python的文件路径操作
    如果你要在代码里读取一个文件,那么你首先要知道这个文件的路径。如果只有一个文件,那么很简单,直接复制这个文件所在的文件夹路径及其文件名即可。而在很多情况下,我们会处理大量的文件,这些文件一般都会按一定...
    99+
    2023-08-31
    python linux windows
  • 一文带你搞懂Python中的pyc文件
    目录pyc 文件的触发pyc 文件的导入pyc 文件包含的内容pyc 文件的写入字节码混淆pyc 文件的触发 上一篇文章我们介绍了字节码,当时提到,py 文件在执行的时候会先被编译成...
    99+
    2022-12-28
    Python pyc文件写入 Python pyc文件 Python pyc
  • 一文搞懂 Elasticsearch 之 Mapping
    作为 Elasticsearch 的“表结构定义”的 Mapping,你可能需要了解下! 这篇文章主要介绍 Mapping、Dynamic Mapping 以及 ElasticSearch 是如何自动判...
    99+
    2018-08-02
    一文搞懂 Elasticsearch Mapping
  • netstat命令,一文搞懂
    netstat命令是一个网络工具,用于显示计算机网络的连接状态和统计数据。它可以列出所有活动的网络连接,包括正在监听的端口、正在建立...
    99+
    2023-09-12
    netstat
  • Nginx详解(一文带你搞懂Nginx)
    前言 最近进入了新篇章的学习,Nginx,特写下详细笔记与大家共享。 目录 前言一、Nginx是什么?二、Nginx的反向代理(扩展:正向代理)三、Nginx的负载均衡什么是负载均衡? 四、Nginx的动静分离!五、Nginx的...
    99+
    2023-08-30
    nginx 服务器
  • 一文搞懂关于 sys.argv 的详解
    目录详解 sys.argvps:sys.argv[]的用法一、介绍二、简单的例子三、输入为 --numa=1  --numb=2 形式和  --numa...
    99+
    2023-01-15
    sys.argv 是什么 python sys.argv sys.argv[]的用法
  • 一文搞懂Node的的事件循环
    本篇文章聊聊Nodejs中的事件循环,希望带大家搞懂Nodejs中的事件循环,从此再也不怕面试官的灵魂发问:谈一下Nodejs的事件循环!想必大家面试的时候,都会被面试官问道:“谈谈Nodejs的事件循环吧”。因为本人也被问道过,但每一次都...
    99+
    2023-05-14
    后端 Node.js
  • 一文搞懂C语言中的文件操作
    目录一、文件操作1、 为什要使用文件操作2、什么是文件3、文件操作的使用一、文件操作 1、 为什要使用文件操作 在c语言中我们完成一个程序后,他并不会对我们的数据进行保存,就像我上一...
    99+
    2022-11-21
    C语言文件操作 C语言 文件
  • 一文带你搞懂JavaScript中数组的特性
    目录前言基本介绍数组类型和判断判断为数组的方式数组索引值和长度索引值是字符串length属性数组的最大长度创建数组的三种方式数组字面量语法Array构造函数Array.of()空位(...
    99+
    2023-05-17
    JavaScript数组特性 JavaScript数组
  • 一文搞懂MySQL预编译
    1、预编译的好处   大家平时都使用过JDBC中的PreparedStatement接口,它有预编译功能。什么是预编译功能呢?它有什么好处呢?   当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语...
    99+
    2022-05-23
    MySQL 预编译 MySQL 编译
  • 一文搞懂Spring中的JavaConfig
    目录配置类注册组件扫描包配置事务注解驱动单元测试加载配置类properties配置文件加载(了解)aspectj注解开关传统spring一般都是基于xml配置的,不过后来新增了许多J...
    99+
    2024-04-02
  • 一文搞懂JSON(JavaScript Object Notation)
    目录JSON出现Json结构Json对象Json对象与JavaScript对象JSON 和 JavaScript 对象互转Json数组复杂数组类型复杂对象数组组合对象包含数组数组包含...
    99+
    2024-04-02
  • 一文搞懂Python爬虫解析器BeautifulSoup4
    本篇文章给大家带来了关于Python的相关知识,其中主要整理了爬虫解析器BeautifulSoup4的相关问题,Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库,它能够通过你喜欢的转换器实现惯用的文档导...
    99+
    2022-07-12
    python
  • 一文搞懂MySQL中EXPLAIN解释命令
    本文主要给大家简单讲讲MySQL中EXPLAIN解释命令,相关专业术语大家可以上网查查或者找一些相关书籍补充一下,这里就不涉猎了,我们就直奔主题吧,希望MySQL中EXPLAIN解释命令这篇文章可以给大家带...
    99+
    2024-04-02
  • 一文带你搞懂Go如何读写Excel文件
    目录1.下载依赖库2.具体操作2.1 生成一个新的Excel文件2.2 向Excel文件中追加内容2.3 解析Excel文件内容2.4 使用Http协议上传并解析Excel文件2.5...
    99+
    2022-11-13
    Go读写Excel文件 Go读写Excel Go Excel
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作