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

本文介绍了在AndroidACodec中,当码流分辨率改变时如何通过OMX_EventPortSettingsChanged事件管理输出缓冲区,涉及buffer释放、新缓冲区分配和自适应播放策略的实现。
摘要由CSDN通过智能技术生成

全新系列文章已更新:


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


关注公众号《青山渺渺》阅读完整内容; 如有问题可在公众号后台私信,也可进入音视频开发技术分享群一起讨论!

在这里插入图片描述

  • 70
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青山渺渺

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

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

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

打赏作者

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

抵扣说明:

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

余额充值