使用 MediaPlayer 類别可以快速的開發影音播放器但是缺少了對於底層缓冲區操作的可能. 本文使用 MediaCodec 類别實作簡易的影像播放器, 並不包含聲音與影音同步與大部分的錯誤處理.

對於MediaPlayer 類别的實作請参考 影音播放器實作 - 使用 MediaPlayer 類别  一文.

<<原始碼>>

GitHub SampleMediaCodec  

<<類别介绍 - MediaExtractor>>

用來取得媒體檔案資訊 (MediaFormat) 與原始影像資料 (SampleData).

<<類别介绍 - MediaCodec>>

處理原始影像資料 (SampleData) 並輸出解碼後影像, 原始影像資料來源為可從 MediaExtractor.readSampleData() 取得.

輸入缓冲區由 getInputBuffer() 取得, 但是需要先呼叫 dequeueInputBuffer() 取得可用缓冲區索引值 (index). 將原始影像資料置入輸入缓冲區後, 呼叫 queueInputBuffer() 讓 MediaCodec 解碼.

解碼後的影像 (輸出缓冲區) 可由 getOutputBuffer() 取得並修改, 同樣的, 需要先呼叫 dequeueOutputBuffer() 取得可用缓冲區索引值, 與呼叫 releaseOutputBuffer() 通知 MediaCodec 輸出缓冲區的操作完成.

<<類别介绍 - MediaFormat>>

配置 MediaCodec 時需要提供的媒體檔案資訊, 不正確的媒體檔案資訊會使 MediaCodec 產生例外事件.

decoder 與 econder 必要欄位請参考 http://developer.android.com/reference/android/media/MediaFormat.html.

<<播放器建立與播放流程>>

主要實作在 SampleMediaCodec.java 中.

1.   MediaExtractor 設定來源影像資源 (R.raw.xxx)
2.   MediaExtractor 取得影像類型 (MediaFormat) and 並選取第一個影像軌 ("video/")
3.   建立 MediaCodec 物件並指定影像類型 (MediaFormat.KEY_MINE)
4.   配置 MediaCodec 物件為 "decoder" 並開始執行 (start())
5.   迴圈直到檔案结尾 (End-Of-Stream)
6.       呼叫 dequeueInputBuffer() 取得可用缓冲區索引值 (index)
7.       呼叫 MediaExtractor.readSampleData() 取得原始影像資料來源
8.       如果為有效影像資料, 呼叫 queueInputBuffer() 置入輸入缓冲區, 讓 MediaCodec 解碼
9.       如果已到到檔案结尾, 通知 MediaCodec BUFFER_FLAG_END_OF_STREAM
10.     呼叫 dequeueOutputBuffer() 取得可用缓冲區索引值
11.     如果為有效輸出缓冲區, 呼叫 releaseOutputBuffer() 通知 MediaCodec 輸出缓冲區的操作完成
12. 迴圈结束
13. 釋放 MediaCodec, MediaExtractor 物件

<<畫面更新率控制>>

畫面輸出至 Surface 由 releaseOutputBuffer() 控制, 更新率控制可透過時間戳記來完成.

每一畫面的時間戳記可由 MediaExtractor.getSampleTime() 取得, 在呼叫 releaseOutputBuffer() 前檢查.

 

long playStartTime = System.currentTimeMillis();
long frameDisplayTime = playStartTime;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
for (;(!eos);) {
int inputBufferIndex = decoder.dequeueInputBuffer(timeoutUs);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = decoder.getInputBuffer(inputBufferIndex);
int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0);
if (sampleSize > 0) {
frameDisplayTime = (mediaExtractor.getSampleTime() >> 10) + playStartTime;
// Video data is valid,send input buffer to MediaCodec for decode
decoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, mediaExtractor.getSampleTime(), 0);
mediaExtractor.advance();
} else {
// End-Of-Stream (EOS)
decoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
eos = true;
}
}

int outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, timeoutUs);
if (outputBufferIndex >= 0) {

// Frame rate control
while(frameDisplayTime > System.currentTimeMillis()) {

sleep(10);
}

// outputBuffer is ready to be processed or rendered.
decoder.releaseOutputBuffer(outputBufferIndex, true /*true:render to surface*/);

// Count FPS
frameCount++;
deltaTime = System.currentTimeMillis() - counterTime;
if (deltaTime > 1000) {
Log.v("SampleMediaCodec", (((float)frameCount / (float)deltaTime) * 1000) + " fps");
counterTime = System.currentTimeMillis();
frameCount = 0;
}
}
}
(GitHub Commit)

(完)

 

文章標籤
創作者介紹
創作者 版大 的頭像
版大

嵌入式系統

版大 發表在 痞客邦 留言(0) 人氣()