Android 13 - Media框架(27)- ACodec(五)

前面几节我们了解了OMXNodeInstance是如何处理setPortMode、allocateBuffer、useBuffer的,这一节我们再回到ACodec,来看看 ACodec start 的其他部分。

我们首先来回顾一下,ACodec start 的状态切换以及处理的事务,我们用一张不太准确的图来表示:

可以看到将 OMX 组件设置为 OMX_StateIdle 之后,OMX 组件会等待所有的 buffer 都分配完成,然后将状态设置完成的消息返回给ACodec层。我们之前说OMX再处理这个状态时会处在阻塞的情况,现在看来,每次接收到buffer都去判断一下有没有收到所有buffer应该就好了,可以不需要阻塞等待 buffer 到来。

ACodec 收到 OMX 组件 OMX_StateIdle 状态设置完成的事件后,会继续将 OMX 组件的状态设置为 OMX_StateExecuting,同时将 ACodec 自身的状态切换到 IdleToExecutingState,到这里组件就开始正式运转起来了,同时会将状态设置完成的事件通知到上层。

ACodec 接收到事件后会正式切换到 ExecutingState,开始运行。

这里会有一个问题,OMX 组件和 ACodec 都正式开始运行了,它们是如何运行起来的,所有的buffer是如何被驱动的?

答案就是在 IdleToExecutingState 的 onOMXEvent 中,在切换到 ExecutingState 之前,会先调用 resume 方法,把 input buffer 交给上层,把output buffer都送给 OMX 组件,这样buffer就开始流转了。

正式看 resume 方法之前,我原先有一个疑惑,LoadedState 状态里,将 OMX 组件状态设置为 OMX_StateExecuting,万一 这个状态设定特别快,ACodec 还没有进入到 IdleToExecutingState ,ACodec 就收到 OMX_StateExecuting 设定完成的事件怎么办呢?是不是 ACodec 就无法执行到 resume 方法了呢?答案是杞人忧天了,ACodec 所有的消息是在一个线程中处理的,sendCommand 和 changeState 方法在同一个函数中执行,属于同一条消息的处理过程,在收到OMX_StateExecuting设定完成时,ACodec 状态时一定进入到 IdleToExecutingState 的。

1、resume

cpp 复制代码
void ACodec::ExecutingState::resume() {
	// 首先判断是否要驱动提交 buffer,只有在 未启动,flush之后才会真正提交
    if (mActive) {
        ALOGV("[%s] We're already active, no need to resume.", mCodec->mComponentName.c_str());
        return;
    }

    submitOutputBuffers();

    // Post all available input buffers
    if (mCodec->mBuffers[kPortIndexInput].size() == 0u) {
        ALOGW("[%s] we don't have any input buffers to resume", mCodec->mComponentName.c_str());
    }

    for (size_t i = 0; i < mCodec->mBuffers[kPortIndexInput].size(); i++) {
        BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(i);
        if (info->mStatus == BufferInfo::OWNED_BY_US) {
            postFillThisBuffer(info);
        }
    }

    mActive = true;
}

我们在上文中已经讲过了,ACodec 通过调用 resume 来驱动 buffer 流转,调用postFillThisBuffer 将 input buffer 传递给 MediaCodec,调用 submitOutputBuffers 将 output buffer 传递给 OMX 组件。

1.1、 submitOutputBuffers

cpp 复制代码
void ACodec::ExecutingState::submitOutputBuffers() {
    submitRegularOutputBuffers();
    if (mCodec->storingMetadataInDecodedBuffers()) {
        submitOutputMetaBuffers();
    }
}

submitOutputBuffers 分为两个步骤,首先会调用 submitRegularOutputBuffers,翻译过来就是 提交常规的 output buffer,这里有个问题,什么是常规 output buffer呢?来看代码:

cpp 复制代码
void ACodec::ExecutingState::submitRegularOutputBuffers() {
    bool failed = false;
    for (size_t i = 0; i < mCodec->mBuffers[kPortIndexOutput].size(); ++i) {
        BufferInfo *info = &mCodec->mBuffers[kPortIndexOutput].editItemAt(i);
		// 有 surface(native window)
        if (mCodec->mNativeWindow != NULL) {
        	// output buffer必须归属于 ACodec 或者是 native window
            if (info->mStatus != BufferInfo::OWNED_BY_US
                    && info->mStatus != BufferInfo::OWNED_BY_NATIVE_WINDOW) {
                ALOGE("buffers should be owned by us or the surface");
                failed = true;
                break;
            }
			// 如果归属于 native window 则直接退出
            if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW) {
                continue;
            }
        } else {
        	// 没有 surface 的情况下,output buffer应该归属于 ACodec
            if (info->mStatus != BufferInfo::OWNED_BY_US) {
                ALOGE("buffers should be owned by us");
                failed = true;
                break;
            }
        }

        ALOGV("[%s] calling fillBuffer %u", mCodec->mComponentName.c_str(), info->mBufferID);
		// 打印 log 检查 fence
        info->checkWriteFence("submitRegularOutputBuffers");
        // 提交 output buffer 给 OMX 组件
        status_t err = mCodec->fillBuffer(info);
        if (err != OK) {
            failed = true;
            break;
        }
    }
	// 如果以上过程出现异常则直接报错
    if (failed) {
        mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
    }
}

首先 output buffer 分为有 Surface 和 无Surface 两种情况,这两种情况下buffer 分别来自于:

  • Surface:在 native window 中分配,归属于 OWNED_BY_NATIVE_WINDOW;
  • no Surface:在 ACodec 中分配,共享内存, 归属于 OWNED_BY_US;

有 surface 的情况下,会检查BufferInfo的归属,如果有buffer归属于组件直接报错,如果有 buffer 归属于 nativewindow,说明graphic buffer还未分配,不需要进行传递。执行到最后我们会发现,BufferInfo 归属于 ACodec 时,该buffer会被传递给 OMX 组件。

没有 surface 的情况下,会检查 BufferInfo 是否归属于 ACodec,如果不是则直接报错。执行到最后会把属于 ACodec 的output buffer 全部传递给 OMX 组件。

综上我们可以得知,BufferInfo 中的 graphic buffer已经被分配,和普通的 output buffer 被认为是 RegularOutput,这两种buffer在一开始就会直接被传递给 OMX 组件。

还有两个问题要注意,初始状态下为什么有surface时,output buffer可能有两个归属呢?这是因为使用有两种情况,我们这里只要求了解 dynamic native window buffer,也就是 BufferInfo 一开始归属于 native window的情况。

这里出现的 fence 我们后面再了解。

1.2、 submitOutputBuffers

上面讲了 regular buffer 是如何提交的,接下来要讲其他buffer是如何提交的。这里的其他指的就是 BufferInfo 中的 graphic buffer 还未分配的情况,之所以要单独拎出来,是因为在提交之前还需要获取 graphic buffer。

调用 submitOutputMetaBuffers 之前要先判断是不是 metadata mode(kPortModeDynamicANWBuffer),我们之前也讲过了,这种模式下,output buffer是动态分配的,一开始是归属于native window的,如果不是这种情况就可以跳过了。

cpp 复制代码
void ACodec::ExecutingState::submitOutputMetaBuffers() {
    // submit as many buffers as there are input buffers with the codec
    // in case we are in port reconfiguring
    for (size_t i = 0; i < mCodec->mBuffers[kPortIndexInput].size(); ++i) {
        BufferInfo *info = &mCodec->mBuffers[kPortIndexInput].editItemAt(i);

        if (info->mStatus == BufferInfo::OWNED_BY_COMPONENT) {
            if (mCodec->submitOutputMetadataBuffer() != OK)
                break;
        }
    }
    if (mCodec->mIsLowLatency) {
        maybePostExtraOutputMetadataBufferRequest();
    }

    // *** NOTE: THE FOLLOWING WORKAROUND WILL BE REMOVED ***
    mCodec->signalSubmitOutputMetadataBufferIfEOS_workaround();
}

看完这段我们要直呼好家伙了,在调用 submitOutputMetadataBuffer 之前会判断 BufferInfo 是否归属于 OWNED_BY_COMPONENT,我们之前刚说过初始状态下,BufferInfo 是归属于 OWNED_BY_NATIVE_WINDOW,也就是说启动一开始,OMX组件是不会获得真正的output buffer的,是不是和我们预期的不一样了呢...

output buffer如何被送给 OMX 组件我们后面会了解到的。

1.3、 postFillThisBuffer

初始状态下,把input buffer送给MediaCodec之前会检查BufferInfo 是否归属于 ACodec,如果是则调用postFillThisBuffer:

cpp 复制代码
void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) {
	// 检查端口是否已经收到eos
    if (mCodec->mPortEOS[kPortIndexInput]) {
        return;
    }

    CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);
	// 给 input buffer 设置初始format
    info->mData->setFormat(mCodec->mInputFormat);
    // 提交给 ACodec
    mCodec->mBufferChannel->fillThisBuffer(info->mBufferID);
    // 解除 BufferInfo 对 MediaCodecBuffer 的引用
    info->mData.clear();
    // 设置bufferinfo 状态
    info->mStatus = BufferInfo::OWNED_BY_UPSTREAM;
}
  1. 检查端口是否已经收到eos,如果是则不会把input buffer回传给上层;
  2. 将默认的input format 设置给 mediaCodecBuffer;
  3. 将 MediaCodecBuffer 传递给 MediaCodec;
  4. 解除 BufferInfo 对 MediaCodecBuffer 的引用,填充数据期间,ACodec无法使用该buffer
  5. 将 BufferInfo 状态设置为 OWNED_BY_UPSTREAM,表示input buffer送给上层填充
相关推荐
sunly_7 小时前
Flutter:启动屏逻辑处理02:启动页
android·javascript·flutter
EasyNTS7 小时前
H5流媒体播放器EasyPlayer.js网页直播/点播播放器如果H.265视频在播放器上播放不流畅,可以考虑的解决方案
javascript·音视频·h.265
Sgq丶7 小时前
Android Studio 配置 proto
android·ide·android studio
小gpt&8 小时前
实现qt拖拽显示或者播放
数据库·qt·音视频
EasyCVR10 小时前
ISUP协议视频平台EasyCVR萤石设备视频接入平台银行营业网点安全防范系统解决方案
大数据·人工智能·物联网·安全·音视频·监控视频接入
_小马快跑_11 小时前
ConstraintLayout 中的ImageFilterView探索:处理图片圆角、亮度、饱和度、图片重叠等
android
IT-sec11 小时前
jquery-picture-cut 任意文件上传(CVE-2018-9208)
android·前端·javascript·安全·web安全·网络安全·jquery
Black蜡笔小新12 小时前
H.265流媒体播放器EasyPlayer.js网页全终端安防视频流媒体播放器可以播放本地视频吗
javascript·音视频·h.265
xiaoduyyy12 小时前
【Android】RecyclerView回收复用机制
android
菊风 Juphoon13 小时前
菊风视频能力平台开发服务正式入驻华为云云商店,成为华为云联营联运合作伙伴
华为云·音视频