Android音視頻-音頻編解碼

前面我們知道了採集音頻,播放音頻,保存音頻數據,我們知道PCM純音頻數據沒有經過壓縮編碼處理的數據是很大的。很有必要了解編解碼來處理這個問題。

簡介

音視頻的編碼方式分爲兩種。

  • 硬編碼:
    用設備GPU去實現編解碼,這樣可以減輕CPU的壓力。

  • 軟編碼:
    讓CPU來進行編解碼,在c層代碼來進行編解碼,因爲c/c++有很多好的編解碼庫。

  • 軟硬編碼對比:
    硬編的好處主要在於速度快,而且系統自帶不需要引入外部的庫,但是特性支持有限,而且硬編的壓縮率一般偏低,而對於軟編碼來說,雖然速度較慢,但是壓縮率比較高,而且支持的H264特性也會比硬編碼多很多,相對來說比較可控。就可用性而言,在4.4+的系統上,MediaCodec的可用性是能夠基本保證的,但是不同等級的機器的編碼器能力會有不少差別,建議可以根據機器的配置,選擇不同的編碼器配置。

在Android 4.1之前沒有提供硬編解碼的API,所以基本都是採用開源的那些庫,比如著名的FFMpeg實現軟編解碼。但是通常情況下,同一平臺同一硬件環境,硬編碼的速度快於軟件編碼,軟編碼使用CPU來進行計算,會消耗一些app的運算效率。在Android4.1出來了一個新的API:MediaCodec可以支持硬編解碼。MediaCodec可以支持對音頻和視頻的編解碼,我們就要學會使用它來進行音頻的編解碼操作。

MediaCodec

簡介

對於API的介紹我們的第一反應,看官網
還發現了一個哥們翻譯的中文版本
總結來說,MediaCodec它是官方提供的硬編碼API,首先對他進行參數的配置,然後把數據扔給它,它在內部完成編碼或者解碼的工作,然後把處理好的數據輸出給我們。
貼一張官網的處理圖片:


這裏寫圖片描述

解析:
MediaCodec採用了兩個緩衝區隊列,異步處理數據。

  • 客戶端從input緩衝區隊列申請empty buffer(調用dequeueInputBuffer方法)
  • 客戶端將要編解碼的數據拷貝到empty buffer,然後放入input緩衝區隊列(調用queueInputBuffer方法)
  • MediaCodec內部從input緩衝區隊列取出一幀數據進行編解碼處理
  • 處理結束後將原始數據buffer置爲empty再放回input緩衝區隊列,將編解碼的數據放入到output緩衝區隊列
  • 客戶端從output緩衝區隊列申請編解碼的buffer(調用dequeueoutputBuffer方法)
  • 客戶端對編解碼後的buffer數據進行渲染或者播放
  • 客戶端處理完上面的步驟後再將該buffer放回到output緩衝區隊列(調用releaseOutputBuffer)

MediaCodec使用流程

  • 創建MediaCodec(調用createDecoderByType方法)
  • 配置MediaCodec(調用configure方法)
  • 開始編解碼(調用start方法)
  • 循環數據輸入輸出
    • dequeueInputBuffer
    • queueInputBuffer
    • dequeueOutputBuffer
    • releaseOutputBuffer
  • 停止編解碼(調用stop方法)
  • 釋放資源(調用release方法)

MediaCodec使用代碼

摘自官網代碼示例

  • 同步使用
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();
  • 異步使用
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();
  • Android 5.0以下使用
MediaCodec codec = MediaCodec.createByCodecName(name);
 codec.configure(format, …);
 codec.start();
//API的區別在這裏
 ByteBuffer[] inputBuffers = codec.getInputBuffers();
 ByteBuffer[] outputBuffers = codec.getOutputBuffers();
 for (;;) {
   int inputBufferId = codec.dequeueInputBuffer(…);
   if (inputBufferId >= 0) {
     // fill inputBuffers[inputBufferId] with valid data
     …
     codec.queueInputBuffer(inputBufferId, …);
   }
   int outputBufferId = codec.dequeueOutputBuffer(…);
   if (outputBufferId >= 0) {
     // outputBuffers[outputBufferId] is ready to be processed or rendered.
     …
     codec.releaseOutputBuffer(outputBufferId, …);
   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
     outputBuffers = codec.getOutputBuffers();
   } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
     // Subsequent data will conform to new format.
     MediaFormat format = codec.getOutputFormat();
   }
 }
 codec.stop();
 codec.release();

我們實現一個實時的音頻錄製播放的demo,查看

參考資料:
可以查看MediaCodec的使用小demo程序
demo整體思路

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章