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编解码的核心原理。

相关推荐
拭心4 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
darkdragonking5 小时前
FLV视频封装格式详解
音视频
带电的小王6 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡6 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道7 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库7 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道8 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe8 小时前
Android Hook - 动态加载so库
android
居居飒9 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He12 小时前
桌面列表小部件不能点击的问题分析
android