天天看點

MediaCodec官網翻譯

來自 Developer 官網:MediaCodec 。

MediaCodec類可用于通路低級媒體編解碼器,即編碼器/解碼器元件。它是 Android 低電平多媒體支援基礎設施的一部分(通常與它們一起使用:MediaExtractor , MediaSync , MediaMuxer , MediaCrypto , MediaDrm , Image , Syrface , 和 AudioTrack )。

廣義而言,編解碼器處理輸入資料以生成輸出資料。它異步處理資料,并使用一組輸入和輸出緩沖區。在一個簡單的級别上,您請求(或接受)一個空的輸入緩沖區,将其填充資料并将其發送到編解碼器進行處理。編解碼器用完資料并将其轉換為空的輸出緩沖區之一。最後,您請求(或接受)已填充的輸出緩沖區,使用其内容并将其釋放回編解碼器。

資料類型

編解碼器對于三種資料進行操作:壓縮資料,原始音頻資料和原始視訊資料。可以使用來處理所有三種資料 ButeBuffer,但是對于原始視訊資料應提供一個Surface以提高編解碼器的性能。Surface 使用本機視訊緩沖區,而不将其映射或複制到 ByteBuffer ;是以,它效率更高。使用 Surface 時,通常不能通路原始視訊資料,但是可以使用 ImageReader 該類來通路不安全的解碼(原始)視訊幀。這可能仍比使用 ByteBuffer 更有效,因為某些本機緩沖區可能已映射到 ByteBuffer 。使用 ByteBuffer 模式時,可以使用 Image 類和 getInput / OutputImage(int) 方法通路原始視訊幀。

壓縮緩沖區

輸入緩沖區(用于解碼器)和輸出緩沖區(用于編碼器)包含由多媒體格式類型決定的壓縮資料。對于視訊類型,這通常是單個壓縮的視訊幀。對于音頻資料,這通常是一個通路單元(一個編碼的音頻段,通常包含幾毫秒的音頻,這由格式類型決定),但是由于緩沖區中可能包含多個編碼的通路單元,是以這一要求并不嚴格。在這兩種情況下,緩存不會在任意的位元組邊界上開始或結束,而是在幀或可通路單元的邊界上開始或結束。

原始音頻緩沖區

原始音頻緩沖區包換 PCM 音頻資料的整個幀,這是按通道順序每個通道的樣本。每個 PCM 音頻樣本都是16位帶符号證書或浮點數(以本機位元組順序)。

原始視訊緩沖區

在 ByteBuffer 模式下,視訊緩沖區根據它們的顔色格式 color format 進行展現,您可以從下面擷取支援的顔色格式作為數組: getCodecInfo().getCapabilitiesForType(…).colorFormats 。視訊編解碼器可以支援三種顔色格式:

 · 本地原始視訊格式:這種格式通過COLOR_FormatSurface标記,并可以與輸入或輸出Surface一起使用。

 · YUV 緩存:(如:COLOR_FormatYUV420Flexible)利用一個輸入或輸出Surface,或在在ByteBuffer模式下,可以通過調用getInput/OutputImage(int)方法使用這些格式。

 · 其它特定格式:通常隻在ByteBuffer模式下被支援。有些顔色格式是特定供應商指定的。其他的一些被定義在 MediaCodecInfo.CodecCapabilities中。這些顔色格式同 flexible format相似,你仍然可以使用 getInput/OutputImage(int)方法。

從 Android5.1 開始,所有視訊編解碼器均支援靈活的 YUV4:2:0 緩沖區。

狀态

在其生命周期内,編解碼器從概念上講處于以下三種狀态之一:停止( Stopped ),執行( Executing )或釋放( Released )。停止( Stopped )的狀态包含了三個子狀态:未初始化( Uninitialized ),已配置( Configured )和錯誤( Error )。執行( Executing )狀态從概念上講經過三個子狀态:重新整理( Flushed ),運作( Running )和流結束( End-of-Stream )。

使用工廠方法之一建立編解碼器時,編解碼器處于未初始化狀态( Uninitialized )。首先,您需要使用 configure() 對編解碼器進行配置,使它進入已配置狀态( Configured ),然後調用 start 将其移動到執行狀态( Executing )。在這狀态下,您可以通過上訴緩沖區隊列操作來處理資料。

執行狀态具有三個子狀态:重新整理( Flushed ),運作( Running )和流結束( End-of-Stream )。 start 編解碼器立即處于重新整理狀态( Flushed ),它将儲存所有緩沖區。一旦第一個輸入緩沖區出隊,編解碼器将移至運作狀态( Running ),在此狀态将花費大部分的時間。當您将輸入緩沖區與流結束标記排隊時,編解碼器将裝華為流結束狀态( End-of-Stream )。在這種狀态下,編解碼器将不再接受其它輸入緩沖區,但仍會生成輸出緩沖區,直到在輸出端達到流結束為止。在處于執行狀态( Executing )時,您可以随時傳回到重新整理狀态( Flushed )。

調用 stop 已将編解碼器傳回到未初始化狀态( Uninitialized ),然後可以再次對其進行配置。使用編解碼器完成操作後,必須通過調用釋放它 release 。

在極少數情況下,編解碼器可能遇到錯誤并進入到錯誤狀态( Error )。使用來自排隊操作額無效傳回值後有時通過異常來傳達此資訊。調用 reset 已使編解碼器再次可用。您可以從任何狀态調用它,已将編解碼器移回未初始化狀态( Uninitialized )。否則,請調用 release 已移至終端的已釋放狀态( Released )。

建立

根據指定的 MediaFormat 使用 MeidaCidecList 建立一個 MediaCodec 的執行個體。解碼檔案或流時,可以從 MediaExtractor.getTrackFormat 獲得所需的格式。并調用 MediaFormat.setFeatureEnabled 添加您想添加的任何屬性,然後調用 MediaCodecList.findDecoderForFormat 擷取可以處理該特定媒體格式的編解碼器的名稱。最後,使用建立編解碼器 createByCodecName。

注意

在 Android5.0 上,傳遞給 MediaCodecList.findDecoder/EncoderForFormat 不能包含幀率 frame rate 。通過調用 format.setString(MediaFormat.KEY_FRAME_RAT,null) 以清除目前任何格式的幀率。

您還可以使用 createDecoder / EncoderByType(String) 方法為特定的 MIME 類型建立首選編解碼器。但是,這不能注入特征,而且建立的編解碼器可能不能處理你期望的格式。

建立安全的解碼器

在Android 4.4 及之前版本,安全的編解碼器(secure codecs)沒有被列在MediaCodecList中,但是仍然可以在系統中使用。安全編解碼器隻能夠通過名字進行執行個體化,其名字是在正常編解碼器的名字後附加.secure辨別(所有安全編解碼器的名字都必須以.secure結尾),調用createByCodecName(String)方法建立安全編解碼器時,如果系統中不存在指定名字的編解碼器就會抛出IOException異常。

從Android 5.0 及之後版本,你可以在媒體格式中使用FEATURE_SecurePlayback屬性來建立一個安全編解碼器。

初始化

建立編解碼器後,如果要異步處理資料,通過設定 setCallback 設定回調。然後,指定特定的媒體格式配置編解碼器。此時,您可以 Surface 為視訊制作指定輸出,即産生原始視訊資料的編解碼器( 例如:視訊解碼器 )。這也是您可以設定安全編解碼器的解密參數的時候(請參閱參考資料 MediaCrypto )。最後,由于某些編解碼器可以在多種模式下運作,是以必須指定該編碼器是作為編碼器還是解碼器使用。

您可以在已配置狀态下查詢結果輸入和輸出格式。您可以在啟動編解碼器之前使用它來驗證最終的配置,如顔色格式。

如果要使用原始視訊資料送視訊消費者處理(将原始視訊資料作為輸入的編解碼器,例如視訊編碼器),你可以在配置好視訊消費者編解碼器(encoder)後調用createInputSurface方法建立一個目的surface來存放輸入資料,如此,調用視訊生産者(decoder)的setInputSurface(Surface)方法将前面建立的目的Surface配置給視訊生産者作為輸出緩存位置。

編解碼器專用資料(Codec-specific 資料)

某些格式,尤其是 AAC 音頻和MPEG4,H.264 和 H.265 視訊格式,要求實際資料的字首是許多包含設定資料或編解碼器特定資料的緩沖區。處理此類壓縮格式時,必須在調用 start() 方法後并且在處理任何幀資料之前交給編碼器。這些資料必須在調用 queueInputBuffer 方法時使用 BUFFER_FLAG_CODEC_CONFIG 标記。

Codec-specific 資料也可以被包含在傳遞給 configure 方法的格式資訊(MediaFormat)中,在 ByteBuffer 條目中以"csd-0", "csd-1"等key标記。這些 keys 一直包含在通過 MediaExtractor 獲得的 Audio Track or Video Track的MediaFormat 中。一旦調用 start() 方法,MediaFormat 中的 Codec-specific 資料會自動送出給編解碼器;你不能顯示的送出這些資料。如果 MediaFormat 中不包含編解碼器指定的資料,你可以根據格式要求,按照正确的順序使用指定數目的緩存來送出 codec-specific 資料。在 H264 AVC 編碼格式下,你也可以連接配接所有的 codec-specific 資料并作為一個單獨的 codec-config buffer 送出。

Android 使用下列的 codec-specific data buffers。對于适當的 MediaMuxer 軌道配置,這些也要在軌道格式中進行設定。每個參數集和标有(*)的特定于編解碼器的資料段均必須以起始碼開頭"\ x00 \ x00 \ x00 \ x01"。

MediaCodec官網翻譯

注意:當編解碼器被立即重新整理或 start 之後不久重新整理,并且在任何輸出 buffer 或輸出格式變化被傳回前需要特别地小心,因為編解碼器的 codec specific data 可能會在 flush 過程中丢失。為保證編解碼器的正常運作,你必須在重新整理後使用标記為BUFFER_FLAG_CODEC_CONFIGbuffers 的 buffers 再次送出這些資料。

編碼器(或者産生壓縮資料的編解碼器)将會在有效的輸出緩存之前産生和傳回編解碼器指定的資料,這些資料會以codec-config flag進行标記。包含codec-specific-data的Buffers沒有有意義的時間戳。 

資料處理

每個編解碼器包含一組輸入和輸出緩沖區(input and output buffers),這些緩存在API調用中通過buffer-id進行引用。成功調用 start() 方法後,用戶端将不會 "擁有" 輸入和輸出緩沖區。在同步模式下,調用 dequeueInput / OutputBuffer() 從編解碼器擷取輸入或輸出緩沖區。在異步模式下,您将通過 Callback.onInputBufferAvailable / Callback.onOutputBufferAvailable 回調自動接入可用的緩沖區。

資料擷取緩沖區後,将其填充資料,然後使用 queueInputBuffer 送出給編解碼器,若使用解密則使用 queueSecureInputBuffer 送出。不要送出帶有相同時間戳的多個輸入緩沖區(除非它是也被同樣标記的codec-specific data)。

在異步模式下通過 onOutputBufferAvailable 方法的回調或者在同步模式下響應 dequeuOutputBuffer 的調用,編解碼器傳回一個隻讀的輸出緩沖區。在這個輸出緩沖區被處理後,調用一個 releaseOutputBuffer 方法将這個緩沖區傳回給編解碼器。

當你不需要立即将緩沖區重新送出/釋放到編解碼器,但保持輸入/輸出緩沖區可能會使編解碼器停止工作,并且這與裝置相關。特别是,編解碼器可能會推遲生成輸出緩沖區,直到所有未完成的緩沖區都已釋放/重新送出。是以,請嘗試盡可能少地保留可用緩沖區。

根據 API版本,您可以通過三種方式處理資料:

MediaCodec官網翻譯

使用緩沖區的異步處理

從 Android5.0 開始,首選方法是調用 configure 之前設定回調來異步處理資料。因為你必須在調用 flush() 方法後再調用 start() 方法才能使編解碼器的狀态轉換為 Running 子狀态并開始接收輸入緩沖區。同樣,在首次調用 start() 編解碼器時,将直接移至 Running 狀态,變開始用過回調傳遞可用的輸入緩沖區。

MediaCodec 通常在異步模式下像這樣使用:

MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() {
  @Override
  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
 
  @Override
  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is equivalent to mOutputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  }
 
  @Override
  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    mOutputFormat = format; // option B
  }
 
  @Override
  void onError(…) {
    …
  }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();
           

使用緩沖區的同步處理

從 Android5.0 開始,即使在同步模式下使用編解碼器,也應該使用 getInput / OutputBuffer(int) 和/或 getInput / OutputImage(int) 檢索輸入和輸出緩沖區 OutputImage(int) 。這允許架構進行某些優化,例如在處理動态内容時。如果調用 getInput /,則會禁用此優化 OutputBuffers() 。

MediaCodec通常在同步模式下按以下方式使用:

MediaCodec codec = MediaCodec.createByCodecName(name);
 MediaFormat mOutputFormat; // member variable
 codec.setCallback(new MediaCodec.Callback() {
  @Override
  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
  }
 
  @Override
  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is equivalent to mOutputFormat
    // outputBuffer is ready to be processed or rendered.
    …
    codec.releaseOutputBuffer(outputBufferId, …);
  }
 
  @Override
  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    mOutputFormat = format; // option B
  }
 
  @Override
  void onError(…) {
    …
  }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); // option B
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();
           

流結束處理

到達輸入資料的末尾時,必須調用 queueInputBuffer 方法中通過指定 BUFFER_FLAG_END_OF_STREAM 标記通知編解碼器。您可以在最後一個有效的輸入緩沖區上執行此操作,或者通過送出另外一個空輸入緩沖區并設定流結束标志來執行此操作。如果使用空緩沖區,則時間戳将被忽略。

編解碼器将繼續傳回輸出緩沖區,直到它發出輸出流結束的信号,這是通過指定 dequeueOutputBuffer 方法中 MediaCodec.BufferInfo的end-of-stream 标記來實作的,或者是通過回調方法 onOutputBufferAvailable 來傳回 end-of-stream 标記。可以在最後一個有效的輸出 buffer 中設定或者在最後一個有效的輸出 buffer 後添加一個空的 buffer 來設定,這種空的buffer的時間戳應該被忽略。

在發出輸入流結束信号後不要送出其他輸入緩沖區。除非一重新整理,停止或重新啟動編解碼器。

使用輸出表面

使用輸出時,資料處理幾乎與 ByteBuffer 模式相同 Surface ;但是,輸出緩沖區将不可通路,并表示為 null 值。如,getOutputBuffer / Image(int) 将傳回 null ,并且 getOutputBuffers 傳回包含 null 的數組。

使用輸出 Surface 時,可以選擇是否在表面上渲染每個輸出緩沖區。您有三種選擇:

· 不渲染緩沖區:調用 releaseOutputBuffer(bufferId , false)。

· 使用預設時間戳渲染緩沖區:調用 releaseOutputBuffer(bufferId , true)。

· 使用特定的時間戳渲染緩沖區:調用 releaseOutputBuffer(bufferId , timestamp)。

從 Android6.0 開始,預設時間戳是緩沖區的 presentationTimeUs(轉換為納秒)。在此之前未定義。

另外從 Android6.0 開始,您可以使用 setOutputSurface 方法動态更改輸出 Surface 。

當使用輸入 Surface 時,将沒有可通路的輸入 buffers ,因為這些buffers将會從輸入 surface 自動地向編解碼器傳輸。調用 dequeueInputBuffer 時将抛出一個 IllegalStateException 異常,調用 getInputBuffers() 将要傳回一個不能寫入的僞 ByteBuffer[] 數組。

調用 signalEndOfInputStream() 方法發送 end-of-stream 信号。調用這個方法後,輸入 surface 将會立即停止向編解碼器送出資料。

搜尋和自适應播放支援

視訊解碼器(通常指處理壓縮視訊資料的編解碼器)關于搜尋-seek和格式轉換(不管它們是否支援)表現不同,且被配置為adaptive playback。你可以通過調用 CodecCapabilities.isFeatureSupported(String) 方法來檢查解碼器是否支援 adaptive playback 。支援 Adaptive playback 的解碼器隻有在編解碼器被配置在 Surface 上解碼時才被激活。

流邊界和關鍵幀

在調用 start() 或 flush() 之後,輸入資料在合适的流邊界開始是非常重要的:第一幀必須是關鍵幀。一個關鍵幀可以完全獨立的被解碼(對于大多數編解碼器它意味着I-frame),并且關鍵幀之後顯示的幀不會引用關鍵幀之前的幀。

下面總結适用于各種視訊格式的關鍵幀:

MediaCodec官網翻譯

對于不支援自适應播放的編解碼器(包括解碼到Surface上解碼器)

為了開始解碼與先前送出的資料(也就是seek後)不相鄰的資料你必須重新整了解碼器。由于所有輸出buffers會在flush的一刻立即撤銷,你可能希望在調用flush方法前等待這些buffers首先被标記為end-of-stream。在調用flush方法後輸入資料在一個合适的流邊界或關鍵幀開始是非常重要的。

注意:flush後送出的資料的格式不能改變;flush()方法不支援格式的不連續性;為此,一個完整的stop()-configure(...)-start()的過程是必要的。

另外注意:如果你調用start()方法後過快地重新整理編解碼器,通常,在收到第一個輸出buffer或輸出format變化前,你需要向這個編解碼器再次送出codec-specific-data。具體檢視codec-specific-data部分以獲得更多資訊。

對于支援并配置為自适應播放的解碼器

為了開始解碼與先前送出的資料(也就是seek後)不相鄰的資料,不必重新整了解碼器;但是,在間斷後傳入的資料必須開始于一個合适的流邊界或關鍵幀。

針對一些視訊格式-也就是H.264、H.265、VP8和VP9,也可以修改圖檔大小或者配置 mid-stream 。為了做到這些你必須将整個新 codec-specific 配置資料與關鍵幀一起打包到一個單獨的buffer中(包括所有的開始資料),并将它作為一個正常的輸入資料送出。

在picture-size被改變後以及任意具有新大小的幀傳回之前,你可以從 dequeueOutputBuffer 方法或 onOutputFormatChanged 回調中得到 INFO_OUTPUT_FORMAT_CHANGED的傳回值。

注意:就像使用 codec-specific data 時的情況,在你修改圖檔大小後立即調用fush()方法時需要非常小心。如果你沒有接收到圖檔大小改變的确認資訊,你需要重試修改圖檔大小的請求。

摘要

嵌套類

BufferInfo : 每個緩沖區中繼資料包含一個偏移量和大小,用于指定關聯的編解碼器(輸出)緩沖區中有效資料的範圍。

Callback : MediaCodec回調接口。

CodecException : 在發生内部編解碼器錯誤時抛出。

CryptoException : 對于安全輸入緩沖區進行排隊時發生加密錯誤時抛出。

CryptoInfo : 描述加密輸入樣本結構的中繼資料。

OnFrameRenderredListener : 在輸入表面渲染了輸出幀時将調用的偵聽器。

常數

BUFFER_FLAG_CODEC_CONFIG  : 這表明這樣标記的緩沖區包含編解碼器初始化/編解碼器特定的資料,而不是媒體資料。

BUFFER_FLAG_END_OF_STREAM : 标志流的結束。

BUFFER_FLAG_KEY_FRAME : 這表明這樣标記的(編碼)緩沖區包含關鍵幀的資料。

BUFFER_FLAG_SYNC_FRAME : 這表明這樣标記的(編碼)緩沖區包含關鍵幀的資料。

CONFIGUER_FLAG_ENCODE : 如果将此編解碼器作為編碼器,傳此标志。

繼續閱讀