接下来的几篇文章,我们将对组件的创建、配置、启动、数据写入、数据处理、数据渲染、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方法有如下几点需要了解:
- CCodec与HAL服务之间有一层封装Codec2Client,所有的调用或消息需要通过它做转发,ClientListener是CCodec注册给Codec2Client使用的;
- 第三步CreateFromService只是创建了一个服务引用,创建组件实例用的可能不是这个服务;
- 第五步CreateComponentByName是真正创建组件实例的地方,它会返回封装有IComponent的Codec2Client::Component对象,调用完成后client指向创建IComponent实例的服务;
- 第八步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(三一)组件的创建与配置
扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。