1. 程式人生 > 其它 >Android MediaCodec的資料處理方式分析

Android MediaCodec的資料處理方式分析

技術標籤:androidandroid

概述

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再次使用。

典型的程式碼設計:

MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
// 非同步模式下需要在configure之前設定callback
codec.setCallback(new MediaCodec.Callback() {

    /**
     * 在onInputBufferAvailable回撥方法中,MediaCodec會通知什麼時候input
     * buffer有效,根據buffer id,呼叫getInputBuffer(id)可以獲得這個buffer,
     * 此時就可以向這個buffer中寫入資料,最後呼叫queueInputBuffer(id, …)提交
     * 給MediaCodec處理。
     */
    @Override
    void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    …
    codec.queueInputBuffer(inputBufferId, …);
    }

    /**
     * 在onOutputBufferAvailable回撥方法中,MediaCodec會通知什麼時候output
     * buffer有效,根據buffer id,呼叫getOutputBuffer(id)可以獲得這個buffer,
     * 此時就可以讀取這個buffer中的資料,最後呼叫releaseOutputBuffer(id, …)釋放
     * 給MediaCodec再次使用。
     */

    @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, …);
    }
  /**
    * 當MediaCodec的output format發生變化是會回撥該方法,一般在start之後都會首先回調該方法
    */
    @Override
    void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
        // Subsequent data will conform to new format.
        // Can ignore if using getOutputFormat(outputBufferId)
        mOutputFormat = format; // option B
    }
    /**
     * MediaCodec執行發生錯誤時會回撥該方法
     */
    @Override
    void onError(…) {
    …
    }
});
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat(); // option B
codec.start(); // start 之後MediaCodec立即進入Running子狀態,並會回撥callback中的方法
// wait for processing to complete
codec.stop();  // stop後MediaCodec進入Uninitialized子狀態
codec.release(); //使用完畢要釋放掉MediaCdoec佔用的資源

使用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,要作出恰當處理

典型的程式碼設計:

MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, ...);
 MediaFormat outputFormat = codec.getOutputFormat(); // option B
 codec.start();  // start()方法後會進入Flushed子狀態
 /**
  * 在一個無限迴圈中不斷地請求Codec是否有可用的input buffer 或 output buffer
  */
 for (;;) {
     int inputBufferId = codec.dequeueInputBuffer(timeoutUs); // 請求是否有可用的input buffer
     if (inputBufferId >= 0) {
         ByteBuffer inputBuffer = codec.getInputBuffer(...); // 獲取input buffer
         // fill inputBuffer with valid data
         ...
         codec.queueInputBuffer(inputBufferId, ...); // 提交資料給Codec
     }
     int outputBufferId = codec.dequeueOutputBuffer(...); // 請求是否有可用的output buffer
     if (outputBufferId >= 0) {
         ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); // 獲取output buffer
         MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
         // bufferFormat is identical to outputFormat
         // outputBuffer is ready to be processed or rendered.
         ...
         codec.releaseOutputBuffer(outputBufferId, ...); // 釋放output buffer供Codec再次使用
     } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
         // Subsequent data will conform to new format.
         // Can ignore if using getOutputFormat(outputBufferId)
         outputFormat = codec.getOutputFormat(); // option B
     }
  }
  codec.stop();
  codec.release(); //釋放資源

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

  》非同步模式下通過回撥函式來自動的傳遞可用的input buffer 或 output buffer

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