編解碼器簡單概念
MediaCodec用來使用底層多媒體編解碼器。
寬泛講編解碼器處理輸入數據生成輸出數據,它的處理是異步的並且使用一系列輸入輸出buffer。簡單層面來說,你請求或者獲取空的輸入buffer,裝滿輸入之後發送到編解碼器處理,在處理結束後編解碼器再將數據轉換到一個輸出的空buffer。最後你請求或者獲取裝滿數據的輸出buffer,消耗完其內容數據之後將該buffer釋放回編解碼器。
1.支持的數據類型
編解碼器支持三種數據:壓縮的數據、原始的音頻數據、原始的視頻數據。所有的三種數據都可以通過使用ByteBuffers來處理,但是對於原始的視頻數據可以使用Surface來提高編解碼器的性能同時可以使用ImageReader獲取視頻的單個幀,即截圖。
對於原始視頻數據,當你使用ByteBuffers來處理時,其buffer的排列是依據他們的顏色格式。視頻編解碼器支持三種顏色格式:
native raw video format:通過COLOR_FormatSurface來標記,可以被用作輸入或者輸出Surface。
flexible YUV buffers(比如 COLOR_FormatYUV420Flexible):可以用作輸入輸出Surface以及ByteBuffers模式。
other, specific formats:這些只能使用ByteBuffers模式處理。
2.編解碼器的狀態
編解碼器的理論狀態是三種:停止、執行、釋放。
停止狀態由包含三個子狀態:錯誤、爲初始化、配置。
執行狀態包含三個子狀態:刷新、運行、結束流。
當使用工廠方法獲得編解碼器對象時,起處於未初始化狀態。首先需要新建MMediaFormat對象,使用configure方法進行配置,此時處於配置狀態。接着調用start方法此時進入執行狀態。在這個狀態可以通過緩衝buffer操作過程數據。
執行狀態包含三個子狀態。當調用start進入刷新子狀態,該狀態持有所有buffer。一旦第一個輸入buffer出隊列編解碼器就進入運行子狀態,這個狀態最耗時。當你入隊列一個輸入buffer帶有ENDOFSTREAM標記時,編解碼器進入結束流子狀態,在這個狀態不在接收輸入buffer,但依然生成輸出buffer。
調用stop方法進入爲初始化狀態,一旦編解碼器使用完成你必須調用release進行釋放。
編解碼器的使用
以下是如何通過代碼使用編解碼器。編解碼器有兩種使用方式:異步使用、同步使用。
1.異步使用
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();
2.同步使用
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();