「音影片直播技術」Android下H264解碼

前言

上一篇文章中我介紹了如何使用MediaCodec編碼,今天我們再來分析一下如何通過 MediaCodec 進行解碼。

為了講解的方便,我們引入了 MediaExtractor 類。它用於打開MP4等媒體文件,並從中抽取出音影片數據。

打開媒體文件

MediaExtractor,音影片數據分離器。每種媒體文件如MP4, FLV, MOOV等都是一種容器,裡邊存放了音頻數據和影片數據。MediaExtractor的作用就是根據容器協議打開容器,並讀取其中的音頻或影片數據。

在容器文件(MP4)中,音頻數據與影片數據是以軌道(�track)的概念存放的。取的是兩條軌道永遠不相交的意思,也就指明音頻數據與影片數據是分別存儲的。

我們使用MediaExtractor類打開媒體文件,它的使用非常簡單,步驟如下:

1. 創建一個MediaExtractor對象。 2. 將媒體文件設置給MediaExtractor對象。 3. 選定要處理的軌道。

我們先來看一下示例程式碼吧

......    MediaExtractor extractor = null;  try {      extractor = new MediaExtractor();      extractor.setDataSource(sourceFile.toString());      int trackIndex = selectTrack(extractor); //根據關鍵字獲取影片Track      if (trackIndex < 0) {          throw new RuntimeException("No video track found in " + mSourceFile);      }      extractor.selectTrack(trackIndex); //選定影片軌        ......    } finally {      if (extractor != null) {          extractor.release();      }  }    ......

通過上面的步驟我們就選好了要處理的影片軌。下面我們來創建解碼器。

創建解碼器

在創建解碼器之前,需要先通過 MediaExtractor 獲取到要處理的影片軌的媒體格式(因為媒體格式中包括了 CSD-0/CSD-1 資訊,這個資訊對於解碼非常重要)。然後通過媒體格式的 mime 資訊創建解碼器。

CSD-0/CSD-1 指的就是 H264中的 PPS 和 SPS。

另外,在配置解碼器時,可以給它傳入一個 Surface,這樣解碼器解碼後,就可以直接將影像幀渲染到 Surface里了。程式碼如下:

......      MediaFormat format = extractor.getTrackFormat(trackIndex);    // Create a MediaCodec decoder, and configure it with the MediaFormat from the  // extractor.  It's very important to use the format from the extractor because  // it contains a copy of the CSD-0/CSD-1 codec-specific data chunks.  String mime = format.getString(MediaFormat.KEY_MIME);  decoder = MediaCodec.createDecoderByType(mime);  decoder.configure(format, mOutputSurface, null, 0);  decoder.start();    ......

解碼

解碼按如下步驟進行:

1. 從InputBuffer隊列中取出一個空閑的InputBuffer。 2. 通過 MediaExtractor 對象從影片軌道中取出H264數據存到InputBuffer中。 3. 將InputBuffer放到InputBuffer隊列中。此時需要解碼的數據已經送入了解碼器。 4. 從OutputBuffer隊列中取OutputBuffer,如果能取到說明已經有解碼好的數據了。 5. 最後調用releaseOutputBuffer釋放OutputBuffer。此時OutputBuffer中的數據將被轉成紋理進行渲染。

示例程式碼如下:

......    while (!outputDone){        ......        // Feed more data to the decoder.      if (!inputDone) {          int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);          if (inputBufIndex >= 0) {                ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];              int chunkSize = extractor.readSampleData(inputBuf, 0);                ......                long presentationTimeUs = extractor.getSampleTime();              decoder.queueInputBuffer(inputBufIndex, 0, chunkSize,                      presentationTimeUs, 0 /*flags*/);                extractor.advance(); //處理下一幀                ......            }      }        if (!outputDone) {          int decoderStatus = decoder.dequeueOutputBuffer(                          mBufferInfo, TIMEOUT_USEC);            ......            if(decoderStatus > 0) {              // As soon as we call releaseOutputBuffer, the buffer will be forwarded              // to SurfaceTexture to convert to a texture.              decoder.releaseOutputBuffer(decoderStatus, doRender); //解碼數據          }          ......      }  }  ......

小結

通過上面的介紹我們知道通過MediaCodec進行解碼也非常的簡單,主要是三大步:

  1. 創建影片解碼器。
  2. 獲取數據。今天我們是通過 MediaExtrator從文件中獲取的。如果是直播系統,則是直接從網上獲取數據。
  3. 在循環中不停的向解碼器喂數據,並從解碼器中取出解碼後的數據。

參考

  1. H264編碼