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

2023-12-15 07:20
文章标签 android 框架 media 13 22 acodec

本文主要是介绍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 bufferif (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、创建 对应数量的 BufferInfofor (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、创建一个 MediaCodecBufferinfo.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,并且回传 IDerr = 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 bufferif (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 minUndequeuedBufsfor (OMX_U32 extraBuffers = 2 + 1; /* condition inside loop */; extraBuffers--) {// 尝试将output buffer 的数量设置为端口所需的最小数量 +  nativewindow最小未出队列的buffer数量 + 3OMX_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 13 - Media框架(22)- ACodec(四)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/495604

相关文章

Spring 框架之Springfox使用详解

《Spring框架之Springfox使用详解》Springfox是Spring框架的API文档工具,集成Swagger规范,自动生成文档并支持多语言/版本,模块化设计便于扩展,但存在版本兼容性、性... 目录核心功能工作原理模块化设计使用示例注意事项优缺点优点缺点总结适用场景建议总结Springfox 是

Python的端到端测试框架SeleniumBase使用解读

《Python的端到端测试框架SeleniumBase使用解读》:本文主要介绍Python的端到端测试框架SeleniumBase使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全... 目录SeleniumBase详细介绍及用法指南什么是 SeleniumBase?SeleniumBase

Android DataBinding 与 MVVM使用详解

《AndroidDataBinding与MVVM使用详解》本文介绍AndroidDataBinding库,其通过绑定UI组件与数据源实现自动更新,支持双向绑定和逻辑运算,减少模板代码,结合MV... 目录一、DataBinding 核心概念二、配置与基础使用1. 启用 DataBinding 2. 基础布局

Android ViewBinding使用流程

《AndroidViewBinding使用流程》AndroidViewBinding是Jetpack组件,替代findViewById,提供类型安全、空安全和编译时检查,代码简洁且性能优化,相比Da... 目录一、核心概念二、ViewBinding优点三、使用流程1. 启用 ViewBinding (模块级

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

C++ HTTP框架推荐(特点及优势)

《C++HTTP框架推荐(特点及优势)》:本文主要介绍C++HTTP框架推荐的相关资料,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. Crow2. Drogon3. Pistache4. cpp-httplib5. Beast (Boos

SpringBoot基础框架详解

《SpringBoot基础框架详解》SpringBoot开发目的是为了简化Spring应用的创建、运行、调试和部署等,使用SpringBoot可以不用或者只需要很少的Spring配置就可以让企业项目快... 目录SpringBoot基础 – 框架介绍1.SpringBoot介绍1.1 概述1.2 核心功能2

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Spring框架中@Lazy延迟加载原理和使用详解

《Spring框架中@Lazy延迟加载原理和使用详解》:本文主要介绍Spring框架中@Lazy延迟加载原理和使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、@Lazy延迟加载原理1.延迟加载原理1.1 @Lazy三种配置方法1.2 @Component