1. 程式人生 > >「音視訊直播技術」Android下H264解碼

「音視訊直播技術」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. 在迴圈中不停的向解碼器喂資料,並從解碼器中取出解碼後的資料。

參考