MediaCodec之Encoder

1.介紹:

MediaCodec類可用于訪問Android底層的媒體編解碼器翻具,也就是浴麻,編碼器/解碼器組件乎完。它是Android底層多媒體支持基本架構(gòu)的一部分(通常與MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, 以及AudioTrack一起使用)佑菩;MediaCodec作為比較年輕的Android多媒體硬件編解碼框架离唐,在終端硬解方案中帶來了很大便利背伴。Android源碼中的CTS部分也給出了很多可以關(guān)于Media編解碼的Demo沸毁。

2.編碼測試

比如CTS測試中代碼中的EncoderTest.java


EncoderTest

做過Android CTS測試的同學(xué)可以搭建Android CTS測試環(huán)境并指定測試內(nèi)容來測試,不是本文的知識(shí)范疇傻寂。但是我建議自己手動(dòng)寫出實(shí)例Demo來測試,這樣自己才能更了解MediaCodec框架和使用MediaCodec框架息尺。
測試步驟:

1.MediaCodec支持能力

獲取MediaCodec支持的數(shù)量,根據(jù)MediaCodec的句柄獲取MediaCodec支持的編碼格式:
MediaCodecList.getCodecCount()
MediaCodecList.getCodecInfoAt(i);
比如通過以下測試代碼疾掰,就可以知道終端MediaCodec的編碼能力:

        int n = MediaCodecList.getCodecCount();
        for (int i = 0; i < n; ++i) {
            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
            String[] supportedTypes = info.getSupportedTypes();
            if(!info.isEncoder()){
                return;
            }
            boolean mime_support = false;
            for (int j = 0; j < supportedTypes.length; ++j) {
                Log.d(TAG, "codec info:" + info.getName()+" supportedTypes:" + supportedTypes[j]);
                if (supportedTypes[j].equalsIgnoreCase(mime)) {
                    mime_support = true;
                }
            }
        }
code surpport
OMX.google.amrnb.encoder audio/3gpp
OMX.google.amrwb.encoder audio/amr-wb
OMX.google.aac.encoder audio/mp4a-latm
OMX.google.flac.encoder audio/flac
OMX.google.mpeg4.encoder video/mp4v-es
OMX.google.h263.encoder video/3gpp
OMX.amlogic.video.encoder.avc video/avc
OMX.google.h264.encoder video/avc
OMX.google.vp8.encoder video/x-vnd.on2.vp8
AACEncoder audio/mp4a-latm

根據(jù)上表搂誉,可知如需編碼H264/AAC的碼流,則選擇對(duì)應(yīng)video/hevc静檬,audio/mp4a-latm的編碼器炭懊。需要注意的是針對(duì)某一個(gè)surpport type可能有多個(gè)code庫支持,比如:
支持解碼video/hevc的就有OMX.amlogic.hevc.decoder.awesome和OMX.google.h265.decoder,在編解碼時(shí)該優(yōu)先選擇哪個(gè)需要看實(shí)際調(diào)試情況拂檩,一般會(huì)優(yōu)先使用某個(gè)硬件(如amlogic,mtk等)類型的編解碼方案凛虽。

2.指定編碼格式

MediaFormat.java的配置[http://web.mit.edu/majapw/MacData/afs/sipb/project/android/docs/reference/android/media/MediaFormat.html]

        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();

        final int kAACProfiles[] = {
            2 /* OMX_AUDIO_AACObjectLC */,
            5 /* OMX_AUDIO_AACObjectHE */,
            39 /* OMX_AUDIO_AACObjectELD */
        };
        final int kSampleRates[] = { 8000, 11025, 22050, 44100, 48000 };
        final int kBitRates[] = { 64000, 128000 };

        for (int k = 0; k < kAACProfiles.length; ++k) {
            for (int i = 0; i < kSampleRates.length; ++i) {
                if (kAACProfiles[k] == 5 && kSampleRates[i] < 22050) {
                    // Is this right? HE does not support sample rates < 22050Hz?
                    continue;
                }
                for (int j = 0; j < kBitRates.length; ++j) {
                    for (int ch = 1; ch <= 2; ++ch) {
                        MediaFormat format  = new MediaFormat();
                        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
                        format.setInteger(MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
                        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, kSampleRates[i]);
                        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, ch);
                        format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
                        formats.push(format);
                    }
                }
            }
        }

3.通過指定的編碼格式創(chuàng)建MediaCodec對(duì)象

        MediaCodec codec = MediaCodec.createByCodecName(componentName);
        try {
            #最重要的一個(gè)配置項(xiàng):format和encode的flag
            codec.configure(
                    format,
                    null /* surface */,
                    null /* crypto */,
                    MediaCodec.CONFIGURE_FLAG_ENCODE);
        } catch (IllegalStateException e) {
            Log.e(TAG, "codec '" + componentName + "' failed configuration.");
        }
        #MediaCodec準(zhǔn)備就緒
        codec.start();

4.mediacode內(nèi)存區(qū)數(shù)據(jù)處理

ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
一起來看一張官方網(wǎng)站的分析圖:


編解碼器通過處理輸入數(shù)據(jù)來產(chǎn)生輸出數(shù)據(jù)。MediaCodec采用異步方式處理數(shù)據(jù)广恢,并且使用了一組輸入輸出緩存(buffer)凯旋。在簡單的層面,你請(qǐng)求或接收到一個(gè)空的輸入緩存(buffer)钉迷,向其中填充滿數(shù)據(jù)并將它傳遞給編解碼器處理至非。編解碼器處理完這些數(shù)據(jù)并將處理結(jié)果輸出至一個(gè)空的輸出緩存(buffer)中。最終糠聪,你請(qǐng)求或接收到一個(gè)填充了數(shù)據(jù)的輸出緩存(buffer),使用完其中的數(shù)據(jù)荒椭,并將其釋放回編解碼器。

int index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, 0);

以上兩個(gè)方法是先從Codec中獲取到一個(gè)空的buffer舰蟆,然后填充buffer趣惠,排到處理隊(duì)列中等待處理,buffer的頭信息包含編解碼配置信息身害,尾信息包含結(jié)束信息

        int numBytesSubmitted = 0;
        boolean doneSubmittingInput = false;
        int numBytesDequeued = 0;
        while (true) {
            int index;
            if (!doneSubmittingInput) {
                index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
                if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
                    if (numBytesSubmitted >= kNumInputBytes) {
                        codec.queueInputBuffer(
                                index,
                                0 /* offset */,
                                0 /* size */,
                                0 /* timeUs */,
                           MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        doneSubmittingInput = true;
                    } else {
                        int size = queueInputBuffer(
                                codec, codecInputBuffers, index);
                        numBytesSubmitted += size;
                    }
                }
            }
 private int queueInputBuffer(MediaCodec codec, ByteBuffer[] inputBuffers, int index) {
        ByteBuffer buffer = inputBuffers[index];
        buffer.clear();
        int size = buffer.limit();
        byte[] zeroes = new byte[size];
        buffer.put(zeroes);//測試時(shí)沒有使用有效數(shù)據(jù)味悄,實(shí)際應(yīng)該put編碼前有效的音視頻數(shù)據(jù)
        codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, 0);
        return size;
  }

通過以上方式編碼器已經(jīng)可以根據(jù)指定編碼格式編碼,再看一下如何獲取編碼后的數(shù)據(jù):

MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
codec.releaseOutputBuffer(index, false /* render */);

這三個(gè)方法是從Codec獲取到一個(gè)編解碼后的buffer

            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
            if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                codecOutputBuffers = codec.getOutputBuffers();
            } else {
                dequeueOutputBuffer(codec, codecOutputBuffers, index, info);
                numBytesDequeued += info.size;
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    if (VERBOSE) {
                        Log.d(TAG, "dequeued output EOS.");
                    }
                    break;
                }
                if (VERBOSE) {
                    Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
                }
            }
private void dequeueOutputBuffer(
            MediaCodec codec, ByteBuffer[] outputBuffers,
            int index, MediaCodec.BufferInfo info) {
        codec.releaseOutputBuffer(index, false /* render */);
}

5.使用MediaMuxer音視頻合成

在使用MediaMuxer混合的時(shí)候,主要的難點(diǎn)就是控制視頻數(shù)據(jù)和音頻數(shù)據(jù)的同步添加,和狀態(tài)的判斷;可以參考CTS集中的MediaMuxerTest.java/DecodeEditEncodeTest塌鸯,注意解碼時(shí)的解碼格式和編碼時(shí)的編碼格式需保持一致侍瑟,才能正常控制buffer大小。

3.結(jié)束語

該篇先介紹大致的編碼知識(shí)涨颜,計(jì)劃下一篇會(huì)詳細(xì)的介紹下把一個(gè)文件解碼后再重新編碼為指定格式费韭,最后合成為新的視頻文件,最近正在研究這個(gè)方向庭瑰,后續(xù)持續(xù)更新星持,感謝關(guān)注!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弹灭,一起剝皮案震驚了整個(gè)濱河市钉汗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鲤屡,老刑警劉巖损痰,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酒来,居然都是意外死亡卢未,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門堰汉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辽社,“玉大人,你說我怎么就攤上這事翘鸭〉吻Γ” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵就乓,是天一觀的道長汉匙。 經(jīng)常有香客問我,道長生蚁,這世上最難降的妖魔是什么噩翠? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮邦投,結(jié)果婚禮上伤锚,老公的妹妹穿的比我還像新娘。我一直安慰自己志衣,他們只是感情好屯援,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著念脯,像睡著了一般狞洋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上和二,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天徘铝,我揣著相機(jī)與錄音耳胎,去河邊找鬼惯吕。 笑死惕它,一個(gè)胖子當(dāng)著我的面吹牛脸候,可吹牛的內(nèi)容都是我干的敬肚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼水评,長吁一口氣:“原來是場噩夢啊……” “哼堡距!你這毒婦竟也來了甲锡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤羽戒,失蹤者是張志新(化名)和其女友劉穎缤沦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體易稠,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缸废,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驶社。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片企量。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亡电,靈堂內(nèi)的尸體忽然破棺而出届巩,到底是詐尸還是另有隱情,我是刑警寧澤份乒,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布恕汇,位于F島的核電站,受9級(jí)特大地震影響或辖,放射性物質(zhì)發(fā)生泄漏拇勃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一孝凌、第九天 我趴在偏房一處隱蔽的房頂上張望方咆。 院中可真熱鬧,春花似錦蟀架、人聲如沸瓣赂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煌集。三九已至,卻和暖如春捌省,著一層夾襖步出監(jiān)牢的瞬間苫纤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卷拘,地道東北人喊废。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像栗弟,于是被迫代替她去往敵國和親污筷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,517評(píng)論 25 707
  • 原文:https://developer.android.com/reference/android/media/...
    thebestofrocky閱讀 6,038評(píng)論 0 6
  • MediaCodec的官方文檔 一乍赫、Android MediaCodec簡單介紹 Android中可以使用Medi...
    黃海佳閱讀 6,149評(píng)論 1 16
  • My code: My test result: 這次題目不是很難瓣蛀。就類似于自己寫一個(gè)加法器。把每個(gè)數(shù)逐漸加上去雷厂,...
    Richardo92閱讀 515評(píng)論 0 1
  • 這是前幾天就想到的題目改鲫,因?yàn)橐恢痹诿ΑΦ钠饕福B冥王星的活動(dòng)文字也空窗了幾天。恩钩杰,言歸正傳纫塌。我對(duì)人性的冷淡,已經(jīng)到...
    飄雨桐V閱讀 131評(píng)論 1 2