小工具知识网

当前位置: 首页 > 行业知识 > Android 13 - Media框架(32)- ACodec(八)

行业知识

Android 13 - Media框架(32)- ACodec(八)

2026-10-29 04:54:17 来源:互联网转载

���了好久都没有更新,前面写的东西都有些忘了,回过头来再看之前写的内容,觉得有很多地方写的不好,或者说现在又有了新的理解,想要重新修改但是需要修改的内容太多,因此决定按照当前的思路把剩余的内容写完。

()

Android ACodec OpenMax部分还有OutputPortSettingsChangedState、Flush、Release、output buffer的处理这四块内容,写完了之后可能会花时间重新再阅读一遍,整理出更系统的内容。加油!

接前面内容,之前我们已经了解了MediaCodec如何启动,ACodec的input/output buffer是如何分配的,以及OMXNodeInstance是如何发消息。接下来将会学习解码启动后正常运转过程的内容。

()

这一节就来看OutputPortSettingsChangedState这个状态。

在之前的学习中我们有提到过,播放过程中码流的分辨率发生了变化,这时候应该怎么办呢?

1、OMX_EventPortSettingsChanged

现在的decoder一般都会支持 Adaptive Playback,所谓自适应播放指的是两部分内容:

  1. 起播时并不一定要设定准确的宽高信息,解码器可以自己解出码流的宽高信息,并且做出调整;
  2. 播放过程中码流的分辨率发生变化,播放器可以自适应调整;

这里说的调整指的就是自己调整output buffer size,不需要我们重启播放器。buffer size调整的过程涉及到原有buffer的释放,以及新的buffer的分配,所以需要做的内容会比较多。

bool ACodec::ExecutingState::onOMXEvent(        OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {    switch (event) {        case OMX_EventPortSettingsChanged:        {            // 检查是否是Output port            CHECK_EQ(data1, (OMX_U32)kPortIndexOutput);            // 获取新的output format            mCodec->onOutputFormatChanged();            if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {                // 将待提交的 buffer 数量设置为 0                mCodec->mMetadataBuffersToSubmit = 0;                // 禁用 output port                CHECK_EQ(mCodec->mOMXNode->sendCommand(                            OMX_CommandPortDisable, kPortIndexOutput),                         (status_t)OK);                // 释放所有没有送给 OMX 组件的output buffer                mCodec->freeOutputBuffersNotOwnedByComponent();                // 进入状态 OutputPortSettingsChangedState                mCodec->changeState(mCodec->mOutputPortSettingsChangedState);            } else if (data2 != OMX_IndexConfigCommonOutputCrop                    && data2 != OMX_IndexConfigAndroidIntraRefresh) {                ALOGV("[%s] OMX_EventPortSettingsChanged 0x%08x",                     mCodec->mComponentName.c_str(), data2);            }            return true;        }        default:            return BaseState::onOMXEvent(event, data1, data2);    }}
  1. ExecutingState状态下,收到OMX_EventPortSettingsChanged消息后,ACodec首先从组件中获取到新的OutputFormat,onOutputFormatChanged方法我们这里不做展开。
  2. 接着将mMetadataBuffersToSubmit置为0,这个操作是让起播时收到OMX_EventPortSettingsChanged,向组件写input 数据时不要再传递output buffer。
  3. 禁用OMX组件的 output port。
  4. 释放所有没有送给 OMX 组件的output buffer。
  5. ACodec进入到OutputPortSettingsChangedState状态。

这里要们先看一下第四点freeOutputBuffersNotOwnedByComponent:

status_t ACodec::freeOutputBuffersNotOwnedByComponent() {    status_t err = OK;    for (size_t i = mBuffers[kPortIndexOutput].size(); i > 0;) {        i--;        BufferInfo *info =            &mBuffers[kPortIndexOutput].editItemAt(i);        // At this time some buffers may still be with the component        // or being drained.        if (info->mStatus != BufferInfo::OWNED_BY_COMPONENT &&            info->mStatus != BufferInfo::OWNED_BY_DOWNSTREAM) {            status_t err2 = freeBuffer(kPortIndexOutput, i);            if (err == OK) {                err = err2;            }        }    }    return err;}

我们可以看到释放buffer前会先检查BufferInfo的状态,如果BufferInfo不属于OMX组件,或者不是在等待渲染的状态,这时候需要调用freeBuffer。

我们再来回顾一下,OutputBuffer 可能有四种状态:

  • BufferInfo::OWNED_BY_US
  • BufferInfo::OWNED_BY_COMPONENT
  • BufferInfo::OWNED_BY_DOWNSTREAM
  • BufferInfo::OWNED_BY_NATIVE_WINDOW

    意思也就是,当OutputBuffer归属于ACodec或者是nativewindow时可以释放。

    status_t ACodec::freeBuffer(OMX_U32 portIndex, size_t i) {    BufferInfo *info = &mBuffers[portIndex].editItemAt(i);    status_t err = OK;    // there should not be any fences in the metadata    if (mPortMode[portIndex] == IOMX::kPortModeDynamicANWBuffer && info->mCodecData != NULL            && info->mCodecData->size() >= sizeof(VideoNativeMetadata)) {        int fenceFd = ((VideoNativeMetadata *)info->mCodecData->base())->nFenceFd;        if (fenceFd >= 0) {            ALOGW("unreleased fence (%d) in %s metadata buffer %zu",                    fenceFd, portIndex == kPortIndexInput ? "input" : "output", i);        }    }    switch (info->mStatus) {        case BufferInfo::OWNED_BY_US:            if (portIndex == kPortIndexOutput && mNativeWindow != NULL) {                (void)cancelBufferToNativeWindow(info);            }            FALLTHROUGH_INTENDED;        case BufferInfo::OWNED_BY_NATIVE_WINDOW:            err = mOMXNode->freeBuffer(portIndex, info->mBufferID);            break;        default:            ALOGE("trying to free buffer not owned by us or ANW (%d)", info->mStatus);            err = FAILED_TRANSACTION;            break;    }    if (info->mFenceFd >= 0) {        ::close(info->mFenceFd);    }    if (portIndex == kPortIndexOutput) {        mRenderTracker.untrackFrame(info->mRenderInfo, i);        info->mRenderInfo = NULL;    }    // remove buffer even if mOMXNode->freeBuffer fails    mBuffers[portIndex].removeAt(i);    return err;}

    对于OWNED_BY_US和OWNED_BY_NATIVE_WINDOW两种状态,freebuffer需要做的内容有一点点不一样,OWNED_BY_US需要多做一个cancelBufferToNativeWindow,可以理解为取消使用的意思,将graphic buffer返回给nativewindow,graphic buffer返回之后再调用freeBuffer,释放OMX组件所持有的graphic buffer了。

    到这,我们要先想想OMX组件什么时候会发送 OMX_EventPortSettingsChanged 事件回来?我理解的是,前一个序列的output全部回传给了上层,下一个序列回传之前会送出事件,处理完成之后再把新的序列的output填充回传。

    如我们上文所说的,output buffer有四种状态,OWNED_BY_NATIVE_WINDOW指的是buffer还未分配,或者已经送到nativewindow等待渲染;OWNED_BY_US表示buffer由ACodec持有,有两种可能,一种是buffer处在ACodec处理的中间状态,还有一种是buffer不需要被处理,由ACodec持有。这两种状态下,output buffer确定不会被使用,所以可以先release。

    OWNED_BY_DOWNSTREAM状态下的buffer表示回传给上层做Avsync,还未render,等待render完成后会release。

    OWNED_BY_COMPONENT状态下的buffer需要等OMX组件把buffer id回传,确认OMX组件不再使用再release。

    2、OutputPortSettingsChangedState

    void ACodec::OutputPortSettingsChangedState::stateEntered() {    ALOGV("[%s] Now handling output port settings change",         mCodec->mComponentName.c_str());    // If we haven't transitioned after 3 seconds, we're probably stuck.    sp msg = new AMessage(ACodec::kWhatCheckIfStuck, mCodec);    msg->setInt32("generation", mCodec->mStateGeneration);    msg->post(3000000);}

    进入OutputPortSettingsChangedState后会设定一个timeout时间,如果3s内还在OutputPortSettingsChangedState状态下就会返回error,意思就是3s内需要完成OMX_EventPortSettingsChanged事件的处理。

    ACodec::BaseState::PortMode ACodec::OutputPortSettingsChangedState::getPortMode(        OMX_U32 portIndex) {    if (portIndex == kPortIndexOutput) {        return FREE_BUFFERS;    }    CHECK_EQ(portIndex, (OMX_U32)kPortIndexInput);    return RESUBMIT_BUFFERS;}

    再来看OutputPortSettingsChangedState状态下的port mode为FREE_BUFFERS,指的是output buffer render完成后就会释放掉。

    bool ACodec::OutputPortSettingsChangedState::onOMXEvent(        OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {    switch (event) {        case OMX_EventCmdComplete:        {            if (data1 == (OMX_U32)OMX_CommandPortDisable) {                if (data2 != (OMX_U32)kPortIndexOutput) {                    ALOGW("ignoring EventCmdComplete CommandPortDisable for port %u", data2);                    return false;                }                ALOGV("[%s] Output port now disabled.", mCodec->mComponentName.c_str());                // 检查output buffer列表是否为空                status_t err = OK;                if (!mCodec->mBuffers[kPortIndexOutput].isEmpty()) {                    ALOGE("disabled port should be empty, but has %zu buffers",                            mCodec->mBuffers[kPortIndexOutput].size());                    err = FAILED_TRANSACTION;                } else {                    mCodec->mAllocator[kPortIndexOutput].clear();                }                // 重新开启output port                if (err == OK) {                    err = mCodec->mOMXNode->sendCommand(                            OMX_CommandPortEnable, kPortIndexOutput);                }                // Clear the RenderQueue in which queued GraphicBuffers hold the                // actual buffer references in order to free them early.                mCodec->mRenderTracker.clear(systemTime(CLOCK_MONOTONIC));                // 重新分配buffer                if (err == OK) {                    err = mCodec->allocateBuffersOnPort(kPortIndexOutput);                    ALOGE_IF(err != OK, "Failed to allocate output port buffers after port "                            "reconfiguration: (%d)", err);                    // 通知上层output buffer发生变化                    mCodec->mCallback->onOutputBuffersChanged();                }                if (err != OK) {                    mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));                    ALOGE("Error occurred while disabling the output port");                }                return true;            } else if (data1 == (OMX_U32)OMX_CommandPortEnable) {                if (data2 != (OMX_U32)kPortIndexOutput) {                    ALOGW("ignoring EventCmdComplete OMX_CommandPortEnable for port %u", data2);                    return false;                }                ALOGV("[%s] Output port now reenabled.", mCodec->mComponentName.c_str());                // 如果还在运行状态则重新提交所有buffer                if (mCodec->mExecutingState->active()) {                    mCodec->mExecutingState->submitOutputBuffers();                }                // 重新返回到ExecutingState                mCodec->changeState(mCodec->mExecutingState);                return true;            }            return false;        }        default:            return BaseState::onOMXEvent(event, data1, data2);    }}

    我们之前在 ExecutingState 看到有发送一条 OMX_CommandPortDisable 消息给OMX组件,当然OMX组件也会返回一条Disable执行完成的消息,问题是什么时候OMX组件会回传这一条消息呢?

    OMX组件收到OMX_CommandPortDisable后,会把所有持有的OutputBuffer都回传给上层,上层处理时检查到port mode为FREE_BUFFERS会直接进入到释放流程,所有OMX组件持有的OutputBuffer回传完毕后就会发送 OMX_EventCmdComplete 消息,表示当前命令执行完成。

    进入到 OMX_EventCmdComplete 事件的处理中,我们会发现会检查 mBuffers 是否为空,我们刚刚分析的流程只能确保OWNED_BY_COMPONENT状态的buffer被释放,却不能保证OWNED_BY_DOWNSTREAM的buffer被释放,那么到这里就一定会抛出error,因此上面的分析肯定有遗漏的地方。

    仔细想想,OMX组件上抛OMX_CommandPortDisable执行完成的时机肯定是有问题的,组件应该是等所有的output buffer都调用了 freeBuffer后才会发出执行完成的命令,这时候mBuffers中就不会有任何buffer存在了。

    所有buffer释放完成后会重新enable output port,然后再次调用 allocateBuffersOnPort 分配 output buffer,这部分内容我们在之前的章节中已经分析过了。buffer重新分配完成后需要发送callback onOutputBuffersChanged 给上层。

    这一系列动作都完成后就会重新进入ExecutingState,同时调用submitOutputBuffers把output buffer提交给OMX组件,但是在之前的分析中我们可以知道这里其实不会调用fillBuffer,最初的output buffer需要和input buffer一起送给OMX组件。

    这里就会有一点点问题了,如果input已经到达了EOS,不会再有input写入,那么OMX岂不是会出现没有OUtput buffer可用的情况,为了处理这个问题,ACodec在submitOutputMetaBuffers中调用了一个方法signalSubmitOutputMetadataBufferIfEOS_workaround,如果input port到达了EOS,而output还未到达,那么会直接提交所有的output buffer。我觉得可能还有一种情况没有考虑到,如果input ring被写满,那么input buffer转不起来了,岂不是OMX组件也会有没有output buffer可用的情况呢?

    好了,到这里这一节就分析结束了,下一节我们将一起学习output buffer是如何被处理的。

上一篇:揭秘drawable,打造吸引人的界面元素

下一篇:多ip服务器怎么建站?