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

相关文章

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

Android Studio 配置国内镜像源的实现步骤

《AndroidStudio配置国内镜像源的实现步骤》本文主要介绍了AndroidStudio配置国内镜像源的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、修改 hosts,解决 SDK 下载失败的问题二、修改 gradle 地址,解决 gradle

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现