Android codec2 视频框架之输出端的内存管理

文章目录

      • 前言
      • setSurface
      • start
      • 从哪个pool中申请buffer
      • 解码后框架的处理流程
      • renderOutbuffer 输出显示

前言

在这里插入图片描述

输出buffer整体的管理流程主要可以分为三个部分:

  1. MediaCodc 和 应用之间的交互 包括设置Surface、解码输出回调到MediaCodec。将输出buffer render或者releas到surface。
  2. MediaCodec到CCodecBufferChannel,主要是传递控制命令
  3. CCodecbufferChannel到componet buffer的封装 传递 控制等等。
  4. componet到bufferqueuepool buffer的申请

外部设置Surface进来,然后把输入buffer 输入,等待输出buffer 的回调,回调回来后 根据音视频同步的策略。在合适的时机renderOutput 送到MediaCodec。

需要了解的几个方面

  1. setSurface内部做了什么处理。
  2. 什么时候有输出的buffer可用?
  3. 输出buffer render到MediaCodec.内部做了什么处理。

setSurface

外部的setSurface调用到 MediaCodec的kWhatSetSurface

  1. MediaCodec::setSurface
    调用下面的connetToSurface 对surface进行连接
 nativeWindowConnect(surface.get(), "connectToSurface(reconnect)");
  1. CCodecBufferChannel::setSurface

根据bufferChanned的信息配置surface,比如配置deuque buffer 的超时时间、
dequeue最大的buffer数,当然这些值在后续可能还会改变,后续在解码器中解码出来的delay改变的话 回重新设置这个delay,
然后在handlework 重新设置最大的可dequeue的buffer 数。赋值mOutputSurface的相关变量。

        Mutexed<OutputSurface>::Locked output(mOutputSurface);output->surface = newSurface;output->generation = generation;
  1. 设置surface到 C2BufferQueueBlockPool 用于后续的解码buffer的申请

在ccodecbufferchannel的start中调用configureProducer设置外部surface的GraphicBufferProducer到
bufferQueueBlockpopl中。

          outputSurface = output->surface ?output->surface->getIGraphicBufferProducer() : nullptr;if (outputSurface) {mComponent->setOutputSurface(outputPoolId_,outputSurface,outputGeneration,maxDequeueCount);}Return<Status> Component::setOutputSurface(uint64_t blockPoolId,const sp<HGraphicBufferProducer2>& surface) {std::shared_ptr<C2BlockPool> pool;GetCodec2BlockPool(blockPoolId, mComponent, &pool);if (pool && pool->getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {if (bqPool) {bqPool->setRenderCallback(cb);bqPool->configureProducer(surface);}}return Status::OK;
}void configureProducer(const sp<HGraphicBufferProducer> &producer,native_handle_t *syncHandle,uint64_t producerId,uint32_t generation,uint64_t usage,bool bqInformation) {        if (producer) {mProducer = producer;mProducerId = producerId;mGeneration = bqInformation ? generation : 0;}}

start

start 中跟输出buffer 相关的主要是两个方面

  1. 可以从surface最大能够dequeue出的buffer 数。由4个值组成 其中
    kSmoothnessFactor为4 kRenderingDepth为3。outputDelay由各个解码组件进行设置
    比如h264的默认设置为8, 同时会在解码过程handlework进行重新设置。

具体来说:

  • 在解码组件中解析到相关的reorder系数变化时 将系统放到输出的work中携带出去。
    在外部的ccodebufferchannel 中取出系统设置到surface中。
  • 实现动态的控制surface最大可以dequeue的buffer 数量。 外部通过dequeue申请的最大的buffer数是通过surface的setMaxDequeuedBufferCount
    设置到bufferqueueproducter 中,后续调用dequeue的时候会进行判断。
  • 比如解码器会重新设置为i4_reorder_depth。i4_reorder_depth 是什么? 怎么赋值的?(显示次序在某帧图像之后,解码次序在某帧图像之前的图像数量的最大值。因为编码器中的B帧不仅有前向参考,还有后向参考。 后向参考要求当前图像编码前,参考的后向图像已经编码完成,所以会导致图像的编码顺序和显示顺序不一样。)hevc 是存储在sps的sps_max_num_reorder_pics语言当中。
hevc'解码为例
ps_dec_op->i4_reorder_depth =
ps_sps->ai1_sps_max_num_reorder_pics[ps_sps->i1_sps_max_sub_layers - 1];mOutputDelay = ps_decode_op->i4_reorder_depth;
ALOGV("New Output delay %d ", mOutputDelay);
C2PortActualDelayTuning::output outputDelay(mOutputDelay);
std::vector<std::unique_ptr<C2SettingResult>> failures;
c2_status_t err =mIntf->config({&outputDelay}, C2_MAY_BLOCK, &failures);
if (err == OK) {work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(outputDelay));}bool CCodecBufferChannel::handleWork(std::unique_ptr<C2Work> work,const sp<AMessage> &outputFormat,const C2StreamInitDataInfo::output *initData) {while (!worklet->output.configUpdate.empty()) {std::unique_ptr<C2Param> param;worklet->output.configUpdate.back().swap(param);worklet->output.configUpdate.pop_back();if (param->forOutput()) {C2PortActualDelayTuning::output outputDelay;if (outputDelay.updateFrom(*param)) {ALOGE("[%s] onWorkDone: updating output delay %u",mName, outputDelay.value);(void)mPipelineWatcher.lock()->outputDelay(outputDelay.value);newOutputDelay = outputDelay.value;needMaxDequeueBufferCountUpdate = true;}}break;if (needMaxDequeueBufferCountUpdate) {int maxDequeueCount = 0;{Mutexed<OutputSurface>::Locked output(mOutputSurface);maxDequeueCount = output->maxDequeueBuffers =numOutputSlots + reorderDepth + kRenderingDepth;if (output->surface) {output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);}}if (maxDequeueCount > 0) {mComponent->setOutputSurfaceMaxDequeueCount(maxDequeueCount);}}}
constexpr size_t kSmoothnessFactor = 4;
constexpr size_t kRenderingDepth = 3;C2PortActualDelayTuning::output outputDelay(0);c2_status_t err = mComponent->query({&iStreamFormat,&oStreamFormat,&kind,&reorderDepth,&reorderKey,&inputDelay,&pipelineDelay,&outputDelay,&secureMode,},{},C2_DONT_BLOCK,nullptr);size_t numOutputSlots = outputDelayValue + kSmoothnessFactorsp<IGraphicBufferProducer> outputSurface;uint32_t outputGeneration;int maxDequeueCount = 0;{Mutexed<OutputSurface>::Locked output(mOutputSurface);maxDequeueCount = output->maxDequeueBuffers = numOutputSlots +reorderDepth.value + kRenderingDepth;outputSurface = output->surface ?output->surface->getIGraphicBufferProducer() : nullptr;if (outputSurface) {output->surface->setMaxDequeuedBufferCount(output->maxDequeueBuffers);}outputGeneration = output->generation;constexpr uint32_t kDefaultOutputDelay = 8;addParameter(DefineParam(mActualOutputDelay, C2_PARAMKEY_OUTPUT_DELAY).withDefault(new C2PortActualDelayTuning::output(kDefaultOutputDelay)).withFields({C2F(mActualOutputDelay, value).inRange(0, kMaxOutputDelay)}).withSetter(Setter<decltype(*mActualOutputDelay)>::StrictValueWithNoDeps).build());if (ps_decode_op->i4_reorder_depth >= 0 && mOutputDelay != ps_decode_op->i4_reorder_depth) {mOutputDelay = ps_decode_op->i4_reorder_depth;ALOGV("New Output delay %d ", mOutputDelay);C2PortActualDelayTuning::output outputDelay(mOutputDelay);std::vector<std::unique_ptr<C2SettingResult>> failures;c2_status_t err =mIntf->config({&outputDelay}, C2_MAY_BLOCK, &failures);if (err == OK) {work->worklets.front()->output.configUpdate.push_back(C2Param::Copy(outputDelay));} else {ALOGE("Cannot set output delay");mSignalledError = true;work->workletsProcessed = 1u;work->result = C2_CORRUPTED;return;}}

从哪个pool中申请buffer

在CCodecBufferChannel::start的时候决定,在下面代码中将pools的allocatedID转为
C2BufferQueueBlockPool。 在这之后调用mComponent->createBlockPool。Codec2Client::Component::createBlockPool调用c2store的c2_status_t createBlockPool()然后调用_createBlockPool,在之前设置了是BUFFERQUEUE,这边就保存了创建好的C2BufferQueueBlockPool。 在后面解码的流程中fetchGrallocBlock,使用的是这个类型的
C2BufferQueueBlockPool。

poolmask的默认值:

int GetCodec2PoolMask() {return property_get_int32("debug.stagefright.c2-poolmask",1 << C2PlatformAllocatorStore::ION |1 << C2PlatformAllocatorStore::BUFFERQUEUE);
}int poolMask = GetCodec2PoolMask();

申请的buffer的类型函数是bufferqueue

if (pools->outputAllocatorId == C2PlatformAllocatorStore::GRALLOC
&& err != C2_OK
&& ((poolMask >> C2PlatformAllocatorStore::BUFFERQUEUE) & 1)) {
pools->outputAllocatorId = C2PlatformAllocatorStore::BUFFERQUEUE;
}
}

bufferqueue的申请调用的是C2PlatformAllocatorStoreImpl的fetchAllocator

case C2PlatformAllocatorStore::BUFFERQUEUE:
res = allocatorStore->fetchAllocator(
C2PlatformAllocatorStore::BUFFERQUEUE, &allocator);
if (res == C2_OK) {
std::shared_ptr<C2BlockPool> ptr(
new C2BufferQueueBlockPool(allocator, poolId), deleter);
*pool = ptr;
mBlockPools[poolId] = ptr;
mComponents[poolId].insert(
mComponents[poolId].end(),
components.begin(), components.end());
}
break;

fetchAllocator返回gralloc的allocator。

std::shared_ptr<C2Allocator> C2PlatformAllocatorStoreImpl::fetchBufferQueueAllocator() {static std::mutex mutex;static std::weak_ptr<C2Allocator> grallocAllocator;std::lock_guard<std::mutex> lock(mutex);std::shared_ptr<C2Allocator> allocator = grallocAllocator.lock();if (allocator == nullptr) {allocator = std::make_shared<C2AllocatorGralloc>(C2PlatformAllocatorStore::BUFFERQUEUE, true);grallocAllocator = allocator;}return allocator;
}

### fetchGraphicBlock 流程

  • fetchGraphicBlock

fetch经过一系列判断和处理 最终调用mProducer的dequeueBuffer

    c2_status_t fetchGraphicBlock(uint32_t width,uint32_t height,uint32_t format,C2MemoryUsage usage,std::shared_ptr<C2GraphicBlock> *block /* nonnull */,C2Fence *fence) {c2_status_t status = fetchFromIgbp_l(width, height, format, usage, block, fence);c2Status = dequeueBuffer(width, height, format, usage,&slot, &bufferNeedsReallocation, &fence);                         if (fence) {static constexpr int kFenceWaitTimeMs = 10;status_t status = fence->wait(kFenceWaitTimeMs);}其中 dequeueBufferReturn<void> transResult = mProducer->dequeueBuffer(Input{width,height,format,androidUsage.asGrallocUsage()},[&status, slot, needsRealloc,fence](HStatus hStatus,int32_t hSlot,Output const& hOutput) {*slot = static_cast<int>(hSlot);if (!h2b(hStatus, &status) ||!h2b(hOutput.fence, fence)) {status = ::android::BAD_VALUE;} else {*needsRealloc =hOutput.bufferNeedsReallocation;}});
  • dequebuffer中的fence 有什么作用

Fence是一种同步机制,用于GraphicBuffer的同步。用来处理跨硬件平台不同的情况(CPU和GPU),尤其是CPU、GPU和HWC之间的同步。另外,也可用于多个时间点之间的同步,当Graphics Buffer的生产者或消费者在对buffer处理完之后,通过fence发出信号,这样系统可以异步queue当前不需要但有可能接下来会使用读写的buffer。

简言之,在合适的时间发一种信号,将先到的buffer拦住,等后来的到达,两者步调一致再一起走。也就是dequeuebuffer 之后并不能直接用这块buffer,需要等待buffer的fence发送上来之后 才可以使用这块buffer。

  • 完整一个获取fetch buffer的流程

dequeueBuffer ---->(获取到slot或fence) fence->wait -----> mProducer->requestBuffer(通过slot 获取到buffer)

将从gralloc 获取到的buffer (native_handle_t)通过调用android::WrapNativeCodec2GrallocHandle转化为C2Handle
这个C2Handle 会生成C2AllocationGralloc,这个alloc最后会new 封装成C2GraphicBlock。这个block就是返回给外部解码申请的地方。

经过上面的这个流程 解码要的共享的buffer 就从gralloc这边申请出来了,然后这个buffer就可以给到后面的解码器使用了,如果是软解就map出虚拟地址,然后将软解后的数据拷贝到里面。但一般厂商不会用软解,正常的实现是这块buffer给到硬件,硬解数据直接写到这块buffer。

解码的buffer准备好之后,会把grallocblock的buffer 转换为c2buffer 然后会放到c2work中output buffers里面。

std::shared_ptr<C2Buffer> buffer
= createGraphicBuffer(std::move(entry->outblock),
C2Rect(mWidth, mHeight).at(left, top));

解码后框架的处理流程

解码后的哪些信息是携带在work里面的, 解码的buffer,

work->worklets.front()->output.flags = (C2FrameData::flags_t)0;
work->worklets.front()->output.buffers.clear();
work->worklets.front()->output.buffers.push_back(buffer);
work->worklets.front()->output.ordinal = work->input.ordinal;
work->workletsProcessed = 1u;
  • 从应用开始, 应用调用的是dequeueOutputBuffer返回的是index 时间戳等等信息,这个调用到mediacodec, mediacodec 从 mAvailPortBuffers 取出可用的buffer。
  • mAvailPortBuffers是通过解码那边 BufferCallback onOutputBufferAvailable来把解码buffer push 到mAvailPortBuffers。这个回调是simpleC2Componet 的finish的listener->onWorkDone_nb调用到CCodec的onWorkDone。
  • onWorkDone调用到mChannel->onWorkDone。 在mChannel的workDone 中 调用handleWork。
  • handlework 里面将解码器传递在work 中outputbuffer 转换为mediacodec的用的index 和 mediaCodecbuffer。同时返回到MediaCodec之前设置的callback。这个最后会返回应用设置callback的地方。
mCallback->onOutputBufferAvailable(index, outBuffer);

这个callback 是从何而来的。 在mediacodec的init的时候会新建一个codec 并将codec设置到codec2里面。mCodec->setCallback(
std::unique_ptrCodecBase::CodecCallback(
new CodecCallback(new AMessage(kWhatCodecNotify, this))));

  • 各个buffer 直接的转换

首先从解码这边出来的是C2GraphicBlock,会在codecbufferchannel中转为index 传递出去给mediacodec 转换过程是 内部有一个
mBuffers数组,在handlework先pushToStash到这里面。然后从这里面取出来。popFromStashAndRegister是这个里面去转换为mediacodec的buffer 和index 的。转换的MediaCodecBuffer, 就是把c2buffer的一个结构体赋值到Codec2Buffer中。c2Buffer->copy(buffer)。

renderOutbuffer 输出显示

  • render的时候传递的是index,同样也是mAvailPortBuffers 取出可用的buffer。
  • 这个buffer 通过status_t err = mBufferChannel->renderOutputBuffer(buffer, renderTimeNs)。将MediaCodecBuffer转换为C2buffer。
  • 从这个C2buffer 中取出C2ConstGraphicBlock, block 在转换为bqslot。 这个slot最后queue到surface那边。
getBufferQueueAssignment(block, &generation, &bqId, &bqSlot)
status = outputIgbp->queueBuffer(static_cast<int>(bqSlot),
input, output);

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

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

相关文章

手机照片一键去水印轻松摆脱不需要的旁观者

是什么让照片中的意外客人成为挑战&#xff1f;我们都经历过这种情况——在热门地标或繁忙的城市街道拍照&#xff0c;不可避免地会在画面中捕捉到陌生人。有时他们会无意中抢尽风头&#xff0c;转移观众的注意力。 这些水印不仅影响了照片的美观度&#xff0c;还给我们的观赏体…

京东数据分析软件(京东平台数据分析):2023年Q3扫地机器人行业消费报告

随着90后、00后逐渐成为消费主力军&#xff0c;他们对生活品质更加关注、健康意识进一步增强&#xff0c;再加上“懒人经济”的盛行&#xff0c;人们对扫地机器人的使用率和关注热情也不断增长。 根据鲸参谋电商数据分析平台的相关数据显示&#xff0c;今年7月份-9月份&#xf…

AI大爆发的时代,未来的年轻人怎样获得机会和竞争力?

文章目录 引言AI与教育工作者教育资源不平衡 这次&#xff0c;狼真的来了。 引言 AI正迅猛地改变着我们的生活。 根据高盛发布的一份报告&#xff0c;AI有可能取代3亿个全职工作岗位&#xff0c;影响全球18%的工作岗位。在欧美&#xff0c;或许四分之一的工作可以用AI完成。另…

第四代智能井盖传感器,更迭智能井盖监测办法

人工检查井盖是一项耗时且效率低下的工作&#xff0c;需要工作人员逐个进行检查。由于这种方式无法实时监测井盖的状态&#xff0c;当井盖出现故障时无法及时将信息反馈给相关人员&#xff0c;从而影响了井盖的维修效率。此外人工检查还受到天气、光线等环境因素的影响较大&…

卷积神经网络(ResNet-50)鸟类识别

文章目录 卷积神经网络&#xff08;CNN&#xff09;mnist手写数字分类识别的实现卷积神经网络&#xff08;CNN&#xff09;多种图片分类的实现卷积神经网络&#xff08;CNN&#xff09;衣服图像分类的实现卷积神经网络&#xff08;CNN&#xff09;鲜花的识别卷积神经网络&#…

基于社交网络算法优化概率神经网络PNN的分类预测 - 附代码

基于社交网络算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于社交网络算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于社交网络优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

一、MySQL-Replication(主从复制)

1.1、MySQL Replication 主从复制&#xff08;也称 AB 复制&#xff09;允许将来自一个MySQL数据库服务器&#xff08;主服务器&#xff09;的数据复制到一个或多个MySQL数据库服务器&#xff08;从服务器&#xff09;。 根据配置&#xff0c;您可以复制数据库中的所有数据库&a…

Flowable工作流基础篇

文章目录 一、Flowable介绍二、Flowable基础1.创建ProcessEngine2.部署流程定义3.启动流程实例4.查看任务5.完成任务6.流程的删除7.查看历史信息 三、Flowable流程设计器1.Eclipse Designer1.1 下载安装Eclipse1.2 安装Flowable插件1.3 创建项目1.4 创建流程图1.5 部署流程 2.F…

Maven工程继承关系,多个模块要使用同一个框架,它们应该是同一个版本,项目中使用的框架版本需要统一管理。

1、父工程pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/PO…

KMP——字符串匹配

朴素匹配的逻辑&#xff1a; 将原串的指针移动至本次发起点的下一个位置&#xff08;b字符处&#xff09;&#xff1b;匹配串的指针移动至起始位置。尝试匹配&#xff0c;发现对不上&#xff0c;原串的指针会一直往后移动&#xff0c;直到能够与匹配串对上位置。 如图&#x…

(02)vite环境变量配置

文章目录 将开发环境和生产环境区分开环境变量vite处理环境变量loadEnv 业务代码需要使用环境变量.env.env.development.env.test修改VITE_前缀 将开发环境和生产环境区分开 分别创建三个vite 的配置文件&#xff0c;并将它们引入vite.config.js vite.base.config.js import…

深入探讨软件测试技术:方法、工具与最佳实践

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 引言 软件测试是软件开发生命周期中至关重要的…

SO3 与so3 SE3与se3 SIM3

文章目录 1 旋转*叉乘1.1 旋转矩阵的导数1.2 物理意义1.3 实例1.4 角轴与反对称矩阵 2 SO3 与so32.1 so3 2 SO32.2 SO3 2 so3 3 SE3 与se33.1 se3 2 SE3:3.2 SE3 2 se3 4 SIM3 与sim35 Adjoint Map 1 旋转*叉乘 1.1 旋转矩阵的导数 根据旋转矩阵的性质&#xff1a; R R T I …

2023年以就业为目的学习Java还有必要吗?

文章目录 1活力四射的 Java2从零开始学会 Java3talk is cheap, show me the code4结语写作末尾 现在学 Java 找工作还有优势吗&#xff1f; 在某乎上可以看到大家对此问题的热议&#xff1a;“2023年以就业为目的学习Java还有必要吗&#xff1f;” 。有人说市场饱和&#xff0c…

基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码

基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于白冠鸡算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于白冠鸡优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络…

django ModelSerializer自定义显示字段

文章目录 前言一、问题二、解决 前言 最近在复习django的时候&#xff0c;发现了一个有趣的问题&#xff0c;解决了之后特意记录下来&#xff0c;以供以后参考。 一、问题 相信大家使用django的时候&#xff0c;被其DRF的强大功能所折服&#xff0c;因为它能通过简单的代码就…

威班11月份PMP模拟考试实录

11月份模拟考试于2023年11月18日在深圳市福田区鹏基商务时空大厦成功举办&#xff01; 本次线下模拟考试依旧是通过线上线下同步的方式进行&#xff0c;在深圳周边的学员直接到达现场参与模拟考试&#xff0c;全国各地不能到达现场的其他学员已提前收到考试所需资料&#xff0…

C++ LibCurl实现Web指纹识别

Web指纹识别是一种通过分析Web应用程序的特征和元数据&#xff0c;以确定应用程序所使用的技术栈和配置的技术。这项技术旨在识别Web服务器、Web应用框架、后端数据库、JavaScript库等组件的版本和配置信息。通过分析HTTP响应头、HTML源代码、JavaScript代码、CSS文件等&#x…

【ARM Trace32(劳特巴赫) 使用介绍 2.3 -- TRACE32 进阶命令之 参数传递介绍】

请阅读【ARM Coresight SoC-400/SoC-600 专栏导读】 文章目录 参数传递命令 ENTRY 参数传递命令 ENTRY ENTRY <parlist>The ENTRY command can be used to Pass parameters to a PRACTICE script or to a subroutineTo return a value from a subroutine 使用示例&am…

《洛谷深入浅出基础篇》P3916 图的遍历——逆向搜索

上链接&#xff1a; P3916 图的遍历 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3916上题干&#xff1a; 题目描述 给出 N 个点&#xff0c;M 条边的有向图&#xff0c;对于每个点 v&#xff0c;求 A(v) 表示从点 v 出发&#xff0c;能到…