Android Codec2 CCodec (三一)组件的创建与配置

接下来的几篇文章,我们将对组件的创建、配置、启动、数据写入、数据处理、数据渲染、flush、release过程做完整的流程分析。本篇我们将一起阅读组件的创建与配置过程。

1、initiateAllocateComponent

CCodec的构造函数会实例化成员CCodecBufferChannel和CCodecConfig:

CCodec::CCodec()
    : mChannel(new CCodecBufferChannel(std::make_shared<CCodecCallbackImpl>(this))),
      mConfig(new CCodecConfig) {
}

成功创建CCodec实例后就可以调用initiateAllocateComponent获取Codec2服务,创建组件实例了:

void CCodec::initiateAllocateComponent(const sp<AMessage> &msg) {
    auto setAllocating = [this] {
        Mutexed<State>::Locked state(mState);
        if (state->get() != RELEASED) {
            return INVALID_OPERATION;
        }
        state->set(ALLOCATING);
        return OK;
    };

    sp<RefBase> codecInfo;
    CHECK(msg->findObject("codecInfo", &codecInfo));
    // For Codec 2.0 components, componentName == codecInfo->getCodecName().

    sp<AMessage> allocMsg(new AMessage(kWhatAllocate, this));
    allocMsg->setObject("codecInfo", codecInfo);
    allocMsg->post();
}

调用initiateAllocateComponent需要传入一个MediaCodecInfo引用。CCodec会发送kWhatAllocate消息到ALooper线程,这边的处理是异步的。

AHandler的onMessageReceived方法会调用到allocate方法:

void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
    // 1. 如果codecInfo为NULL,将无法创建组件
    if (codecInfo == nullptr) {
        mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
        return;
    }
    ALOGD("allocate(%s)", codecInfo->getCodecName());
    // 2. 创建Codec2Client::Listener
    mClientListener.reset(new ClientListener(this));

    AString componentName = codecInfo->getCodecName();
    std::shared_ptr<Codec2Client> client;

    // 3. 获取default service,并返回service的client封装Codec2Client
    client = Codec2Client::CreateFromService("default");
    if (client) {
        ALOGI("setting up '%s' as default (vendor) store", client->getServiceName().c_str());
        // 4. 如果Codec2Client不为NULL,则设置为偏好
        SetPreferredCodec2ComponentStore(
                std::make_shared<Codec2ClientInterfaceWrapper>(client));
    }
    // 5. 调用CreateComponentByName创建组件实例
    std::shared_ptr<Codec2Client::Component> comp;
    c2_status_t status = Codec2Client::CreateComponentByName(
            componentName.c_str(),
            mClientListener,
            &comp,
            &client);

    // 6. 将组件与CCodecBufferChannel绑定
    mChannel->setComponent(comp);
    // 7. 切换CCodec的状态
    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;
    };
    if (tryAndReportOnError(setAllocated) != OK) {
        return;
    }

    Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
    const std::unique_ptr<Config> &config = *configLocked;
    // 8. 使用创建组件实例的服务,获取其参数反射对象IConfigurable
    status_t err = config->initialize(mClient->getParamReflector(), comp);
    // 9. 请求组件参数
    config->queryConfiguration(comp);
    // 10. 向MediaCodec发送callbcak通知组件创建完成,结束阻塞
    mCallback->onComponentAllocated(componentName.c_str());
}

对于allocate方法有如下几点需要了解:

  1. CCodec与HAL服务之间有一层封装Codec2Client,所有的调用或消息需要通过它做转发,ClientListener是CCodec注册给Codec2Client使用的;
  2. 第三步CreateFromService只是创建了一个服务引用,创建组件实例用的可能不是这个服务;
  3. 第五步CreateComponentByName是真正创建组件实例的地方,它会返回封装有IComponent的Codec2Client::Component对象,调用完成后client指向创建IComponent实例的服务;
  4. 第八步Config initialize,它会把创建组件实例的服务作为参数。

CreateComponentByName最终会调用到Codec2Client::createComponent:

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;
    // 1. 创建HidlListener
    sp<Component::HidlListener> hidlListener = new Component::HidlListener{};
    // 2. 绑定到Codec2Client::Listener(ClientListener)
    hidlListener->base = listener;
    Return<void> transStatus;
    if (mBase1_2) { 
        // 3. 调用IComponentStore的createComponent_1_2方法,创建组件实例
        transStatus = mBase1_2->createComponent_1_2(
            name,
            hidlListener,
            ClientManager::getInstance(),
            [&status, component, hidlListener](
                    Status s,
                    const sp<IComponent>& c) {
                // 4. 将返回的IComponent封装为Codec2Client::Component
                *component = std::make_shared<Codec2Client::Component>(c);
                hidlListener->component = *component;
            });
    }

    status = (*component)->setDeathListener(*component, listener);
    // 4. 将IComponentStore进程的ClientManager与组件绑定
    (*component)->mBufferPoolSender->setReceiver(mHostPoolManager);
    return status;
}
  • createComponent需要传入两个参数,一个是组件名称,另一个是Listener(ClientListener),第三个参数component为返回值;
  • 调用createComponent_1_2时需要传入组件名称,HidlListener(IComponentListener)和ClientManager,这是CCodec所在进程的BufferPool。函数调用会返回一个IComponent对象,Codec2Client会将它封装为Codec2Client::Component。

Codec2Client::Component的构造函数如下:

Codec2Client::Component::Component(const sp<Base1_2>& base)
      : Configurable{
            [base]() -> sp<IConfigurable> {
                Return<sp<IComponentInterface>> transResult1 =
                        base->getInterface();
                if (!transResult1.isOk()) {
                    return nullptr;
                }
                Return<sp<IConfigurable>> transResult2 =
                        static_cast<sp<IComponentInterface>>(transResult1)->
                        getConfigurable();
                return transResult2.isOk() ?
                        static_cast<sp<IConfigurable>>(transResult2) :
                        nullptr;
            }()
        },
        mBase1_0{base},
        mBase1_1{base},
        mBase1_2{base},
        mBufferPoolSender{std::make_unique<BufferPoolSender>()},
        mOutputBufferQueue{std::make_unique<OutputBufferQueue>()} {
}

Codec2Client::Component:继承于Configurable,后续使用中看到的comp->config实际上就是在对组件的参数接口做操作。要注意的是构造函数中创建了两个对象:BufferPoolSender和OutputBufferQueue,后续的数据处理过程我们会与它们打交道。

Codec2Client::Component创建完成后,会调用BufferPoolSender的setReceiver方法,将ComponentStore进程的ClientManager与Component绑定,双向绑定后就可以通过BufferPool实现buffer的跨进程传输了。


中场休息

接下来就进入了ComponentStore(HAL)进程。

Return<void> ComponentStore::createComponent_1_2(
        const hidl_string& name,
        const sp<IComponentListener>& listener,
        const sp<IClientManager>& pool,
        createComponent_1_2_cb _hidl_cb) {

    sp<Component> component;
    std::shared_ptr<C2Component> c2component;
    // 1. 使用C2ComponentStore创建C2Component实例
    Status status = static_cast<Status>(
            mStore->createComponent(name, &c2component));

    if (status == Status::OK) {
        // 2. 载入组件参数
        onInterfaceLoaded(c2component->intf());
        // 3. 封装C2Component
        component = new Component(c2component, listener, this, pool);
        if (!component) {
            status = Status::CORRUPTED;
        } else {
            // 4. 记录组件创建时间,将组件加入全局变量引用
            reportComponentBirth(component.get());
            if (component->status() != C2_OK) {
                status = static_cast<Status>(component->status());
            } else {
                // 5. 给C2Component注册回调函数
                component->initListener(component);
                if (component->status() != C2_OK) {
                    status = static_cast<Status>(component->status());
                }
            }
        }
    }
    _hidl_cb(status, component);
    return Void();
}

组件实例是由C2ComponentStore创建,为了跨进程传输需要用IComponent做封装。C2ComponentStore会打开lib,调用lib中的CreateCodec2Factory方法创建出C2ComponentFactory实例,最终使用C2ComponentFactory创建出组件实例。

extern "C" ::C2ComponentFactory* CreateCodec2Factory() {
    ALOGV("in %s", __func__);
    return new ::android::C2SoftHevcDecFactory();
}

class C2SoftHevcDecFactory : public C2ComponentFactory {
public:
    virtual c2_status_t createComponent(
            c2_node_id_t id,
            std::shared_ptr<C2Component>* const component,
            std::function<void(C2Component*)> deleter) override {
        *component = std::shared_ptr<C2Component>(
                new C2SoftHevcDec(COMPONENT_NAME,
                                 id,
                                 std::make_shared<C2SoftHevcDec::IntfImpl>(mHelper)),
                deleter);
        return C2_OK;
    }
}

创建C2SoftHevcDec实例需要有三个参数,分别为组件名称(由C2SoftHevcDec定义)、id(暂未使用)和IntfImpl,IntfImpl定义有组件使用的所有默认参数。

class C2SoftHevcDec::IntfImpl : public SimpleInterface<void>::BaseParams {
public:
    explicit IntfImpl(const std::shared_ptr<C2ReflectorHelper> &helper)
        : SimpleInterface<void>::BaseParams(
                helper,
                COMPONENT_NAME,
                C2Component::KIND_DECODER,
                C2Component::DOMAIN_VIDEO,
                MEDIA_MIMETYPE_VIDEO_HEVC) {
        noPrivateBuffers(); // TODO: account for our buffers here
        noInputReferences();
        noOutputReferences();
        noInputLatency();
        // ...
    }

SimpleInterface<void>::BaseParams 是一个工具类,它定义了一组公用参数以及参数配置方法,IntfImpl继承自它可以很方便地定义参数。

C2SoftHevcDec::C2SoftHevcDec(
        const char *name,
        c2_node_id_t id,
        const std::shared_ptr<IntfImpl> &intfImpl)
    : SimpleC2Component(std::make_shared<SimpleInterface<IntfImpl>>(name, id, intfImpl)),
        mIntf(intfImpl),
        mDecHandle(nullptr),
        mOutBufferFlush(nullptr),
        mIvColorformat(IV_YUV_420P),
        mOutputDelay(kDefaultOutputDelay),
        mWidth(320),
        mHeight(240),
        mHeaderDecoded(false),
        mOutIndex(0u) {
}

C2SoftMpeg2Dec继承自SimpleC2Component,所以再来看一下SimpleC2Component的构造函数:

SimpleC2Component::SimpleC2Component(
        const std::shared_ptr<C2ComponentInterface> &intf)
    : mDummyReadView(DummyReadView()),
      mIntf(intf),
      mLooper(new ALooper),
      mHandler(new WorkHandler) {
    mLooper->setName(intf->getName().c_str());
    (void)mLooper->registerHandler(mHandler);
    mLooper->start(false, false, ANDROID_PRIORITY_VIDEO);
}

SimpleC2Component创建了一个ALooper并启动了它。

最后再看一下对C2Component的封装:

Component::Component(
        const std::shared_ptr<C2Component>& component,
        const sp<IComponentListener>& listener,
        const sp<ComponentStore>& store,
        const sp<::android::hardware::media::bufferpool::V2_0::
        IClientManager>& clientPoolManager)
      : mComponent{component},
        mInterface{new ComponentInterface(component->intf(),
                                          store->getParameterCache())},
        mListener{listener},
        mStore{store},
        mBufferPoolSender{clientPoolManager} {
    // Retrieve supported parameters from store
    // TODO: We could cache this per component/interface type
    mInit = mInterface->status();
}

看到这我们会发现Codec2Client传递的参数(pool和listener)没有给C2Component使用,它们会一同与C2Component实例封装为Component(IComponent)。C2Component使用的callback(C2Component::Listener)需要让ComponentStore调用initListener才能注册下去。

到这组件创建就完成了,用图来帮助记忆整体的调用层级:

2、initiateConfigureComponent


原文阅读:
Android Codec2(三一)组件的创建与配置

扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。

Android中,编解码6K视频可以通过使用CCodec来实现。CCodecAndroid Q以后的版本采用的一种加载解码插件的方式。在CCodec中,可以通过添加相应的信息到kComponents列表中来支持自定义解码库。这些信息包括组件名称、软件编解码库后缀名和组件角色。在NuPlayer中,解码器由NuPlayer::Decoder进行抽象。在NuPlayer开始后,首先完成了IMediaSource的开始,然后通过异步消息kWhatScanSources开始执行NuPlayer::instantiateDecoder()实例化解码器。对于音频解码器,会创建DecoderPassThroughDecoder,而对于视频解码器,只会创建Decoder。在Decoder的configure()方法中,会通过MediaCodec::CreateByType()创建MediaCodec,并通过MediaCodecList::findMatchingCodecs()查找支持当前解码格式的解码器的名字。创建MediaCodec时,会初始化mGetCodecBase为一个std::function对象,后续的MediaCodec::init()会调用此lambda函数来创建更底层的CodecBase,其中包括CCodec创建。因此,通过使用CCodec和相关的组件,可以实现Android上对6K视频的编解码。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [Android 12 原生播放器的编解码 Codec 2](https://blog.youkuaiyun.com/liyangzmx/article/details/124582754)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Android 系统软解码方案实现](https://blog.youkuaiyun.com/shijiheike/article/details/130107438)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值