Android MediaCodec 退坑指南
MediaCodec 是 Android 音視訊開發中不可能繞過的環節,但它真的不太好用,小水坑太多。今天我們就趟趟小水坑,走進科學,oh,不,走進 MediaCodec。
歡迎轉載,但請務必帶上本人資訊,謝謝!連結地址: ofollow,noindex">http://www.woaitqs.cc/2018/11/26/how-to-use-mediacodec-correctly/
MediaCodec Introduction
MediaCodec 是 Android 多媒體支援框架中的一員,其他還包括 MediaExtractor、MediaSync、MediaMuxer、MediaDrm 等等,負責編解碼相關的工作。接下來我們簡單地介紹下 MediaCodec 是如何工作的。
工作流程
In broad terms,編解碼器處理輸入資料併產生輸出資料,MediaCodec 使用輸入輸出快取,非同步處理資料。簡要地說,一般的處理步驟如下
- 請求一個空的輸入 input buffer
- 填入資料、並將其交給 MediaCodec
- MediaCodec 處理資料後,將處理後的資料放在一個空的 output buffer
- 獲取填充資料了的 output buffer,得到其中的資料,然後將其返還給 MediaCodec。
下圖是對步驟的闡釋
工作流程
支援的資料型別
我們的大腦不是萬能的,MediaCodec 也不是什麼資料都能支援的。一般在有限的範圍內運轉的系統,穩定且可靠,嘿嘿嘿。MediaCodec 支援三種資料格式,下面分別介紹:
-
Compressed Data
既然是編解碼器,那麼勢必會處理對應視訊、音訊格式的壓縮資料,也就是 Encode 的輸出資料、Decoder的輸入資料。我們將這一類資料,統稱為壓縮資料。壓縮資料格式,取決於 MediaFormat | Android Developers 。對於視訊資料而言,通常是一幀資料;音訊資料,一般是單個處理單元(包括多少微秒的資料)。一般情況下,除非指定為
BUFFER_FLAG_PARTIAL_FRAME
,否則不會出現半個幀的情況。 -
Raw Audio Buffers
編解碼器,需要編碼對應的音訊資料,那麼就肯定會處理音訊格式資料,也就是 PCM 資料。對於音訊編碼格式,只有 ENCODING_PCM_16BIT 確認被各 System \ Rom 支援哦。
這裡簡要滴摘錄下 Wiki 上關於 PCM 資料的定義。
How PCM Work?
PCM就是把一個時間連續,取值連續的模擬訊號變換成時間離散,取值離散的數字訊號後在通道中傳輸。簡而言之PCM就是對模擬訊號先抽樣,再對樣值幅度量化,編碼的過程。例如聽到的聲音就是模擬訊號,然後對聲音取樣,量化,編碼產生數字訊號。相對自然界聲音訊號,任何音訊編碼都是有損的,在計算機應用中,能達到高保真的就是PCM編碼,因此PCM約定成俗成了無損編碼,對於聲音而言,我們通常採用PCM編碼。
-
Raw Video Buffers
a. Native raw video format. 也就是MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface,注意這種格式,並不是所有 Android 手機都支援的哦。
b. YUV buffers. 可以用在 Surface 格式,或者 ByteBuffer 格式中。Google 表示在 API 22 以後,所有機型都保證支援 YUV 4:2:0 格式。
c. Other formats. 這就看廠商心情了,可以通過 MediaCodecInfo.CodecCapabilities 來進行查詢。
狀態機
MediaCodec 狀態機
MediaCodec 大體上分為三種狀態、Stopped、Executing和 Released。狀態機工作圖如上,其後就是很具體的呼叫細節了。
具體的狀態切換就不翻譯了,大家可以通過上面的圖,結合英文看一看。
When you create a codec using one of the factory methods, the codec is in the Uninitialized state. First, you need to configure it via configure(…), which brings it to the Configured state, then call start() to move it to the Executing state. In this state you can process data through the buffer queue manipulation described above.
The Executing state has three sub-states: Flushed, Running and End-of-Stream. Immediately after start() the codec is in the Flushed sub-state, where it holds all the buffers. As soon as the first input buffer is dequeued, the codec moves to the Running sub-state, where it spends most of its life. When you queue an input buffer with the end-of-stream marker, the codec transitions to the End-of-Stream sub-state. In this state the codec no longer accepts further input buffers, but still generates output buffers until the end-of-stream is reached on the output. You can move back to the Flushed sub-state at any time while in the Executing state using flush().
Call stop() to return the codec to the Uninitialized state, whereupon it may be configured again. When you are done using a codec, you must release it by calling release().
On rare occasions the codec may encounter an error and move to the Error state. This is communicated using an invalid return value from a queuing operation, or sometimes via an exception. Call reset() to make the codec usable again. You can call it from any state to move the codec back to the Uninitialized state. Otherwise, call release() to move to the terminal Released state.
MediaCodec Usage Tips
接下來講講,如何用正確的姿勢
來使用 MediaCodec,畢竟這傢伙在不同手機、不同系統上面都表現不同。對它的正確使用,不太考驗技術,更考驗經驗和細心。
Create Instance
首先是如何建立 MediaCodec,在建立 MediaCodec 時,需要弄清楚自己是想建立 Encoder 還是 Decoder。
在知曉 Media Types 的情況下,可通過 createDecoderByType, createEncoderByType, createByCodecName 方法來獲取例項。
問題在於如何確定手機是否支援該 MimeType 呢?在 API 21 上,可以使用 MediaCodecList.findDecoderForFormat、MediaCodecList.findEncoderForFormat 這兩個方法來進行獲取,如果有滿足需求的Codec,會返回對應 MimeType。這裡需要說明的是,需要在 LOLLIPOP 版本上,:: 清除掉 Format 中關於 KEY_FRAME_RATE 的設定 ::,無疑這是一個小坑。
Android 底層的多媒體框架採用了 OpenMax 標準,但具體的硬體編解碼功能,則是有各個產商負責的,這就導致不同手機可能差異很多,也是 Android 多媒體框架相容性問題大的根源。那麼我們如何來處理哪怕僅僅是建立就會碰到的相容性問題呢?
以下,包括接下來章節的內容,大部分都是通過官方的 Compatibility Definition Document 和 Supported media formats | Android Developers 來作為參考的,後面就不重複解釋了。
Audio Codec
MediaCodec Support Details
從上圖可以看到,對於大部分音訊解碼而言,都是支援的。在實際開發中,當解碼一個音訊的時候,不用做什麼特殊處理,處理好可能的 Crash,正常解碼就行。反倒是,在音訊編碼的時候需要注意。對於有錄音Mic許可權的手機而言,官方保證支援 PCM/WAVE 可以用於編碼,但我們對這個的使用較少。另外可以看到我們熟悉的 MP3 格式,並不在支援之列喲。我們使用的是另一種幾乎所有裝置都支援的編碼格式,m4a,其 MimeType 就是 “::audio/mp4a-latm::”。
m4a 聽上去很奇怪,其實就是 MPEG-4 Audio 的簡寫。蘋果公司為了區分包含視訊、音訊的MP4檔案與只有音訊的MP4檔案,對後者進行了單獨命名::.m4a::,這種格式對現今絕大部分移動裝置都支援。
M4A is a file extension for an audio file encoded with advanced audio coding (AAC) which is a lossy compression. M4A was generally intended as the successor to MP3, which had not been originally designed for audio only but was layer III in an MPEG 1 or 2 video files. M4A stands for MPEG 4 Audio.
Video Codec
Video Support Details
VP8/9 適用性沒有 H.264 來的廣,就略過不談了;263 在 7.0 以上才可以用而且沒有H.264來的廣;265相關的,Android 不太支援。最後也就剩下 H.264 AVC 啦。關於 H.264 AVC Main Profile (MP) 與 H.264 AVC Main Profile (HP) 了,得在更高階的 Android 系統(7.0 +) 才開始支援,而且會有一定的相容性問題,這個在後面會專門提及。綜合起來呢,要先做到最全的相容性,讓幾乎在所有機型上都能運作,選用 H.264 BaseLine 作為編碼器,是一個不錯的選擇。
Configuration
在建立好 MediaCodec 過後,需要對其進行配置,這樣 MediaCodec 的狀態就可以由 uninitiated 變成 configured。
configure
這裡最重要的引數是 MediaFormat,要敲黑板啦!
- 某些 MediaFormat 沒有設定的話,會導致 MediaCodec 丟擲 IllegalStateException 哦! all keys not marked optional are mandatory !!
- 引數設定不對,也可能會丟擲異常哦。比如設定一個超出手機能力範圍外的解析度。
Video 所必須的 Format Setting。
Video Format Setting
Audio 所必須的 Format Setting。
Audio Format Setting
下面說幾個需要特別注意的幾個設定項。
KEY_WIDTH 與 KEY_HEIGHT
在編碼的時候,請務必保證兩者是16的整數倍。
KEY_BIT_RATE & KEY_FRAME_RATE
BIT_RATE 沒有什麼太大的坑,注意下單位就好 bits/seconds. 下面的圖展示了,官方要求裝置對於不同解析度下需要支援的最低解析度。
FRAME_RATE 也同樣,幾乎都被要求支援至少 30 FPS。(
體會一下)
編碼的最小支援
解碼的最小支援
KEY_COLOR_FORMAT
CDD 文件中要求,::If device implementations include a video decoder or encoder,Video encoders and decoders MUST support YUV420 flexible color format(COLOR_FormatYUV420Flexible)::。COLOR_FormatYUV420Flexible 是在 API 21 才開始引入的,而且是一種 魔法
格式,可以代表很多格式。
Chroma planes are subsampled by 2 both horizontally and vertically. Use this format with Image. This format corresponds to ImageFormat.YUV_420_888, and can represent the COLOR_FormatYUV411Planar, COLOR_FormatYUV411PackedPlanar, COLOR_FormatYUV420Planar, COLOR_FormatYUV420PackedPlanar, COLOR_FormatYUV420SemiPlanar and COLOR_FormatYUV420PackedSemiPlanar formats.
雖然這種格式被21版本後所有手機都支援,但使用的時候需要結合 Image | Android Developers 來使用,getOutputImage / getInputImage。
在更低的版本時,需要設定具體的 Color_Format,基本上大部分裝置都支援 COLOR_FormatYUV420SemiPlanar 或者 COLOR_FormatYUV420Planar。我們只需要處理這兩種 Color_Format 就可以了。
這裡還有一個坑,是在某些裝置上 ColorFormat 所表示的格式可能是反的,比如NV12 與 NV21。建議儘可能地使用前面章節提到的Surface input API 來規避這些問題。
KEY_I_FRAME_INTERVAL
通常的方案是設定為 1s,對於圖片電影等等特殊情況,這裡可以設定為 0,表示希望每一幀都是 KeyFrame。
但要想達到精確的幀率控制不太現實,儘量使用 Surface,儘量在流程中不佔用過多資源,沒有明顯示卡頓耗時,這樣得出的幀率相對更好一些。
KEY_PROFILE & KEY_LEVEL
這裡設定編碼級別,注意這裡 Profile 必須和 level 配對,下面的程式碼是一個簡單的示例。
profile
這裡 Profile 級別的設定是一個深坑。
- 深坑 NO.1 -> 在 Android 7.0 以前,不管你怎麼設定 Profile 級別,最後都會使用 BaseLine,因為這在原始碼裡面寫死了。。。
- 深坑 NO.2 -> Main 或者 High Profile 能夠帶來更高的壓縮比和其他好處,但並不意味著它的相容性就很好。在某雜糧手機,Oppo手機的部分機型中,使用 High Profile 會導致 pts 和 dts 時間不一致,從而在播放的時候會畫面來回跳動。(
至今不知道怎麼爬出這個坑,還是老實用回 BaseLine 了)
儘管如此,還是建議大家儘量使用 High/Main 這樣的 Profile,新方案新技術,只有使用才能發揮價值。
額外地說一下,我們開發錄音功能時,使用 AudioRecorder,並將相應的資料送給編碼器。這裡在配置 AudioRecorder 時,最好將 PCM 的格式設定為 ::ENCODING_PCM_16BIT::,取樣率設定為 ::44100Hz:: (44100 is currently the only rate that is guaranteed to work on all devices),聲道設定為::單聲道:: ({@link AudioFormat#CHANNEL_IN_MONO} is guaranteed to work on all devices)。
。
KEY_IS_ADTS
A key mapping to a value of 1 if the content is AAC audio and audio frames are prefixed with an ADTS header. The associated value is an integer (0 or 1). This key is only supported when decoding content, it cannot be used to configure an encoder to emit ADTS output.
這個設定項只在解碼音訊的時候才有用,單獨拎出來還是因為其有坑。通常情況下,我們不需要去理會 ADTS,直接使用回撥中的 Format 就可以了。但在魅族某些機型上,Format 可能會有錯誤,對於這種情況時,還是手動設定 KEY_IS_ADTS 為 1 比較好。
Other Tips
真正在使用 MediaCodec 的時候,還會遇到一些小坑,接下來簡單地介紹下。(
)
- 保證 output 資料與 muxer 資料,在同一個執行緒上,否則可能出現花屏等現象。
- createInputSurface() 需要在 configure 之後,與 start 之前,否賊可能建立失敗。
- 處理資料時,需要處理 position 與 offset。
還有一些其他 Tips,諸君一起來更新呀~
參考資料
- Android MediaCodec stuff
- Android Compatibility Definition Document
- MediaCodec | Android Developers
- tests/tests/media/src/android/media/cts - platform/cts - Git at Google
- GitHub - OnlyInAmerica/HWEncoderExperiments: Deprecated ( See https://github.com/Kickflip/kickflip-android-sdk for my current work). Experiments with Android 4.3’s MediaCodec and MediaMuxer
- GitHub - videolan/vlc: VLC media player - All pull requests are ignored, please follow https://wiki.videolan.org/Sending_Patches_VLC/
- tests/tests/media/src/android/media/cts - platform/cts - Git at Google
文件資訊
- 版權宣告:自由轉載-非商用-非衍生-保持署名( 創意共享3.0許可證 )
- 發表日期:2018年11月26日
- 社交媒體:weibo.com/woaitqs
- Feed訂閱: www.woaitqs.cc/feed.xml
最後更新時間: