一,MediaCodec工作原理
MediaCodec類Android提供的用於訪問低層多媒體編/解碼器接口,它是Android低層多媒體架構的一部分,通常與MediaExtractor、MediaMuxer、AudioTrack結合使用,能夠編解碼諸如H.264、H.265、AAC、3gp等常見的音視頻格式。
廣義而言,MediaCodec的工作原理就是處理輸入數據以產生輸出數據。具體來說,MediaCodec在編解碼的過程中使用了一組輸入/輸出緩存區來同步或異步處理數據:首先,客戶端向獲取到的編解碼器輸入緩存區寫入要編解碼的數據並將其提交給編解碼器,待編解碼器處理完畢後將其轉存到編碼器的輸出緩存區,同時收回客戶端對輸入緩存區的所有權;然後,客戶端從獲取到編解碼輸出緩存區讀取編碼好的數據進行處理,待處理完畢後編解碼器收回客戶端對輸出緩存區的所有權。不斷重複整個過程,直至編碼器停止工作或者異常退出。
mediacodec的作用是處理輸入的數據生成輸出數據。首先生成一個輸入數據緩衝區,將數據填入緩衝區提供給codec,codec會採用異步的方式處理這些輸入的數據,然後將填滿輸出緩衝區提供給消費者,消費者消費完後將緩衝區返還給codec。
二,MediaCodec編碼過程
在整個編解碼過程中,MediaCodec的使用會經歷配置、啟動、數據處理、停止、釋放幾個過程,相應的狀態可歸納為停止(Stopped),執行(Executing)以及釋放(Released)三個狀態,而Stopped狀態又可細分為未初始化(Uninitialized)、配置(Configured)、異常( Error),Executing狀態也可細分為讀寫數據(Flushed)、運行(Running)和流結束(End-of-Stream)。
MediaCodec整個狀態結構圖如下:

從上圖可知,當MediaCodec被創建後會進入未初始化狀態,待設置好配置信息並調用start()啟動後,MediaCodec會進入運行狀態,並且可進行數據讀寫操作。如果在這個過程中出現了錯誤,MediaCodec會進入Stopped狀態,我們就是要使用reset方法來重置編解碼器,否則MediaCodec所持有的資源最終會被釋放。當然,如果MediaCodec正常使用完畢,我們也可以向編解碼器發送EOS指令,同時調用stop和release方法終止編解碼器的使用。
三,MediaCodec API 說明
MediaCodec可以處理具體的視頻流,主要有這幾個方法:
- getInputBuffers:獲取需要編碼數據的輸入流隊列,返回的是一個ByteBuffer數組
- queueInputBuffer:輸入流入隊列
- dequeueInputBuffer:從輸入流隊列中取數據進行編碼操作
- getOutputBuffers:獲取編解碼之後的數據輸出流隊列,返回的是一個ByteBuffer數組
- dequeueOutputBuffer:從輸出隊列中取出編碼操作之後的數據
- releaseOutputBuffer:處理完成,釋放ByteBuffer數據
四,MediaCodec基本使用
所有的同步模式的 MediaCodec API都遵循一個模式:
創建並配置一個 MediaCodec 對象
循環直到完成:
如果輸入緩衝區就緒,讀取一個輸入塊,並複製到輸入緩衝區中
如果輸出緩衝區就緒,複製輸出緩衝區的數據
釋放 MediaCodec 對象
(1) 創建編/解碼器
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
當然,MediaCodec還提供了一個createByCodecName (String name)方法,支持使用組件的具體名稱來創建編解碼器。但是該方法使用起來有些麻煩,且官方是建議最好是配合MediaCodecList使用,因為MediaCodecList記錄了所有可用的編解碼器。當然,我們也可以使用該類對傳入的minmeType參數進行判斷,以匹配出MediaCodec對該mineType類型的編解碼器是否支持。
以指定MIME類型為“video/avc”為例,代碼如下:
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方法,該方法首先對MediaFormat存儲的數據map進行提取,然後調用本地方法native-configure實現對編解碼器的配置工作。在配置時,configure方法需要傳入format、surface、crypto、flags參數,其中format為MediaFormat的實例,它使用”key-value”鍵值對的形式存儲多媒體數據格式信息;surface用於指明解碼器的數據源來自於該surface;crypto用於指定一個MediaCrypto對象,以便對媒體數據進行安全解密;flags指明配置的是編碼器(CONFIGURE_FLAG_ENCODE)。
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);
以上代碼是在編碼H.264時的配置方法,createVideoFormat(“video/avc”, 640 ,480)為”video/avc”類型(即H.264)編碼器的MediaFormat對象,需要指定視頻數據的寬高,如果編解碼音頻數據,則調用MediaFormat的createAudioFormat(String mime, int sampleRate,int channelCount)的方法。除了一些諸如視頻幀率、音頻採樣率等配置參數,這裡需要着重講解一下
MediaFormat.KEY_COLOR_FORMAT配置屬性,該屬性用於指明video編碼器的顏色格式,具體選擇哪種顏色格式與輸入的視頻數據源顏色格式有關。比如,我們都知道Camera預覽採集的圖像流通常為NV21或YV12,那麼編碼器需要指定相應的顏色格式,否則編碼得到的數據可能會出現花屏、疊影、顏色失真等現象。MediaCodecInfo.CodecCapabilities.存儲了編碼器所有支持的顏色格式,常見顏色格式映射如下:
原始數據 編碼器
NV12(YUV420sp) ———> COLOR_FormatYUV420PackedSemiPlanar
NV21 ———-> COLOR_FormatYUV420SemiPlanar
YV12(I420) ———-> COLOR_FormatYUV420Planar
當編解碼器配置完畢後,就可以調用MediaCodec的start()方法,該方法會調用低層native_start()方法來啟動編碼器,並調用低層方法ByteBuffer[] getBuffers(input)來開闢一系列輸入、輸出緩存區。
start()方法源碼如下:
public final void start() {
native_start();
synchronized(mBufferLock) {
cacheBuffers(true /* input */);
cacheBuffers(false /* input */);
}
}
(3) 數據處理
MediaCodec支持兩種模式編解碼器,即同步synchronous、異步asynchronous,所謂同步模式是指編解碼器數據的輸入和輸出是同步的,編解碼器只有處理輸出完畢才會再次接收輸入數據;而異步編解碼器數據的輸入和輸出是異步的,編解碼器不會等待輸出數據處理完畢才再次接收輸入數據。這裡,我們主要介紹下同步編解碼,因為這種方式我們用得比較多。我們知道當編解碼器被啟動後,每個編解碼器都會擁有一組輸入和輸出緩存區,但是這些緩存區暫時無法被使用,只有通過MediaCodec的
dequeueInputBuffer/dequeueOutputBuffer方法獲取輸入輸出緩存區授權,通過返回的ID來操作這些緩存區。下面我們通過一段官方提供的代碼,進行擴展分析:
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat(); // option B
codec.start();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// fill inputBuffer with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat is identical to outputFormat
// outputBuffer is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} 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();
從上面代碼可知,當編解碼器start後,會進入一個for(;;)循環,該循環是一個死循環,以實現不斷地去從編解碼器的輸入緩存池中獲取包含數據的一個緩存區,然後再從輸出緩存池中獲取編解碼好的輸出數據。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/210363.html