Android OpenGL ES 十二.MediaCodec錄制視頻之MediaCodec(轉(zhuǎn)載整理)

實(shí)現(xiàn)錄制音視頻也有兩種方案,分別是MediaRecorder和MediaCodec

什么是MediaRecorder

MediaRecorder是安卓提供的一個用于音視頻采集的類

MediaRecorder的優(yōu)缺點(diǎn)

優(yōu)點(diǎn)
可以實(shí)現(xiàn)直接錄制視頻 使用方便,得到就是編碼和封裝好的音視頻文件暴浦,可以直接使用
缺點(diǎn)
無法獲取原始數(shù)據(jù)乎赴,不能對每一幀數(shù)據(jù)進(jìn)行處理,無法支持我們程序中自己需要的一些邏輯纫雁,比方需要錄制灰度視頻纲辽。

由于不滿足我的需求缨睡,所以這里就不再對MediaRecorder講解了和二,那接下來我們來說說MediaCodec


什么是MediaCodec

MediaCodec
類是Android平臺提供的用于訪問低層多媒體硬件編/解碼器接口徘铝,它是Android低層多媒體架構(gòu)的一部分,通常與MediaExtractor惯吕、MediaMuxer惕它、AudioTrack結(jié)合使用,能夠編解碼諸如H.264废登、H.265淹魄、AAC、3gp等常見的音視頻格式堡距。一般來說H.264的AVC視頻編碼和AAC的音頻編碼是最常見的甲锡。

MediaCodec工作原理

MediaCodec的工作原理就是處理輸入數(shù)據(jù)以產(chǎn)生輸出數(shù)據(jù)兆蕉。具體來說,MediaCodec在編解碼的過程中使用了一組輸入/輸出緩存區(qū)來同步或異步處理數(shù)據(jù):首先缤沦,客戶端向獲取到的編解碼器輸入緩存區(qū)寫入要編解碼的數(shù)據(jù)并將其提交給編解碼器虎韵,待編解碼器處理完畢后將其轉(zhuǎn)存到編碼器的輸出緩存區(qū),同時收回客戶端對輸入緩存區(qū)的所有權(quán)缸废;然后包蓝,客戶端從獲取到編解碼輸出緩存區(qū)讀取編碼好的數(shù)據(jù)進(jìn)行處理,待處理完畢后編解碼器收回客戶端對輸出緩存區(qū)的所有權(quán)企量。不斷重復(fù)整個過程测萎,直至編碼器停止工作或者異常退出。

MediaCodec生命周期中的狀態(tài)

mediacodec分為三種狀態(tài)届巩,Stopped, ExecutingReleased硅瞧。一張圖表示(這張圖是從網(wǎng)上直接下載下來使用的):

image

Stopped狀態(tài)包含三個子狀態(tài):Uninitialized, ConfiguredError,Executing同樣包含三個狀態(tài):Flushed, RunningEnd-of-Stream恕汇。

在mediacodec的使用過程中必須遵守圖里標(biāo)出的流程腕唧,否則會發(fā)生錯誤。
比方拇勃,沒有調(diào)用start()方法四苇,就開始sotp()會報(bào)錯。
以解碼器為例方咆,講解一下使用流程月腋。當(dāng)使用工廠方法創(chuàng)建mediacodec并且指定為解碼后,進(jìn)入U(xiǎn)ninitialized狀態(tài)瓣赂,調(diào)用configure方法后榆骚,進(jìn)入Configured狀態(tài),然后調(diào)用start方法進(jìn)入Executing狀態(tài)煌集。

進(jìn)入Executing狀態(tài)后妓肢,首先到達(dá)Flush狀態(tài),此時mediacodec會持有所有的數(shù)據(jù)苫纤,當(dāng)?shù)谝粋€inputbufffer從隊(duì)列中取出時碉钠,立即進(jìn)入Running狀態(tài)局扶,這個時間很短怠苔。然后就可以調(diào)用dequeueInputBuffer和getInputBuffer來獲取用戶可用的緩沖區(qū)少漆,用戶填滿數(shù)據(jù)后調(diào)用queueinputbuffer方法返回給解碼器戴卜,解碼器大部分時間都會工作在Running狀態(tài)。當(dāng)想inputbufferqueue中輸入一幀標(biāo)記EndOfStream的時候悍手,進(jìn)入End-of-Stream狀態(tài)证薇,在這種狀態(tài)下桥氏,解碼器不再接受任何新的數(shù)據(jù)輸入乍赫,緩沖區(qū)中的數(shù)據(jù)和標(biāo)記EndOfStream最終會執(zhí)行完畢瓣蛀。在任何時候都可以調(diào)用flush方法回到Flush狀態(tài)陆蟆。

調(diào)用stop方法會使mediacode進(jìn)入 Uninitialized狀態(tài),這時候可以執(zhí)行configure方法來進(jìn)入下一循環(huán)惋增。當(dāng)mediacodec使用完畢后必須調(diào)用release方法來釋放所有的資源叠殷。

在某些情況下,例如取出緩沖區(qū)索引時器腋,mediacodec會發(fā)生錯誤進(jìn)入Error狀態(tài)溪猿,此時調(diào)用reset方法來是mediacodec重新處于Uninitialized狀態(tài),或者調(diào)用release來結(jié)束解碼纫塌。

MediaCodec API 說明

MediaCodec 主要的API做一個介紹:

  • MediaCodec創(chuàng)建:
    • createDecoderByType/createEncoderByType:根據(jù)特定MIME類型(如"video/avc")創(chuàng)建codec。
    • createByCodecName:知道組件的確切名稱(如OMX.google.mp3.decoder)的時候讲弄,根據(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ì)列状飞,返回的是一個ByteBuffer數(shù)組。
    • getOutputBuffers:獲取編解碼之后的數(shù)據(jù)輸出流隊(duì)列书斜,返回的是一個ByteBuffer數(shù)組诬辈。
  • flush:清空的輸入和輸出端口。
  • stop:終止decode/encode會話
  • release:釋放編解碼器實(shí)例使用的資源荐吉。

MediaCodec創(chuàng)建編/解碼器

MediaCodec主要提供了createEncoderByType(String type)焙糟、createDecoderByType(String type)兩個方法來創(chuàng)建編解碼器,它們均需要傳入一個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參數(shù)配置

private void initVideoCodec(int width, int height) {
    try {
        // https://developer.android.google.cn/reference/android/media/MediaCodec mediacodec官方介紹
        // 比方MediaCodec的幾種狀態(tài)
        // avc即h264編碼
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,width,height);
        // 設(shè)置顏色格式
        // 本地原始視頻格式(native raw video format):這種格式通過COLOR_FormatSurface標(biāo)記穿撮,并可以與輸入或輸出Surface一起使用
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        // 設(shè)置碼率,通常碼率越高痪欲,視頻越清晰悦穿,但是對應(yīng)的視頻也越大
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE,width * height * 4);

        // 設(shè)置幀率 三星s21手機(jī)camera預(yù)覽時,支持的幀率為10-30
        // 通常這個值越高勤揩,視頻會顯得越流暢咧党,一般默認(rèn)設(shè)置成30,你最低可以設(shè)置成24陨亡,不要低于這個值傍衡,低于24會明顯卡頓深员,微信為28
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);
        // 設(shè)置 I 幀間隔的時間
        // 通常的方案是設(shè)置為 1s,對于圖片電影等等特殊情況蛙埂,這里可以設(shè)置為 0倦畅,表示希望每一幀都是 KeyFrame
        // IFRAME_INTERVAL是指的幀間隔,這是個很有意思的值绣的,它指的是叠赐,關(guān)鍵幀的間隔時間。通常情況下屡江,你設(shè)置成多少問題都不大芭概。
        // 比如你設(shè)置成10,那就是10秒一個關(guān)鍵幀惩嘉。但是罢洲,如果你有需求要做視頻的預(yù)覽,那你最好設(shè)置成1
        // 因?yàn)槿绻阍O(shè)置成10文黎,那你會發(fā)現(xiàn)惹苗,10秒內(nèi)的預(yù)覽都是一個截圖
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,5);

        // 創(chuàng)建編碼器
        // https://www.codercto.com/a/41316.html MediaCodec 退坑指南
        mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
        mMediaCodec.configure(mediaFormat,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);

        // 相機(jī)的像素?cái)?shù)據(jù)繪制到該 surface 上面
        mSurface = mMediaCodec.createInputSurface();

        videoEncoderThread = new VideoEncoderThread(videoRecorderReference);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

可以用微信錄制一個短視頻,然后看下參數(shù):



比較有參照意思的參數(shù):

//錄制了4.917秒
Duration/String                  : 4 秒 917 毫秒
//每秒一幀
Format_Settings_RefFrames/String : 4 幀
//視頻寬高
Width/String                     : 288 像素
Height/String                    : 640 像素
//1601*1024除以288*640=8.9耸峭,KEY_BIT_RATE和寬高比接近9
OverallBitRate/String            : 1 601 kb/s
//格式avc桩蓉,和MIMETYPE_VIDEO_AVC對應(yīng)
Format/String                    : AVC
"FileExtension"                  : "mp4",
//avc也是mpeg-4
"Format"                         : "MPEG-4",
//幀率
FrameRate/String                 : 28.067 FPS
//yuv420,在camera預(yù)覽時設(shè)定的nv21對應(yīng)
ColorSpace                       : YUV
ChromaSubsampling/String         : 4:2:0
//聲音的格式劳闹,再補(bǔ)充
Format/String                    : AAC LC
Channel(s)/String                : 1 聲道
SamplingRate/String              : 44.1 kHz
FrameRate/String                 : 43.066 FPS (1024 SPF)

configure

    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:指定一個crypto對象,用于對媒體數(shù)據(jù)進(jìn)行安全解密位他。對于非安全的編解碼器氛濒,傳null。
  • int flags:當(dāng)組件是編碼器時鹅髓,flags指定為常量CONFIGURE_FLAG_ENCODE舞竿。

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

  • 媒體數(shù)據(jù)的格式指定為key/value對骗奖。key是字符串。值可以integer、long执桌、float鄙皇、String或ByteBuffer。
  • 特性元數(shù)據(jù)被指定為string/boolean對仰挣。

開始錄制

我們通過對MediaCodec參數(shù)進(jìn)行配置伴逸,然后得到一個MediaCodec

 mMediaCodec.start();

結(jié)束錄制

這里需要注意一下釋放的順序,一定得是按照下面的順序進(jìn)行資源釋放的

 mMediaCodec.signalEndOfInputStream();
 mMediaCodec.stop();
 mMediaCodec.release();

● 下面看一段源碼:當(dāng)編解碼器start后膘壶,會進(jìn)入一個for(;;)循環(huán)错蝴,該循環(huán)是一個死循環(huán),以實(shí)現(xiàn)不斷地去從編解碼器的輸入緩存池中獲取包含數(shù)據(jù)的一個緩存區(qū)颓芭,然后再從輸出緩存池中獲取編解碼好的輸出數(shù)據(jù)顷锰。

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

上層數(shù)據(jù)獲取

  do {
      if (mMediaCodec != null) {
          int outBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, -1);
          if (outBufferIndex >= 0) {
              ByteBuffer bb = mMediaCodec.getOutputBuffer(outBufferIndex);
              //這里獲取到原始數(shù)據(jù),然后根據(jù)相關(guān)需求可對數(shù)據(jù)進(jìn)行處理
          }
          if (outBufferIndex >= 0) {
              mMediaCodec.releaseOutputBuffer(outBufferIndex, false);
          }
      }
  }
  while (isStarted);

具體使用代碼如:https://github.com/445979241/opengles

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亡问,一起剝皮案震驚了整個濱河市馍惹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌玛界,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件悼吱,死亡現(xiàn)場離奇詭異慎框,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)后添,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門笨枯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人遇西,你說我怎么就攤上這事馅精。” “怎么了粱檀?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵洲敢,是天一觀的道長。 經(jīng)常有香客問我茄蚯,道長压彭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任渗常,我火速辦了婚禮壮不,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘皱碘。我一直安慰自己询一,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著健蕊,像睡著了一般菱阵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绊诲,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天送粱,我揣著相機(jī)與錄音,去河邊找鬼掂之。 笑死抗俄,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的世舰。 我是一名探鬼主播动雹,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跟压!你這毒婦竟也來了胰蝠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤震蒋,失蹤者是張志新(化名)和其女友劉穎茸塞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體查剖,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钾虐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了笋庄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片效扫。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖直砂,靈堂內(nèi)的尸體忽然破棺而出菌仁,到底是詐尸還是另有隱情,我是刑警寧澤静暂,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布济丘,位于F島的核電站,受9級特大地震影響籍嘹,放射性物質(zhì)發(fā)生泄漏闪盔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一辱士、第九天 我趴在偏房一處隱蔽的房頂上張望泪掀。 院中可真熱鬧,春花似錦颂碘、人聲如沸异赫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塔拳。三九已至鼠证,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間靠抑,已是汗流浹背量九。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颂碧,地道東北人荠列。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像载城,于是被迫代替她去往敵國和親肌似。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359

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