这一节我们一起过一下组件的启动与数据写入过程。
1、initiateStart
调用initiateStart方法会发消息kWhatStart,最终消息会有AHandler来处理。start方法不长,但是做的事情非常多!
void CCodec::start() {
// 1. 调用组件的start方法
c2_status_t err = comp->start();
sp<AMessage> inputFormat;
sp<AMessage> outputFormat;
status_t err2 = OK;
bool buffersBoundToCodec = false;
{
Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
const std::unique_ptr<Config> &config = *configLocked;
inputFormat = config->mInputFormat;
// start triggers format dup
outputFormat = config->mOutputFormat = config->mOutputFormat->dup();
if (config->mInputSurface) {
err2 = config->mInputSurface->start();
config->mInputSurfaceDataspace = config->mInputSurface->getDataspace();
}
buffersBoundToCodec = config->mBuffersBoundToCodec;
}
// 2. 调用CCodecBufferChannel的start方法
err2 = mChannel->start(inputFormat, outputFormat, buffersBoundToCodec);
// 3. 调用prepareInitialInputBuffers
std::map<size_t, sp<MediaCodecBuffer>> clientInputBuffers;
err2 = mChannel->prepareInitialInputBuffers(&clientInputBuffers);
mCallback->onStartCompleted();
// 4. 调用requestInitialInputBuffers
mChannel->requestInitialInputBuffers(std::move(clientInputBuffers));
}
start总共干了4件事情:
- 调用组件的start方法;
- 调用CCodecBufferChannel的start方法;
- 调用prepareInitialInputBuffers;
- 调用requestInitialInputBuffers;
接下来我们会对这四步一一展开。
1.1 component start
Codec2Client和IComponent没有对start做过多处理,直接一路进入到SimpleC2Component::start方法中:
c2_status_t SimpleC2Component::start() {
// 1.
Mutexed<ExecState>::Locked state(mExecState);
if (state->mState == RUNNING) {
return C2_BAD_STATE;
}
bool needsInit = (state->mState == UNINITIALIZED);
state.unlock();
// 2.
if (needsInit) {
sp<AMessage> reply;
(new AMessage(WorkHandler::kWhatInit, mHandler))->postAndAwaitResponse(&reply);
int32_t err;
CHECK(reply->findInt32("err", &err));
if (err != C2_OK) {
return (c2_status_t)err;
}
} else {
// 3.
(new AMessage(WorkHandler::kWhatStart, mHandler))->post();
}
// 4.
state.lock();
state->mState = RUNNING;
return C2_OK;
}
组件创建之后ExecState的默认状态为UNINITIALIZED。进入start方法首先会检查状态是否为RUNNING,如果是说明上层重复调用了start;如果不是则继续向下,判断状态是否为UNINITIALIZED,如果是说明组件创建之后还未启动,如果不是说明组件已经进入STOPPED状态。
如果组件当前状态为UNINITIALIZED,会发送一条kWhatInit消息:
void SimpleC2Component::WorkHandler::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatInit: {
int32_t err = thiz->onInit();
Reply(msg, &err);
[[fallthrough]];
}
case kWhatStart: {
mRunning = true;
break;
}
}
}
处理这条消息会分为两步,一是调用组件实现的onInit方法初始化decoder,然后将mRunning置为true,这部分我们在前面的文章中已经详细了解了,这边不再展开。
1.2 CCodecBufferChannel start
CCodecBufferChannel的start方法之前已经做过比较详细的学习,这里只做一些简单展开。start方法向组件请求一系列信息,然后对输入输出端口做不同的处理:
- input:根据参数判断组件要使用的inputAllocatorId,创建对应的input BlockPool,最后选择合适的端口管理类,根据是否要用ArrayMode将端口管理类转换为数组模式;
- output:根据参数判断组件要使用的outputAllocatorId,调用组件的createBlockPool在HAL中创建output BlockPool,选择合适的端口管理类,调用组件的setOutputSurface方法;
- 初始化PipelineWatcher;
HAL进程中的BlockPool创建是由Component完成的,最终会把BlockPoolId返回给CCodecBufferChannel。
Return<void> Component::createBlockPool(
uint32_t allocatorId,
createBlockPool_cb _hidl_cb) {
std::shared_ptr<C2BlockPool> blockPool;
c2_status_t status = ComponentStore::GetFilterWrapper()->createBlockPool(
static_cast<C2PlatformAllocatorStore::id_t>(allocatorId),
mComponent,
&blockPool);
if (blockPool) {
mBlockPools.emplace(blockPool->getLocalId(), blockPool);
}
_hidl_cb(static_cast<Status>(status),
blockPool ? blockPool->getLocalId() : 0,
new CachedConfigurable(
std::make_unique<BlockPoolIntf>(blockPool)));
return Void();
}
Component和C2Component实例没有继承关系,C2Component是如何知道它要使用哪个pool实例的?答案是通过参数设定告知组件要使用哪个BlockPoolId。
// CCodecBufferChannel::start
std::unique_ptr<C2PortBlockPoolsTuning::output> poolIdsTuning =
C2PortBlockPoolsTuning::output::AllocUnique({ pools->outputPoolId });
std::vector<std::unique_ptr<C2SettingResult>> failures;
err = mComponent->config({ poolIdsTuning.get() }, C2_MAY_BLOCK, &failures);
// SimpleC2Component::processQueue
c2_status_t err = intf()->query_vb(
{ &outputFormat },
{ C2PortBlockPoolsTuning::output::PARAM_TYPE },
C2_DONT_BLOCK,
¶ms);
if (params.size()) {
C2PortBlockPoolsTuning::output *outputPools =
C2PortBlockPoolsTuning::output::From(params[0].get());
if (outputPools && outputPools->flexCount() >= 1) {
poolId = outputPools->m.values[0];
}
}
std::shared_ptr<C2BlockPool> blockPool;
err = GetCodec2BlockPool(poolId, shared_from_this(), &blockPool);
BlockPool实例的生命周期是如何管理的呢?_C2BlockPoolCache中管理着一个弱引用,GetCodec2BlockPool会把弱引用转换为强引用,这样就实现了BlockPool的获取。当所有的强引用都释放掉,智能指针的析构函数会自动将弱引用从_C2BlockPoolCache清除。
再来看一下setOutputSurface,这个方法之前已经讲过了,所以只做一些简单展开。这个方法是Codec2Client::Component里的方法,它主要是对OutputBufferQueue和组件做一些配置。
c2_status_t Codec2Client::Component::setOutputSurface(
C2BlockPool::local_id_t blockPoolId,
const sp<IGraphicBufferProducer>& surface,
uint32_t generation,
int maxDequeueCount) {
// 1.
mOutputBufferQueue->configure(surface, generation, bqId, maxDequeueCount, mBase1_2 ?
&syncObj : nullptr);
// 2.
Return<Status> transStatus = syncObj ?
mBase1_2->setOutputSurfaceWithSyncObj(
static_cast<uint64_t>(blockPoolId),
bqId == 0 ? nullHgbp : igbp, *syncObj) :
mBase1_0->setOutputSurface(
static_cast<uint64_t>(blockPoolId),
bqId == 0 ? nullHgbp : igbp);
OutputBufferQueue::configure会创建一个同步对象,setOutputSurface会把这个同步对象设置给组件。
Return<Status> Component::setOutputSurfaceWithSyncObj(
uint64_t blockPoolId, const sp<HGraphicBufferProducer2>& surface,
const SurfaceSyncObj& syncObject) {
std::shared_ptr<C2BlockPool> pool;
// 1. 根据id拿到BlockPool
GetCodec2BlockPool(blockPoolId, mComponent, &pool);
if (pool && pool->getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {
std::shared_ptr<C2BufferQueueBlockPool> bqPool =
std::static_pointer_cast<C2BufferQueueBlockPool>(pool);
C2BufferQueueBlockPool::OnRenderCallback cb =
[this](uint64_t producer, int32_t slot, int64_t nsecs) {
// TODO: batch this
hidl_vec<IComponentListener::RenderedFrame> rendered;
rendered.resize(1);
rendered[0] = { producer, slot, nsecs };
(void)mListener->onFramesRendered(rendered).isOk();
};
if (bqPool) {
const native_handle_t *h = syncObject.syncMemory;
native_handle_t *syncMemory = h ? native_handle_clone(h) : nullptr;
uint64_t bqId = syncObject.bqId;
uint32_t generationId = syncObject.generationId;
uint64_t consumerUsage = syncObject.consumerUsage;
// 2. 注册rendercallback
bqPool->setRenderCallback(cb);
// 3. 设置同步对象
bqPool->configureProducer(surface, syncMemory, bqId,
generationId, consumerUsage);
}
}
return Status::OK;
}
setOutputSurfaceWithSyncObj会把同步对象设置给BlockPool,要注意的是只有C2BufferQueueBlockPool会使用同步对象。
1.3 prepareInitialInputBuffers
prepareInitialInputBuffers用于分配出所有的input buffer,返回index和MediaCodecBuffer:
status_t CCodecBufferChannel::prepareInitialInputBuffers(
std::map<size_t, sp<MediaCodecBuffer>> *clientInputBuffers) {
if (mInputSurface) {
return OK;
}
// 1. 获取input buffer数量
size_t numInputSlots = mInput.lock()->numSlots;
{
Mutexed<Input>::Locked input(mInput);
while (clientInputBuffers->size() < numInputSlots) {
size_t index;
sp<MediaCodecBuffer> buffer;
if (!input->buffers->requestNewBuffer(&index, &buffer)) {
break;
}
clientInputBuffers->emplace(index, buffer);
}
}
if (clientInputBuffers->empty()) {
ALOGW("[%s] start: cannot allocate memory at all", mName);
return NO_MEMORY;
} else if (clientInputBuffers->size() < numInputSlots) {
ALOGD("[%s] start: cannot allocate memory for all slots, "
"only %zu buffers allocated",
mName, clientInputBuffers->size());
} else {
ALOGV("[%s] %zu initial input buffers available",
mName, clientInputBuffers->size());
}
return OK;
}
对于不同的端口管理类,requestNewBuffer分配/获取buffer的方式不同,index的计算方式也不同。MediaCodecBuffer的封装参考之前的文章。
CCodec调用prepareInitialInputBuffers,返回值会存在一个临时的map list中,不会有一个持久存储区来存储拿到的buffer。
1.4 requestInitialInputBuffers
requestInitialInputBuffers会将刚刚拿到的临时的map list中的buffer一个一个交还给上层应用:
status_t CCodecBufferChannel::requestInitialInputBuffers(
std::map<size_t, sp<MediaCodecBuffer>> &&clientInputBuffers) {
C2StreamBufferTypeSetting::output oStreamFormat(0u);
C2PrependHeaderModeSetting prepend(PREPEND_HEADER_TO_NONE);
c2_status_t err = mComponent->query({ &oStreamFormat, &prepend }, {}, C2_DONT_BLOCK, nullptr);
if (err != C2_OK && err != C2_BAD_INDEX) {
return UNKNOWN_ERROR;
}
// 1. flush之后调用requestInitialInputBuffers会进入此case,暂时忽略
std::list<std::unique_ptr<C2Work>> flushedConfigs;
mFlushedConfigs.lock()->swap(flushedConfigs);
if (!flushedConfigs.empty()) {
{
Mutexed<PipelineWatcher>::Locked watcher(mPipelineWatcher);
PipelineWatcher::Clock::time_point now = PipelineWatcher::Clock::now();
for (const std::unique_ptr<C2Work> &work : flushedConfigs) {
watcher->onWorkQueued(
work->input.ordinal.frameIndex.peeku(),
std::vector(work->input.buffers),
now);
}
}
err = mComponent->queue(&flushedConfigs);
}
// 2. 暂不了解
if (oStreamFormat.value == C2BufferData::LINEAR &&
(!prepend || prepend.value == PREPEND_HEADER_TO_NONE) &&
!clientInputBuffers.empty()) {
size_t minIndex = clientInputBuffers.begin()->first;
sp<MediaCodecBuffer> minBuffer = clientInputBuffers.begin()->second;
for (const auto &[index, buffer] : clientInputBuffers) {
if (minBuffer->capacity() > buffer->capacity()) {
minIndex = index;
minBuffer = buffer;
}
}
minBuffer->setRange(0, 0);
minBuffer->meta()->clear();
minBuffer->meta()->setInt64("timeUs", 0);
if (queueInputBufferInternal(minBuffer) != OK) {
ALOGW("[%s] Error while queueing an empty buffer to get CSD",
mName);
return UNKNOWN_ERROR;
}
clientInputBuffers.erase(minIndex);
}
// 3. 将buffer送回到上层
for (const auto &[index, buffer] : clientInputBuffers) {
mCallback->onInputBufferAvailable(index, buffer);
}
return OK;
}
代码很长,我们只要知道用了onInputBufferAvailable将buffer返回到上层就可以了。
2、queueInputBuffer
上层写入input data后可以调用queueInputBuffer将buffer送给组件:
status_t CCodecBufferChannel::queueInputBuffer(const sp<MediaCodecBuffer> &buffer) {
QueueGuard guard(mSync);
if (!guard.isRunning()) {
ALOGD("[%s] No more buffers should be queued at current state.", mName);
return -ENOSYS;
}
return queueInputBufferInternal(buffer);
}
原文阅读:
Android Codec2(三二)组件启动与数据写入
扫描下方二维码,关注公众号《青山渺渺》阅读音视频开发内容。