MediaCodec的使用介紹

目錄

  1. 概述
  2. 支持的數(shù)據(jù)類型
  3. 使用MediaCodec的編解碼流程
  4. MediaCodec生命周期
  5. MediaCodec API簡介
  6. 同步和異步API的使用流程
  7. 示例程序

參考

1. 概述

推薦以官方文檔[1]作為主要的參考问慎,其中有詳細(xì)的介紹杯缺。

MediaCodec是Android提供的用于對(duì)音視頻進(jìn)行編解碼的類,它通過訪問底層的codec來實(shí)現(xiàn)編解碼的功能舔庶。是Android media基礎(chǔ)框架的一部分,通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, SurfaceAudioTrack 一起使用错忱。

歷史:

  • Android 4.1, API16倘待,MediaCodec的首個(gè)版本。
  • Android 4.3, API18膝昆,擴(kuò)展了一種通過 Surface 提供輸入的方法(createInputSurface)丸边,這樣允許輸入來自于攝像頭的預(yù)覽或者是經(jīng)過OpenGL ES渲染叠必。
  • Android 5.0, API21,引入了“異步模式”妹窖。

強(qiáng)烈建議從示例代碼開始了解MediaCodec纬朝,而不是試圖從文檔把它搞清楚篮绿。

2. 支持的數(shù)據(jù)類型

編解碼器支持的數(shù)據(jù)類型: 壓縮的音視頻據(jù)侈玄、原始音頻數(shù)據(jù)和原始視頻數(shù)據(jù)揪罕。

  • 數(shù)據(jù)通過ByteBuffers類來表示郊愧。
  • 可以設(shè)置Surface來獲取/呈現(xiàn)原始的視頻數(shù)據(jù)玷犹,Surface使用本地的視頻buffer杀餐,不需要進(jìn)行ByteBuffers拷貝广恢∈硎螅可以讓編解碼器的效率更高嫉沽。
  • 通常在使用Surface的時(shí)候辟犀,無法訪問原始的視頻數(shù)據(jù),但是可以使用ImageReader訪問解碼后的原始視頻幀绸硕。在使用ByteBuffer的模式下堂竟,可以使用Image類和getInput/OutputImage(int)獲取原始視頻幀。

壓縮數(shù)據(jù)

  • MediaFormat#KEY_MIME格式類型玻佩。
  • 對(duì)于視頻類型出嘹,通常是一個(gè)單獨(dú)的壓縮視頻幀。
  • 對(duì)于音頻數(shù)據(jù)夺蛇,通常是一個(gè)單獨(dú)的訪問單元(一個(gè)編碼的音頻段通常包含由格式類型決定的幾毫秒的音頻)疚漆,但是這個(gè)要求稍微寬松一些,因?yàn)橐粋€(gè)buffer可能包含多個(gè)編碼的音頻訪問單元刁赦。
  • 在這兩種情況下娶聘,buffer都不會(huì)在任意字節(jié)邊界上開始或結(jié)束,而是在幀/訪問單元邊界上開始或結(jié)束甚脉,除非它們被BUFFER_FLAG_PARTIAL_FRAME標(biāo)記丸升。

原始音頻buffer

原始音頻buffer包含PCM音頻數(shù)據(jù)的整個(gè)幀,這是每個(gè)通道按通道順序的一個(gè)樣本牺氨。每個(gè)樣本都是一個(gè) AudioFormat#ENCODING_PCM_16BIT狡耻。

原始視頻buffer

在ByteBuffer模式下,視頻buffer根據(jù)它們的MediaFormat#KEY_COLOR_FORMAT進(jìn)行布局猴凹∫恼可以從getCodecInfo(). MediaCodecInfo.getCapabilitiesForType.CodecCapability.colorFormats獲取支持的顏色格式。視頻編解碼器可以支持三種顏色格式:

  • native raw video format: CodecCapabilities.COLOR_FormatSurface郊霎,可以與輸入/輸出的Surface一起使用沼头。
  • flexible YUV buffers 例如CodecCapabilities.COLOR_FormatYUV420Flexible, 可以使用getInput/OutputImage(int)與輸入/輸出Surface一起使用,也可以在ByteBuffer模式下使用进倍。
  • other, specific formats: 通常只支持ByteBuffer模式土至。有些顏色格式是廠商特有的,其他定義在CodecCapabilities猾昆。對(duì)于等價(jià)于flexible格式的顏色格式陶因,可以使用getInput/OutputImage(int)。

從Build.VERSION_CODES.LOLLIPOP_MR1.開始垂蜗,所有視頻編解碼器都支持flexible的YUV 4:2:0 buffer楷扬。

3. 使用MediaCodec的編解碼流程

一些編解碼器對(duì)于它們的buffer要求是比較特殊的,比如內(nèi)存對(duì)齊或是有特定的最小最大限制么抗,為了適應(yīng)廣泛的可能性毅否,buffer分配是由編解碼器實(shí)現(xiàn)的亚铁。

  • 這看起來和“零拷貝”原則是相悖的蝇刀,但大部分情況發(fā)生拷貝的幾率是比較小的,因?yàn)榫幗獯a器并不需要復(fù)制或調(diào)整這些數(shù)據(jù)來滿足要求徘溢,而且大多數(shù)情況可以直接使用buffer吞琐,比如直接從磁盤或網(wǎng)絡(luò)讀取數(shù)據(jù)到buffer中,不需要復(fù)制然爆。

MediaCodec采用異步方式處理數(shù)據(jù)站粟,并且使用了一組輸入輸出buffer(ByteBuffer)。

  1. 使用者從MediaCodec請(qǐng)求一個(gè)空的輸入buffer(ByteBuffer)曾雕,填充滿數(shù)據(jù)后將它傳遞給MediaCodec處理奴烙。
  2. MediaCodec處理完這些數(shù)據(jù)并將處理結(jié)果輸出至一個(gè)空的輸出buffer(ByteBuffer)中。
  3. 使用者從MediaCodec獲取輸出buffer的數(shù)據(jù)剖张,消耗掉里面的數(shù)據(jù)切诀,使用完輸出buffer的數(shù)據(jù)之后,將其釋放回編解碼器搔弄。

流程如下圖所示:


image.png

4. MediaCodec的生命周期

MediaCodec的生命周期有三種狀態(tài):Stopped幅虑、Executing、Released顾犹。

  • Stopped倒庵,包含三種子狀態(tài):Uninitialized、Configured炫刷、Error擎宝。
  • Executing,包含三種子狀態(tài):Flushed浑玛、Running绍申、End-of-Stream。


    image.png

Stopped的三種子狀態(tài):

  1. Uninitialized:當(dāng)創(chuàng)建了一個(gè)MediaCodec對(duì)象锄奢,此時(shí)處于Uninitialized狀態(tài)失晴【缒澹可以在任何狀態(tài)調(diào)用reset()方法使MediaCodec返回到Uninitialized狀態(tài)。
  2. Configured:使用configure(…)方法對(duì)MediaCodec進(jìn)行配置轉(zhuǎn)為Configured狀態(tài)涂屁。
  3. Error:MediaCodec遇到錯(cuò)誤時(shí)進(jìn)入Error狀態(tài)书在。錯(cuò)誤可能是在隊(duì)列操作時(shí)返回的錯(cuò)誤或者異常導(dǎo)致的。

Executing的三種子狀態(tài):

  1. Flushed:在調(diào)用start()方法后MediaCodec立即進(jìn)入Flushed子狀態(tài)拆又,此時(shí)MediaCodec會(huì)擁有所有的緩存儒旬。可以在Executing狀態(tài)的任何時(shí)候通過調(diào)用flush()方法返回到Flushed子狀態(tài)帖族。
  2. Running:一旦第一個(gè)輸入緩存(input buffer)被移出隊(duì)列栈源,MediaCodec就轉(zhuǎn)入Running子狀態(tài),這種狀態(tài)占據(jù)了MediaCodec的大部分生命周期竖般。通過調(diào)用stop()方法轉(zhuǎn)移到Uninitialized狀態(tài)甚垦。
  3. End-of-Stream:將一個(gè)帶有end-of-stream標(biāo)記的輸入buffer入隊(duì)列時(shí),MediaCodec將轉(zhuǎn)入End-of-Stream子狀態(tài)涣雕。在這種狀態(tài)下艰亮,MediaCodec不再接收之后的輸入buffer,但它仍然產(chǎn)生輸出buffer直到end-of-stream標(biāo)記輸出挣郭。

Released

  1. 當(dāng)使用完MediaCodec后迄埃,必須調(diào)用release()方法釋放其資源。調(diào)用 release()方法進(jìn)入最終的Released狀態(tài)兑障。

5. MediaCodec API簡介

上面MediaCodec的生命周期的圖中包含了MediaCodec一些主要的方法侄非,下面對(duì)
MediaCodec 主要的API做一個(gè)介紹:

  • MediaCodec創(chuàng)建:
    • createDecoderByType/createEncoderByType:根據(jù)特定MIME類型(如"video/avc")創(chuàng)建codec。
    • createByCodecName:知道組件的確切名稱(如OMX.google.mp3.decoder)的時(shí)候流译,根據(jù)組件名創(chuàng)建codec逞怨。使用MediaCodecList可以獲取組件的名稱。
  • configure:配置解碼器或者編碼器先蒋。
  • start:成功配置組件后調(diào)用start骇钦。
  • buffer處理的接口
    • dequeueInputBuffer:從輸入流隊(duì)列中取數(shù)據(jù)進(jìn)行編碼操作。
    • queueInputBuffer:輸入流入隊(duì)列竞漾。
    • dequeueOutputBuffer:從輸出隊(duì)列中取出編碼操作之后的數(shù)據(jù)眯搭。
    • releaseOutputBuffer:處理完成,釋放ByteBuffer數(shù)據(jù)业岁。
    • getInputBuffers:獲取需要編碼數(shù)據(jù)的輸入流隊(duì)列鳞仙,返回的是一個(gè)ByteBuffer數(shù)組。
    • getOutputBuffers:獲取編解碼之后的數(shù)據(jù)輸出流隊(duì)列笔时,返回的是一個(gè)ByteBuffer數(shù)組棍好。
  • flush:清空的輸入和輸出端口。
  • stop:終止decode/encode會(huì)話
  • release:釋放編解碼器實(shí)例使用的資源。

5.1 MediaCodec創(chuàng)建

MediaCodec的一個(gè)實(shí)例處理一種特定類型的數(shù)據(jù)(例如MP3音頻或H.264視頻)借笙,進(jìn)行編碼或解碼操作扒怖。

MediaCodec創(chuàng)建:

  1. 可以使用MediaCodecList為特定的媒體格式創(chuàng)建一個(gè)MediaCodec。
  • 可以從MediaExtractor#getTrackFormat獲得track的格式业稼。
  • 使用MediaFormat#setFeatureEnabled注入想要添加的任何特性盗痒。
  • 然后調(diào)用MediaCodecList#findDecoderForFormat來獲取能夠處理該特定媒體格式的編解碼器的名稱。
  • 最后低散,使用createByCodecName(字符串)創(chuàng)建編解碼器俯邓。
  1. 還可以使用createDecoder/EncoderByType(java.lang.String)為特定MIME類型創(chuàng)建首選的編解碼器。但是熔号,這不能用于注入特性稽鞭,并且可能會(huì)創(chuàng)建一個(gè)不能處理特定媒體格式的編解碼器。

5.2 configure

配置codec引镊。

    public void configure(
            MediaFormat format,
            Surface surface, MediaCrypto crypto, int flags);
  • MediaFormat format:輸入數(shù)據(jù)的格式(解碼器)或輸出數(shù)據(jù)的所需格式(編碼器)朦蕴。傳null等同于傳遞MediaFormat#MediaFormat作為空的MediaFormat。
  • Surface surface:指定Surface祠乃,用于解碼器輸出的渲染梦重。如果編解碼器不生成原始視頻輸出(例如兑燥,不是視頻解碼器)和/或想配置解碼器輸出ByteBuffer亮瓷,則傳null。
  • MediaCrypto crypto:指定一個(gè)crypto對(duì)象降瞳,用于對(duì)媒體數(shù)據(jù)進(jìn)行安全解密嘱支。對(duì)于非安全的編解碼器,傳null挣饥。
  • int flags:當(dāng)組件是編碼器時(shí)除师,flags指定為常量CONFIGURE_FLAG_ENCODE。

MediaFormat:封裝描述媒體數(shù)據(jù)格式的信息(包括音頻或視頻)扔枫,以及可選的特性元數(shù)據(jù)汛聚。

  • 媒體數(shù)據(jù)的格式指定為key/value對(duì)。key是字符串短荐。值可以integer倚舀、long、float忍宋、String或ByteBuffer痕貌。
  • 特性元數(shù)據(jù)被指定為string/boolean對(duì)。

5.3 dequeueInputBuffer

public final int dequeueInputBuffer(long timeoutUs)
  • 返回用于填充有效數(shù)據(jù)的輸入buffer的索引糠排,如果當(dāng)前沒有可用的buffer舵稠,則返回-1。
  • long timeoutUs:等待可用的輸入buffer的時(shí)間。
    • 如果timeoutUs == 0哺徊,則立即返回室琢。
    • 如果timeoutUs < 0,則無限期等待可用的輸入buffer落追。
    • 如果timeoutUs > 0研乒,則等待“timeoutUs”微秒。

5.4 queueInputBuffer

在指定索引處填充輸入buffer后淋硝,使用queueInputBuffer將buffer提交給組件雹熬。

特定于codec的數(shù)據(jù)

  • 許多codec要求實(shí)際壓縮的數(shù)據(jù)流之前必須有“特定于codec的數(shù)據(jù)”,即用于初始化codec的設(shè)置數(shù)據(jù)谣膳,如
    • AVC視頻中的PPS/SPS竿报。
    • vorbis音頻中的code tables。
    public native final void queueInputBuffer(
            int index,
            int offset, int size, long presentationTimeUs, int flags)
  • int index:以前調(diào)用dequeueInputBuffer(long)返回的輸入buffer的索引继谚。
  • int offset:數(shù)據(jù)開始時(shí)輸入buffer中的字節(jié)偏移量烈菌。
  • int size:有效輸入數(shù)據(jù)的字節(jié)數(shù)。
  • long presentationTimeUs:此buffer的PTS(以微秒為單位)花履。
  • int flags:一個(gè)由BUFFER_FLAG_CODEC_CONFIG和BUFFER_FLAG_END_OF_STREAM標(biāo)志組成的位掩碼芽世。雖然沒有被禁止,但是大多數(shù)codec并不對(duì)輸入buffer使用BUFFER_FLAG_KEY_FRAME標(biāo)志诡壁。
    • BUFFER_FLAG_END_OF_STREAM:用于指示這是輸入數(shù)據(jù)的最后一部分济瓢。
    • BUFFER_FLAG_CODEC_CONFIG:通過指定這個(gè)標(biāo)志,可以在start()或flush()之后直接提交特定于codec的數(shù)據(jù)buffer妹卿。但是旺矾,如果您使用包含這些密鑰的媒體格式配置編解碼器,它們將在啟動(dòng)后由MediaCodec直接自動(dòng)提交夺克。因此箕宙,不建議使用BUFFER_FLAG_CODEC_CONFIG標(biāo)志,只建議高級(jí)用戶使用铺纽。

5.5 dequeueOutputBuffer

從MediaCodec獲取輸出buffer柬帕。

    public final int dequeueOutputBuffer(
            @NonNull BufferInfo info, long timeoutUs) 
  • 返回值:已成功解碼的輸出buffer的索引或INFO_*常量之一(INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED 或 INFO_OUTPUT_BUFFERS_CHANGED)。
    • 返回INFO_TRY_AGAIN_LATER而timeoutUs指定為了非負(fù)值狡门,表示超時(shí)了陷寝。
    • 返回INFO_OUTPUT_FORMAT_CHANGED表示輸出格式已更改,后續(xù)數(shù)據(jù)將遵循新格式融撞。
  • BufferInfo info:輸出buffer的metadata盼铁。
  • long timeoutUs:含義同dequeueInputBuffer中的timeoutUs參數(shù)。

BufferInfo

    public final static class BufferInfo {
        public void set(
                int newOffset, int newSize, long newTimeUs, int newFlags);
        public int offset;
        public int size;
        public long presentationTimeUs;
        public int flags;
    };
  • offset:buffer中數(shù)據(jù)的起始偏移量尝偎。
    • 注意設(shè)備之間的offset是不一致的饶火。在一些設(shè)備上鹏控,offset是相對(duì)裁剪矩形的左上角像素,而在大多數(shù)設(shè)備上肤寝,offset是相對(duì)整個(gè)幀的左上角像素当辐。
  • size:buffer中的數(shù)據(jù)量(以字節(jié)為單位)。如果是0則表示buffer中沒有數(shù)據(jù)鲤看,可以丟棄缘揪。0大小的buffer的唯一用途是攜帶流結(jié)束標(biāo)記。
  • presentationTimeUs:buffer的PTS(以微秒為單位)义桂。來源于相應(yīng)輸入buffer一起傳入的PTS找筝。對(duì)于大小為0的buffer,應(yīng)該忽略這個(gè)值慷吊。
  • flags:與buffer關(guān)聯(lián)的標(biāo)識(shí)信息袖裕,flags包含如下取值:
    • BUFFER_FLAG_KEY_FRAME:buffer包含關(guān)鍵幀的數(shù)據(jù)。
    • BUFFER_FLAG_CODEC_CONFIG:buffer包含編解碼器初始化/編解碼器特定的數(shù)據(jù)溉瓶,而不是媒體數(shù)據(jù)急鳄。
    • BUFFER_FLAG_END_OF_STREAM:標(biāo)志著流的結(jié)束,即在此之后沒有buffer可用堰酿,除非后面跟著flush疾宏。
    • BUFFER_FLAG_PARTIAL_FRAME:buffer只包含幀的一部分,解碼器應(yīng)該對(duì)數(shù)據(jù)進(jìn)行批處理触创,直到在解碼幀之前出現(xiàn)沒有該標(biāo)志的buffer為止坎藐。
    public static final int BUFFER_FLAG_KEY_FRAME = 1;
    public static final int BUFFER_FLAG_CODEC_CONFIG = 2;
    public static final int BUFFER_FLAG_END_OF_STREAM = 4;
    public static final int BUFFER_FLAG_PARTIAL_FRAME = 8;

5.6 releaseOutputBuffer

使用此方法將輸出buffer返回給codec或?qū)⑵滗秩驹谳敵鰏urface。

public void releaseOutputBuffer (int index, 
                boolean render)
  • boolean render:如果在配置codec時(shí)指定了一個(gè)有效的surface嗅榕,則傳遞true會(huì)將此輸出buffer在surface上渲染顺饮。一旦不再使用buffer,該surface將把buffer釋放回codec凌那。

6. 同步和異步API的使用流程

6.1 同步API的使用流程

- 創(chuàng)建并配置MediaCodec對(duì)象。
- 循環(huán)直到完成:
  - 如果輸入buffer準(zhǔn)備好了:
    - 讀取一段輸入吟逝,將其填充到輸入buffer中
  - 如果輸出buffer準(zhǔn)備好了:
    - 從輸出buffer中獲取數(shù)據(jù)進(jìn)行處理帽蝶。
- 處理完畢后,release MediaCodec 對(duì)象块攒。

官方文檔中給出的同步API的代碼示例

 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();

6.2 異步API的使用流程

在Android 5.0, API21励稳,引入了“異步模式”。

- 創(chuàng)建并配置MediaCodec對(duì)象囱井。
- 給MediaCodec對(duì)象設(shè)置回調(diào)MediaCodec.Callback
- 在onInputBufferAvailable回調(diào)中:
    - 讀取一段輸入驹尼,將其填充到輸入buffer中
- 在onOutputBufferAvailable回調(diào)中:
    - 從輸出buffer中獲取數(shù)據(jù)進(jìn)行處理。
- 處理完畢后庞呕,release MediaCodec 對(duì)象新翎。

官方文檔中給出的異步API的代碼示例

 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();

7. 示例程序

Android源碼中的CTS部分給出了很多可以關(guān)于Media編解碼的Demo[2]程帕,可以去參考和學(xué)習(xí)。

7.1 設(shè)備支持的解碼器

MediaCodecList可用于獲取設(shè)備支持的編解碼器的名字地啰、能力愁拭,以查找合適的編解碼器。

以下是獲取編解碼器組件名稱的示例代碼:

        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);//REGULAR_CODECS參考api說明
        MediaCodecInfo[] codecs = list.getCodecInfos();
        Log.d(TAG, "Decoders: ");
        for (MediaCodecInfo codec : codecs) {
            if (codec.isEncoder())
                continue;
            Log.d(TAG, codec.getName());
        }
        Log.d(TAG, "Encoders: ");
        for (MediaCodecInfo codec : codecs) {
            if (codec.isEncoder())
                Log.d(TAG, codec.getName());
        }

不同設(shè)備支持的類型不同亏吝,下面本機(jī)獲取到編解碼器組件的名稱:

Decoders: 
OMX.google.mp3.decoder
OMX.google.amrnb.decoder
OMX.google.amrwb.decoder
OMX.google.aac.decoder
OMX.google.g711.alaw.decoder
OMX.google.g711.mlaw.decoder
OMX.google.vorbis.decoder
OMX.google.opus.decoder
OMX.google.raw.decoder
OMX.google.gsm.decoder
OMX.qcom.video.decoder.avc
OMX.qcom.video.decoder.mpeg4
OMX.qcom.video.decoder.mpeg2
OMX.qcom.video.decoder.h263
OMX.qcom.video.decoder.vc1
OMX.qcom.video.decoder.divx
OMX.qcom.video.decoder.divx311
OMX.qcom.video.decoder.divx4
OMX.qcom.video.decoder.vp8
OMX.qcom.video.decoder.vp9
OMX.qcom.video.decoder.hevc
OMX.ffmpeg.dsd.decoder
OMX.ffmpeg.dts.decoder
OMX.ffmpeg.adpcm.decoder
OMX.qti.audio.decoder.flac
OMX.google.mpeg4.decoder
OMX.google.h263.decoder
OMX.google.h264.decoder
OMX.google.hevc.decoder
OMX.google.vp8.decoder
OMX.google.vp9.decoder
Encoders: 
OMX.google.aac.encoder
OMX.google.amrnb.encoder
OMX.google.amrwb.encoder
OMX.google.flac.encoder
OMX.qcom.video.encoder.avc
OMX.qcom.video.encoder.mpeg4
OMX.qcom.video.encoder.h263
OMX.qcom.video.encoder.vp8
OMX.qcom.video.encoder.hevc
OMX.google.h263.encoder
OMX.google.h264.encoder
OMX.google.mpeg4.encoder
OMX.google.vp8.encoder
  • OMX.google.開頭的通常是軟件編解碼器岭埠。
  • OMX.[硬件廠商名稱]開頭的通常是硬件編解碼器。

5.2 AAC解碼為PCM的示例

public class AACToPCM {
    private static final String TAG = "AACToPCM";
    public static final int ERROR_INPUT_INVALID = 100;
    public static final int ERROR_OUTPUT_FAILED = 200;
    public static final int ERROR_OPEN_CODEC = 300;
    public static final int OK = 0;
    private static final int TIMEOUT_USEC = 0;
    private MediaExtractor mExtractor;
    private MediaFormat mFormat;
    private MediaCodec mDecoder;
    private FileOutputStream mFos;
    private ByteBuffer[] mInputBuffers;
    private ByteBuffer[] mOutputBuffers;
    private boolean mDecodeEnd;
    public AACToPCM() {}

     private int checkPath(String path) {
        if (path == null || path.isEmpty()) {
            Log.d(TAG, "invalid path, path is empty");
            return ERROR_INPUT_INVALID;
        }
        File file = new File(path);
        if (!file.isFile()) {
            Log.d(TAG, "path is not a file, path:" + path);
            return ERROR_INPUT_INVALID;
        } else if (!file.exists()) {
            Log.d(TAG, "file not exists, path:" + path);
            return ERROR_INPUT_INVALID;
        } else {
            Log.d(TAG, "path is a file, path:" + path);
        }
        return OK;
    }
    public int decodeAACToPCM(String audioPath, String pcmPath) {
        int ret;
        if (OK != (ret = openInput(audioPath))) {
            return ret;
        }
        if (OK != (ret = openOutput(pcmPath))) {
            return ret;
        }
        if (OK != (ret = openCodec(mFormat))) {
            return ret;
        }
        mDecodeEnd = false;
        while (!mDecodeEnd) {
            if (OK != (ret = decode(mDecoder, mExtractor))) {
                Log.d(TAG, "decode failed, ret=" + ret);
                break;
            }
        }
        close();
        return ret;
    }

     private int decode(MediaCodec codec, MediaExtractor extractor) {
        Log.d(TAG, "decode");
        int inputIndex = codec.dequeueInputBuffer(TIMEOUT_USEC);
        if (inputIndex >= 0) {
            ByteBuffer inputBuffer;
            if (Build.VERSION.SDK_INT >= 21) {
                inputBuffer = codec.getInputBuffer(inputIndex);
            } else {
                inputBuffer = mInputBuffers[inputIndex];
            }
            inputBuffer.clear();
            int sampleSize = extractor.readSampleData(inputBuffer, 0);
            if (sampleSize < 0) {//read end
                codec.queueInputBuffer(inputIndex, 0, 0, 0L,
                        MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            } else {
                codec.queueInputBuffer(inputIndex, 0, sampleSize, extractor.getSampleTime(), 0);
                extractor.advance();
            }
        } 

         MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
        if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {//TIMEOUT
            Log.d(TAG, "INFO_TRY_AGAIN_LATER");//TODO how to declare this info
            return OK;
        } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            Log.d(TAG, "output format changed");
            return OK;
        } else if (outputIndex < 0) {
            Log.d(TAG, "outputIndex=" + outputIndex);
            return OK;
        } else {
            ByteBuffer outputBuffer;
            if (Build.VERSION.SDK_INT >= 21) {
                outputBuffer = codec.getOutputBuffer(outputIndex);
            } else {
                outputBuffer = mOutputBuffers[outputIndex];
            }
            byte[] buffer = new byte[bufferInfo.size];
            outputBuffer.get(buffer);
            try {
                Log.d(TAG, "output write, size="+ bufferInfo.size);
                mFos.write(buffer);
                mFos.flush();
            } catch (IOException e) {
                e.printStackTrace();
                return ERROR_OUTPUT_FAILED;
            }
            codec.releaseOutputBuffer(outputIndex, false);
            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                mDecodeEnd = true;
            }
        }
        return OK;
    }

     private int openCodec(MediaFormat format) {
        Log.d(TAG, "openCodec, format mime:" + format.getString(MediaFormat.KEY_MIME));
        try {
            mDecoder = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
        } catch (IOException e) {
            e.printStackTrace();
            return ERROR_OPEN_CODEC;
        }
        mDecoder.configure(format, null, null, 0);
        mDecoder.start();
        if (Build.VERSION.SDK_INT < 21) {
            mInputBuffers = mDecoder.getInputBuffers();
            mOutputBuffers = mDecoder.getOutputBuffers();
        }
        return OK;
    }

     private int openInput(String audioPath) {
        Log.d(TAG, "openInput audioPath:" + audioPath);
        int ret;
        if (OK != (ret = checkPath(audioPath))) {
            return ret;
        }
        mExtractor = new MediaExtractor();
        int audioTrack = -1;
        boolean hasAudio = false;
        try {
            mExtractor.setDataSource(audioPath);
            for (int i = 0; i < mExtractor.getTrackCount(); ++i) {
                MediaFormat format = mExtractor.getTrackFormat(i);
                String mime = format.getString(MediaFormat.KEY_MIME);
                Log.d(TAG, "mime=" + mime);
                if (mime.startsWith("audio/")) {
                    audioTrack = i;
                    hasAudio = true;
                    mFormat = format;
                    break;
                }
            }
            if (!hasAudio) {
                Log.d(TAG, "input contain no audio");
                return ERROR_INPUT_INVALID;
            }
            mExtractor.selectTrack(audioTrack);
        } catch (IOException e) {
            return ERROR_INPUT_INVALID;
        }
        return OK;
    }

     private int openOutput(String outputPath) {
        Log.d(TAG, "openOutput outputPath:" + outputPath);
        try {
            mFos = new FileOutputStream(outputPath);
        } catch (IOException e) {
            return ERROR_OUTPUT_FAILED;
        }
        return OK;
    }

     private void close() {
        mExtractor.release();
        mDecoder.stop();
        mDecoder.release();
        try {
            mFos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蔚鸥,一起剝皮案震驚了整個(gè)濱河市惜论,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌止喷,老刑警劉巖来涨,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異启盛,居然都是意外死亡蹦掐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門僵闯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卧抗,“玉大人,你說我怎么就攤上這事鳖粟∩珩桑” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵向图,是天一觀的道長泳秀。 經(jīng)常有香客問我,道長榄攀,這世上最難降的妖魔是什么嗜傅? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮檩赢,結(jié)果婚禮上吕嘀,老公的妹妹穿的比我還像新娘。我一直安慰自己贞瞒,他們只是感情好偶房,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著军浆,像睡著了一般棕洋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乒融,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天掰盘,我揣著相機(jī)與錄音摄悯,去河邊找鬼。 笑死庆杜,一個(gè)胖子當(dāng)著我的面吹牛射众,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晃财,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼叨橱,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了断盛?” 一聲冷哼從身側(cè)響起罗洗,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钢猛,沒想到半個(gè)月后伙菜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡命迈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年贩绕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壶愤。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淑倾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出征椒,到底是詐尸還是另有隱情娇哆,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布勃救,位于F島的核電站碍讨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蒙秒。R本人自食惡果不足惜勃黍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望税肪。 院中可真熱鬧溉躲,春花似錦、人聲如沸益兄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽净捅。三九已至,卻和暖如春辩块,著一層夾襖步出監(jiān)牢的瞬間蛔六,已是汗流浹背荆永。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留国章,地道東北人具钥。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像液兽,于是被迫代替她去往敵國和親骂删。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 原文:https://developer.android.com/reference/android/media/...
    thebestofrocky閱讀 6,049評(píng)論 0 6
  • MediaCodec的官方文檔 一四啰、Android MediaCodec簡單介紹 Android中可以使用Medi...
    黃海佳閱讀 6,162評(píng)論 1 16
  • 3宁玫、使用 MediaCodec創(chuàng)建之后,需要通過start()方法進(jìn)行開啟柑晒。MediaCodec有輸入緩沖區(qū)隊(duì)列和...
    韓瞅瞅閱讀 1,296評(píng)論 0 1
  • 午飯時(shí)我們家聊起如意匙赞,仔問如意是什么佛掖? 他爸說是玉或者其他材質(zhì)做的古時(shí)的禮器! 我張嘴啊了半天。涌庭。...
    欖娘閱讀 594評(píng)論 0 2
  • 問題: Next Greater Element I You are given two arrays (with...
    石榴蒂凡尼_21e4閱讀 253評(píng)論 0 0