Android MediaCodec 使用說明

最近公司要求提供一個支持 Android 硬件轉(zhuǎn)碼的底層庫波势,所以自己從頭去看了 MediaCodec 相關(guān)的知識翎朱,費了老大的勁終于完成了。目前的硬件轉(zhuǎn)碼使用 MediaCodec 進行解碼和編碼尺铣,然后使用 FFmpeg 進行文件封裝(為了支持文件分塊)拴曲。這篇文章主要介紹一些 MediaCodec 的基礎(chǔ)知識和使用方式,后面會寫如何利用 FFmpeg 封裝 MediaCodec 編碼后的數(shù)據(jù)以及 FFmpeg 分塊封裝的文章迄埃。

MediaCodec 可以用來獲得安卓底層的多媒體編碼疗韵,可以用來編碼和解碼,它是安卓 low-level 多媒體基礎(chǔ)框架的重要組成部分侄非。

media_codec.png

MediaCodec 的作用是處理輸入的數(shù)據(jù)生成輸出數(shù)據(jù)蕉汪。首先生成一個輸入數(shù)據(jù)緩沖區(qū),將數(shù)據(jù)填入緩沖區(qū)提供給 codec逞怨,codec 會采用異步的方式處理這些輸入的數(shù)據(jù)者疤,然后將填滿輸出緩沖區(qū)提供給消費者,消費者消費完后將緩沖區(qū)返還給 codec叠赦。

接收的數(shù)據(jù)

MediaCodec 接受三種數(shù)據(jù)格式:壓縮數(shù)據(jù)驹马,原始音頻數(shù)據(jù)和原始視頻數(shù)據(jù)。

這三種數(shù)據(jù)都可以使用 ByteBuffer 作為載體傳輸給 MediaCodec 來處理除秀。但是當(dāng)使用原始視頻數(shù)據(jù)時糯累,最好采用 Surface 作為輸入源來替代 ByteBuffer,這樣效率更高册踩,因為 Surface 使用的更底層的視頻數(shù)據(jù)泳姐,不會映射或復(fù)制到 ByteBuffer 緩沖區(qū)。

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

壓縮數(shù)據(jù)可以作為解碼器的輸入數(shù)據(jù)或者編碼器的輸出數(shù)據(jù)暂吉,需要指定數(shù)據(jù)格式胖秒,這樣 codec 才能知道如何處理這些壓縮數(shù)據(jù)缎患。

對于視頻數(shù)據(jù)而言,通常是一幀數(shù)據(jù)阎肝;音頻數(shù)據(jù)挤渔,一般是單個處理單元。

原始音頻數(shù)據(jù)

原始音頻數(shù)據(jù)即編碼器的輸入數(shù)據(jù)风题,解碼器的輸出數(shù)據(jù)判导。包含整個 PCM 音頻數(shù)據(jù)幀,這是通道順序中每個通道的一個樣本俯邓。每個采樣都是以本地字節(jié)順序的 16 位有符號整數(shù)骡楼。

原始視頻數(shù)據(jù)

原始視頻數(shù)據(jù)也是編碼器的輸入數(shù)據(jù),解碼器的輸出數(shù)據(jù)稽鞭。即yuv數(shù)據(jù),MediaCodec主要支持的格式為:

  • native raw video format : COLOR_FormatSurface引镊,用來處理 Surface 模式的數(shù)據(jù)輸入輸出
  • flexible YUV buffers : 例如 COLOR_FormatYUV420Flexible
  • specific formats: 支持ByteBuffer模式朦蕴,有一些廠家會定制

使用流程

編解碼器處理輸入數(shù)據(jù)并產(chǎn)生輸出數(shù)據(jù),MediaCodec 使用輸入輸出緩存弟头,異步處理數(shù)據(jù)吩抓。

  1. 請求一個空的輸入 input buffer
  2. 填入數(shù)據(jù)、并將其交給 MediaCodec
  3. MediaCodec 處理數(shù)據(jù)后赴恨,將處理后的數(shù)據(jù)放在一個空的 output buffer
  4. 獲取填充數(shù)據(jù)了的 output buffer疹娶,得到其中的數(shù)據(jù),然后將其返還給 MediaCodec

首先了解下 MediaCodec 中的生命周期

同步狀態(tài).png

MediaCodec 大體上分為三種狀態(tài):Stopped伦连、Executing 和 Released雨饺。

創(chuàng)建 MediaCodec

首先是如何創(chuàng)建 MediaCodec,在知道 MimeType 的情況下惑淳,可以通過 createDecoderByType, createEncoderByType, createByCodecName 方法來獲取實例额港。

如果不知道 MimeType,可以使用 MediaCodecList.findDecoderForFormat歧焦、 MediaCodecList.findEncoderForFormat 來獲取移斩。

創(chuàng)建成功之后,MediaCodec 進入 Uninitialized 狀態(tài)绢馍。

Configuration

在創(chuàng)建好 MediaCodec 之后向瓷,需要對其進行設(shè)置,這樣 MediaCodec 的狀態(tài)就可以由 uninitialized 變成 configured

public void configure(
            @Nullable MediaFormat format,
            @Nullable Surface surface, @Nullable MediaCrypto crypto,
            @ConfigureFlag int flags) {
        configure(format, surface, crypto, null, flags);
}
public void configure(
            @Nullable MediaFormat format, @Nullable Surface surface,
            @ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
        configure(format, surface, null,
                descrambler != null ? descrambler.getBinder() : null, flags);
}

這里最重要的參數(shù)是 MediaFormat, 如果某些參數(shù)沒有設(shè)置的話舰涌,會導(dǎo)致 MediaCodec 拋出 IllegalStateException.

Video 所必須的 Format Setting

Encoder Decoder
KEY_MIME ?? ??
KEY_BIT_RATE ?? ?
KEY_WIDTH ?? ??
KEY_HEIGHT ?? ??
KEY_COLOR_FORMAT ?? ?
KYE_FRAME_RATE ?? ?
KEY_I_FRAME_INTERVAL ?? ?

Audio 所必須的 Format Setting

Encoder Decoder
KEY_MIME ?? ??
KEY_BIT_RATE ?? ?
KEY_CHANNEL_COUNT ?? ??
KEY_SAMPLE_RATE ?? ??

輸入數(shù)據(jù)與獲取編解碼后的數(shù)據(jù)

從 5.0 開始猖任,首選方法是在調(diào)用 configure 方法之前通過設(shè)置回調(diào)來異步處理數(shù)據(jù)。所以這里就直接介紹異步模式下如何輸入需要編解碼的數(shù)據(jù)舵稠,以及如何獲取編解碼后的數(shù)據(jù)

異步模式
異步狀態(tài).png

官方示例代碼:

MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat; // member variable
// 設(shè)置回調(diào)方法
codec.setCallback(new MediaCodec.Callback() {
   /**
    * mediacodec 存在可用輸入緩沖
    */
   @Override
   void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
     // 可通過 MediaExtractor 讀取 video 或 audio 數(shù)據(jù)超升,然后填充數(shù)據(jù)到緩沖區(qū)
     …
     codec.queueInputBuffer(inputBufferId, …);
   }

   /**
    * 輸出緩沖填充完數(shù)據(jù)后
    */
   @Override
   void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
     // 獲取輸出緩沖(其中包含編解碼后數(shù)據(jù))
     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
     MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); 
     // 處理編解碼后的數(shù)據(jù)
     …
     // 返還輸出緩沖給 codec
     codec.releaseOutputBuffer(outputBufferId, …);
   }

    /**
     * 輸出格式發(fā)生變化
     */
   @Override
   void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
     // Subsequent data will conform to new format.
     // Can ignore if using getOutputFormat(outputBufferId)
     mOutputFormat = format; 
   }

    /**
     * 發(fā)生錯誤
     */
   @Override
   void onError(…) {
     …
   }
 });
 codec.configure(format, …);
 mOutputFormat = codec.getOutputFormat(); 
 codec.start();
 // wait for processing to complete
 codec.stop();
 codec.release();

看一個幾個重要的方法

ByteBuffer getInputBuffer(int index)

該方法返回一個已清空入宦、可寫入的 input 緩沖區(qū),通過調(diào)用 ByteBuffer.put(data) 方法將 data 中的數(shù)據(jù)放到緩沖區(qū)室琢,然后調(diào)用

/**
 * @param index              - 緩沖區(qū)索引
 * @param offset             - 緩沖區(qū)提交數(shù)據(jù)的起始位置
 * @param size               - 提交的數(shù)據(jù)長度
 * @param presentationTimeUs - 時間戳
 * @param flags              - BUFFER_FLAG_CODEC_CONFIG:配置信息乾闰;               
 *                             BUFFER_FLAG_END_OF_STREAM:結(jié)束標(biāo)志; 
 *                             BUFFER_FLAG_KEY_FRAME:關(guān)鍵幀
 */
void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags)

就可以將緩沖區(qū)返回給 codec盈滴。

ByteBuffer getOutputBuffer(int index)

該方法返回一個 output 緩沖區(qū)涯肩,包含解碼或編碼后的數(shù)據(jù)。

void releaseOutputBuffer(int index, boolean render)
void releaseOutputBuffer(int index, long renderTimeStampNs)

這兩個方法都會釋放 index 所指向的緩沖區(qū)巢钓。

處理完需要編/解碼的數(shù)據(jù)之后病苗,調(diào)用 stop & release 方法釋放 MediaCodec。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末症汹,一起剝皮案震驚了整個濱河市硫朦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌背镇,老刑警劉巖咬展,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瞒斩,居然都是意外死亡破婆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門胸囱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祷舀,“玉大人,你說我怎么就攤上這事烹笔∩殉叮” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵箕宙,是天一觀的道長嚎朽。 經(jīng)常有香客問我,道長柬帕,這世上最難降的妖魔是什么哟忍? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮陷寝,結(jié)果婚禮上锅很,老公的妹妹穿的比我還像新娘。我一直安慰自己凤跑,他們只是感情好爆安,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仔引,像睡著了一般扔仓。 火紅的嫁衣襯著肌膚如雪褐奥。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天翘簇,我揣著相機與錄音撬码,去河邊找鬼。 笑死版保,一個胖子當(dāng)著我的面吹牛呜笑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彻犁,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼叫胁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汞幢?” 一聲冷哼從身側(cè)響起驼鹅,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎森篷,沒想到半個月后谤民,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡疾宏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了触创。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坎藐。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哼绑,靈堂內(nèi)的尸體忽然破棺而出岩馍,到底是詐尸還是另有隱情,我是刑警寧澤抖韩,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布蛀恩,位于F島的核電站,受9級特大地震影響茂浮,放射性物質(zhì)發(fā)生泄漏双谆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一席揽、第九天 我趴在偏房一處隱蔽的房頂上張望顽馋。 院中可真熱鬧,春花似錦幌羞、人聲如沸寸谜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熊痴。三九已至他爸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間果善,已是汗流浹背诊笤。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留岭埠,地道東北人盏混。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像惜论,于是被迫代替她去往敵國和親许赃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

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