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

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 buffersif (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

void ACodec::ExecutingState::submitOutputBuffers() {submitRegularOutputBuffers();if (mCodec->storingMetadataInDecodedBuffers()) {submitOutputMetaBuffers();}
}

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

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 windowif (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应该归属于 ACodecif (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 检查 fenceinfo->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的,如果不是这种情况就可以跳过了。

void ACodec::ExecutingState::submitOutputMetaBuffers() {// submit as many buffers as there are input buffers with the codec// in case we are in port reconfiguringfor (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:

void ACodec::BaseState::postFillThisBuffer(BufferInfo *info) {// 检查端口是否已经收到eosif (mCodec->mPortEOS[kPortIndexInput]) {return;}CHECK_EQ((int)info->mStatus, (int)BufferInfo::OWNED_BY_US);// 给 input buffer 设置初始formatinfo->mData->setFormat(mCodec->mInputFormat);// 提交给 ACodecmCodec->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送给上层填充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/588369.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

EOS运行启动 keosd 和 nodeos

EOS运行启动 keosd 和 nodeos 启动keosd:启动nodeos 安装完eosio后keosd和nodeos是一起带着的&#xff0c;我把EOS的运行keosd 和 nodeos官网教程放上来&#xff1a; https://developers.eos.io/welcome/latest/getting-started-guide/local-development-environment/start-nod…

Python Pymysql实现数据存储

什么是 PyMySQL&#xff1f; PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库&#xff0c;Python2 中则使用mysqldb。 PyMySQL 遵循 Python 数据库 API v2.0 规范&#xff0c;并包含了 pure-Python MySQL 客户端库。 PyMySQL 安装 在使用 PyMySQL 之前&#xf…

软件工程期末复习习题

知识点总结 第一章&#xff1a;软件工程概述 1、软件的定义&#xff1a;在运行中能提供所希望的功能与性能的程序使程序能够正确运行的数据及其结构描述软件研制过程和方法所用的文档。 2、软件危机&#xff1a;软件开发的生产率远远不能满足客观需要。开发的软件产品往往不能…

【PowerMockito:编写单元测试过程中采用when打桩失效的问题】

问题描述 正如上图所示&#xff0c;采用when打桩了&#xff0c;但是&#xff0c;实际执行的时候还是返回null。 解决方案 打桩时直接用any() 但是这样可能出现一个mybatisplus的异常&#xff0c;所以在测试类中需要加入以下代码片段&#xff1a; Beforepublic void setUp() …

你真的懂Hello World!吗?(编译与链接,静态链接与动态链接)

&#x1f4ab;Hello World! 对于大家来说Hello World!应该是最熟悉不过的一句话&#xff0c;我们从Hello World!走进了计算机的世界&#xff0c;但是你真的了解Hello World!吗&#xff1f;你又思考过它背后蕴含的机理吗&#xff1f;他是怎么从代码变成程序的你真的思考过吗&…

G1收集器简介

G1是一款既收集新生代又收集老年代的收集器&#xff0c;使用它可以实现整个java堆的gc&#xff0c;它有两个非常重要的新概念&#xff1a;region和remember set&#xff08;简称rset&#xff09;。 region是啥&#xff1f; G1中虽然保留了新生代和老年代的概念&#xff0c;但是…

Spring Boot程序输出远程访问IP

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《SpringBoot开发》。&#x1f3af;&#x1f3af;…

03 HAL库下UART的使用

引言&#xff1a; 需要使用到的uart调试工具在文章最后的资料里面 题外话&#xff1a;uart和usart的区别 UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;和USART&#xff08;Universal Synchronous/Asynchronous Receiver/Transmitter&#xff09;…

获取Windows10系统原始安装日期

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 目标 获取Windows10系统最原始的安装日期&#xff1b;例如&#xff1a;刚买电脑时安装系统的时间。 步骤 第一步&#xff0c;请打开PowerShell&#xff0c;单击Windows P…

评价类问题:层次分析法

引言&#xff1a;打分法 一、解决评价类问题的三个方面 二、如何寻找评价准则 三、如何确定权重 &#xff08;1&#xff09;分而治之的思想 &#xff08;2&#xff09; 层次分析法思想&#xff1a; 指标重要性&#xff08;满意程度&#xff09; 判断矩阵&#xff08;正反矩…

面试题-JVM 初级面试题(40道含答案)

author: 小郑说编程 JVM 初级面试题 1、对象在哪块内存分配&#xff1f; 数组和对象在堆内存分配&#xff1b;某些对象没有逃逸出方法&#xff0c;可能被优化为在栈上分配 2、谈谈 JVM 中的常量池 JDK 1.8 开始 字符串常量池&#xff1a;存放在堆中&#xff0c;包括 Strin…

分布式【4. 什么是 CAP?】

什么是 CAP&#xff1f; C 代表 Consistency&#xff0c;一致性&#xff0c;是指所有节点在同一时刻的数据是相同的&#xff0c;即更新操作执行结束并响应用户完成后&#xff0c;所有节点存储的数据会保持相同。 A 代表 Availability&#xff0c;可用性&#xff0c;是指系统提…

[原创][R语言]股票分析实战[6]:正则表达式提取子字符串

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

LeetCode75| 滑动窗口

目录 643 子数组最大平均数 | 1456 定长子串中元音的最大数目 1004 最大连续1的个数 ||| 1493 删掉一个元素以后全为1的最长子数组 643 子数组最大平均数 | class Solution { public:double findMaxAverage(vector<int>& nums, int k) {double sum 0;double re…

数据结构与算法笔记

数据结构&#xff1a; 就是指一组数据的存储结构 算法&#xff1a; 就是操作数据的一组方法 数据结构和算法 两者关系 数据结构和算法是相辅相成的。数据结构是为算法服务的&#xff0c;算法要作用在特定的数据结构之上。 数据结构是静态的&#xff0c;它只是组织数据的一…

Quartz调度引擎基于MySQL的高可用架构调度延迟分析与解决方案

1、Quartz默认使用的高可用架构 在Quartz的官方文档中&#xff0c;介绍了一种默认的高可用架构&#xff0c;基于数据库实现。该方案中&#xff0c;多台Quartz服务器连接同一个数据库&#xff0c;单台服务器每次调度检索并锁定一批Trigger用于触发&#xff0c;锁定过程中将先从…

Kubernetes网络-VXLAN

一. 网络基础 1. 计算机网络的分层 如今连接方式也越来也丰富&#xff0c;网线、WiFi、蓝牙、光纤&#xff0c;甚至我们普通的电线、照明所用的灯光&#xff0c;都可以作为接入网络的介质。如此庞大的网络&#xff0c;丰富多样的设备&#xff0c;计算机网络技术能把它们统一起…

认识计算机网络——计算机网络的组成

计算机网络是由多个计算机和网络设备组成的系统&#xff0c;通过通信协议实现数据传输和信息交换。它是现代社会信息技术的重要支撑&#xff0c;广泛应用于各个领域。本文将介绍计算机网络的主要组成部分&#xff0c;包括硬件设备、软件协议和网络服务。 一、硬件设备 计算机网…

46、激活函数 - Relu 激活

本节介绍一个在神经网络中非常常见的激活函数 - Relu 激活函数。 什么是ReLU激活函数 ReLU 英文名为 Rectified Linear Unit,又称修正线性单元,是一种简单但很有效的激活函数,它的定义如下: 即当输入 x 大于零时,输出等于他自己;当输入小于等于零时,输出为零,下面是re…

【Android进阶篇】Android中PreferenceScreen的作用和详细用法介绍

1&#xff0c;PreferenceScreen的作用 在Android开发中&#xff0c;PreferenceScreen是一个非常重要的布局控件&#xff0c;主要用于创建设置界面&#xff08;settings page&#xff09;。它可以包含多个Preference子项&#xff0c;如CheckBoxPreference, ListPreference等&am…