Android 13 - Media框架(32)- ACodec(八)

拖了好久都没有更新,前面写的东西都有些忘了,回过头来再看之前写的内容,觉得有很多地方写的不好,或者说现在又有了新的理解,想要重新修改但是需要修改的内容太多,因此决定按照当前的思路把剩余的内容写完。
Android ACodec OpenMax部分还有OutputPortSettingsChangedState、Flush、Release、output buffer的处理这四块内容,写完了之后可能会花时间重新再阅读一遍,整理出更系统的内容。加油!

接前面内容,之前我们已经了解了MediaCodec如何启动,ACodec的input/output buffer是如何分配的,以及OMXNodeInstance是如何发消息。接下来将会学习解码启动后正常运转过程的内容。

这一节就来看OutputPortSettingsChangedState这个状态。

在之前的学习中我们有提到过,播放过程中码流的分辨率发生了变化,这时候应该怎么办呢?

1、OMX_EventPortSettingsChanged

现在的decoder一般都会支持 Adaptive Playback,所谓自适应播放指的是两部分内容:

  1. 起播时并不一定要设定准确的宽高信息,解码器可以自己解出码流的宽高信息,并且做出调整;
  2. 播放过程中码流的分辨率发生变化,播放器可以自适应调整;

这里说的调整指的就是自己调整output buffer size,不需要我们重启播放器。buffer size调整的过程涉及到原有buffer的释放,以及新的buffer的分配,所以需要做的内容会比较多。

bool ACodec::ExecutingState::onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {switch (event) {case OMX_EventPortSettingsChanged:{// 检查是否是Output portCHECK_EQ(data1, (OMX_U32)kPortIndexOutput);// 获取新的output formatmCodec->onOutputFormatChanged();if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {// 将待提交的 buffer 数量设置为 0mCodec->mMetadataBuffersToSubmit = 0;// 禁用 output portCHECK_EQ(mCodec->mOMXNode->sendCommand(OMX_CommandPortDisable, kPortIndexOutput),(status_t)OK);// 释放所有没有送给 OMX 组件的output buffermCodec->freeOutputBuffersNotOwnedByComponent();// 进入状态 OutputPortSettingsChangedStatemCodec->changeState(mCodec->mOutputPortSettingsChangedState);} else if (data2 != OMX_IndexConfigCommonOutputCrop&& data2 != OMX_IndexConfigAndroidIntraRefresh) {ALOGV("[%s] OMX_EventPortSettingsChanged 0x%08x",mCodec->mComponentName.c_str(), data2);}return true;}default:return BaseState::onOMXEvent(event, data1, data2);}
}
  1. ExecutingState状态下,收到OMX_EventPortSettingsChanged消息后,ACodec首先从组件中获取到新的OutputFormat,onOutputFormatChanged方法我们这里不做展开。
  2. 接着将mMetadataBuffersToSubmit置为0,这个操作是让起播时收到OMX_EventPortSettingsChanged,向组件写input 数据时不要再传递output buffer。
  3. 禁用OMX组件的 output port。
  4. 释放所有没有送给 OMX 组件的output buffer。
  5. ACodec进入到OutputPortSettingsChangedState状态。

这里要们先看一下第四点freeOutputBuffersNotOwnedByComponent:

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前会先检查BufferInfo的状态,如果BufferInfo不属于OMX组件,或者不是在等待渲染的状态,这时候需要调用freeBuffer。

我们再来回顾一下,OutputBuffer 可能有四种状态:

  • BufferInfo::OWNED_BY_US
  • BufferInfo::OWNED_BY_COMPONENT
  • BufferInfo::OWNED_BY_DOWNSTREAM
  • BufferInfo::OWNED_BY_NATIVE_WINDOW

意思也就是,当OutputBuffer归属于ACodec或者是nativewindow时可以释放。

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 metadataif (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 failsmBuffers[portIndex].removeAt(i);return err;
}

对于OWNED_BY_US和OWNED_BY_NATIVE_WINDOW两种状态,freebuffer需要做的内容有一点点不一样,OWNED_BY_US需要多做一个cancelBufferToNativeWindow,可以理解为取消使用的意思,将graphic buffer返回给nativewindow,graphic buffer返回之后再调用freeBuffer,释放OMX组件所持有的graphic buffer了。

到这,我们要先想想OMX组件什么时候会发送 OMX_EventPortSettingsChanged 事件回来?我理解的是,前一个序列的output全部回传给了上层,下一个序列回传之前会送出事件,处理完成之后再把新的序列的output填充回传。

如我们上文所说的,output buffer有四种状态,OWNED_BY_NATIVE_WINDOW指的是buffer还未分配,或者已经送到nativewindow等待渲染;OWNED_BY_US表示buffer由ACodec持有,有两种可能,一种是buffer处在ACodec处理的中间状态,还有一种是buffer不需要被处理,由ACodec持有。这两种状态下,output buffer确定不会被使用,所以可以先release。

OWNED_BY_DOWNSTREAM状态下的buffer表示回传给上层做Avsync,还未render,等待render完成后会release。

OWNED_BY_COMPONENT状态下的buffer需要等OMX组件把buffer id回传,确认OMX组件不再使用再release。

2、OutputPortSettingsChangedState

void ACodec::OutputPortSettingsChangedState::stateEntered() {ALOGV("[%s] Now handling output port settings change",mCodec->mComponentName.c_str());// If we haven't transitioned after 3 seconds, we're probably stuck.sp<AMessage> msg = new AMessage(ACodec::kWhatCheckIfStuck, mCodec);msg->setInt32("generation", mCodec->mStateGeneration);msg->post(3000000);
}

进入OutputPortSettingsChangedState后会设定一个timeout时间,如果3s内还在OutputPortSettingsChangedState状态下就会返回error,意思就是3s内需要完成OMX_EventPortSettingsChanged事件的处理。

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

再来看OutputPortSettingsChangedState状态下的port mode为FREE_BUFFERS,指的是output buffer render完成后就会释放掉。

bool ACodec::OutputPortSettingsChangedState::onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {switch (event) {case OMX_EventCmdComplete:{if (data1 == (OMX_U32)OMX_CommandPortDisable) {if (data2 != (OMX_U32)kPortIndexOutput) {ALOGW("ignoring EventCmdComplete CommandPortDisable for port %u", data2);return false;}ALOGV("[%s] Output port now disabled.", mCodec->mComponentName.c_str());// 检查output buffer列表是否为空status_t err = OK;if (!mCodec->mBuffers[kPortIndexOutput].isEmpty()) {ALOGE("disabled port should be empty, but has %zu buffers",mCodec->mBuffers[kPortIndexOutput].size());err = FAILED_TRANSACTION;} else {mCodec->mAllocator[kPortIndexOutput].clear();}// 重新开启output portif (err == OK) {err = mCodec->mOMXNode->sendCommand(OMX_CommandPortEnable, kPortIndexOutput);}// Clear the RenderQueue in which queued GraphicBuffers hold the// actual buffer references in order to free them early.mCodec->mRenderTracker.clear(systemTime(CLOCK_MONOTONIC));// 重新分配bufferif (err == OK) {err = mCodec->allocateBuffersOnPort(kPortIndexOutput);ALOGE_IF(err != OK, "Failed to allocate output port buffers after port ""reconfiguration: (%d)", err);// 通知上层output buffer发生变化mCodec->mCallback->onOutputBuffersChanged();}if (err != OK) {mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));ALOGE("Error occurred while disabling the output port");}return true;} else if (data1 == (OMX_U32)OMX_CommandPortEnable) {if (data2 != (OMX_U32)kPortIndexOutput) {ALOGW("ignoring EventCmdComplete OMX_CommandPortEnable for port %u", data2);return false;}ALOGV("[%s] Output port now reenabled.", mCodec->mComponentName.c_str());// 如果还在运行状态则重新提交所有bufferif (mCodec->mExecutingState->active()) {mCodec->mExecutingState->submitOutputBuffers();}// 重新返回到ExecutingStatemCodec->changeState(mCodec->mExecutingState);return true;}return false;}default:return BaseState::onOMXEvent(event, data1, data2);}
}

我们之前在 ExecutingState 看到有发送一条 OMX_CommandPortDisable 消息给OMX组件,当然OMX组件也会返回一条Disable执行完成的消息,问题是什么时候OMX组件会回传这一条消息呢?

OMX组件收到OMX_CommandPortDisable后,会把所有持有的OutputBuffer都回传给上层,上层处理时检查到port mode为FREE_BUFFERS会直接进入到释放流程,所有OMX组件持有的OutputBuffer回传完毕后就会发送 OMX_EventCmdComplete 消息,表示当前命令执行完成。

进入到 OMX_EventCmdComplete 事件的处理中,我们会发现会检查 mBuffers 是否为空,我们刚刚分析的流程只能确保OWNED_BY_COMPONENT状态的buffer被释放,却不能保证OWNED_BY_DOWNSTREAM的buffer被释放,那么到这里就一定会抛出error,因此上面的分析肯定有遗漏的地方。

仔细想想,OMX组件上抛OMX_CommandPortDisable执行完成的时机肯定是有问题的,组件应该是等所有的output buffer都调用了 freeBuffer后才会发出执行完成的命令,这时候mBuffers中就不会有任何buffer存在了。

所有buffer释放完成后会重新enable output port,然后再次调用 allocateBuffersOnPort 分配 output buffer,这部分内容我们在之前的章节中已经分析过了。buffer重新分配完成后需要发送callback onOutputBuffersChanged 给上层。

这一系列动作都完成后就会重新进入ExecutingState,同时调用submitOutputBuffers把output buffer提交给OMX组件,但是在之前的分析中我们可以知道这里其实不会调用fillBuffer,最初的output buffer需要和input buffer一起送给OMX组件。

这里就会有一点点问题了,如果input已经到达了EOS,不会再有input写入,那么OMX岂不是会出现没有OUtput buffer可用的情况,为了处理这个问题,ACodec在submitOutputMetaBuffers中调用了一个方法signalSubmitOutputMetadataBufferIfEOS_workaround,如果input port到达了EOS,而output还未到达,那么会直接提交所有的output buffer。我觉得可能还有一种情况没有考虑到,如果input ring被写满,那么input buffer转不起来了,岂不是OMX组件也会有没有output buffer可用的情况呢?

好了,到这里这一节就分析结束了,下一节我们将一起学习output buffer是如何被处理的。

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

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

相关文章

密码学——二次剩余

引言 二次剩余在许多密码学算法和数论问题中具有重要的作用,这个概念涉及到同余方程等概念。 同余 学习二次剩余的概念,首先要了解同余方程的概念,首先默认研究范围为整数,假设 f ( x ) f(x) f(x)是一个整系数多项式,我们讨论是否有整数值x满足同余式 f (

(已解决)mac中全局安装express-generator后,爆出zsh: command not found: express

问题描述&#xff1a; 在mac中全局安装express-generator后&#xff0c;在终端输入express命令提示&#xff1a;zsh: command not found: express 原因分析&#xff1a; 因为系统没有找到express命令的位置。 解决步骤&#xff1a; 检查是否正确安装了express-generator&…

react-JSX基本使用

1.目标 能够知道什么是JSX 能够使用JSX创建React元素 能够在JSX中使用JS表达式 能够使用JSX的条件渲染和列表渲染 能够给JSX添加样式 2.目录 JSX的基本使用 JSX中使用JS表达式 JSX的条件渲染 JSX的列表渲染 JSX的样式处理 3.JSX的基本使用 3.1 createElement()的问题 A. …

Git - 开启2FA

Refrence: [Github实战]双重认证2FA 如何 设置/更改[手把手][2022]_enable two-factor authentication (2fa)-CSDN博客

在Node.js中如何实现用户身份验证和授权

当涉及到构建安全的应用程序时&#xff0c;用户身份验证和授权是至关重要的一环。在Node.js中&#xff0c;我们可以利用一些流行的库和技术来实现这些功能&#xff0c;确保我们的应用程序具有所需的安全性。本篇博客将介绍如何在Node.js中实现用户身份验证和授权。 用户身份验…

“智农”-大棚可视化

基于自主可控的数字孪生技术、物联网技术、大数据技术&#xff0c;构建全流程的新型农业一体化管理平台&#xff0c;围绕产运销管理全流程&#xff0c;实现生产->存储->包装->运输->销售的全链条管理。融合农业数据管理、农业数据预警显示、多维数据综合显示、农产…

Jvm之内存泄漏

1 内存溢出 1.1 概念 java.lang.OutOfMemoryError&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现OutOfMemoryError。产生该错误的原因主要包括&#xff1a;JVM内存过小。程序不严密&#xff0c;产生了过多的垃圾。 程序体现: 内…

微服务架构 SpringCloud

单体应用架构 将项目所有模块(功能)打成jar或者war&#xff0c;然后部署一个进程--医院挂号系统&#xff1b; > 优点: > 1:部署简单:由于是完整的结构体&#xff0c;可以直接部署在一个服务器上即可。 > 2:技术单一:项目不需要复杂的技术栈&#xff0c;往往一套熟悉的…

LabVIEW水下温盐深数据一体化采集与分析

LabVIEW水下温盐深数据一体化采集与分析 开发一个基于LabVIEW的水下温盐深数据一体化采集与分析系统&#xff0c;实现海洋环境监测的自动化和精确化。通过集成温度、盐度和深度传感器&#xff0c;结合USB数据采集卡&#xff0c;利用LabVIEW软件开发的图形化界面&#xff0c;实…

编程笔记 html5cssjs 095 JavaScript 第三方库或框架

编程笔记 html5&css&js 095 JavaScript 第三方库或框架 一、流行的JavaScript第三方库和框架二、Node.js简介三、Bootstrap简介四、jQuery简介五、React简介六、Vue.js简介 JavaScript的第三方库和框架是构建现代Web应用程序不可或缺的部分&#xff0c;它们提供了预封装…

Elasticsearch的基本安装教程,Elasticsearch+SpringBoot实现简单的增删改查功能

Elasticsearch 是一个开源的分布式搜索和分析引擎,最初由 Elastic 公司开发。它是基于 Apache Lucene 的搜索引擎构建的,提供了强大的搜索和分析功能,并支持实时数据检索和分析。 Elasticsearch 被设计用来处理大规模的数据集,它具有以下几个主要特点: 分布式架构: Elast…

淘宝关键词搜索API、搜索商品接口、获取商品列表商品id

淘宝搜索引擎的工作原理&#xff1a; 淘宝搜索引擎的工作原理是基于搜索引擎的核心技术——爬虫和索引&#xff0c;通过对海量数据的抓取、分析和存储&#xff0c;提供给用户最准确的搜索结果。 具体来说&#xff0c;淘宝搜索引擎的工作流程如下&#xff1a; 企业级api数据…

腾讯云服务器4核8G性能,和阿里云比怎么样?

腾讯云4核8G服务器支持多少人在线访问&#xff1f;支持25人同时访问。实际上程序效率不同支持人数在线人数不同&#xff0c;公网带宽也是影响4核8G服务器并发数的一大因素&#xff0c;假设公网带宽太小&#xff0c;流量直接卡在入口&#xff0c;4核8G配置的CPU内存也会造成计算…

12.Prometheus配置

平凡也就两个字: 懒和惰; 成功也就两个字: 苦和勤; 优秀也就两个字: 你和我。 跟着我从0学习JAVA、spring全家桶和linux运维等知识,带你从懵懂少年走向人生巅峰,迎娶白富美! 关注微信公众号【 IT特靠谱 】,每天都会分享技术心得~ 1.Prometheus配置 Prometheus服务通常可以…

这波知识点分享可得接稳了!非线性模型线性化方法技巧!

现在电力系统优化方向的文章几乎都要提及将非线性模型线性化&#xff0c;使用的方法大致可包括分段线性化&#xff08;最基础&#xff09;&#xff0c;混合整数线性化方法&#xff0c;绝对值法&#xff0c;大M方法&#xff0c;关于非线性模型线性化方法的文章和推文介绍也数不胜…

解决方案各缩写解释 OR/IR/SF/SR/AR

OR (Offering Requirement,产品包需求&#xff09;: 来自公司内、外部的原始需求。 IR (Initial Requirement,初始需求) : 站在内部客户/市场角度&#xff0c;以准确的语言重新描述的需求。 SF (System Feature,系统特性)&#xff1a; 描述该版本为解决客户问题所具备的重大能…

JAVA AQS源码深度讲解和分析

为方便理解&#xff0c;本文章以非公平锁ReentrantLock()为例作为突破讲解方法lock。 前置知识&#xff1a;JAVA AQS源码分析前置知识-CSDN博客 ReentrantLock的原理 Lock接口的实现类&#xff0c;基本都是通过聚合了一个队列同步器的子类完成线程访问控制的 从最简单的lock方…

C语言————结构体

接下来我们来了解C语言中很重要的内容&#xff1a;结构体。虽然到现在我们可以创建常量&#xff0c;变量&#xff0c;数组&#xff0c;但是存储的都是相同类型的数据&#xff0c;如果我们需要写入不同数据类型的信息怎么办&#xff0c;例如常见的身份证上的信息&#xff0c;有身…

springboot+vue+mysql+easyexcel实现文件导出+导出的excel单元格添加下拉列表

Excel导出 EasyExcel官方文档 官方文档本身写的非常详细&#xff0c;我就是根据官方文档内的写Excel里web中的写实现的导出 后端 对象 需要写一个实体类 其中涉及到一些用到的EasyExcel的注解 ColumnWidth(20) 列宽设为20&#xff0c;自定义的&#xff0c;放在实体类上面是…

Postgresql中触发器的使用

在PostgreSQL中&#xff0c;触发器是一种特殊类型的函数&#xff0c;它会自动在数据库上执行特定操作之前或之后触发。这些操作通常是INSERT、UPDATE或DELETE语句。触发器可以用来执行数据校验、自动更新或维护表之间的关联。 触发器组件 触发器函数&#xff1a;这是实际执行…