簡介
從 API 16開始,Android提供了MediaCodec類以便開發者更加靈活的處理音視頻的編解碼,較MeidaPlay提供了更加豐富、完善的操作接口。具體詳見:這里
正文
MediaCodec類可用于訪問Android底層的媒體編解碼器。例如:編碼/解碼組件。它是Android為多媒體支持提供的底層接口的一部分,通常(MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface 和AudioTrack)一起使用。
編解碼流程
一個編解碼器可以處理輸入的數據來產生輸出的數據,編解碼器使用一組輸入和輸出緩沖器來異步處理數據。你可以創建一個空的輸入緩沖區,填充數據后發送到編解碼器進行處理。編解碼器使用輸入的數據進行轉換,然后輸出到一個空的輸出緩沖區。最后你獲取到輸出緩沖區的數據,消耗掉里面的數據,釋放回編解碼器。如果后續還有數據需要繼續處理,編解碼器就會重復這些操作。輸出流程如下:
[圖片上傳失敗...(image-ad446d-1553137245958)]
Data Types
編解碼器處理三種類型的數據:壓縮數據、原始音頻數據、原始視頻數據。上述三種數據類型都可以通過ByteBuffers進行處理。但是需要提供一個Surface來為提高編解碼體驗(顯示視頻圖像)。Surface直接使用本地視頻數據buffers,而不是通過映射或復制的方式;因此,這樣做的顯得更加高效。通常在使用Surface的時候你不能夠直接訪問原始視頻數據,{但是你可以使用ImageReader類來訪問不可靠的解碼后(或原始)的視頻幀}。這可能仍然比使用ByteBuffers更加有效率,一些原始的buffers可能已經映射到了 direct ByteBuffers。當使用ByteBuffer模式,你可以通過使用Image類和getInput/OutputImage(int)來訪問到原始視頻數據幀。
Compressed Buffers
輸入的buffers(用于解碼)和輸出的buffers(用于編碼)包含的壓縮數據是由媒體格式決定的。針對視頻類型是一個壓縮的單幀。針對音頻數據通常是一個單個可訪問單元(一個編碼后的音頻區段通常包含由特定格式類型決定的幾毫秒音頻數據),但這種通常也不是十分嚴格,一個buffer可能包含多個可訪問的音頻單元。在這兩種情況下,buffers通常不開始或結束于任意的字節邊界,而是結束于幀/可訪問單元的邊界。
Raw Audio Buffers
原始的音頻數據buffers包含整個PCM音頻幀數據,這是在通道順序下每一個通道的樣本。每一個樣本就是一個 16-bit signed integer in native byte order。
Raw Video Buffers
ByteBuffer模式下視頻buffers的的展現是由他們的 color format確定的。你可以通過調用 getCodecInfo().getCapabilitiesForType(…).colorFormats方法獲得其支持的顏色格式數組。視頻編解碼器可能支持三種類型的顏色格式:
- native raw video format: 被COLOR_FormatSurface標記,其可與輸入或輸出Surface一起使用。
- flexible YUV buffers: 這些與輸入/輸出Surface一起使用,以及在ByteBuffer模式中,通過調用getInput/OutputImage(int)方法
- other, specific formats: 通常只在ByteBuffer模式下被支持。有些顏色格式是特定供應商指定的。其他的一些被定義在 MediaCodecInfo.CodecCapabilities中。顏色格式是一個很靈活的格式,你仍然可以使用 getInput/OutputImage(int)方法。
從LOLLIPOP_MR1 API起所有視頻編解碼器支持靈活的YUV 4:2:0 buffers.
States
從概念上講在整個生命周期中編解碼器對象存在于三種狀態之一:Stopped, Executing 或 Released。整體的Stoped狀態實際是由三種狀態的集成:Uninitialized, Configured以及 Error,而從概念上將Executing狀態的執行時通過三個子狀態:Flushed, Running 以及 End-of-Stream。
[圖片上傳失敗...(image-5c907-1553137245958)]
當你使用工廠方法之一創建一個編解碼器的時候,它的狀態是處于Uninitialized狀態。首先,你需要通過configure(…)方法配置它,以此進入Configured 狀態。然后,通過調用start()方法轉入Executing 狀態。在這個狀態下你可以通過上述buffer隊列操作過程數據。
? Executing狀態包含三個子狀態: Flushed, Running 以及 End-of-Stream。在調用start()方法后編解碼器立即進入Flushed 的子狀態,其同時包含所有的buffers。當第一個輸入buffer一旦出隊列,編解碼器就轉入Running 的子狀態,這個狀態占了編解碼器的大部分生命周期時間。當你以end-of-stream marker標記一個入隊列的輸入buffer,則編解碼器就轉入End-of-Stream 子狀態。在這個狀態,編解碼器不在接收以后傳入的輸入buffers,但它仍然產生輸出buffers直到輸出buffer到達end-of-stream狀態。你可以在Executing狀態的任何時候通過調用flush()狀態返回Flushed 的子狀態。或者通過調用stop()方法返回編解碼器的Uninitialized 狀態,因此這個編解碼器需要再次configured 。當你使用完編解碼器后,你必須調用release()方法釋放其資源。
? 在極少情況下編解碼器可能會遇到錯誤并進入Error 狀態。這個錯誤可能是在隊列操作時返回一個錯誤的值或者有時候產生了一個異常導致的。通過調用reset()方法使編解碼器再次可用。你可以在任何狀態調用reset()方法使編解碼器返回Uninitialized 狀態。否則,調用release()方法進入最終的Released 狀態。
Creation
通過MediaCodecList創建一個指定MediaFormat的MediaCodec對象。在解碼文件或流時,你可以通過調用MediaExtractor.getTrackFormat方法獲得所需的格式。通過調用MediaFormat.setFeatureEnabled方法你可以注入任意想要添加的特定特性,然后調用MediaCodecList.findDecoderForFormat方法獲得可以處理這種特定媒體格式的編解碼器的名字。最后,通過調用createByCodecName(String)方法創建一個編解碼器。
注意,在API LOLLIPOP上,傳遞給MediaCodecList.findDecoder/EncoderForFormat的格式必須不能包含幀率。通過調用format.setString(MediaFormat.KEY_FRAME_RATE, null)方法清除任何存在于當前格式中的幀率。
你也可以通過調用createDecoder/EncoderByType(String)方法創建一個首選的MIME類型的編解碼器。然而,不能夠用于注入特性,以及創建了一個不能處理期望的特定媒體格式的編解碼器。
Creating secure decoders
在版本API KITKAT_WATCH 及以前,secure 編解碼器在MediaCodecList中沒有列出來,但是仍然可以在這個系統中使用。secure 編解碼器的存在只能夠通過名字實例化,通過在通常的編解碼器添加".secure"(所有的secure 解碼器名稱必須以".secure"結尾),如果系統上不存在指定的編解碼器則createByCodecName(String)方法將拋出一個IOException 異常。
Initialization
在創建了編解碼器后,如果你想異步地處理數據那么可以通過setCallback方法設置一個回調方法。然后,通過指定的媒體格式configure 這個編解碼器。這段時間你可以為視頻原始數據產生者(例如視頻解碼器)指定輸出Surface。此時你也可以為secure 編解碼器設置解碼參數(詳見MediaCrypto) 。最后,因為有些編解碼器可以操作于多種模式,你必須指定是想讓他作為一個解碼器或編碼器運行。
從API LOLLIPOP起,你可以在Configured 狀態查詢輸入和輸出格式的結果。在開始編解碼前你可以通過這個結果來驗證配置的結果,例如,顏色格式。
如果你想通過視頻處理者處理原始輸入視頻buffers,一個處理原始視頻輸入的編解碼器,例如視頻編碼器,在配置完成后通過調用createInputSurface()方法為你的輸入數據創建一個目標Surface。通過先前創建的persistent input surface調用setInputSurface(Surface)配置這個編解碼器。
Codec-specific Data
? 有些格式,特別是ACC音頻和MPEG4,H.264和H.265視頻格式要求以包含特定數量的構建數據buffers或者codec-specific數據為前綴的實際數據。當處理這樣的壓縮格式時,這些數據必須在start()方法后和任何幀數據之前提交給編解碼器。這些數據必須在調用queueInputBuffer方法時用BUFFER_FLAG_CODEC_CONFIG標記。
? Codec-specific數據也可以被包含在傳遞給configure的ByteBuffer的格式里面,包含的keys是 "csd-0", "csd-1"等。這些keys通常包含在通過MediaExtractor獲得的軌道MediaFormat中。這個格式中的Codec-specific數據將在接近start()方法時自動提交給編解碼器;你不能顯示的提交這些數據。如果這個格式不包含編解碼器指定的數據,你也可以選擇在這個編解碼器中以這個格式所要求的并以正確的順序傳遞特定數量的buffers來提交這些數據。還有,你也可以連接所有的codec-specific數據并作為一個單獨的codec-config buffer提交。
? Android 使用以下codec-specific數據buffers。{這些也被要求在軌道配置的格式軌道屬性MediaMuxer中進行配置}。所有設置的參數以及被標記為(*)的codec-specific-data必須以 "\x00\x00\x00\x01"字符開頭。
[圖片上傳失敗...(image-e8373c-1553137245958)]
注意:當編解碼器被立即flushed 或start之后不久,并且在任何輸出buffer或輸出格式變化被返回前需要特別地小心,編解碼器的code specific 數據可能會在flush過程中丟失。為保證編解碼器的正常運行,你必須在刷新后通過buffers再次提交被標記為BUFFER_FLAG_CODEC_CONFIGbuffers的這些數據。
? 編碼器(或者產生壓縮數據的編解碼器)將在任何合法的輸出buffer前創建并返回被標記為 codec-config flag的codec specific data 。包含codec-specific-data 的Buffers含有沒有意義的時間戳。
Data Processing
API中每一個編解碼器維護一組被一個buffer-ID引用的輸入和輸出buffers。當成功調用start()方法后客戶端將“擁有”輸入和輸出buffers。在同步模式下,通過調用dequeueInput/OutputBuffer(…) 從編解碼器獲得(具有所有權的)一個輸入或輸出buffer。在異步模式下,你可以通過MediaCodec.Callback.onInput/OutputBufferAvailable(…)的回調方法自動地獲得可用的buffers.
在獲得一個輸入buffer,在使用解密方式下通過queueInputBuffer或queueSecureInputBuffer向編解碼器填充相應數據。不要提交多個具有相同時間戳的輸入bufers(除非他的codec-specific 數據時那樣標記的)。
在異步模式下,編解碼器將通過onOutputBufferAvailable的回調返回一個只讀的輸出buffer,或者在同步模式下響應dequeuOutputBuffer的調用。在輸出buffer被處理后,調用releaseOutputBuffer方法中其中一個將這個buffer返回給編解碼器。
在異步模式下,編解碼器將通過onOutputBufferAvailable的回調返回一個只讀的輸出buffer,或者在同步模式下響應dequeuOutputBuffer的調用。在輸出buffer被處理后,調用releaseOutputBuffer方法中其中一個將這個buffer返回給編解碼器。
你不需要立即向編解碼器重新提交或釋放buffers,{獲得的輸入或輸出buffers可能失去編解碼器},當然這些行為依賴于設備情況。具體地說,編解碼器可能推遲產生輸出buffers直到輸出的buffers被釋放或重新提交。因此,盡可能保存可用的buffers。
根據API版本情況,你有三種方式處理相關數據:
[圖片上傳失敗...(image-a3f2ee-1553137245958)]
Asynchronous Processing using Buffers
從LOLLIPOP API版本開始,首選的異步處理數據的方法是通過在調用configure前設置異步的回調方法。異步模式將間接地修改狀態轉換情況,因為你必須在flush()方法后調用start()方法將編解碼器的狀態轉換為Running 子狀態并開始接收輸入buffers。同樣,初始化調用start方法將編解碼器的狀態直接變化為Running 子狀態并通過回調方法開始傳遞可用的輸入buufers。
[圖片上傳失敗...(image-5790fe-1553137245958)]
Synchronous Processing using Buffers
從LOLLIPOP API版本開始,在同步模式下使用編解碼器你應該通過getInput/OutputBuffer(int) 和/或 getInput/OutputImage(int) 檢索輸入和輸出buffers。這允許通過框架進行某些優化,例如,在處理動態內容過程中。如果你調用getInput/OutputBuffers()方法這種優化是不可用的。
注意,不要在同時使用buffers和buffer時產生混淆。特別地,僅僅在調用start()方法后或取出一個值為 INFO_OUTPUT_FORMAT_CHANGED的輸出buffer ID后你才可以直接調用getInput/OutputBuffers方法。
End-of-stream Handling
當到達輸入數據的結尾,你必須向這個編解碼器在調用queueInputBuffer方法中指定BUFFER_FLAG_END_OF_STREAM 來標記輸入數據。你可以在最后一個合法的輸入buffer上做這些操作,或者提交一個以 end-of-stream 標記的額外的空的輸入buffer。如果使用一個空的buffer,它的時間戳將被忽略。
編解碼器將繼續返回輸出buffers,直到在這個設置在indequeueOutputBuffer 里的 MediaCodec.BufferInfo 中被同樣標記為 end-of-stream 的輸出流結束的時候或者通過onOutputBufferAvailable返回。這些可以被設置在最后一個合法的輸出buffer上,或者在最后一個合法的buffer后的一個空buffer。那樣的空buffer的時間戳將被忽略。
不要在輸入流被標記為結束后提交額外的輸入buffers,除非這個編解碼器被flushed,或者stopped 和restarted。
Using an Output Surface
在使用一個輸出Surface時,其數據處理基本上與處理ByteBuffer模式相同。然而,這個輸出buffers將不可訪問,并且被描述為null值。例如,調用getOutputBuffer/Image(int)將返回null,以及調用getOutputBuffers()將返回一個只包含null-s的數組。
Using an Input Surface
當使用輸入Surface時,將沒有可訪問的輸入buffers,因為這些buffers將會從輸入surface自動地向編解碼器傳輸。調用dequeueInputBuffer時將拋出一個IllegalStateException,調用getInputBuffers()將要返回一個不能寫入的假的ByteBUffer[]數組。調用signalEndOfInputStream() 方法標記end-of-stream。調用這個方法后,輸入surface將會立即停止向編解碼器提交數據。
Seeking & Adaptive Playback Support
{視頻解碼器(通常是消費壓縮視頻數據的編解碼器)關于seek和格式變化的行為是不同的,不管他們是否支持以及被配置為adaptive playback}。你可以通過調用CodecCapabilities.isFeatureSupported(String)方法來檢查解碼器是否支持adaptive playback 。只有在編解碼器被配置在Surface上解碼時支持Adaptive playback播放的解碼器才被激活。
Stream Boundary and Key Frames
在調用start()或flush()方法后輸入數據以合適的流邊界開始是非常重要的:其第一幀必須是關鍵幀。一個關鍵幀能夠通過其自身完全解碼(針對大多數編解碼器它是一個I幀),沒有幀能夠在關鍵幀之前或之后顯示。
下面的表格針對不同的格式總結了合適的關鍵幀。
[圖片上傳失敗...(image-56fcbf-1553137245958)]
MediaCodec API 說明
MediaCodec可以處理具體的視頻流,主要有這幾個方法:
- configure:配置為編碼器
- start:成功地配置組件后,調用start方法。
- getInputBuffers:獲取需要編碼數據的輸入流隊列,返回的是一個ByteBuffer數組
- queueInputBuffer:輸入流入隊列
- dequeueInputBuffer:從輸入流隊列中取數據進行編碼操作
- getOutputBuffers:獲取編解碼之后的數據輸出流隊列,返回的是一個ByteBuffer數組
- dequeueOutputBuffer:從輸出隊列中取出編碼操作之后的數據
- releaseOutputBuffer:處理完成,釋放ByteBuffer數據
- stop:完成解碼/編碼任務后,需注意的是codec任然處于活躍狀態且準備重新start。
- flush:沖洗組件的輸入和輸出端口
- release:釋放codec實例使用的資源。
- reset:使codec返回到初始(未初始化)狀態。