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