Android Media Framework(十五)ACodec - Ⅲ

这一篇我们一起来了解ACodec的Buffer分配流程。

1、initiateStart

首先对上一篇内容做一点补充,configureCodec执行完成后组件的状态没有变化,仍处在OMX_StateLoaded。因此,当我们调用initiateStart时,发出的消息将由ACodec::LoadedState来处理。

bool ACodec::LoadedState::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case ACodec::kWhatStart:
        {
            onStart();
            handled = true;
            break;
        }
    }
}        

onStart是ACodec::LoadedState内部的方法,调用此方法首先会发送命令给OMX组件,将状态切换至OMX_StateIdle,命令送出成功后就将ACodec状态切换至LoadedToIdleState。

void ACodec::LoadedState::onStart() {
    ALOGV("onStart");

    status_t err = mCodec->mOMXNode->sendCommand(OMX_CommandStateSet, OMX_StateIdle);
    if (err != OK) {
        mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
    } else {
        mCodec->changeState(mCodec->mLoadedToIdleState);
    }
}

命令发出后OMX组件会进入到LoadedToIdleState这个中间状态等待buffer分配。

2、LoadedToIdleState

​ACodec进入到LoadedToIdleState后,buffer分配就开始了。如果分配成功,ACodec会等待组件状态切换完成的消息;如果分配失败则会用callback上抛error,同时清理已经分配的buffer。

void ACodec::LoadedToIdleState::stateEntered() {
    ALOGV("[%s] Now Loaded->Idle", mCodec->mComponentName.c_str());

    status_t err;
    if ((err = allocateBuffers()) != OK) {
        ALOGE("Failed to allocate buffers after transitioning to IDLE state "
             "(error 0x%08x)",
             err);

        mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
        // ...
        mCodec->changeState(mCodec->mLoadedState);
    }
}

buffer分配通过调用allocateBuffers方法完成:

status_t ACodec::LoadedToIdleState::allocateBuffers() {
    status_t err = mCodec->allocateBuffersOnPort(kPortIndexInput);
    if (err != OK) {
        return err;
    }

    err = mCodec->allocateBuffersOnPort(kPortIndexOutput);
    if (err != OK) {
        return err;
    }

    mCodec->mCallback->onStartCompleted();

    return OK;
}

ACodec会调用allocateBuffersOnPort先为input端口分配buffer,然后再为output端口分配buffer,因此我们看到的input buffer id总是比output buffer id小。

buffer分配完成后立刻调用CodecCallback通知MediaCodec start执行完成。这里提出一个小小的疑问:为什么没有等OMX组件的event发回来再调用CodecCallback呢?

3、allocateBuffersOnPort

本文将allocateBuffersOnPort分为两部分来了解:

  1. Decoder Output且使用NativeWindow的情况;
  2. 其他情况;

这一节我们先看“其他”,这部分比较简单。

进入函数体,首先会获取要分配的buffer size,这些信息都存储在组件的PortDefinition中,我们可以通过调用getParameter获取端口定义,从中拿到端口使用的buffer size。

    OMX_PARAM_PORTDEFINITIONTYPE def;
    InitOMXParams(&def);
    def.nPortIndex = portIndex;

    err = mOMXNode->getParameter(
            OMX_IndexParamPortDefinition, &def, sizeof(def));

如果是Encoder且输入要用Metadata,即PortMode为kPortModeDynamicANWBuffer或kPortModeDynamicNativeHandle,这时候input buffer size需要重新计算,大小等于Metadata size。

    const IOMX::PortMode &mode = mPortMode[portIndex];
    size_t bufSize = def.nBufferSize;
    if (mode == IOMX::kPortModeDynamicANWBuffer) {
        bufSize = sizeof(VideoNativeMetadata);
    } else if (mode == IOMX::kPortModeDynamicNativeHandle) {
        bufSize = sizeof(VideoNativeHandleMetadata);
    }

接下来是对buffer size做校验,如果大小超过kMaxCodecBufferSize(8192 * 4096 * 4 bytes)就返回error。这段代码中我们看到DataConverter,这个类是做数据转换用的。

    size_t conversionBufferSize = 0;

    sp<DataConverter> converter = mConverter[portIndex];
    if (converter != NULL) {
        if (portIndex == kPortIndexInput) {
            conversionBufferSize = converter->sourceSize(bufSize);
        } else {
            conversionBufferSize = converter->targetSize(bufSize);
        }
    }

    size_t alignment = 32; 

    if (bufSize == 0 || max(bufSize, conversionBufferSize) > kMaxCodecBufferSize) {
        ALOGE("b/22885421");
        return NO_MEMORY;
    }

    size_t alignedSize = align(bufSize, alignment);
    size_t alignedConvSize = align(conversionBufferSize, alignment);
    if (def.nBufferCountActual > SIZE_MAX / (alignedSize + alignedConvSize)) {
        ALOGE("b/22885421");
        return NO_MEMORY;
    }

下面这部分是在获取Allocator,在PortMode被设置为kPortModePresetSecureBuffer的情况下,会要求组件分配buffer。而在其他情况下,则是使用TAllocator来分配buffer。

    if (mode != IOMX::kPortModePresetSecureBuffer) {
        mAllocator[portIndex] = TAllocator::getService("ashmem");
        if (mAllocator[portIndex] == nullptr) {
            ALOGE("hidl allocator on port %d is null",
                    (int)portIndex);
            return NO_MEMORY;
        }
    }

3.1、BufferInfo

每次循环分配buffer之前都会先创建出一个BufferInfo出来,ACodec用它记录buffer信息和运行过程中buffer所处的状态。

    struct BufferInfo {
        enum Status {
            OWNED_BY_US,
            OWNED_BY_COMPONENT,
            OWNED_BY_UPSTREAM,
            OWNED_BY_DOWNSTREAM,
            OWNED_BY_NATIVE_WINDOW,
            UNRECOGNIZED,            // not a tracked buffer
        };

        static inline Status getSafeStatus(BufferInfo *info) {
            return info == NULL ? UNRECOGNIZED : info->mStatus;
        }

        IOMX::buffer_id mBufferID;
        Status mStatus;
        unsigned mDequeuedAt;

        sp<MediaCodecBuffer> mData;  // the client's buffer; if not using data conversion, this is
                                     // the codec buffer; otherwise, it is allocated separately
        sp<RefBase> mMemRef;         // and a reference to the IMemory, so it does not go away
        sp<MediaCodecBuffer> mCodecData;  // the codec's buffer
        sp<RefBase> mCodecRef;            // and a reference to the IMemory

        sp<GraphicBuffer> mGraphicBuffer;
        bool mNewGraphicBuffer;
        int mFenceFd;
        FrameRenderTracker::Info *mRenderInfo;

        // The following field and 4 methods are used for debugging only
        bool mIsReadFence;
        // Store |fenceFd| and set read/write flag. Log error, if there is already a fence stored.
        void setReadFence(int fenceFd, const char *dbg);
        void setWriteFence(int fenceFd, const char *dbg);
        // Log error, if the current fence is not a read/write fence.
        void checkReadFence(const char *dbg);
        void checkWriteFence(const char *dbg);
    };

BufferInfo的各个字段意义如下:

4、allocateOutputMetadataBuffers

5、allocateOutputBuffersFromNativeWindow

6、IdleToExecutingState

关注公众号《青山渺渺》阅读全文
请添加图片描述

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值