Android CCodec Codec2 (三二)组件启动与数据写入

这一节我们一起过一下组件的启动与数据写入过程。

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件事情:

  1. 调用组件的start方法;
  2. 调用CCodecBufferChannel的start方法;
  3. 调用prepareInitialInputBuffers;
  4. 调用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,
        &params);

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(三二)组件启动与数据写入

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青山渺渺

感谢支持

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

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

打赏作者

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

抵扣说明:

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

余额充值