MediaCodec類可以訪問底層媒體編解碼框架(StageFright 或 OpenMAX),即編解碼組件茂浮,它是Android基本的多媒體支持基礎(chǔ)架構(gòu)的一部分,通常和MediaExtractor只损、MediaSync呆盖、MediaMuxer、MediaCrypto那伐、MediaDrm踏施、Image、Surface和AudioTrack一起使用喧锦。它本身并不是Codec读规,它通過調(diào)用底層編解碼組件獲得了Codec的能力。
MediaCodec的工作方式
MediaCodec處理輸入數(shù)據(jù)產(chǎn)生輸出數(shù)據(jù)燃少。當(dāng)異步處理數(shù)據(jù)時束亏,使用一組輸入和輸出Buffer隊列。通常阵具,在邏輯上碍遍,客戶端請求(或接收)數(shù)據(jù)后填入預(yù)先設(shè)定的空輸入緩沖區(qū),輸入Buffer填滿后將其傳遞到MediaCodec并進行編解碼處理阳液。之后MediaCodec編解碼后的數(shù)據(jù)填充到一個輸出Buffer中怕敬。最后,客戶端請求(或接收)輸出Buffer帘皿,消耗輸出Buffer中的內(nèi)容东跪,用完后釋放,給回MediaCodec重新填充輸出數(shù)據(jù)。
必須保證輸入和輸出隊列同時非空虽填,即至少有一個輸入Buffer和輸出Buffer才能工作丁恭。
MediaCodec狀態(tài)周期圖
在MediaCodec的生命周期中存在三種狀態(tài) :Stopped、Executing斋日、Released牲览。
Stopped狀態(tài)實際上還可以處在三種狀態(tài):Uninitialized、Configured恶守、Error第献。
Executing狀態(tài)也分為三種子狀態(tài):Flushed, Running、End-of-Stream兔港。
從上圖可以看出:
- 當(dāng)創(chuàng)建編解碼器的時候處于未初始化狀態(tài)庸毫。首先你需要調(diào)用configure(…)方法讓它處于Configured狀態(tài),然后調(diào)用start()方法讓其處于Executing狀態(tài)押框。在Executing狀態(tài)下岔绸,你就可以使用上面提到的緩沖區(qū)來處理數(shù)據(jù)。
- Executing的狀態(tài)下也分為三種子狀態(tài):Flushed, Running盒揉、End-of-Stream。在start() 調(diào)用后刚盈,編解碼器處于Flushed狀態(tài)挂脑,這個狀態(tài)下它保存著所有的緩沖區(qū)藕漱。一旦第一個輸入buffer出現(xiàn)了,編解碼器就會自動運行到Running的狀態(tài)崭闲。當(dāng)帶有end-of-stream標(biāo)志的buffer進去后,編解碼器會進入End-of-Stream狀態(tài)刁俭,這種狀態(tài)下編解碼器不在接受輸入buffer,但是仍然在產(chǎn)生輸出的buffer牍戚。此時你可以調(diào)用flush()方法侮繁,將編解碼器重置于Flushed狀態(tài)。
- 調(diào)用stop()將編解碼器返回到未初始化狀態(tài)如孝,然后可以重新配置。 完成使用編解碼器后锁孟,您必須通過調(diào)用release()來釋放它。
- 在極少數(shù)情況下品抽,編解碼器可能會遇到錯誤并轉(zhuǎn)到錯誤狀態(tài)桑包。 這是使用來自排隊操作的無效返回值或有時通過異常來傳達的。 調(diào)用reset()使編解碼器再次可用哑了。 您可以從任何狀態(tài)調(diào)用它來將編解碼器移回未初始化狀態(tài)烧颖。 否則,調(diào)用 release()動到終端釋放狀態(tài)拆火。
MediaCodec 基本使用流程:
- createEncoderByType/createDecoderByType
- configure
- start
- while(true) {
- dequeueInputBuffer
- queueInputBuffer
- dequeueOutputBuffer
- releaseOutputBuffer
}
- stop
- release
- 實時采集音頻并編碼
為了保證兼容性涂圆,推薦的配置是 44.1kHz、單通道模狭、16 位精度踩衩。首先創(chuàng)建并配置 AudioRecord 和 MediaCodec
// 輸入源 麥克風(fēng)
private final static int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
// 采樣率 44.1kHz,所有設(shè)備都支持
private final static int SAMPLE_RATE = 44100;
// 通道 單聲道锚赤,所有設(shè)備都支持
private final static int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
// 精度 16 位褐鸥,所有設(shè)備都支持
private final static int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
// 通道數(shù) 單聲道
private static final int CHANNEL_COUNT = 1;
// 比特率
private static final int BIT_RATE = 96000;
public void createAudio() {
mBufferSizeInBytes = AudioRecord.getMinBufferSize(AudioEncoder.SAMPLE_RATE, AudioEncoder.CHANNEL_CONFIG, AudioEncoder.AUDIO_FORMAT);
if (mBufferSizeInBytes <= 0) {
throw new RuntimeException("AudioRecord is not available, minBufferSize: " + mBufferSizeInBytes);
}
Log.i(TAG, "createAudioRecord minBufferSize: " + mBufferSizeInBytes);
mAudioRecord = new AudioRecord(AudioEncoder.AUDIO_SOURCE, AudioEncoder.SAMPLE_RATE, AudioEncoder.CHANNEL_CONFIG, AudioEncoder.AUDIO_FORMAT, mBufferSizeInBytes);
int state = mAudioRecord.getState();
Log.i(TAG, "createAudio state: " + state + ", initialized: " + (state == AudioRecord.STATE_INITIALIZED));
}
public void createMediaCodec() throws IOException {
MediaCodecInfo mediaCodecInfo = CodecUtils.selectCodec(MIMETYPE_AUDIO_AAC);
if (mediaCodecInfo == null) {
throw new RuntimeException(MIMETYPE_AUDIO_AAC + " encoder is not available");
}
Log.i(TAG, "createMediaCodec: mediaCodecInfo " + mediaCodecInfo.getName());
MediaFormat format = MediaFormat.createAudioFormat(MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNEL_COUNT);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
mMediaCodec = MediaCodec.createEncoderByType(MIMETYPE_AUDIO_AAC);
mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
}
然后開始錄音晶疼,得到原始音頻數(shù)據(jù),再編碼為 AAC 格式锭吨。這個地方會阻塞調(diào)用的線程寒匙,而且編碼比較耗時躏将,一定要在主線程之外調(diào)用考蕾。
public void offerEncoder(AudioRecord record, boolean endOfStream) throws Exception {
try {
int e = this.mEncoder.dequeueInputBuffer(0L);
// 當(dāng)輸入緩沖區(qū)有效時,就是>=0
if(e >= 0) {
// 輸入Buffer 隊列,用于傳送數(shù)據(jù)進行編碼
ByteBuffer[] inputBuffers = this.mEncoder.getInputBuffers();
ByteBuffer bufferCache = inputBuffers[e];
int audioSize = record.read(bufferCache, bufferCache.remaining());
if(audioSize != AudioRecord.ERROR_INVALID_OPERATION
&& audioSize != AudioRecord.ERROR_BAD_VALUE) {
int flag = endOfStream?4:0;
// 通知編碼器編碼
this.mEncoder.queueInputBuffer(e, 0, audioSize, this.mLastPresentationTimeUs, flag);
if(this.presentationInterval == 0) {
this.presentationInterval = (int)((float)audioSize / (float)this.sampleByteSizeInSec * 1000000.0F);
}
// 時間戳保證遞增就是
this.mLastPresentationTimeUs += (long)this.presentationInterval;
} else {
Log.w(TAG, "offerEncoder : error audioSize = " + audioSize);
}
} else if(endOfStream) {
this.unExpectedEndOfStream = true;
}
} catch (Exception e) {
if(null != this.mCallback) {
this.mCallback.onStatus(AudioWorker.ENCODE_OFFER_ERROR, new Object[]{e});
}
}
}
public void drainEncoder(boolean endOfStream) throws Exception {
while(true) {
// 輸出Buffer隊列, 用于取到編碼后的數(shù)據(jù)
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
// 拿到輸出緩沖區(qū)的索引
int bufferIndex = this.mEncoder.dequeueOutputBuffer(info, 0L);
ByteBuffer[] buffers = this.mEncoder.getOutputBuffers();
if(bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat data1 = this.mEncoder.getOutputFormat();
if(null != this.mCallback) {
this.mCallback.onStatus(AudioWorker.STATUS_START, new Object[]{data1});
}
} else if(bufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
this.mEncoder.getOutputBuffers();
} else {
if(bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
if(endOfStream && !this.unExpectedEndOfStream) {
continue;
}
} else {
if(bufferIndex < 0) {
Log.w(TAG, "AudioEncoderCore.drainEncoder : bufferIndex < 0 ");
continue;
}
ByteBuffer data = buffers[bufferIndex];
if(null != data) {
data.position(info.offset);
data.limit(info.offset + info.size);
}
if (info.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
if (null != this.mCallback) {
this.mCallback.onStatus(AudioWorker.STATUS_HEAD, new Object[]{data, info});
}
} else if(null != this.mCallback) {
this.mCallback.onStatus(AudioWorker.STATUS_DATA, new Object[]{data, info});
}
this.mEncoder.releaseOutputBuffer(bufferIndex, false);
if((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 0) {
continue;
}
}
return;
}
}
}
dequeueInputBuffer 返回緩沖區(qū)索引塞帐,如果索引小于 0 ,則表示當(dāng)前沒有可用的緩沖區(qū)荷鼠。它的參數(shù) timeoutUs 表示超時時間 榔幸,畢竟用的是 MediaCodec 的同步模式,如果沒有可用緩沖區(qū)牍疏,就會阻塞指定參數(shù)時間拨齐,如果參數(shù)為負數(shù),則會一直阻塞下去奏黑。
queueInputBuffer 方法將數(shù)據(jù)入隊時,除了要傳遞出隊時的索引值馁害,然后還需要傳入當(dāng)前緩沖區(qū)的時間戳 presentationTimeUs 和當(dāng)前緩沖區(qū)的一個標(biāo)識 flag 蹂匹。
其中,時間戳通常是緩沖區(qū)渲染的時間忍啸,而標(biāo)識則有多種標(biāo)識履植,標(biāo)識當(dāng)前緩沖區(qū)屬于那種類型:
BUFFER_FLAG_CODEC_CONFIG
標(biāo)識當(dāng)前緩沖區(qū)攜帶的是編解碼器的初始化信息,并不是媒體數(shù)據(jù)
BUFFER_FLAG_END_OF_STREAM
結(jié)束標(biāo)識凿滤,當(dāng)前緩沖區(qū)是最后一個了,到了流的末尾
BUFFER_FLAG_KEY_FRAME
表示當(dāng)前緩沖區(qū)是關(guān)鍵幀信息翁脆,也就是 I 幀信息
在編碼的時候可以計算當(dāng)前緩沖區(qū)的時間戳,也可以直接傳遞 0 就好了沙热,對于標(biāo)識也可以直接傳遞 0 作為參數(shù)罢缸。
把數(shù)據(jù)傳入給 MediaCodec 之后,通過 dequeueOutputBuffer 方法取出編解碼后的數(shù)據(jù),除了指定超時時間外蛾洛,還需要傳入 MediaCodec.BufferInfo 對象,這個對象里面有著編碼后數(shù)據(jù)的長度钞螟、偏移量以及標(biāo)識符谎碍。
取出 MediaCodec.BufferInfo 內(nèi)的數(shù)據(jù)之后,根據(jù)不同的標(biāo)識符進行不同的操作:
BUFFER_FLAG_CODEC_CONFIG
表示當(dāng)前數(shù)據(jù)是一些配置數(shù)據(jù)拯啦,在 H264 編碼中就是 SPS 和 PPS 數(shù)據(jù)熔任,也就是 00 00 00 01 67 和 00 00 00 01 68 開頭的數(shù)據(jù),這個數(shù)據(jù)是必須要有的甫匹,它里面有著視頻的寬惦费、高信息。
BUFFER_FLAG_KEY_FRAME
關(guān)鍵幀數(shù)據(jù)薪贫,對于 I 幀數(shù)據(jù)瞧省,也就是開頭是 00 00 00 01 65 的數(shù)據(jù)吠各,
BUFFER_FLAG_END_OF_STREAM
表示結(jié)束勉抓,MediaCodec 工作結(jié)束
對于返回的 flags ,不符合預(yù)定義的標(biāo)識藕筋,則可以直接寫入隐圾,那些數(shù)據(jù)可能代表的是 H264 中的 P 幀 或者 B 幀。