Android MediaCodec的數據處理方式分析


 

*由於工作需要,需要利用MediaCodec實現Playback及Transcode等功能,故在學習過程中翻譯了Google官方的MediaCodec API文檔,由於作者水平限制,文中難免有錯誤和不恰當之處,望批評指正。

*轉載請注明出處:http://www.cnblogs.com/roger-yu/

概述

Android MediaCodec可以訪問底層的media codecs,我們很容易利用MediaCodec來構建encoder或decoder來實現音視頻編碼和音視頻解碼的功能。

簡單點兒理解,一個Codec(可以認為是一個MediaCodec的實例對象)就相當於一個“處理器”:處理輸入數據,並產生輸出數據。

如下圖所示,每一個Codec都維護着一組 input buffers 和 output buffers。開始時Codec擁有所有buffers的所有權,Client(可以暫且理解為MediaCodec之外寫的程序)無法向 input buffer 寫入數據,也無法讀取 output buffer 中的數據。數據處理開始后,Client向Codec請求一個(同步模式)或者接收到(異步模式)一個空的 input buffer,將要處理的數據寫入到該buffer中,然后提交給Codec處理,Codec處理完數據后會將處理的結果寫入到一個空的 output buffer 中,之后Client就可以請求或接收到這個存有結果的 output buffer,Client對結果使用完畢后就可以release這個output buffer,Codec就可以再次使用這個buffer,如此過程完成整個的處理。

 

Android MediaCodec主要有3種數據處理的方式:

  1. 使用Buffers的異步處理方式(Asynchronous Processing using Buffers)

  2. 使用Buffers的同步處理方式(Synchronous Processing using Buffers)

  3. 使用Buffer數組的同步處理方式(Synchronous Processing using Buffer Arrays (deprecated))

依據Android版本不同可以采用不同的方式,如下圖:

目前最常用的是前兩種模式,故接下來重點講解。

使用Buffers的異步處理方式(Asynchronous Processing using Buffers)

基本處理流程:

注意:

  1. 在調用configure配置MediaCodec之前需要為MediaCodec設置callback,需要實現MediaCodec.Callback接口並重寫其中的方法:onInputBufferAvailable 、onOutputBufferAvailable、onOutputFormatChanged、onError,工作時MediaCodec會利用    這四個回調方法來自動的通知Client什么時候input buffer有效,什么時候output buffer有效,什么時候media format發生變化,什么時候運行出錯,也是在這些方法中Client向Codec送入數據並得到處理的結果及獲取Codec的一些其他信息。

  2. 異步模式下MediaCodec的狀態轉換會有些許不同,在調用start方法后會直接進入Running狀態;

  異步處理模式下,調用MediaCodec.start()后Codec 立即進入Running子狀態,通過設置的callback中的回調方法onInputBufferAvailable()會自動收到可用(empty)的input buffer,此時可以根據input buffer id調用getInputBuffer(id)得到這個buffer,並將需要的處理的數據寫入該buffer中,最后調用queueInputBuffer(id, ...)將該buffer提交給Codec處理;Codec每處理完一幀數據就會將處理結果寫入一個空的output buffer,並通過回調函數onOutputBufferAvailable來通知Client來讀取結果,Client可以根據output bufffer id調用getOutputBuffer(id)獲取該buffer並讀取結果,完畢后可以調用releaseOutputBuffer(id, ...)釋放該buffer給Codec再次使用。

典型的代碼設計:

 1 MediaCodec codec = MediaCodec.createByCodecName(name);
 2 MediaFormat mOutputFormat; // member variable
 3 // 異步模式下需要在configure之前設置callback
 4 codec.setCallback(new MediaCodec.Callback() {
 5 
 6     /**
 7      * 在onInputBufferAvailable回調方法中,MediaCodec會通知什么時候input
 8      * buffer有效,根據buffer id,調用getInputBuffer(id)可以獲得這個buffer,
 9      * 此時就可以向這個buffer中寫入數據,最后調用queueInputBuffer(id, …)提交
10      * 給MediaCodec處理。
11      */
12     @Override
13     void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
14     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
15     // fill inputBuffer with valid data
16 17     codec.queueInputBuffer(inputBufferId, …);
18     }
19 
20     /**
21      * 在onOutputBufferAvailable回調方法中,MediaCodec會通知什么時候output
22      * buffer有效,根據buffer id,調用getOutputBuffer(id)可以獲得這個buffer,
23      * 此時就可以讀取這個buffer中的數據,最后調用releaseOutputBuffer(id, …)釋放
24      * 給MediaCodec再次使用。
25      */
26 
27     @Override
28     void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …)     {
29         ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
30         MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
31         // bufferFormat is equivalent to mOutputFormat
32         // outputBuffer is ready to be processed or rendered.
33 34         codec.releaseOutputBuffer(outputBufferId, …);
35     }
36   /**
37     * 當MediaCodec的output format發生變化是會回調該方法,一般在start之后都會首先回調該方法
38     */
39     @Override
40     void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
41         // Subsequent data will conform to new format.
42         // Can ignore if using getOutputFormat(outputBufferId)
43         mOutputFormat = format; // option B
44     }
45     /**
46      * MediaCodec運行發生錯誤時會回調該方法
47      */
48     @Override
49     void onError(…) {
50 51     }
52 });
53 codec.configure(format, …);
54 mOutputFormat = codec.getOutputFormat(); // option B
55 codec.start(); // start 之后MediaCodec立即進入Running子狀態,並會回調callback中的方法
56 // wait for processing to complete
57 codec.stop();  // stop后MediaCodec進入Uninitialized子狀態
58 codec.release(); //使用完畢要釋放掉MediaCdoec占用的資源                   
View Code

 

 使用Buffers的同步處理方式(Synchronous Processing using Buffers)

基本處理流程:

  同步模式下,MediaCodec調用start()方法后會進入Flushed子狀態,然后第一次調用dequeueInputBuffer()后才會進入Running子狀態。

  這種模式下,程序需要在一個無限循環中通過調用dequeueInputBuffer(...)和dequeueOutputBuffer(...)來不斷地請求Codec是否有可用的input buffer 或 output buffer:

      > 如果有可用的input buffer:根據得到的buffer id,調用getInputBuffer(id)獲取該buffer,並向其中寫入待處理的數據,然后調用queueInputBuffer(id,..)提交到Codec進行處理

      > 如果有可用的output buffer: 根據得到的buffer id,調用getOutputBuffer(id)獲取該buffer,讀取其中的處理結果,然后調用releaseOutputBuffer(id,..)釋放該buffer供Codec再次使用

      > 處理過程中還可能受到一些特殊標記的buffer id,比如MediaCodec.INFO_OUTPUT_FORMAT_CHANGED,要作出恰當處理

典型的代碼設計:

 

 1  MediaCodec codec = MediaCodec.createByCodecName(name);
 2  codec.configure(format, ...);
 3  MediaFormat outputFormat = codec.getOutputFormat(); // option B
 4  codec.start();  // start()方法后會進入Flushed子狀態
 5  /**
 6   * 在一個無限循環中不斷地請求Codec是否有可用的input buffer 或 output buffer
 7   */
 8  for (;;) {
 9      int inputBufferId = codec.dequeueInputBuffer(timeoutUs); // 請求是否有可用的input buffer
10      if (inputBufferId >= 0) {
11          ByteBuffer inputBuffer = codec.getInputBuffer(...); // 獲取input buffer
12          // fill inputBuffer with valid data
13          ...
14          codec.queueInputBuffer(inputBufferId, ...); // 提交數據給Codec
15      }
16      int outputBufferId = codec.dequeueOutputBuffer(...); // 請求是否有可用的output buffer
17      if (outputBufferId >= 0) {
18          ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); // 獲取output buffer
19          MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
20          // bufferFormat is identical to outputFormat
21          // outputBuffer is ready to be processed or rendered.
22          ...
23          codec.releaseOutputBuffer(outputBufferId, ...); // 釋放output buffer供Codec再次使用
24      } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
25          // Subsequent data will conform to new format.
26          // Can ignore if using getOutputFormat(outputBufferId)
27          outputFormat = codec.getOutputFormat(); // option B
28      }
29   }
30   codec.stop(); 
31   codec.release(); //釋放資源
View Code

 

異步模式與同步模式的區別在於:

  》異步模式下通過回調函數來自動的傳遞可用的input buffer 或 output buffer

  》同步模式下需要通過dequeueInputBuffer(...)或dequeueOutputBuffer(...)來請求獲取可用的input buffer 或 output buffer

 

 

微信掃一掃,關注玖零日記,獲取更多相關資訊源碼 -- 雖無面朝大海,依舊春暖花開

 

 

 


免責聲明!

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



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