Android 13 - Media框架(30)- MediaCodec(五)

前一节我们了解了input buffer写入的流程,知道了起播写前几笔数据时会先获取graphic buffer,这一节我们就一起来了解下dequeueBufferFromNativeWindow是如何工作的。

1、dequeueBufferFromNativeWindow

ACodec::BufferInfo *ACodec::dequeueBufferFromNativeWindow() {// 判断是否有 native windowANativeWindowBuffer *buf;CHECK(mNativeWindow.get() != NULL);// tunnel mode无需获取graphic bufferif (mTunneled) {ALOGW("dequeueBufferFromNativeWindow() should not be called in tunnel"" video playback mode mode!");return NULL;}if (mFatalError) {ALOGW("not dequeuing from native window due to fatal error");return NULL;}int fenceFd = -1;do {// 从 native window 获取 bufferstatus_t err = mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf, &fenceFd);if (err != 0) {ALOGE("dequeueBuffer failed: %s(%d).", asString(err), err);return NULL;}bool stale = false;for (size_t i = mBuffers[kPortIndexOutput].size(); i > 0;) {i--;// 获取一个 BufferInfo,index从大到小BufferInfo *info = &mBuffers[kPortIndexOutput].editItemAt(i);// 如果bufferinfo中的mGraphicBuffer不为null,且mGraphicBuffer的handle等于获取的buffer的handleif (info->mGraphicBuffer != NULL &&info->mGraphicBuffer->handle == buf->handle) {// Since consumers can attach buffers to BufferQueues, it is possible// that a known yet stale buffer can return from a surface that we// once used.  We can simply ignore this as we have already dequeued// this buffer properly.  NOTE: this does not eliminate all cases,// e.g. it is possible that we have queued the valid buffer to the// NW, and a stale copy of the same buffer gets dequeued - which will// be treated as the valid buffer by ACodec.// 如果buffer的状态不是属于native window,则说明这个buffer是过时的bufferif (info->mStatus != BufferInfo::OWNED_BY_NATIVE_WINDOW) {ALOGI("dequeued stale buffer %p. discarding", buf);stale = true;break;}ALOGV("dequeued buffer #%u with age %u, graphicBuffer %p",(unsigned)(info - &mBuffers[kPortIndexOutput][0]),mDequeueCounter - info->mDequeuedAt,info->mGraphicBuffer->handle);// 否则直接将bufferinfo返回,同时给bufferinfo设置write fenceinfo->mStatus = BufferInfo::OWNED_BY_US;info->setWriteFence(fenceFd, "dequeueBufferFromNativeWindow");updateRenderInfoForDequeuedBuffer(buf, fenceFd, info);return info;}}// It is also possible to receive a previously unregistered buffer// in non-meta mode. These should be treated as stale buffers. The// same is possible in meta mode, in which case, it will be treated// as a normal buffer, which is not desirable.// TODO: fix this.if (!stale && !storingMetadataInDecodedBuffers()) {ALOGI("dequeued unrecognized (stale) buffer %p. discarding", buf);stale = true;}// 如果是过时的buffer则重新从native window获取graphic bufferif (stale) {// TODO: detach stale buffer, but there is no API yet to do it.buf = NULL;}} while (buf == NULL);// get oldest undequeued buffer// 计算没有使用时间最长的bufferBufferInfo *oldest = NULL;for (size_t i = mBuffers[kPortIndexOutput].size(); i > 0;) {i--;BufferInfo *info =&mBuffers[kPortIndexOutput].editItemAt(i);if (info->mStatus == BufferInfo::OWNED_BY_NATIVE_WINDOW &&(oldest == NULL ||// avoid potential issues from counter rolling overmDequeueCounter - info->mDequeuedAt >mDequeueCounter - oldest->mDequeuedAt)) {oldest = info;}}// it is impossible dequeue a buffer when there are no buffers with ANWCHECK(oldest != NULL);// it is impossible to dequeue an unknown buffer in non-meta mode, as the// while loop above does not completeCHECK(storingMetadataInDecodedBuffers());// 将获取到的graphic buffer绑定到buffer info上,并且标注上是新的graphic buffer// discard buffer in LRU info and replace with new bufferoldest->mGraphicBuffer = GraphicBuffer::from(buf);oldest->mNewGraphicBuffer = true;oldest->mStatus = BufferInfo::OWNED_BY_US;// 设置 write fenceoldest->setWriteFence(fenceFd, "dequeueBufferFromNativeWindow for oldest");mRenderTracker.untrackFrame(oldest->mRenderInfo);oldest->mRenderInfo = NULL;ALOGV("replaced oldest buffer #%u with age %u, graphicBuffer %p",(unsigned)(oldest - &mBuffers[kPortIndexOutput][0]),mDequeueCounter - oldest->mDequeuedAt,oldest->mGraphicBuffer->handle);updateRenderInfoForDequeuedBuffer(buf, fenceFd, oldest);return oldest;
}

dequeueBufferFromNativeWindow 的代码比较长,但是如果了解buffer机制很容易就知道这段代码是什么意思了,接下来就来谈谈我的理解。

在开始解码流程之初,我们都知道output buffer没有指向实际的graphic buffer,而正常工作的状态下,一个 BufferInfo 会对应有一个 graphic buffer,但是这里的绑定关系可能会变化的,因为从native window 中获取的 buffer 可能是新的 buffer,这时候就要选择绑定到某一个 BufferInfo 上了。了解了这个内容,我们再来看代码:

  • 调用mNativeWindow->dequeueBuffer获取graphic buffer;
  • 遍历所有的 BufferInfo,查找是否有handle相同的BufferInfo,如果有就说明这个graphic buffer已经绑定过 BufferInfo 了,接下来就要检查 graphic buffer的状态,如果BufferInfo存储的状态不是OWNED_BY_NATIVE_WINDOW,就说明这块buffer已经被使用了,还没有还给native window,这块buffer是不能够再被使用的,因此需要重新dequeueBuffer;
  • 如果dequeue出来的graphic buffer没有对应的 BufferInfo,说明这是一块新的 graphic buffer,需要绑定到一个 BufferInfo,绑定到哪一个上面呢?
    • ACodec 给出的方法是绑定到一个最久没有被使用的 BufferInfo 上;如何判断最久没有被使用呢?遍历所有的BufferInfo,判断归属于OWNED_BY_NATIVE_WINDOW,并且mDequeuedAt最小的BufferInfo;ACodec 有一个成员 mDequeueCounter ,decoder每次输出一帧,这个计数值会加一;BufferInfo有一个成员mDequeuedAt,这个值用于记录当前BufferInfo在第几帧被使用(填上输出);有了这两个值,就可以知道当前哪个 BufferInfo 最久没有被使用了。
    • 最新绑定的 BufferInfo 还有一个成员 mNewGraphicBuffer 会被置为 true,说明这是一个新的 graphic buffer,需要注册给 OMX 组件。
  • dequeueBuffer 获取 graphic buffer的同时会拿到一个 fence,fence翻译为篱笆,其实就是保护的意思;为什么要这个东西呢?dequeueBuffer 有一种特殊情况,graphic buffer已经被填充返回给 native window,但是buffer中的内容还没有被消费,这时候就重新被获取,并让decoder去填充;但是这很明显是有问题的,decoder需要等待 buffer 中的内容被消费完才能填充这个 buffer,但是graphic buffer的填充和使用是在两个不同的进程当中的,要如何知道buffer被消费完成呢?或者知道buffer被填充完成呢?这里就用fence来实现,填充完成时用fence加锁,等到消费完成解锁,才能继续填充,大致就是这个意思。
  • FrameRenderTracker我们后面再研究是干什么用的。

到这 dequeueBufferFromNativeWindow 就分析完成了,拿到graphic buffer之后,就要把它送给decoder使用了。

2、fillBuffer

status_t ACodec::fillBuffer(BufferInfo *info) {status_t err;// Even in dynamic ANW buffer mode, if the graphic buffer is not changing,// send sPreset instead of the same graphic buffer, so that OMX server// side doesn't update the meta. In theory it should make no difference,// however when the same buffer is parcelled again, a new handle could be// created on server side, and some decoder doesn't recognize the handle// even if it's the same buffer.if (!storingMetadataInDecodedBuffers() || !info->mNewGraphicBuffer) {err = mOMXNode->fillBuffer(info->mBufferID, OMXBuffer::sPreset, info->mFenceFd);} else {err = mOMXNode->fillBuffer(info->mBufferID, info->mGraphicBuffer, info->mFenceFd);}info->mNewGraphicBuffer = false;info->mFenceFd = -1;if (err == OK) {info->mStatus = BufferInfo::OWNED_BY_COMPONENT;}return err;
}

fillBuffer 的代码比较简单,就是调用 OMXNode 的 fillBuffer 方法,但是分为两种情况:

  • 当 BufferInfo 没有发生变化时,调用 fillBuffer 时传入参数为 OMXBuffer::sPreset;什么叫没有发生变化,当output buffer 使用普通buffer,buffer就不会发生变化;当graphic buffer没有发生变化,BufferInfo也视作为没有发生变化;
  • 当 BufferInfo 发生变化时,调用 fillBuffer 传入参数为info->mGraphicBuffer;当BufferInfo中绑定了新的graphic buffer时,需要把graphic buffer重新绑定给decoder,因此需要作为参数传递给OMX。

3、OMXNodeInstance::fillBuffer

status_t OMXNodeInstance::fillBuffer(IOMX::buffer_id buffer, const OMXBuffer &omxBuffer, int fenceFd) {Mutex::Autolock autoLock(mLock);if (mHandle == NULL) {return DEAD_OBJECT;}// 找到id对应的 Buffer headerOMX_BUFFERHEADERTYPE *header = findBufferHeader(buffer, kPortIndexOutput);if (header == NULL) {ALOGE("b/25884056");return BAD_VALUE;}// 判断buffer type是否为kBufferTypeANWBufferif (omxBuffer.mBufferType == OMXBuffer::kBufferTypeANWBuffer) {// 将 graphic buffer 绑定给 bufferMetastatus_t err = updateGraphicBufferInMeta_l(kPortIndexOutput, omxBuffer.mGraphicBuffer, buffer, header);if (err != OK) {CLOG_ERROR(fillBuffer, err, FULL_BUFFER((intptr_t)header->pBuffer, header, fenceFd));return err;}} else if (omxBuffer.mBufferType != OMXBuffer::kBufferTypePreset) {return BAD_VALUE;}// 重置buffer状态header->nFilledLen = 0;header->nOffset = 0;header->nFlags = 0;// 等待fence释放// meta now owns fenceFdstatus_t res = storeFenceInMeta_l(header, fenceFd, kPortIndexOutput);if (res != OK) {CLOG_ERROR(fillBuffer::storeFenceInMeta, res, EMPTY_BUFFER(buffer, header, fenceFd));return res;}{Mutex::Autolock _l(mDebugLock);mOutputBuffersWithCodec.add(header);CLOG_BUMPED_BUFFER(fillBuffer, WITH_STATS(EMPTY_BUFFER(buffer, header, fenceFd)));}// 传递给 OMX 组件OMX_ERRORTYPE err = OMX_FillThisBuffer(mHandle, header);if (err != OMX_ErrorNone) {CLOG_ERROR(fillBuffer, err, EMPTY_BUFFER(buffer, header, fenceFd));Mutex::Autolock _l(mDebugLock);mOutputBuffersWithCodec.remove(header);}return StatusFromOMXError(err);
}
  • 当ACodec有新的graphic buffer传递下来时,OMXNodeInstance 检查到 buffer type 为 kBufferTypeANWBuffer,会将新的graphic buffer 绑定到 BufferMeta上。
  • 等待fence释放完成后就可以让decoder使用了。
status_t OMXNodeInstance::storeFenceInMeta_l(OMX_BUFFERHEADERTYPE *header, int fenceFd, OMX_U32 portIndex) {// propagate fence if component supports it; wait for it otherwiseOMX_U32 metaSize = portIndex == kPortIndexInput ? header->nFilledLen : header->nAllocLen;if (mMetadataType[portIndex] == kMetadataBufferTypeANWBuffer&& metaSize >= sizeof(VideoNativeMetadata)) {VideoNativeMetadata &nativeMeta = *(VideoNativeMetadata *)(header->pBuffer);if (nativeMeta.nFenceFd >= 0) {ALOGE("fence (%d) already exists in meta", nativeMeta.nFenceFd);if (fenceFd >= 0) {::close(fenceFd);}return ALREADY_EXISTS;}nativeMeta.nFenceFd = fenceFd;} else if (fenceFd >= 0) {CLOG_BUFFER(storeFenceInMeta, "waiting for fence %d", fenceFd);sp<Fence> fence = new Fence(fenceFd);return fence->wait(IOMX::kFenceTimeoutMs);}return OK;
}

在等待fence释放之前有个判断:

  • 当 mMetadataType 为 kMetadataBufferTypeANWBuffer 时,会判断 BufferHeader 的 nAllocLen 是否大于等于 VideoNativeMetadata 的大小。我这里就盲猜,这是在判断 BufferHeader 是否已经绑定 graphic buffer;
  • 当BufferHeader还没有绑定graphic buffer时,会先等待fence释放,这里可能会有阻塞的情况出现,这是不符合预期的,这边不能出现阻塞!

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

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

相关文章

基于Matlab的车道线检测技术研究与实现

一、摘要 车道线检测是自动驾驶和智能交通系统的重要组成部分&#xff0c;它对于车辆的导航和控制具有重要的作用。本文主要研究了基于Matlab的车道线检测技术&#xff0c;包括图像预处理、边缘检测、霍夫变换等步骤&#xff0c;并实现了一个车道线检测系统。实验结果表明&…

死锁的处理策略“预防死锁”-第三十七天

目录 前言 破坏互斥条件 破坏不剥夺条件 破坏请求和保持条件 静态分配法 破坏循环等待条件 顺序资源分配法 本节思维导图 前言 死锁的产生必须满足四个必要条件&#xff0c;只要其中一个或几个条件不满足&#xff0c;死锁就不会发生 破坏互斥条件 互斥条件&#xff1a;…

Swift并发的结构化编程

并发&#xff08;concurrency&#xff09; 早期的计算机 CPU 都是单核的&#xff0c;操作系统为了达到同时完成多个任务的效果&#xff0c;会将 CPU 的执行时间分片&#xff0c;多个任务在同一个 CPU 核上按时间先后交替执行。由于 CPU 执行速度足够地快&#xff0c;给人的错觉…

京东商品SKU API:跨境电商平台提升用户活跃度的关键

一.京东获得JD商品sku信息 API的介绍 京东获得JD商品sku信息 API是京东平台提供的一套接口&#xff0c;允许第三方开发者获取京东商品的各种信息&#xff0c;包括商品的SKU信息。通过这个API&#xff0c;跨境电商平台可以获取到关于商品的详细数据&#xff0c;如商品名称、规格…

【python入门】day17:模块化编程、math库常见函数

什么叫模块 模块的导入 导入所有&#xff1a;import 模块名称 导入指定&#xff1a;from 模块名称 import 函数/变量/类 python的math库 什么是math库 Python的math库是Python的内建库之一&#xff0c;它提供了许多数学函数&#xff0c;包括三角函数、对数函数、幂函数等&a…

Scikit-Learn线性回归(四)

Scikit-Learn线性回归四:梯度下降 1、梯度下降1.1、梯度下降概述1.2、梯度下降及原理1.3、梯度下降的实现2、梯度下降法求解线性回归的最优解2.1、梯度下降法求解的原理2.2、梯度下降法求解线性回归的最优解2.3、梯度下降法求解线性回归案例(波士顿房价预测)3、Scikit-Learn…

Android:FragmentActivity

FragmentActivity是androidx中提供的所有用来支持Fragments的Activity的基类,通常我们新建工程时,MainActivity继承自AppCompatActivity,而AppCompatActivity继承自FragmentActivity。 public class AppCompatActivity extends FragmentActivity implements AppCompatCallb…

我的创作纪念日三年收获和感悟

机缘 我刚开始接触创作也是最近几年开始&#xff0c;当初就是希望自己的收获分享给大家&#xff0c;不仅使自己成长&#xff0c;也可以带着大家一起成长&#xff0c;独乐乐不如众乐乐&#xff0c;人都是自私的以前我都是看到好的知识文章都是自己藏起来&#xff0c;发现收获的…

flink on k8s几种创建方式

在此之前需要部署一下私人docker仓库&#xff0c;教程搭建 Docker 镜像仓库 注意&#xff1a;每台节点的daemon.json都需要配置"insecure-registries": ["http://主机IP:8080"] 并重启 一、session 模式 Session 模式是指在 Kubernetes 上启动一个共享的…

NSSCTF 简单包含

开启环境: 使用POST传flag&#xff0c;flag目录/var/www/html/flag.php 先使用post来尝试读取该flag.php 没反应: 查看一下源码index.php&#xff0c;看有什么条件 base64解密: <?php$path $_POST["flag"];if (strlen(file_get_contents(php://input)) <…

Qt/C++编写视频监控系统82-自定义音柱显示

一、前言 通过音柱控件实时展示当前播放的声音产生的振幅的大小&#xff0c;得益于音频播放组件内置了音频振幅的计算&#xff0c;可以动态开启和关闭&#xff0c;开启后会对发送过来的要播放的声音数据&#xff0c;进行运算得到当前这个音频数据的振幅&#xff0c;类似于分贝…

SpringSecurity-2.7中跨域问题

SpringSecurity-2.7中跨域问题 访问测试 起因 写这篇的起因是会了解到 SSM(CrosOrigin)解决跨域,但是会在加入SpringSecurity配置后,这个跨域解决方案就失效了,而/login这个请求上是无法添加这个注解或者通过配置(WebMvcConfig)去解决跨域,所以只能使用SpringSecurity提供的.c…

AI原生应用开发“三板斧”亮相WAVE SUMMIT+2023

面对AI应用创新的风口跃跃欲试&#xff0c;满脑子idea&#xff0c;却苦于缺乏技术背景&#xff0c;不得不望而却步&#xff0c;这曾是许多开发者的苦恼&#xff0c;如今正在成为过去。 12月28日&#xff0c;WAVE SUMMIT深度学习开发者大会2023在北京举办。百度AI技术生态总经理…

Python基本的文件操作,包括ope

Python基本的文件操作&#xff0c;包括 ope 以下代码演示了Python基本的文件操作&#xff0c;包括 open&#xff0c;read&#xff0c;write&#xff1a; 实例(Python 3.0) # Filename : test.py # author by : www.dida100.com # 写文件 with open("test.txt", …

CMake入门教程【核心篇】宏模板(macro)

&#x1f608;「CSDN主页」&#xff1a;传送门 &#x1f608;「Bilibil首页」&#xff1a;传送门 &#x1f608;「本文的内容」&#xff1a;CMake入门教程 &#x1f608;「动动你的小手」&#xff1a;点赞&#x1f44d;收藏⭐️评论&#x1f4dd; 文章目录 1. 定义宏1.1 基本语…

二分查找(一)

算法原理 原理&#xff1a;当一个序列有“二段性”的时候&#xff0c;就可以使用二分查找算法。 适用范围&#xff1a;根据规律找一个点&#xff0c;能将这个数组分成两部分&#xff0c;根据规律能有选择性的舍去一部分&#xff0c;进而在另一个部分继续查找。 除了最普通的…

Ps:创建基于颜色的蒙版

有时候画面上的某种颜色显得不是很和谐&#xff0c;如下图所示。 将画面上的某种颜色换掉&#xff0c;也是得到创意效果的一种重要手段。 演示视频 如果能创建好相关颜色的蒙版&#xff0c;这样在替换颜色的时候就会更加方便。 ◆ ◆ ◆ 创建基于颜色的蒙版 主要思路&#xf…

最大公约数算法

最大公约数算法&#xff1a; 以下代码用于实现最大公约数算法&#xff1a; 实例(Python 3.0) # Filename : test.py # author by : www.dida100.com # 定义一个函数 def hcf(x, y): """该函数返回两个数的最大公约数""" # 获取最小…

【动态规划】C++算法:44 通配符匹配

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 动态规划 LeetCode44 通配符匹配 给你一个输入字符串 (s) 和一个字符模式 &#xff0c;请你实现一个支持 ‘?’ 和 ‘’ 匹配规则的通配符匹配&#xff1a; ‘?’ 可以匹配任何单个字符。 ’ 可以匹配…

122基于matlab的CSO-SVM,BA-SVM模式识别模型

基于matlab的CSO-SVM&#xff0c;BA-SVM模式识别模型。优化SVM的两个参数晚上最佳参数确定。输出分类识别结果和准确率。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 122鸡群优化算法蝙蝠优化算法 (xiaohongshu.com)