Android 13 - Media框架(31)- ACodec(七)

之前的章节中我们解了 input buffer 是如何传递给 OMX 的,以及Output buffer 是如何分配并且注册给 OMX 的。这一节我们就来看ACodec是如何处理OMX的Callback的。

1、OMXNodeInstance Callback

这一节我们只大致记录Callback是如何传递给ACodec的。在之前的学习中我们了解到OMXNodeInstance中会有一个专门的线程来处理OMX的callback,这个线程的作用是把Callback按照时间顺序回传给ACodec。

CallbackDispatcher中维护了一个list,将消息回传给ACodec时并不是将list中的消息一条一条回传的,而是将list中所有的消息一次性回传,这也就是为什么ACodec处理OMXNodeInstance的消息时会有循环遍历。

在调用CodecObserver做消息上抛之前,会调用OMXNodeInstance::handleMessage 对消息做预处理,这里的预处里包括是否要将buffer做拷贝等等。

2、onOMXEmptyBufferDone

OMX使用完input buffer后,消息上抛到ACodec层,ACodec 会调用onOMXEmptyBufferDone再处理input buffer。

bool ACodec::BaseState::onOMXEmptyBufferDone(IOMX::buffer_id bufferID, int fenceFd) {
   
    ALOGV("[%s] onOMXEmptyBufferDone %u",
         mCodec->mComponentName.c_str(), bufferID);

    BufferInfo *info = mCodec->findBufferByID(kPortIndexInput, bufferID);
    BufferInfo::Status status = BufferInfo::getSafeStatus(info);
    // 检查 Buffer 状态
    if (status != BufferInfo::OWNED_BY_COMPONENT) {
   
        ALOGE("Wrong ownership in EBD: %s(%d) buffer #%u", _asString(status), status, bufferID);
        mCodec->dumpBuffers(kPortIndexInput);
        if (fenceFd >= 0) {
   
            ::close(fenceFd);
        }
        return false;
    }
    // input buffer 回到 ACodec
    info->mStatus = BufferInfo::OWNED_BY_US;

    // input buffers cannot take fences, so wait for any fence now
    (void)mCodec->waitForFence(fenceFd, "onOMXEmptyBufferDone");
    fenceFd = -1;

    // still save fence for completeness
    info->setWriteFence(fenceFd, "onOMXEmptyBufferDone");

    // We're in "store-metadata-in-buffers" mode, the underlying
    // OMX component had access to data that's implicitly refcounted
    // by this "MediaBuffer" object. Now that the OMX component has
    // told us that it's done with the input buffer, we can decrement
    // the mediaBuffer's reference count.
    info->mData->meta()->setObject("mediaBufferHolder", sp<MediaBufferHolder>(nullptr));
	// 获取当前的 PortMode
    PortMode mode = getPortMode(kPortIndexInput);

    switch (mode) {
   
        case KEEP_BUFFERS:
            break;

        case RESUBMIT_BUFFERS:
            postFillThisBuffer(info);
            break;

        case FREE_BUFFERS:
        default:
            ALOGE("SHOULD NOT REACH HERE: cannot free empty output buffers");
            return false;
    }

    return true;
}

对input buffer的处理很简单,检查当前ACodec处在的状态并作出反应,如果处在 ExecutingState 则调用 postFillThisBuffer 将 Buffer 提交给 MediaCodec,同时清除 ACodec 存储的 mData。其他状态下则持有 input buffer 不会将其回传给 MediaCodec。

2、onOMXFillBufferDone

ACodec 处理 output buffer 的代码比较长,但是也不难,接下来就做分解学习:

首先有个 debug log,我们可以打开宏TRACK_BUFFER_TIMING来使用这部分内容,把input buffer写给 OMX 时会将pts以及调用时间做记录,在output buffer回传回来时,检查pts,打印出解码该帧消耗的时间。

#if TRACK_BUFFER_TIMING
    index = mCodec->mBufferStats.indexOfKey(timeUs);
    if (index >= 0) {
   
        ACodec::BufferStats *stats = &mCodec->mBufferStats.editValueAt(index);
        stats->mFillBufferDoneTimeUs = ALooper::GetNowUs();

        ALOGI("frame PTS %lld: %lld",
                timeUs,
                stats->mFillBufferDoneTimeUs - stats->mEmptyBufferTimeUs);

        mCodec->mBufferStats.removeItemsAt(index);
        stats = NULL;
    }
#endif

记录output BufferInfo是在第几帧被使用,mDequeueCounter可以看作是当前解码的帧数。

    info->mDequeuedAt = ++mCodec->mDequeueCounter;
    info->mStatus = BufferInfo::OWNED_BY_US;

2.1 Executing

在 Executing 状态下,会检查output buffer flag 和 size:

  • output buffer size为0,flag 不是 OMX_BUFFERFLAG_EOS,说明没有解出有效数据,重新回传给 OMX 使用;
  • output buffer size为0,flag 是 OMX_BUFFERFLAG_EOS,ACodec 已经收到 EOS,重新把 buffer 交给 OMX;
  • 其他情况说明数据有效,或者是flag是 OMX_BUFFERFLAG_EOS,需要把output buffer回传给上层。
        case RESUBMIT_BUFFERS:
        {
   
        	// 如果output buffer长度为0,flag 不是 OMX_BUFFERFLAG_EOS
        	// 如果output buffer长度为0,已将收到 EOS
        	// 重新把 output buffer 提交给 OMX
            if (rangeLength == 0 && (!(flags & OMX_BUFFERFLAG_EOS)
                    || mCodec->mPortEOS[kPortIndexOutput])) {
   
                ALOGV("[%s] calling fillBuffer %u",
                     mCodec->mComponentName.c_str(), info->mBufferID);

                err = mCodec->fillBuffer(info);
                if (err != OK) {
   
                    mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
                    return true;
                }
                break;
            }

            sp<MediaCodecBuffer> buffer = info->mData;
            // ......
            // 设定 pts
            buffer->meta()->setInt64("timeUs", timeUs);
			// 解除 ACodec 引用
            info->mData.clear();
			// 调用 drainThisBuffer
            mCodec->mBufferChannel->drainThisBuffer(info->mBufferID, flags);

            info->mStatus = BufferInfo::OWNED_BY_DOWNSTREAM;
			// 如果 flag 为 OMX_BUFFERFLAG_EOS,将PortEOS置为true
            if (flags & OMX_BUFFERFLAG_EOS) {
   
                ALOGV("[%s] saw output EOS", mCodec->mComponentName.c_str());

                mCodec->mCallback->onEos(mCodec->mInputEOSResult);
                mCodec->mPortEOS[kPortIndexOutput] = true;
            }
            break;
        }

2.2 OutputPortSettingsChangedState

onOMXFillBufferDone 中有个比较特殊的case:FREE_BUFFERS,表示所有回传上来的 output buffer 都需要被释放。不难发现 FREE_BUFFERS 是属于 OutputPortSettingsChangedState 的,所以我们要先了解 OutputPortSettingsChangedState 这个状态是什么。

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

    CHECK_EQ(portIndex, (OMX_U32)kPortIndexInput);

    return RESUBMIT_BUFFERS;
}

有以下两种情况:

  1. 在播放之前,我们并不知道输出output buffer格式;
  2. 播放过程中,码流格式发生变化,原先的output buffer并不适用新的输出;

出现这两种情况时需要重新分配output buffer,这个分配的过程ACodec的OutputPortSettingsChangedState中处理。

bool ACodec::ExecutingState::onOMXEvent(
        OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
   
    switch (event) {
   
        case OMX_EventPortSettingsChanged:
        {
   
        	// 检查是不是output端口发生变化
            CHECK_EQ(data1, (OMX_U32)kPortIndexOutput);
			// 调用 ACodec onOutputFormatChanged
            mCodec->onOutputFormatChanged();

            if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {
   
                mCodec->mMetadataBuffersToSubmit = 0;
                // 关闭 OMX 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;
        }
    }
}

Executing 状态下,如果ACodec收到OMX_EventPortSettingsChanged事件,则会进入端口设置改变的处理流程中:

  1. 首先检查设置改变的端口是否是 output 端口;
  2. 调用 onOutputFormatChanged 方法;
  3. OMX_CommandPortDisable 关闭 ouput port的使用;
  4. 释放没有被omx持有的output buffer;
  5. 切换到 OutputPortSettingsChangedState;

从这个流程中我们大致可以猜测,处理PortSettingsChanged事件,需要把所有的output buffer销毁。buffer可能有两种状态,一种是被OMX持有,另一种是被上层持有(ACodec、MediaCodec),这两种状态的销毁流程是不太一样的,被OMX持有的output buffer需要回到上层才能被销毁。

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在freeBuffer中完成:

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

相关推荐

  1. Android 13 - Media框架31)- ACodec

    2024-01-17 04:24:01       48 阅读
  2. Android 13 - Media框架33)- ACodec(九)

    2024-01-17 04:24:01       85 阅读
  3. Android 13 - Media框架(20)- ACodec(二)

    2024-01-17 04:24:01       47 阅读
  4. Android 13 - Media框架(21)- ACodec(三)

    2024-01-17 04:24:01       56 阅读
  5. Android 13 - Media框架(22)- ACodec(四)

    2024-01-17 04:24:01       116 阅读
  6. Android 13 - Media框架(21)- ACodec(三)

    2024-01-17 04:24:01       55 阅读
  7. Android 13 - Media框架30)- MediaCodec(五)

    2024-01-17 04:24:01       58 阅读
  8. Android 13 - Media框架(22)- ACodecBufferChannel

    2024-01-17 04:24:01       48 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-17 04:24:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-17 04:24:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-01-17 04:24:01       87 阅读
  4. Python语言-面向对象

    2024-01-17 04:24:01       96 阅读

热门阅读

  1. 我被领导骂了

    2024-01-17 04:24:01       50 阅读
  2. SpringCloud服务之间Feign调用不会带上请求头header

    2024-01-17 04:24:01       54 阅读
  3. 主键、外键、建表范式、MySQL索引、用户管理

    2024-01-17 04:24:01       44 阅读
  4. 1. FPGA概述

    2024-01-17 04:24:01       44 阅读
  5. 1.5 面试经典150题 - 轮转数组

    2024-01-17 04:24:01       50 阅读
  6. Spring Boot整理-Spring Boot的优势

    2024-01-17 04:24:01       47 阅读
  7. C语言中的命名规则(期末版)

    2024-01-17 04:24:01       56 阅读
  8. 什么是WiMAX技术?WiMAX宽带技术的关键技术

    2024-01-17 04:24:01       58 阅读
  9. 2024.1.13 Kafka六大机制和Structured Streaming

    2024-01-17 04:24:01       45 阅读
  10. 隐私计算的技术体系有哪些

    2024-01-17 04:24:01       56 阅读
  11. monorepo工程开发交互nodejs命令行程序

    2024-01-17 04:24:01       54 阅读