前面我們知道了採集音頻,播放音頻,保存音頻數據,我們知道PCM純音頻數據沒有經過壓縮編碼處理的數據是很大的。很有必要了解編解碼來處理這個問題。
簡介
音視頻的編碼方式分爲兩種。
硬編碼:
用設備GPU去實現編解碼,這樣可以減輕CPU的壓力。軟編碼:
讓CPU來進行編解碼,在c層代碼來進行編解碼,因爲c/c++有很多好的編解碼庫。- 軟硬編碼對比:
硬編的好處主要在於速度快,而且系統自帶不需要引入外部的庫,但是特性支持有限,而且硬編的壓縮率一般偏低,而對於軟編碼來說,雖然速度較慢,但是壓縮率比較高,而且支持的H264特性也會比硬編碼多很多,相對來說比較可控。就可用性而言,在4.4+的系統上,MediaCodec的可用性是能夠基本保證的,但是不同等級的機器的編碼器能力會有不少差別,建議可以根據機器的配置,選擇不同的編碼器配置。
在Android 4.1之前沒有提供硬編解碼的API,所以基本都是採用開源的那些庫,比如著名的FFMpeg實現軟編解碼。但是通常情況下,同一平臺同一硬件環境,硬編碼的速度快於軟件編碼,軟編碼使用CPU來進行計算,會消耗一些app的運算效率。在Android4.1出來了一個新的API:MediaCodec可以支持硬編解碼。MediaCodec可以支持對音頻和視頻的編解碼,我們就要學會使用它來進行音頻的編解碼操作。
MediaCodec
簡介
對於API的介紹我們的第一反應,看官網
還發現了一個哥們翻譯的中文版本
總結來說,MediaCodec它是官方提供的硬編碼API,首先對他進行參數的配置,然後把數據扔給它,它在內部完成編碼或者解碼的工作,然後把處理好的數據輸出給我們。
貼一張官網的處理圖片:
解析:
MediaCodec採用了兩個緩衝區隊列,異步處理數據。
- 客戶端從input緩衝區隊列申請empty buffer(調用dequeueInputBuffer方法)
- 客戶端將要編解碼的數據拷貝到empty buffer,然後放入input緩衝區隊列(調用queueInputBuffer方法)
- MediaCodec內部從input緩衝區隊列取出一幀數據進行編解碼處理
- 處理結束後將原始數據buffer置爲empty再放回input緩衝區隊列,將編解碼的數據放入到output緩衝區隊列
- 客戶端從output緩衝區隊列申請編解碼的buffer(調用dequeueoutputBuffer方法)
- 客戶端對編解碼後的buffer數據進行渲染或者播放
- 客戶端處理完上面的步驟後再將該buffer放回到output緩衝區隊列(調用releaseOutputBuffer)
MediaCodec使用流程
- 創建MediaCodec(調用createDecoderByType方法)
- 配置MediaCodec(調用configure方法)
- 開始編解碼(調用start方法)
- 循環數據輸入輸出
- dequeueInputBuffer
- queueInputBuffer
- dequeueOutputBuffer
- releaseOutputBuffer
- 停止編解碼(調用stop方法)
- 釋放資源(調用release方法)
MediaCodec使用代碼
摘自官網代碼示例
- 同步使用
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();
- 異步使用
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();
- Android 5.0以下使用
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, …);
codec.start();
//API的區別在這裏
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
for (;;) {
int inputBufferId = codec.dequeueInputBuffer(…);
if (inputBufferId >= 0) {
// fill inputBuffers[inputBufferId] with valid data
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
// outputBuffers[outputBufferId] is ready to be processed or rendered.
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = codec.getOutputBuffers();
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// Subsequent data will conform to new format.
MediaFormat format = codec.getOutputFormat();
}
}
codec.stop();
codec.release();
我們實現一個實時的音頻錄製播放的demo,查看