zoukankan      html  css  js  c++  java
  • andorid平台下omx解码组件一些思考

      安卓平台下音视频编解码相关的库,在Android10以前,通过OpenMax适配到多媒体框架下;从Android10开始,启用了新的一套方案Codec2.0来对接(软件)编解码库,旨

    于取代ACodec与OpenMAX,它可以看作是一套新的对接MediaCodec的中间件,其往上对接MediaCodec Native层,往下提供新的API标准供编解码使用,相当于ACodec 2.0,

    再往后的版本(A12)会慢慢移除掉omx,来执行mainline计划,目标有几个:减小碎片化、新的buffer管理机制提高性能、组件间更多功能支持。

      本篇文章基于以前整理的对Android下OMX插件的一些总结,分析了sprd对OMX的(解码)实现内在机制,当然这些实现,其实还是参考了谷歌的软件omx组件的实现方案。

    谷歌的omx组件都是对接软件编解码库,而各个芯片原厂都有自己的硬件编解码器,因此需要实现omx组件来对接自家的编解码驱动。可以将omx组件理解为HAL层。

      在这里需要明确几个概念,从高层往底层依次介绍:

    OMX框架——OMXCodec,谷歌的一套omx解决方案,参考这里的文件,OMXMaster.cpp通过addPlugin("libstagefrighthw.so")来加载vendor提供的插件库。

    OMX插件——plugin,vendor厂商简单封装OMX组件,提供libstagefrighthw.so库出去。

    OMX组件——component,直接对接codec_lib,这层实现跨平台api接口(对外透出OMX_Core.h中定义的接口,例如OMX_Set/GetParameter、SendCommand、Empty/FillThisBuffer),

          屏蔽掉底层编解码的细节,供外部client调用。

    codec实现——软件或硬件方案实现音视频编解码。

      基于OMX解码组件实现,一些思考总结如下:

    Q1:do{...} while (pBufCtrl->iRefCount > 0)针对outQueue是干什么的?
      大概是找到pBufCtrl->iRefCount=0的那个,更新outHeader指针,即将要被填充yuv的那个。
    Q2:带B帧的视频文件解封装与解码的行为模式
      解封装:其携带的pts不一定是依次递增,因为inHeader->nTimeStamp其实为dts。其是文件中按顺序存放的一帧帧待解码数据,例如有可能按照这个顺序:I B B P B B P,但依次携带

          的dts为:0 80 120 40 160 200 250。mp4标准中用ctts表示dts。
      解码: 送入的inHeader中的data,在将其送入解码器时,不一定能成功解码(更确切说是出yuv图,即使是I和P帧),空了需要深入调查一下原因。

      因此,带B帧的视频,最终会导致,从MediaCodec使用者的角度上看,其拿到解码后的buf_idx不是按照顺序来的,而不带B帧的,则得到的是按顺序来的。
    Q3:inHeader的data送入到decoder进行解码后,都会notifyEmptyBufferDone()吗?

      是的,只有在将这笔数据完全cusume后,才会返回给omx框架层。更精确来说,inHeader->nFilledLen的数据送往decoder,但有可能分两次送给decoder才能用完,例如x264转码得到的文件,

      经extractor解析分流后,得到的第一帧是enc_info+I_frame,enc_info通常是编码器构建版本,编码时使用的参数列表。

    Q4:pts/dts相关
      outHeader链表中,成功解码出的yuv(dec_out.frameEffective: 1),才会drainOneOutputBuffer()(其中会notifyFillBufferDone()),但送出去的pts值不一定是本inHeader所携带的pts(见下条解释)。
      解码一帧(出yuv图),解码器内部会修改dec_out.pts,使其线性自增的方式增长,注意不是根据dec_in.nTimeStamp来修改的(因为其不是线性递增的),真正原因是driver内部调用了

      H264Dec_find_smallest_pts(),其实这里可以将其做到OMX组件中,但做到组件中会导致OMX组件的设计太臃肿,不易读,因为做组件的同事跟做解码器的同事理解的深入情况不同,做组件的同事

      可能不太清楚pts为什么会这样变化。

    Q5:outHeader被解码后yuv填充后,在被通过notifyFillBufferDone()被render使用后,会被release吗?

      换另外一种说法,有没有可能被解码器内部解码其他帧时参考使用?VSP_bind/unbind_cb就是由codec driver控制pBufCtrl->iRefCount变化的。虽然还给render模块,但毕竟没有释放(iRefCount=0的

    才可以被释放——重复使用),因此可以被后续解码所使用,这就是Q1问题的原因。

    Q6:一个outHeader绑定一个固定的BufferCtrlStruct(outHeader->pOutputPortPrivate)吗?

      是的!
    Q7:组件component管理被引用yuv_buf的内在逻辑
      一方面是解耦的需要,另一方面又引入了组件设计的复杂性,另一个方面是效率的考虑。
      从解耦方面说,outport侧需要dma_buf(ion),而这些buf最合适在comp模块去分配,或者由disp模块分配后传给comp最后送给codec_driver使用,ion内存在codec_driver侧分配则会导致二者过耦合。
      从复杂性方面说,outHeader队列中包含已成功解码出yuv的,这些buf有可能被后续帧解码时所引用,因此有些(iRefCount>1)不能直接在下次解码时被用作output_buf以防止被覆盖,因此需要comp侧

        去管理这些outHeader何时能被使用,并且codec_driver需要bind/unbind_cb的回调函数来修改iRefCount的引用计数值,因为只有codec才知道哪个buf被引用或解引用。如果是codec_driver内部自己

        分配和管理buf(解码参考引用队列,其时上面由comp侧来分配的,codec也需要维护解码参考引用队列,只是这些buf在外部),则就不需要外部comp来管理这些buf何时可以被重复使用了,在解码后

        只需告诉comp是否解码出yuv和其addr,这就是安卓原生SoftAvcDec的工作模式(大部分中间buf由cb进行分配,配解码一帧的dec_out的yuv_buf可以由comp分配,指示codec在成功解码出一帧后将图

        像copy到这个addr处)。

      从效率上说,dma_buf最好是disp模块分配,避免yuv数据重复搬运,如果是codec模块内部分配,则需要将已解码出的yuv搬到disp模块,如果是comp侧分配,也需要拷贝,因为disp模块使用的内存是固定

        区域位置的,其他模块只能map得到其addr,但disp模块中不能用map方式得到其他模块分配的mem。
    Q8:disp到comp的内存map
      针对output侧,使用安卓framework的graphic native buffer,即用iUseAndroidNativeBuffer[OMX_DirOutput]=true表示output侧内存使用方式
      映射方法:

      GraphicBufferMapper &mapper = GraphicBufferMapper::get();
      usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER;
      mapper.lock((const native_handle_t*)outHeader->pBuffer, usage, bounds, &vaddr); //拿到disp中buf的虚拟地址:vaddr
      dec_out参数配给codec:
      uint8_t *yuv = (uint8_t *)((uint8_t *)vaddr + outHeader->nOffset);
      (*mH264Dec_SetCurRecPic)(mHandle, yuv, (uint8 *)picPhyAddr, (void *)outHeader, mPicId); //配输出内存,yuv为vir_addr,picPhyAddr为其对应的phy_addr
      (*mH264DecDecode)(mHandle, &dec_in,&dec_out); //配输入内存并解码
    Q9:显示模块mem到编码/解码组件的内存映射
      参考gralloc.cpp模块mem数据映射。
      编码组件的buf可能来自于gralloc模块或camera模块,使用如下方式映射:
      第一个字节(在mStoreMetaData=true条件下,inHeader->pBuffer+inHeader->nOffset)的值表明了数据来源type:kMetadataBufferTypeCameraSource/kMetadataBufferTypeGrallocSource
      虽然可能是kMetadataBufferTypeCameraSource,但其还是从grlloc模块分配用的,即sensor采集一帧图像填数据,即可进行预览一帧,同时将其送往enc模块进行编码。
      编码组件的设计,也期望将解码出的数据直接送到gralloc而不希望大块内存的memcpy,因此需要从grlloc模块映射得到out_buf。

  • 相关阅读:
    Windows Bat命令常用操作
    Centos下安装Geth
    Centos常用操作
    为什么CEdit使用SetSel无法取消选择?
    VC编译参数/Zm问题
    XX.exe已经停止工作,的处理兼容性
    excel表,Alt+F11调用出vb语言处理excel表内容,很方便
    在限制了可选日期范围的calendar中,使几个日期不可选的方法
    Agile敏捷开发
    做软件的这些年
  • 原文地址:https://www.cnblogs.com/Dreaming-in-Gottingen/p/14587652.html
Copyright © 2011-2022 走看看