Android 13 - Media框架(22)- ACodec(四)

前面两节我们了解了 ACodec 的创建及配置流程,配置完成后 ACodec 进入了 LoadedState,这一节开始将会了解 ACodec 的启动过程。

调用 ACodec::initiateStart 方法发出的 kWhatStart 消息将有 LoadedState 状态来处理,这个方法会向 OMX 组件发送命令OMX_CommandStateSet ,将组件的状态设定为 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);
    }
}

切换状态时调用 LoadedToIdleState 的 stateEntered 方法,来为 OMX 组件分配 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->mOMXNode->sendCommand(
                OMX_CommandStateSet, OMX_StateLoaded);
        if (mCodec->allYourBuffersAreBelongToUs(kPortIndexInput)) {
            mCodec->freeBuffersOnPort(kPortIndexInput);
        }
        if (mCodec->allYourBuffersAreBelongToUs(kPortIndexOutput)) {
            mCodec->freeBuffersOnPort(kPortIndexOutput);
        }

        mCodec->changeState(mCodec->mLoadedState);
    }
}

stateEntered 中主要调用了 allocateBuffers 方法,如果返回结果有问题,那么会将状态回滚到 LoadedState。

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;
}

allocateBuffers 中将 buffer 分配完成后,就会调用 callback 通知 MediaCodec 完成阻塞调用了。我们上面说到将 OMX 组件状态设置为 OMX_StateIdle,这个状态下,OMX 组件处理这个消息时应该是处于一个阻塞的状态,阻塞是在等待上层 buffer 分配完成,一旦完成后就会向 ACodec 发送一条消息,表示事件处理完成了(buffer准备完成),这时候 ACodec 将会再向 OMX 组件发送状态设置命令,将组件状态设置为 OMX_StateExecuting,组件就正式开始工作了。

bool ACodec::LoadedToIdleState::onOMXEvent(
        OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
    switch (event) {
        case OMX_EventCmdComplete:
        {
            status_t err = OK;
            if (data1 != (OMX_U32)OMX_CommandStateSet
                    || data2 != (OMX_U32)OMX_StateIdle) {
                ALOGE("Unexpected command completion in LoadedToIdleState: %s(%u) %s(%u)",
                        asString((OMX_COMMANDTYPE)data1), data1,
                        asString((OMX_STATETYPE)data2), data2);
                err = FAILED_TRANSACTION;
            }

            if (err == OK) {
                err = mCodec->mOMXNode->sendCommand(
                    OMX_CommandStateSet, OMX_StateExecuting);
            }

            if (err != OK) {
                mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
            } else {
                mCodec->changeState(mCodec->mIdleToExecutingState);
            }

            return true;
        }

        default:
            return BaseState::onOMXEvent(event, data1, data2);
    }
}

上面这段主要是要理解,OMX组件在处理 OMX_StateIdle 这条命令时会处在一个阻塞的状态

接下来我们就要一起看 buffer 是如何分配的,如果已经了解我们上一节看的 Port Mode,那么这部分还是很简单的。

代码比较长,我们把代码分成两部分来看:

  1. 有native window的情况下分配output buffer;

  2. 对 input buffer ,以及没有 native window 时的 output buffer 进行分配;

首先我们来看第一部分:

    // 1、在有native window的情况下分配output buffer
    if (mNativeWindow != NULL && portIndex == kPortIndexOutput) {
        if (storingMetadataInDecodedBuffers()) {
            err = allocateOutputMetadataBuffers();
        } else {
            err = allocateOutputBuffersFromNativeWindow();
        }
    }

我们在上一篇中了解到有native window 时,output mode会有两种情况,一种是 tunnel mode;另一种是 kPortModeDynamicANWBuffer,也就是所谓的 MetaData mode,这里我们先看这种模式。

1、allocateOutputMetadataBuffers

status_t ACodec::allocateOutputMetadataBuffers() {
    CHECK(storingMetadataInDecodedBuffers());
	// 1、调用方法获取 native window 可用的 output buffer 数量以及大小
    OMX_U32 bufferCount, bufferSize, minUndequeuedBuffers;
    status_t err = configureOutputBuffersFromNativeWindow(
            &bufferCount, &bufferSize, &minUndequeuedBuffers,
            mFlags & kFlagPreregisterMetadataBuffers /* preregister */);
    if (err != OK)
        return err;
    mNumUndequeuedBuffers = minUndequeuedBuffers;

    ALOGV("[%s] Allocating %u meta buffers on output port",
         mComponentName.c_str(), bufferCount);
	// 2、创建 对应数量的 BufferInfo
    for (OMX_U32 i = 0; i < bufferCount; i++) {
        BufferInfo info;
        info.mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW;
        info.mFenceFd = -1;
        info.mRenderInfo = NULL;
        info.mGraphicBuffer = NULL;
        info.mNewGraphicBuffer = false;
        info.mDequeuedAt = mDequeueCounter;
		// 3、创建一个 MediaCodecBuffer
        info.mData = new MediaCodecBuffer(mOutputFormat, new ABuffer(bufferSize));

        // Initialize fence fd to -1 to avoid warning in freeBuffer().
        ((VideoNativeMetadata *)info.mData->base())->nFenceFd = -1;

        info.mCodecData = info.mData;
		// 4、调用 useBuffer 让 OMX 组件使用一块空的 buffer,并且回传 ID
        err = mOMXNode->useBuffer(kPortIndexOutput, OMXBuffer::sPreset, &info.mBufferID);
        mBuffers[kPortIndexOutput].push(info);

        ALOGV("[%s] allocated meta buffer with ID %u",
                mComponentName.c_str(), info.mBufferID);
    }
	// 5、计算需要提交的 output buffer 数量
    mMetadataBuffersToSubmit = bufferCount - minUndequeuedBuffers;
    return err;
}

这个方法大致做了以下5个事情:

  1. 获取 native window 可用的 output buffer 数量以及大小;
  2. 创建对应数量的 BufferInfo;
  3. 为 BufferInfo 中的 mData 字段分配空间;
  4. 调用 useBuffer 让 OMX 组件使用一块空的 buffer,并且回传 ID 并与当前的 BufferInfo 相绑定;
  5. 计算需要提交的 output buffer 数量;

1.1、configureOutputBuffersFromNativeWindow

status_t ACodec::configureOutputBuffersFromNativeWindow(
        OMX_U32 *bufferCount, OMX_U32 *bufferSize,
        OMX_U32 *minUndequeuedBuffers, bool preregister) {

    OMX_PARAM_PORTDEFINITIONTYPE def;
    InitOMXParams(&def);
    def.nPortIndex = kPortIndexOutput;
	// 获取 OMX 组件定义的 output port 的定义,定义中会有 output buffer 的数量
    status_t err = mOMXNode->getParameter(
            OMX_IndexParamPortDefinition, &def, sizeof(def));

    if (err == OK) {
        err = setupNativeWindowSizeFormatAndUsage(
                mNativeWindow.get(), &mNativeWindowUsageBits,
                preregister && !mTunneled /* reconnect */);
    }
    if (err != OK) {
        mNativeWindowUsageBits = 0;
        return err;
    }
	// 设置从 nativewindow 中获取buffer这个动作为阻塞的
    static_cast<Surface *>(mNativeWindow.get())->setDequeueTimeout(-1);

    // Exits here for tunneled video playback codecs -- i.e. skips native window
    // buffer allocation step as this is managed by the tunneled OMX omponent
    // itself and explicitly sets def.nBufferCountActual to 0.
    // 如果是 tunnel mode,那么端口的 buffer 数量为0,不需要从上层获取 output buffer
    if (mTunneled) {
        ALOGV("Tunneled Playback: skipping native window buffer allocation.");
        def.nBufferCountActual = 0;
        err = mOMXNode->setParameter(
                OMX_IndexParamPortDefinition, &def, sizeof(def));

        *minUndequeuedBuffers = 0;
        *bufferCount = 0;
        *bufferSize = 0;
        return err;
    }
	// 从 native window 获取最小的还未出队列的 buffer 数量
    *minUndequeuedBuffers = 0;
    err = mNativeWindow->query(
            mNativeWindow.get(), NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
            (int *)minUndequeuedBuffers);

    if (err != 0) {
        ALOGE("NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS query failed: %s (%d)",
                strerror(-err), -err);
        return err;
    }

	// 重新计算OMX 组件上 output buffer 的数量
    // FIXME: assume that surface is controlled by app (native window
    // returns the number for the case when surface is not controlled by app)
    // FIXME2: This means that minUndeqeueudBufs can be 1 larger than reported
    // For now, try to allocate 1 more buffer, but don't fail if unsuccessful

    // Use conservative allocation while also trying to reduce starvation
    //
    // 1. allocate at least nBufferCountMin + minUndequeuedBuffers - that is the
    //    minimum needed for the consumer to be able to work
    // 2. try to allocate two (2) additional buffers to reduce starvation from
    //    the consumer
    //    plus an extra buffer to account for incorrect minUndequeuedBufs
    for (OMX_U32 extraBuffers = 2 + 1; /* condition inside loop */; extraBuffers--) {
    	// 尝试将output buffer 的数量设置为端口所需的最小数量 +  nativewindow最小未出队列的buffer数量 + 3
        OMX_U32 newBufferCount =
            def.nBufferCountMin + *minUndequeuedBuffers + extraBuffers;
        def.nBufferCountActual = newBufferCount;
        err = mOMXNode->setParameter(
                OMX_IndexParamPortDefinition, &def, sizeof(def));

        if (err == OK) {
            *minUndequeuedBuffers += extraBuffers;
            break;
        }

        ALOGW("[%s] setting nBufferCountActual to %u failed: %d",
                mComponentName.c_str(), newBufferCount, err);
        /* exit condition */
        if (extraBuffers == 0) {
            return err;
        }
    }
	// 设置 native window 的buffer数量
    err = native_window_set_buffer_count(
            mNativeWindow.get(), def.nBufferCountActual);

    if (err != 0) {
        ALOGE("native_window_set_buffer_count failed: %s (%d)", strerror(-err),
                -err);
        return err;
    }
	// 设置 buffercount 和 buffersize
    *bufferCount = def.nBufferCountActual;
    *bufferSize =  def.nBufferSize;
    return err;
}

由于不了解 Graphic 相关的内容,所以这部分只能边看边猜,以下是我自己的理解,可能有误

  1. 计算output buffer 数量时首先会从 OMX 组件获取输出端口的配置,配置中定义有最小和最大需要的 buffer 数量;
  2. 将从 native window 中 deque buffer 这个动作设置为阻塞的;
  3. 如果是 tunnel mode,不会从上层获取 output buffer,buffer 的数量设置为0;
  4. 从 native window 获取最小的还未出队列的 buffer 数量;
  5. 重新计算OMX 组件上真实使用的 output buffer 的数量;
  6. 设置 native window 的 buffer数量为真实使用的buffer 的数量。

这里对 nBufferCountActual (真实使用的buffer数量)的计算比较令人疑惑,上面的代码中有一个循环,会尝试将 nBufferCountMin (最小 buffer 数量)+ minUndequeuedBuffers + extra 作为真实值,并且尝试设定给组件,只要这个值没有超过最大值就可以成功设定。

这里的 minUndequeuedBuffers 代表什么意思呢?上文中的 mMetadataBuffersToSubmit = bufferCount - minUndequeuedBuffers 又是代表什么意思呢?

我的理解是支持OMX运行的最少的output buffer 数量为 nBufferCountMin,那么就先分配这么多个(指的就是mMetadataBuffersToSubmit),如果转不过来要请求新的了,再去 minUndequeuedBuffers 个中获取新的;意思可能是上来不是用全力,而是随着需求的改变来改变native window分配的buffer数量。

你可能感兴趣的:(Android,Media,android,Framework,Media,多媒体)