Android MediaCodec踩坑筆記

關於編解碼,FFMpeg不香嗎,爲什麼要吊死在Android的MediaCodec上?對於這個問題,我也很無奈,FFMpeg很香,但是因爲包體積、效率等問題引發的工作業務的需要,使我不得不在Android MediaCodec的摧殘下苟且偷生。MediaCodec的api比較簡單,用來寫demo毫無難度,讓人痛不欲生的是它的兼容性問題。使用MediaCodec遇到的問題,往往都是和機型、版本、某類媒體文件相關的問題。從開始使用MediaCodec到現在,遇到了許多問題,很多解決了後過了許久又忘記了,這篇博客作爲一個記錄,也爲後來不得不趟坑的人提供一點點經驗吧。

編碼器或者解碼器Config時候崩潰

這個問題對於MediaCodec的使用者來說應該是一個比較普遍的問題了,在MediaCodec進行config崩潰,往往會給出具體的崩潰信息,比較常見的是格式不支持,最多的是寬高不支持或者編解碼器數量限制。

一個手機能夠解碼或者編碼視頻能力是有限的,在手機的/etc/media_codec.xml中是有說明的,比如使用一個15年左右的低端機型去解碼1080p的視頻,大概率會失敗。當然,時代發展,限制的低端機型也大多支持1080p,那不妨解碼4k視頻試試。

另外,在現在的一些能夠支持1080p解碼的機型上,使用MediaCodec同時解碼兩路1080p視頻也會出現config失敗的問題,比如SM-J250f、SM-J700f等手機。這種情況,就只能避免這麼使用了,引入軟解,或者把1080p視頻先轉成小視頻等。

解碼視頻,渲染出來的圖像異常

這個一般是新手可能會遇到的問題,不是MediaCodec的問題,而是使用者的問題。有時候爲了加速解碼或者是其他的原因,使用者在進行解碼的時候,會對一些幀進行丟棄,但是如果丟棄後,解碼幀不是從關鍵幀開始的話,到下一個關鍵幀之前,解碼出來的圖像,都會是花屏的圖像。所以需要做的就是要保證每次解碼的流程開始時,塞入給解碼的數據要從關鍵幀開始,並且不要隨意丟輸入幀。否則不會只有MediaCodec解碼會花屏,FFMpeg同樣無解。

在使用MediaCodec解碼到ByteBuffer中,然後拿出來渲染,沒有丟幀也有可能會遇到這個問題。這種情況下一般也不是解碼器的問題,而是渲染的問題,比如stride和width不匹配,在渲染的時候需要對數據進行處理。或者是顏色空間不正確,導致了渲染的色彩不正確等問題。

解碼Mp4中的音頻,seek到0後解碼,解碼出的音頻數據不是從頭開始的

遇到了使用MediaCodec進行Mp4音頻解碼,MediaExtractor已經選中音頻軌道,並且seek到0後,播放出來的音頻並不是從頭開始的問題。並且同樣的視頻,在有的手機上是好的,有的手機上又有問題,不僅僅是使用MediaCodec,使用MediaPlayer直接來播放也會遇到同樣的問題。調試後發現,解碼出來的數據前面有很多幀給出來的pts是對的,但是數據的size一直爲0,而塞入的數據又的確沒啥問題。

在有問題的手機上,使用另外一個不存在問題的視頻,和存在問題的視頻對比了下,解碼器解碼時候的MediaFormat,發現有問題的視頻帶有"encoder-delay"標記,值爲1,而正常的視頻是沒有的。google了下發現罪魁禍首果然是它,Android P此問題存在,Android Q版本進行了修復。本地解決方案將"encoder-delay"標記值設置爲0即可修復音頻不是從頭開始的問題。

音頻解碼無數據

三星J200G解碼mp4中的音頻,格式爲mp4a-latm時遇到的偶現的問題。MediaCodec未返回錯誤,dequeueInputBuffer返回值正常,可以請求到buffer,但是dequeueOutputBuffer一直無法獲得輸出。log中有error信息:

4-18 12:27:47.926 32443-2244/com.uc.vmate E/ACodec: OMXCodec::onEvent, OMX_ErrorStreamCorrupt
04-18 12:27:47.931 2175-2266/? E/SEC_AAC_DEC: saacd_decode() failed ret_val: -5, Indata 0x 21 1b 94 a5, length : 1326
04-18 12:27:47.931 2175-2266/? E/SEC_AAC_DEC: ASI 0x 11, 90 00 00
04-18 12:27:47.931 32443-2244/com.uc.vmate E/ACodec: OMXCodec::onEvent, OMX_ErrorStreamCorrupt
04-18 12:27:47.936 2175-2266/? E/SEC_AAC_DEC: saacd_decode() failed ret_val: -5, Indata 0x 21 1b 94 bd, length : 1314

最後發現問題不是出在解碼器上,而是出在MediaExtractor上,MediaExtractor使用的時候,內部可能出錯,導致MediaExtractor解封裝的時候給的數據存在異常。

視頻解碼時,獲取輸入輸出Buffer始終爲-1

在Oppo A37wf 5.1.1系統的手機上進行視頻解碼的時候,遇到了一個問題。同時解碼兩路1080p視頻,結果一路正常,一路解碼出一幀後,dequeueInputBuffer和dequeueOutputBuffer始終返回-1,無報錯。而看grafika是可以同時解碼的。對比了下我的使用和grafika中的示例,發現最終的原因是我在使用MediaCodec進行解碼的時候,並不是dequeueOutputBuffer後就會releaseOutputBuffer,保證同時只會持有一幀數據。而是同時持有了兩幀的outputBufer。

把代碼改成只持有一個outputBuffer就正常了。回頭再看了下Android cts中MediaCodec相關代碼,並沒有對同時多路持有多個Buffer進行測試,所以最終只能認命了,避免掉持有多幀不立即釋放的情況。這裏也就不得不提一個建議了,要想使用MediaCodec時,代碼無bug,使用盡量不超出Android cts的範圍,超出範圍的使用就要做好面對一堆bug的準備了。

藉助SurfaceTexture解碼,圖像方向不正確

MediaCodec解碼視頻可以直接將視頻圖像解碼到Surface上,而Surface又可以通過SurfaceTexture來進行構建,也就是說MediaCodec可以直接將圖像解碼到SurfaceTexture上,當我們需要對解碼後的視頻進行圖像處理時,通常會選擇這樣的方式。

對於帶有旋轉或者裁邊信息的視頻,我們通常需要在渲染SurfaceTexture的紋理時,將其紋理矩陣信息通過getTransformMatrix拿到,然後去做渲染處理。但是在一些情況下,我們通過getTransformMatrix並不能拿到正確的矩陣。依舊是在Oppo A37wf這個手機上,使用兩路1080p視頻解碼,會遇到一路視頻正常,一路視頻的SurfaceTexture無法通過getTransformMatrix獲得正確的Matrix的情況。在grafika上也存在同樣的問題。

進一步跟進,發現在出錯的那一路解碼器中,codec.getOutputFormat()得到的Format中有一個using-sw-renderer字段被設爲1了,所以根本原因就是這個手機用Surface的方式同時硬解兩路1080p視頻,會有一路渲染使用的軟件渲染的方式進行渲染,而這個垃圾手機軟件渲染又有bug。 在MediaCodec.cpp可以找到設置和處理相關源碼,但是找到可以強制改回使用硬件渲染的方式。

我使用的解決方案是在發現解碼使用的是軟件渲染的OutputFormat時,對獲取的矩陣進行判斷,如果矩陣信息和OutputFormat中的信息不匹配的話,重新計算一個渲染矩陣出來,然後渲染時候使用重新計算的矩陣。計算方式可以參考native層源碼GLConsumer.cpp中的計算。直接copy過來就能用了。


歡迎轉載,轉載請保留文章出處。湖廣午王的博客[http://blog.csdn.net/junzia/article/details/106036509]


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章