Android音視頻處理之MediaCodec

MediaCodec是Android中媒體編解碼器,能夠對媒體進行編/解碼

MediaCodec採用同步/異步方式處理數據,而且使用了一組輸入輸出緩存(ByteBuffer)。經過請求一個空的輸入緩存(ByteBuffer),向其中填充滿數據並將它傳遞給編解碼器處理。編解碼器處理完這些數據並將處理結果輸出至一個空的輸出緩存(ByteBuffer)中。使用完輸出緩存的數據以後,將其釋放回編解碼器web


MediaCodec的生命週期有三種狀態:中止態-Stopped、執行態-Executing、釋放態-Released
數組

中止狀態(Stopped)包括了三種子狀態:未初始化(Uninitialized)、配置(Configured)、錯誤(Error)。
緩存

執行狀態(Executing)會經歷三種子狀態:刷新(Flushed)、運行(Running)、流結束(End-of-Stream)安全


(1)當建立了一個MediaCodec對象,此時MediaCodec處於Uninitialized狀態。
bash

MediaCodec主要提供了createEncoderByType(String type)、createDecoderByType(String type)兩個方法來建立編解碼器,它們均須要傳入一個MIME類型多媒體格式。常見的MIME類型多媒體格式以下:
異步

video/x-vnd.on2.vp8 - VP8 video (i.e. video in .webm) 
video/x-vnd.on2.vp9 - VP9 video (i.e. video in .webm) 
video/avc - H.264/AVC video 
video/mp4v-es - MPEG4 video 
video/3gpp - H.263 video 
audio/3gpp - AMR narrowband audio 
audio/amr-wb - AMR wideband audio 
audio/mpeg - MPEG1/2 audio layer III 
audio/mp4a-latm - AAC audio (note, this is raw AAC packets, not packaged in LATM!) 
audio/vorbis - vorbis audio 
audio/g711-alaw - G.711 alaw audio 
audio/g711-mlaw - G.711 ulaw audio 
複製代碼

String AUDIO_MIME = "audio/mp4a-latm";
MediaCodec mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME);複製代碼

MediaCodec還提供了一個createByCodecName (String name)方法,支持使用組件的具體名稱來建立編解碼器。可是該方法使用起來有些麻煩,且官方是建議最好是配合MediaCodecList使用,由於MediaCodecList記錄了全部可用的編解碼器。另外,咱們也可使用該類對傳入的minmeType參數進行判斷,以匹配出MediaCodec對該mineType類型的編解碼器是否支持。以指定MIME類型爲"video/avc"爲例,代碼以下:
ide

private static MediaCodecInfo selectCodec(String mimeType) {
     // 獲取全部支持編解碼器數量
     int numCodecs = MediaCodecList.getCodecCount();
     for (int i = 0; i < numCodecs; i++) {
        // 編解碼器相關性信息存儲在MediaCodecInfo中
         MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
         // 判斷是否爲編碼器
         if (!codecInfo.isEncoder()) {
             continue;
         }
        // 獲取編碼器支持的MIME類型,並進行匹配
         String[] types = codecInfo.getSupportedTypes();
         for (int j = 0; j < types.length; j++) {
             if (types[j].equalsIgnoreCase(mimeType)) {
                 return codecInfo;
             }
         }
     }
     return null;
 }
複製代碼

(2)建立完MediaCodec以後,須要使用configure(…)方法對MediaCodec進行配置,這時MediaCodec轉爲Configured狀態。 
ui

編解碼器配置使用的是MediaCodec的configure方法,在配置時,configure方法須要傳入format、surface、crypto、flags參數,其中format爲MediaFormat的實例,它使用」key-value」鍵值對的形式存儲多媒體數據格式信息;surface用於指明解碼器的數據源來自於該surface;crypto用於指定一個MediaCrypto對象,以便對媒體數據進行安全解密;flags指明配置的是編碼器(CONFIGURE_FLAG_ENCODE)。
this

MediaFormat mFormat = MediaFormat.createVideoFormat("video/avc", 640 ,480);     // 建立MediaFormat
mFormat.setInteger(MediaFormat.KEY_BIT_RATE,600);       // 指定比特率
mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);  // 指定幀率
mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,mColorFormat);  // 指定編碼器顏色格式  
mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,10); // 指定關鍵幀時間間隔
mVideoEncodec.configure(mFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
複製代碼

(3)配置完MediaCodec以後,調用MediaCodec的start()方法使其轉入Executing狀態。編碼

在調用start()方法後MediaCodec當即進入Flushed子狀態,此時MediaCodec會擁有全部的緩存。一旦第一個輸入緩存(input buffer)被移出隊列,MediaCodec就轉入Running子狀態,這種狀態佔據了MediaCodec的大部分生命週期。當你將一個帶有end-of-stream marker標記的輸入緩存入隊列時,MediaCodec將轉入End-of-Stream子狀態。在這種狀態下,MediaCodec再也不接收以後的輸入緩存,但它仍然產生輸出緩存直到end-of- stream標記輸出。你能夠在Executing狀態的任什麼時候候經過調用flush()方法返回到Flushed子狀態。

下面看看整個過程:

調用MediaCodec的start()方法,此時MediaCodec處於Executing狀態,能夠經過getInputBuffers()方法和getOutputBuffers()方法獲取緩存隊列:

mAudioEncoder.start();
mAudioInputBuffers = mAudioEncoder.getInputBuffers();
mAudioOutputBuffers = mAudioEncoder.getOutputBuffers();
複製代碼

當MediaCodec處於Executing狀態以後就能夠對數據進行處理了。

  • 首先經過dequeueInputBuffer(long timeoutUs)請求一個輸入緩存,timeoutUs表明等待時間,設置爲-1表明無限等待:

int inputBufIndex = mAudioEncoder.dequeueInputBuffer(1000);
複製代碼

返回的整型變量爲請求到的輸入緩存的index,經過getInputBuffers()獲得的是輸入緩存數組,經過index和輸入緩存數組能夠獲得當前請求的輸入緩存,在使用以前要clear一下,避免以前的緩存數據影響當前數據

mInputBuffer = mAudioInputBuffers[inputBufIndex];
mInputBuffer.clear();
複製代碼

也能夠直接經過mAudioEncoder.getInputBuffer(inputBufIndex)獲得輸入緩存

  • 接着就是把數據添加到輸入緩存中,並調用queueInputBuffer(...)把緩存數據入隊

mInputBuffer.put(bytes, 0, BUFFER_SIZE_IN_BYTES);
mAudioEncoder.queueInputBuffer(inputBufIndex, 0, BUFFER_SIZE_IN_BYTES, (1000000 * mEncodedSize / AUDIO_BYTE_PER_SAMPLE), 0);
複製代碼

獲取輸出緩存和獲取輸入緩存相似

  • 首先經過dequeueOutputBuffer(BufferInfo info, long timeoutUs)來請求一個輸出緩存,這裏須要傳入一個BufferInfo對象,用於存儲ByteBuffer的信息

int outputBufIndex = mAudioEncoder.dequeueOutputBuffer(mOutBufferInfo, 1000);
複製代碼

public final static class BufferInfo {
    public void set(
            int newOffset, int newSize, long newTimeUs, @BufferFlag int newFlags) {
        offset = newOffset;
        size = newSize;
        presentationTimeUs = newTimeUs;
        flags = newFlags;
    }
    public int offset // 偏移量
    public int size;    // 緩存區有效數據大小
    public long presentationTimeUs; // 顯示時間戳
    public int flags;                   // 緩存區標誌

    @NonNull
    public BufferInfo dup() {
        BufferInfo copy = new BufferInfo();
        copy.set(offset, size, presentationTimeUs, flags);
        return copy;
    }
};
複製代碼

  • 而後經過返回的index獲得輸出緩存,並經過BufferInfo獲取ByteBuffer的信息,咱們能夠獲得當前數據是否Codec-specific Data:

mOutBuffer = mAudioOutputBuffers[outputBufIndex];
if ((mOutBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
    // Codec-specific Data, 這裏能夠從ByteBuffer中獲取csd參數
    // audioFormat.setByteBuffer("csd-0", mOutBuffer);
} else {
    // 處理數據
}
mAudioEncoder.releaseOutputBuffer(outputBufIndex, false);
複製代碼

也能夠直接經過mAudioEncoder.getOutputBuffer(outputBufIndex)獲得輸出緩存

注意必定要調用releaseOutputBuffer方法。

經過調用stop()方法使MediaCodec返回到Uninitialized狀態,所以這個MediaCodec能夠再次從新配置 。

當使用完MediaCodec後,必須調用release()方法釋放其資源。

在極少狀況下MediaCodec會遇到錯誤並進入Error狀態。這個錯誤多是在隊列操做時返回一個錯誤的值或者有時候產生了一個異常致使的。經過調用 reset()方法使MediaCodec再次可用。你能夠在任何狀態調用reset()方法使MediaCodec返回到Uninitialized狀態。不然,調用 release()方法進入最終的Released狀態。

參考文章: 

https://www.jianshu.com/p/30e596112015 
https://blog.csdn.net/AndrExpert/article/details/79578149複製代碼
相關文章
相關標籤/搜索