Android音视频 MediaCodec框架-启动编码(4)

Android音视频 MediaCodec框架-启动编码

简述

上一节我们介绍了MediaCodec框架创建编码器流程,编解码的流程其实基本是一样的,只是底层的最终的实现组件不同,所以我们只看启动编码流程。

MediaCodec启动编码

从MediaCodec的start方法开始。

1.1 MediaCodec.start

调用jni方法native_start

复制代码
public final void start() {
    native_start();
}

1.2 native_start

调用JMediaCodec的start方法。

复制代码
static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) {
    ALOGV("android_media_MediaCodec_start");

    sp<JMediaCodec> codec = getMediaCodec(env, thiz);

    // ... JMediaCodec状态检测
    // 详见1.3
    status_t err = codec->start();

    // ...
}

1.3 JMediaCodec::start

调用MediaCodec的start方法。

复制代码
status_t JMediaCodec::start() {
    // 详见1.4
    return mCodec->start();
}

1.4 MediaCodec::start

发送kWhatStart的AMessage通知CCodec2 start,这个流程和init很类似,处理消息start的流程详见1.5

复制代码
status_t MediaCodec::start() {
    sp<AMessage> msg = new AMessage(kWhatStart, this);

    sp<AMessage> callback;

    status_t err;
    std::vector<MediaResourceParcel> resources;
    resources.push_back(MediaResource::CodecResource(mFlags & kFlagIsSecure,
            toMediaResourceSubType(mDomain)));
    resources.push_back(MediaResource::GraphicMemoryResource(1));
    for (int i = 0; i <= kMaxRetry; ++i) {
        if (i > 0) {
            // ...
            sp<AMessage> response;
            err = PostAndAwaitResponse(mConfigureMsg, &response);
            // ...
        }

        // ...
    }
    return err;
}

1.5 MediaCodec::onMessageReceived

消息处理方法,调用CCodec的initiateStart

复制代码
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    // ...
    case kWhatStart:
    {
        // ...
        // 更新状态到STARTING
        setState(STARTING);
        // 调用CCodec的initiateStart,详见1.6
        mCodec->initiateStart();
        break;
    }
    // ...
}

1.6 CCodec::initiateStart

这里只是修改了状态到STARTING,然后做了消息转发。

复制代码
void CCodec::initiateStart() {
    auto setStarting = [this] {
        Mutexed<State>::Locked state(mState);
        if (state->get() != ALLOCATED) {
            return UNKNOWN_ERROR;
        }
        state->set(STARTING);
        return OK;
    };
    if (tryAndReportOnError(setStarting) != OK) {
        return;
    }

    (new AMessage(kWhatStart, this))->post();
}

1.7 CCodec::onMessageReceived

调用CCodec::start方法。

复制代码
void CCodec::onMessageReceived(const sp<AMessage> &msg) {
    // ...
    case kWhatStart: {
        // 调用start方法
        setDeadline(now, 1500ms, "start");
        start();
        break;
    }
    // ...
}

1.8 CCodec::start

该方法主要做了几件事:

调用Component的start方法,Codec2的Component都是基于SimpleC2Component实现的,SimpleC2Component中处理了一些状态管理的逻辑,不同的编解码组件继承了SimpleC2Component,实现生命周期回调方法,例如onInit等。

从CCodecConfig获取mOutputFormat或者inputFormat信息,前者是解码时会有,后者是编码才会有的。

调用CCodecBufferChannel::start。

复制代码
void CCodec::start() {
    // ...
    // 调用Component start,Codec2的Component都是基于SimpleC2Component实现的,SimpleC2Component中处理了一些状态管理的逻辑
    // 不同的编解码最终实现都继承于SimpleC2Component,然后实现对应的生命周期回调方法做自己的事,由于我们H264的实现里没有做什么事,就不看了。
    c2_status_t err = comp->start();
    if (err != C2_OK) {
        mCallback->onError(toStatusT(err, C2_OPERATION_Component_start),
                        ACTION_CODE_FATAL);
        return;
    }
    sp<AMessage> inputFormat;
    sp<AMessage> outputFormat;
    status_t err2 = OK;
    bool buffersBoundToCodec = false;
    {
        // CCodecConfig是在之前初始化的,里面有配置信息,这里mOutputFormat是解码时候才有的,而inputFormat则是编码的时候才有
        // mInputSurface是编码时候配置了InputSurface时传入的参数,表示编码数据来源于一个Surface
        // 我们之前在SurfaceFlinger章节说过,Surface是表示一个窗口,可以作为图像BufferQueue的一个生产者。
        Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
        const std::unique_ptr<Config> &config = *configLocked;
        inputFormat = config->mInputFormat;
        outputFormat = config->mOutputFormat = config->mOutputFormat->dup();
        if (config->mInputSurface) {
            err2 = config->mInputSurface->start();
            config->mInputSurfaceDataspace = config->mInputSurface->getDataspace();
        }
        buffersBoundToCodec = config->mBuffersBoundToCodec;
    }
    if (err2 != OK) {
        mCallback->onError(err2, ACTION_CODE_FATAL);
        return;
    }
    // 调用CCodecBufferChannel::start,详见1.9
    err2 = mChannel->start(inputFormat, outputFormat, buffersBoundToCodec);
    if (err2 != OK) {
        mCallback->onError(err2, ACTION_CODE_FATAL);
        return;
    }

    // ... 更新状态


    std::map<size_t, sp<MediaCodecBuffer>> clientInputBuffers;
    // 根据input里numSlots数量,调用input->buffers->requestNewBuffer创建buffer填充clientInputBuffers。
    err2 = mChannel->prepareInitialInputBuffers(&clientInputBuffers);
    if (err2 != OK) {
        ALOGE("Initial preparation for Input Buffers failed");
        mCallback->onError(err2, ACTION_CODE_FATAL);
        return;
    }
    // 回调MediaCodec onStartCompleted
    mCallback->onStartCompleted();

    mChannel->requestInitialInputBuffers(std::move(clientInputBuffers));
}

1.9 CCodecBufferChannel::start

这个方法很长,主要是一些参数的初始化,分为编码和解码的情况,其中最重要的参数就是C2BlockPool和input/output。

以编码为例,input里面有一个buffers,这个buffers有多种类型,buffers会持有一个C2BlockPool来分配内存,而C2BlockPool又会通过Allocator分配C2Buffer,这里的Allocator也是在hal层,Allocator是通过AllocatorStore到hal获取到。

所以相当于在上层使用一个buffers来控制buffer到分配释放等,这个buffers类型比如LinearInputBuffers,GraphicInputBuffers,GraphicMetadataInputBuffers等,而buffers会通过C2BlockPool分配buffer,而C2BlockPool会通过Allocator到hal层获取buffer。

解码的情况类似,这里就不细说了。

复制代码
status_t CCodecBufferChannel::start(
        const sp<AMessage> &inputFormat,
        const sp<AMessage> &outputFormat,
        bool buffersBoundToCodec) {
    C2StreamBufferTypeSetting::input iStreamFormat(0u);
    C2StreamBufferTypeSetting::output oStreamFormat(0u);
    C2ComponentKindSetting kind;
    C2PortReorderBufferDepthTuning::output reorderDepth;
    C2PortReorderKeySetting::output reorderKey;
    C2PortActualDelayTuning::input inputDelay(0);
    C2PortActualDelayTuning::output outputDelay(0);
    C2ActualPipelineDelayTuning pipelineDelay(0);
    C2SecureModeTuning secureMode(C2Config::SM_UNPROTECTED);
    // ... 参数初始化以及检测

    // 由C2AllocateStore来获取Allocation,而Allocation是用于分配buffer的,有不同类型的buffer,对应底层分配的内存可能也不同,例如dma
    std::shared_ptr<C2AllocatorStore> allocatorStore = GetCodec2PlatformAllocatorStore();
    int poolMask = GetCodec2PoolMask();
    C2PlatformAllocatorStore::id_t preferredLinearId = GetPreferredLinearAllocatorId(poolMask);
    // 编码
    if (inputFormat != nullptr) {
        // ... 参数配置
        // 构造C2BlockPool
        std::shared_ptr<C2BlockPool> pool;
        {
            Mutexed<BlockPools>::Locked pools(mBlockPools);

            // ... 参数检查配置

            if ((poolMask >> pools->inputAllocatorId) & 1) {
                // 根据inputAllocatorId构造C2BlockPool,C2BlockPool里面持有Allocator,通过擦欧总Allocator管理Buffer的构建
                err = CreateCodec2BlockPool(pools->inputAllocatorId, nullptr, &pool);
                // ...
            } else {
                err = C2_NOT_FOUND;
            }
            // ...异常处理

            pools->inputPool = pool;
        }

        bool forceArrayMode = false;
        Mutexed<Input>::Locked input(mInput);
        // ...构造填充input对象
        // 其中input->buffers会根据buffer的类型不同而不同
        // 这里的关系是input->buffers会最终提供给CCodec分配Buffer的能力,而input->buffers是通过前面构造的C2BlockPool来获取或者释放buffer
        // 而C2BlockPool通过持有的Allocator来分配不同Buffer,不同的Buffer的区别在于底层可能使用不同的系统调用来分配内存,可能是dma之类的。
        
        input->buffers->setFormat(inputFormat);

        if (err == C2_OK) {
            input->buffers->setPool(pool);
        } else {
            // TODO: error
        }

        if (forceArrayMode) {
            input->buffers = input->buffers->toArrayMode(numInputSlots);
        }
    }

    // 解码,这里和上面编码类似,主要是初始化一些必要对象。
    if (outputFormat != nullptr) {
        sp<IGraphicBufferProducer> outputSurface;
        uint32_t outputGeneration;
        int maxDequeueCount = 0;
        {
            Mutexed<OutputSurface>::Locked output(mOutputSurface);
            maxDequeueCount = output->maxDequeueBuffers = numOutputSlots +
                    reorderDepth.value + mRenderingDepth;
            outputSurface = output->surface ?
                    output->surface->getIGraphicBufferProducer() : nullptr;
            if (outputSurface) {
                output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);
            }
            outputGeneration = output->generation;
        }

        bool graphic = (oStreamFormat.value == C2BufferData::GRAPHIC);
        C2BlockPool::local_id_t outputPoolId_;
        C2BlockPool::local_id_t prevOutputPoolId;

        {
            Mutexed<BlockPools>::Locked pools(mBlockPools);
            // ... 初始化BlockPools,和解码流程构造C2BlockPools类似。

        }

        Mutexed<Output>::Locked output(mOutput);
        // ... 构造output,和上面input类似

        // 如果接受输出是一个Surface,通知给Component
        if (outputSurface) {
            mComponent->setOutputSurface(
                    outputPoolId_,
                    outputSurface,
                    outputGeneration,
                    maxDequeueCount);
        } else {
            // ...
        }
        // ...

    }
    // 编解码监测器初始化
    if (inputFormat || outputFormat) {
        Mutexed<PipelineWatcher>::Locked watcher(mPipelineWatcher);
        watcher->inputDelay(inputDelayValue)
                .pipelineDelay(pipelineDelayValue)
                .outputDelay(outputDelayValue)
                .smoothnessFactor(kSmoothnessFactor);
        watcher->flush();
    }

    mInputMetEos = false;
    // 初始化buffer的锁
    mSync.start();
    return OK;
}

MediaCodec 获取buffer index

在创建并启动解码器后,我们会通过dequeueInputBuffer获取一个Buffer index,然后再通过getInputBuffers获取所有Buffer数组,然后根据索引在Buffer数组中获取buffer,往里面写入需要编码的数据,接下来我们来看看这个流程。

2.1 MediaCodec.dequeueInputBuffer

jni调用native_dequeueInputBuffer。

复制代码
public final int dequeueInputBuffer(long timeoutUs) {
    // ...
    // 详见2.2
    int res = native_dequeueInputBuffer(timeoutUs);
    // ...
    return res;
}

2.2 android_media_MediaCodec_dequeueInputBuffer

调用JMediaCodec::dequeueInputBuffer

复制代码
static jint android_media_MediaCodec_dequeueInputBuffer(
        JNIEnv *env, jobject thiz, jlong timeoutUs) {
    // ...
    size_t index;
    // JMediaCodec::dequeueInputBuffer,详见2.3
    status_t err = codec->dequeueInputBuffer(&index, timeoutUs);

    if (err == OK) {
        return (jint) index;
    }

    return throwExceptionAsNecessary(env, err, codec);
}

2.3 JMediaCodec::dequeueInputBuffer

C++层的MediaCodec,MediaCodec::dequeueInputBuffer

复制代码
status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
    // 详见2.4
    return mCodec->dequeueInputBuffer(index, timeoutUs);
}

2.4 MediaCodec::dequeueInputBuffer

发送kWhatDequeueInputBuffer消息。

复制代码
status_t MediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
    sp<AMessage> msg = new AMessage(kWhatDequeueInputBuffer, this);
    msg->setInt64("timeoutUs", timeoutUs);

    sp<AMessage> response;
    status_t err;
    // 发送kWhatDequeueInputBuffer消息,处理逻辑详见2.5
    if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
        return err;
    }

    CHECK(response->findSize("index", index));

    return OK;
}

2.5 MediaCodec::onMessageReceived

调用handleDequeueInputBuffer处理,并且发送kWhatDequeueInputTimedOut消息来配置超时监测。

复制代码
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
    //...

        case kWhatDequeueInputBuffer:
        {
            sp<AReplyToken> replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));
            // ...
            // 详见2.6
            if (handleDequeueInputBuffer(replyID, true /* new request */)) {
                break;
            }

            // ... 通过发送kWhatDequeueInputTimedOut消息配置超时监测
            break;
        }
        // ...
    }
}

2.6 MediaCodec::handleDequeueInputBuffer

通过dequeuePortBuffer获取buffer index,然后返回index。

复制代码
bool MediaCodec::handleDequeueInputBuffer(const sp<AReplyToken> &replyID, bool newRequest) {
    // ...异常处理
    // 详见2.7
    ssize_t index = dequeuePortBuffer(kPortIndexInput);

    if (index < 0) {
        CHECK_EQ(index, -EAGAIN);
        return false;
    }
    // 返回index
    sp<AMessage> response = new AMessage;
    response->setSize("index", index);
    response->postReply(replyID);

    return true;
}

2.7 MediaCodec::dequeuePortBuffer

mAvailPortBuffers里是两个int数组,分别给编码和解码使用,这里的int数组用于存储可以使用的buffer index。这个方法做的事就是从这个int数组里获取一个index,并且将它从数组里移除。

复制代码
ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {
    CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);

    BufferInfo *info = peekNextPortBuffer(portIndex);
    if (!info) {
        return -EAGAIN;
    }
    // 从availBuffers获取可用buffer index,并且把他从数组中移除。  
    std::list<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
    size_t index = *availBuffers->begin();
    CHECK_EQ(info, &mPortBuffers[portIndex][index]);
    availBuffers->erase(availBuffers->begin());

    // ...

    return index;
}

MediaCodec 获取input buffer数组

3.1 MediaCodec.getInputBuffers

复制代码
public ByteBuffer[] getInputBuffers() {
    synchronized (mBufferLock) {
        // ...
        // java层有一个buffer数组缓存,第一次需要通过jni去获取,详见3.2
        if (mCachedInputBuffers == null) {
            cacheBuffersLocked(true /* input */);
        }
        // ...
        return mCachedInputBuffers;
    }
} 

3.2 MediaCodec.cacheBuffersLocked

通过jni获取buffers,并缓存记录。

复制代码
private void cacheBuffersLocked(boolean input) {
    ByteBuffer[] buffers = null;
    try {
        // 通过jni获取buffer,详见3.3
        buffers = getBuffers(input);
        invalidateByteBuffersLocked(buffers);
    } catch (IllegalStateException e) {
        // we don't get buffers in async mode
    }
    // ...
    // 缓存记录结果
    if (input) {
        mCachedInputBuffers = buffers;
    } else {
        mCachedOutputBuffers = buffers;
    }
}

3.3 JMediaCodec::getBuffers

jni方法通过调用JMediaCodec的getBuffers,而JMediaCodec又通过调用c++层的MediaCodec的getInputBuffers来获取buffers。

复制代码
status_t JMediaCodec::getBuffers(
        JNIEnv *env, bool input, jobjectArray *bufArray) const {
    Vector<sp<MediaCodecBuffer> > buffers;
    // 详见3.4
    status_t err =
        input
            ? mCodec->getInputBuffers(&buffers)
            : mCodec->getOutputBuffers(&buffers);

    // ...将C++层的buffers通过jni关联给java层
    return OK;
}

3.4 MediaCodec::getInputBuffers

发送kWhatGetBuffers。

复制代码
status_t MediaCodec::getInputBuffers(Vector<sp<MediaCodecBuffer> > *buffers) const {
    sp<AMessage> msg = new AMessage(kWhatGetBuffers, this);
    msg->setInt32("portIndex", kPortIndexInput);
    msg->setPointer("buffers", buffers);

    sp<AMessage> response;
    return PostAndAwaitResponse(msg, &response);
}

3.5 MediaCodec::onMessageReceived

我们目前流程是在获取inputBuffer,通过inputBuffer给编码器传入编码前的数据,而如果我们配置了InputSurface,以InputSurface为输入,则不需要获取Buffer。

通过CCodecBufferChannel::getInputBufferArray来获取Buffer数组。

复制代码
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    // ...
    case kWhatGetBuffers:
    {
        
        // ...异常处理

        dstBuffers->clear();
        // 通过getInputBufferArray获取buffer,如果配置来InputSurface则不会走这个模式。详见3.6
        if (portIndex != kPortIndexInput || !mHaveInputSurface) {
            if (portIndex == kPortIndexInput) {
                mBufferChannel->getInputBufferArray(dstBuffers);
            } else {
                mBufferChannel->getOutputBufferArray(dstBuffers);
            }
        }

        mApiUsageMetrics.isArrayMode = true;

        (new AMessage)->postReply(replyID);
        break;
    }
    // ...
}

3.6 CCodecBufferChannel::getInputBufferArray

这里会调用input->buffers->getArray来获取Buffer,这里input->buffers是在start的过程中就已经初始化的,具体是在1.8节,调用mChannel->prepareInitialInputBuffers时候分配的,其中会循环调用buffers的requestNewBuffer方法,我们之前说过buffers里面有pool,而pool会通过allocator来分配buffer。

复制代码
void CCodecBufferChannel::getInputBufferArray(Vector<sp<MediaCodecBuffer>> *array) {
    array->clear();
    Mutexed<Input>::Locked input(mInput);

    if (!input->buffers) {
        ALOGE("getInputBufferArray: No Input Buffers allocated");
        return;
    }
    if (!input->buffers->isArrayMode()) {
        input->buffers = input->buffers->toArrayMode(input->numSlots);
    }

    input->buffers->getArray(array);
}

MediacCodec queueInputBuffer

4.1 MediaCodec.queueInputBuffer

调用jni层native_queueInputBuffer

复制代码
public final void queueInputBuffer(
        int index,
        int offset, int size, long presentationTimeUs, int flags)
    throws CryptoException {
    // ...
    try {
        // 详见4.2
        native_queueInputBuffer(
                index, offset, size, presentationTimeUs, flags);
    } catch (CryptoException | IllegalStateException e) {
        revalidateByteBuffer(mCachedInputBuffers, index, true /* input */);
        throw e;
    }
}

4.2 android_media_MediaCodec_queueInputBuffer

和之前一样,jni会调用JMediaCodec的queueInputBuffer方法。

复制代码
static void android_media_MediaCodec_queueInputBuffer(
        JNIEnv *env,
        jobject thiz,
        jint index,
        jint offset,
        jint size,
        jlong timestampUs,
        jint flags) {
    // ...
    // 详见4.3
    status_t err = codec->queueInputBuffer(
            index, offset, size, timestampUs, flags, &errorDetailMsg);

    throwExceptionAsNecessary(
            env, err, ACTION_CODE_FATAL,
            codec->getExceptionMessage(errorDetailMsg.c_str()).c_str());
}

4.3 JMediaCodec::queueInputBuffer

调用MediaCodec的queueInputBuffer

复制代码
status_t JMediaCodec::queueInputBuffer(
        size_t index,
        size_t offset, size_t size, int64_t timeUs, uint32_t flags,
        AString *errorDetailMsg) {
            //详见4.4
    return mCodec->queueInputBuffer(
            index, offset, size, timeUs, flags, errorDetailMsg);
}

4.4 MediaCodec::queueInputBuffer

发送kWhatQueueInputBuffer消息。

复制代码
status_t MediaCodec::queueInputBuffer(
        size_t index,
        size_t offset,
        size_t size,
        int64_t presentationTimeUs,
        uint32_t flags,
        AString *errorDetailMsg) {
    if (errorDetailMsg != NULL) {
        errorDetailMsg->clear();
    }

    sp<AMessage> msg = new AMessage(kWhatQueueInputBuffer, this);
    msg->setSize("index", index);
    msg->setSize("offset", offset);
    msg->setSize("size", size);
    msg->setInt64("timeUs", presentationTimeUs);
    msg->setInt32("flags", flags);
    msg->setPointer("errorDetailMsg", errorDetailMsg);

    sp<AMessage> response;
    return PostAndAwaitResponse(msg, &response);
}

4.5 MediaCodec::onMessageReceived

调用onQueueInputBuffer处理任务。

复制代码
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
    //...
    case kWhatQueueInputBuffer:
    {
        // ...

        status_t err = UNKNOWN_ERROR;
        if (!mLeftover.empty()) {
            mLeftover.push_back(msg);
            size_t index;
            msg->findSize("index", &index);
            err = handleLeftover(index);
        } else {
            // 详见4.6
            err = onQueueInputBuffer(msg);
        }

        PostReplyWithError(replyID, err);
        break;
    }
    // ...
}

4.6 MediaCodec::onQueueInputBuffer

处理一些参数的检查和传递,这里分需要加密和不需要加密的两种场景,我们不看加密的逻辑,这里主要构建来一个新的buffer,把数据填充,并且调用attachBuffer把queue进来的buffer里面的数据拷贝到新建到buffer里。

最后调用queueInputBuffer。

复制代码
status_t MediaCodec::onQueueInputBuffer(const sp<AMessage> &msg) {
    // ... 参数预处理和检查,将一些参数配置到buffer

    // ... 加密逻辑

    if (c2Buffer || memory) {
        sp<AMessage> tunings = NULL;
        if (msg->findMessage("tunings", &tunings) && tunings != NULL) {
            onSetParameters(tunings);
        }
        status_t err = OK;
        if (c2Buffer) {
            // 拷贝数据到新的buffer
            err = mBufferChannel->attachBuffer(c2Buffer, buffer);
        } else if (memory) {
            AString errorDetailMsg;
            err = mBufferChannel->attachEncryptedBuffer(
                    memory, (mFlags & kFlagIsSecure), key, iv, mode, pattern,
                    offset, subSamples, numSubSamples, buffer, &errorDetailMsg);
            if (err != OK && hasCryptoOrDescrambler()
                    && (mFlags & kFlagUseCryptoAsync)) {
                // ... 加密逻辑
            }
        } else {
            // error log
        }
        // ...
    }

    // ...

    if (hasCryptoOrDescrambler() && !c2Buffer && !memory) {
        // 需要加密场景
    } else {
        // 调用queueInputBuffer,详见4.7
        err = mBufferChannel->queueInputBuffer(buffer);
        // ...
    }

    // ...

    return err;
}

4.7 CCodecBufferChannel::queueInputBuffer

调用queueInputBufferInternal。

复制代码
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);
}

4.8 CCodecBufferChannel::queueInputBufferInternal

这里主要会解析buffer,然后将buffer里的信息封装到一个C2Work中,然后把C2Work存到一个C2Work数组,调用Component的queue来处理这个这个任务。

这里Component的queue会在hal层调用到对方的queue_nb方法,这里就是SimpleC2Component的::SimpleC2Component。

复制代码
status_t CCodecBufferChannel::queueInputBufferInternal(
        sp<MediaCodecBuffer> buffer,
        std::shared_ptr<C2LinearBlock> encryptedBlock,
        size_t blockSize) {
    // ...获取buffer里的参数
    
    // ...构造一个C2Work数组和一个C2Work,后续最终会提交这个数组,将信息封装在C2Work中
    std::list<std::unique_ptr<C2Work>> items;
    std::unique_ptr<C2Work> work(new C2Work);
    work->input.ordinal.timestamp = timeUs;
    work->input.ordinal.frameIndex = mFrameIndex++;
    work->input.ordinal.customOrdinal = timeUs;
    work->input.buffers.clear();

    sp<Codec2Buffer> copy;
    bool usesFrameReassembler = false;

    if (buffer->size() > 0u) {
        Mutexed<Input>::Locked input(mInput);
        std::shared_ptr<C2Buffer> c2buffer;
        // 将buffer数据存到c2buffer
        if (!input->buffers->releaseBuffer(buffer, &c2buffer, false)) {
            return -ENOENT;
        }
        
        // ...
        if (input->frameReassembler) {
            usesFrameReassembler = true;
            input->frameReassembler.process(buffer, &items);
        } else {
            // ...
            // 将c2buffer存到C2Work里。
            work->input.buffers.push_back(c2buffer);
            if (encryptedBlock) {
                work->input.infoBuffers.emplace_back(C2InfoBuffer::CreateLinearBuffer(
                        kParamIndexEncryptedBuffer,
                        encryptedBlock->share(0, blockSize, C2Fence())));
            }
        }
    } else if (eos) {
        // ...
    }
    if (usesFrameReassembler) {
        // ...
    } else {
        work->input.flags = (C2FrameData::flags_t)flags;
        // TODO: fill info's

        work->input.configUpdate = std::move(mParamsToBeSet);
        if (tunnelFirstFrame) {
            C2StreamTunnelHoldRender::input tunnelHoldRender{
                0u /* stream */,
                C2_TRUE /* value */
            };
            work->input.configUpdate.push_back(C2Param::Copy(tunnelHoldRender));
        }
        work->worklets.clear();
        work->worklets.emplace_back(new C2Worklet);
        // 将C2Work存到items里
        items.push_back(std::move(work));

        eos = eos && buffer->size() > 0u;
    }
    if (eos) {
        // ...
    }
    c2_status_t err = C2_OK;
    if (!items.empty()) {
        // ...
        // 将C2Work数组提交给Component(通过hal层到真正实现编解码逻辑实现),这里是SimpleC2Component::queue_nb,详见4.9
        err = mComponent->queue(&items);
    }
    if (err != C2_OK) {
        // ...
    } else {
        // ...释放buffer
    }

    feedInputBufferIfAvailableInternal();
    return err;
}

4.9 SimpleC2Component::queue_nb

这里把前面传过来的C2Work都放到mWorkQueue中,然后发送了kWhatProcess消息触发处理逻辑。

复制代码
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();
        // 将items里面的数据放到mWorkQueue中
        while (!items->empty()) {
            queue->push_back(std::move(items->front()));
            items->pop_front();
        }
    }
    if (queueWasEmpty) {
        // 发送kWhatProcess,详见4.10  
        (new AMessage(WorkHandler::kWhatProcess, mHandler))->post();
    }
    return C2_OK;
}

4.10 SimpleC2Component::processQueue

这里主要就是会调用process方法,这个方法是由子类实现的,SimpleC2Component是用于管理流程框架的模版,它的子类来实现具体编解码逻辑。

例如H264的编解码就是由子类C2SoftAacDec和C2SoftAacEnc实现的,我们本节不会介绍编解码的细节,下一节会介绍H264编解码的一些核心原理。

复制代码
bool SimpleC2Component::processQueue() {
    // ...
    {
        Mutexed<WorkQueue>::Locked queue(mWorkQueue);
        if (queue->empty()) {
            return false;
        }

        generation = queue->generation();
        drainMode = queue->drainMode();
        isFlushPending = queue->popPendingFlush();

        // 从队列中取出最前面的任务
        work = queue->pop_front();
        hasQueuedWork = !queue->empty();
    }
    if (isFlushPending) {
        // flush的回调,这里SimpleC2Component是一个处理流程的架构,最终需要由它的子类来实现对应生命周期所做的事。  
        c2_status_t err = onFlush_sm();
        // ...
    }

    if (!mOutputBlockPool) {
        // ...创建用于管理输出Buffer的Pool
    }
    // ...
    // ... input内buffer的检测

    // 调用 process,也是由子类实现的。  
    // 传入的参数是work和用于管理输出buffer的Pool
    process(work, mOutputBlockPool);
    // ...
    return hasQueuedWork;
}

小结

本节介绍了MediaCodec编解码的流程,从java层MediaCodec调用到JMediaCodec,然后调用C++层到MediaCodec,通过发送对应流程的消息,触发CCodec,而里面通过一个CCodecBufferChannel来管理这些逻辑,CCodecBufferChannel里面分input和output分别用于管理编解码,他们中都有一个buffers用于管理所有buffer,通过一个C2BlockPool来管理buffer的分配,C2BlockPool内有一个Allocator,会通过hal层进行最终的buffer分配释放。

CCodecBufferChannel工作会通过Component调用hal层,传参数到SimpleC2Component,SimpleC2Component的子类进行最终的编解码操作。

下一节我们会介绍H264编解码的核心原理。

相关推荐
黄林晴3 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我3 小时前
flutter 之真手势冲突处理
android·flutter
法的空间4 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止4 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭4 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech4 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831674 小时前
为何Handler的postDelayed不适合精准定时任务?
android
kaixin_啊啊5 小时前
突破限制:Melody远程音频管理新体验
音视频
叽哥5 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin
Cui晨5 小时前
Android RecyclerView展示List<View> Adapter的数据源使用View
android