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的值為當前碼流所攜帶的pts)。出現這種情況的原因是,編碼順序並非按照輸入YUV的順序(pts)來添加dts時間戳。

                      例如, 假設幀率為25fps,輸入yuv序列分別為: Y0   Y1    Y2    Y3    Y4    Y5    Y6

                                       假設上面每幀圖像對應輸出幀類型為: I0   B1    B2    P3    B4    B5    P6

                                                 那么,每幀圖像pts(ms)為: 0    40   80    120  160   200  240 (依次遞增,即按輸入yuv所攜帶的pts來依次遞增)

                                   然而,其每幀對應的dts(編碼順序)為:0    80   120   40    200  240   160 (即先編碼Y0為I幀,Y1和Y2先緩沖不編碼,收到Y3后再編碼為P幀,收到Y4和Y5時再編碼Y1和Y2為B幀)

                                   編碼器輸出碼流順序為編碼順序,即先吐出Y0圖像的碼流I0,其攜帶的pts和dts都為0;再吐出Y3的碼流P3,其pts和dts分別為120和40;接下來再吐出Y1的B1,pts和dts分別為40和80。

                                   而真正進行文件封裝時,肯定按照輸出碼流順序來順序寫碼流數據,即先放I0的,再放P3的,再放B1的,最后再放B2的。。。

                                          回到逆過程中,在播放時,我們肯定期望先放Y0圖像,再Y1,再Y2等等依次。。。

                                          解封裝時,依次輸出的碼流所攜帶的時間戳,經常看到不是線性遞增的,因為其是pts,類似這樣的:0  120  40  80  240  160  200。。。(其潛在要求,如果該幀能夠被成功解碼,則該

                                   圖像應該在這個時間點進行渲染)

                                          而對於解碼器來說,即使按照送來的碼流幀順序可以依次解碼(先I0再P3,再B1,再B2),但其不一定為會立即吐出yuv圖像,其會按照pts大小來依次吐出圖像,先吐出pts=0對應的Y0

                                   再吐出pts=40的Y1,接下來是pts=80的Y2,最后才是pts=120的Y3。。。例如,libavcodec解碼時,使用AVFrame中的pkt_pts參數(該bitstream所帶的pts)來表示該yuv的待渲染時間戳(pts)。

                                          mp4封裝標准中用ctts和stts來共同表示dts和pts(無B幀時,就沒有ctts的box)。

  解碼: 送入的inHeader中的data,在將其送入解碼器時,不一定能成功解碼(更確切說是出yuv圖,即使是I和P幀,各種decoder都是這種行為?),空了需要深入調查一下原因。

  因此,帶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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM