1.介紹:
MediaCodec類可用于訪問Android底層的媒體編解碼器翻具,也就是浴麻,編碼器/解碼器組件乎完。它是Android底層多媒體支持基本架構(gòu)的一部分(通常與MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, 以及AudioTrack一起使用)佑菩;MediaCodec作為比較年輕的Android多媒體硬件編解碼框架离唐,在終端硬解方案中帶來了很大便利背伴。Android源碼中的CTS部分也給出了很多可以關(guān)于Media編解碼的Demo沸毁。
2.編碼測試
比如CTS測試中代碼中的EncoderTest.java
做過Android CTS測試的同學(xué)可以搭建Android CTS測試環(huán)境并指定測試內(nèi)容來測試,不是本文的知識(shí)范疇傻寂。但是我建議自己手動(dòng)寫出實(shí)例Demo來測試,這樣自己才能更了解MediaCodec框架和使用MediaCodec框架息尺。
測試步驟:
1.MediaCodec支持能力
獲取MediaCodec支持的數(shù)量,根據(jù)MediaCodec的句柄獲取MediaCodec支持的編碼格式:
MediaCodecList.getCodecCount()
MediaCodecList.getCodecInfoAt(i);
比如通過以下測試代碼疾掰,就可以知道終端MediaCodec的編碼能力:
int n = MediaCodecList.getCodecCount();
for (int i = 0; i < n; ++i) {
MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
String[] supportedTypes = info.getSupportedTypes();
if(!info.isEncoder()){
return;
}
boolean mime_support = false;
for (int j = 0; j < supportedTypes.length; ++j) {
Log.d(TAG, "codec info:" + info.getName()+" supportedTypes:" + supportedTypes[j]);
if (supportedTypes[j].equalsIgnoreCase(mime)) {
mime_support = true;
}
}
}
code | surpport |
---|---|
OMX.google.amrnb.encoder | audio/3gpp |
OMX.google.amrwb.encoder | audio/amr-wb |
OMX.google.aac.encoder | audio/mp4a-latm |
OMX.google.flac.encoder | audio/flac |
OMX.google.mpeg4.encoder | video/mp4v-es |
OMX.google.h263.encoder | video/3gpp |
OMX.amlogic.video.encoder.avc | video/avc |
OMX.google.h264.encoder | video/avc |
OMX.google.vp8.encoder | video/x-vnd.on2.vp8 |
AACEncoder | audio/mp4a-latm |
根據(jù)上表搂誉,可知如需編碼H264/AAC的碼流,則選擇對(duì)應(yīng)video/hevc静檬,audio/mp4a-latm的編碼器炭懊。需要注意的是針對(duì)某一個(gè)surpport type可能有多個(gè)code庫支持,比如:
支持解碼video/hevc的就有OMX.amlogic.hevc.decoder.awesome和OMX.google.h265.decoder,在編解碼時(shí)該優(yōu)先選擇哪個(gè)需要看實(shí)際調(diào)試情況拂檩,一般會(huì)優(yōu)先使用某個(gè)硬件(如amlogic,mtk等)類型的編解碼方案凛虽。
2.指定編碼格式
MediaFormat.java的配置[http://web.mit.edu/majapw/MacData/afs/sipb/project/android/docs/reference/android/media/MediaFormat.html]
LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
final int kAACProfiles[] = {
2 /* OMX_AUDIO_AACObjectLC */,
5 /* OMX_AUDIO_AACObjectHE */,
39 /* OMX_AUDIO_AACObjectELD */
};
final int kSampleRates[] = { 8000, 11025, 22050, 44100, 48000 };
final int kBitRates[] = { 64000, 128000 };
for (int k = 0; k < kAACProfiles.length; ++k) {
for (int i = 0; i < kSampleRates.length; ++i) {
if (kAACProfiles[k] == 5 && kSampleRates[i] < 22050) {
// Is this right? HE does not support sample rates < 22050Hz?
continue;
}
for (int j = 0; j < kBitRates.length; ++j) {
for (int ch = 1; ch <= 2; ++ch) {
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
format.setInteger(MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, kSampleRates[i]);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, ch);
format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
formats.push(format);
}
}
}
}
3.通過指定的編碼格式創(chuàng)建MediaCodec對(duì)象
MediaCodec codec = MediaCodec.createByCodecName(componentName);
try {
#最重要的一個(gè)配置項(xiàng):format和encode的flag
codec.configure(
format,
null /* surface */,
null /* crypto */,
MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (IllegalStateException e) {
Log.e(TAG, "codec '" + componentName + "' failed configuration.");
}
#MediaCodec準(zhǔn)備就緒
codec.start();
4.mediacode內(nèi)存區(qū)數(shù)據(jù)處理
ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
一起來看一張官方網(wǎng)站的分析圖:
編解碼器通過處理輸入數(shù)據(jù)來產(chǎn)生輸出數(shù)據(jù)。MediaCodec采用異步方式處理數(shù)據(jù)广恢,并且使用了一組輸入輸出緩存(buffer)凯旋。在簡單的層面,你請(qǐng)求或接收到一個(gè)空的輸入緩存(buffer)钉迷,向其中填充滿數(shù)據(jù)并將它傳遞給編解碼器處理至非。編解碼器處理完這些數(shù)據(jù)并將處理結(jié)果輸出至一個(gè)空的輸出緩存(buffer)中。最終糠聪,你請(qǐng)求或接收到一個(gè)填充了數(shù)據(jù)的輸出緩存(buffer),使用完其中的數(shù)據(jù)荒椭,并將其釋放回編解碼器。
int index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, 0);
以上兩個(gè)方法是先從Codec中獲取到一個(gè)空的buffer舰蟆,然后填充buffer趣惠,排到處理隊(duì)列中等待處理,buffer的頭信息包含編解碼配置信息身害,尾信息包含結(jié)束信息
int numBytesSubmitted = 0;
boolean doneSubmittingInput = false;
int numBytesDequeued = 0;
while (true) {
int index;
if (!doneSubmittingInput) {
index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
if (numBytesSubmitted >= kNumInputBytes) {
codec.queueInputBuffer(
index,
0 /* offset */,
0 /* size */,
0 /* timeUs */,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
doneSubmittingInput = true;
} else {
int size = queueInputBuffer(
codec, codecInputBuffers, index);
numBytesSubmitted += size;
}
}
}
private int queueInputBuffer(MediaCodec codec, ByteBuffer[] inputBuffers, int index) {
ByteBuffer buffer = inputBuffers[index];
buffer.clear();
int size = buffer.limit();
byte[] zeroes = new byte[size];
buffer.put(zeroes);//測試時(shí)沒有使用有效數(shù)據(jù)味悄,實(shí)際應(yīng)該put編碼前有效的音視頻數(shù)據(jù)
codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, 0);
return size;
}
通過以上方式編碼器已經(jīng)可以根據(jù)指定編碼格式編碼,再看一下如何獲取編碼后的數(shù)據(jù):
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
codec.releaseOutputBuffer(index, false /* render */);
這三個(gè)方法是從Codec獲取到一個(gè)編解碼后的buffer
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
} else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
} else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
} else {
dequeueOutputBuffer(codec, codecOutputBuffers, index, info);
numBytesDequeued += info.size;
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
if (VERBOSE) {
Log.d(TAG, "dequeued output EOS.");
}
break;
}
if (VERBOSE) {
Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
}
}
private void dequeueOutputBuffer(
MediaCodec codec, ByteBuffer[] outputBuffers,
int index, MediaCodec.BufferInfo info) {
codec.releaseOutputBuffer(index, false /* render */);
}
5.使用MediaMuxer音視頻合成
在使用MediaMuxer混合的時(shí)候,主要的難點(diǎn)就是控制視頻數(shù)據(jù)和音頻數(shù)據(jù)的同步添加,和狀態(tài)的判斷;可以參考CTS集中的MediaMuxerTest.java/DecodeEditEncodeTest塌鸯,注意解碼時(shí)的解碼格式和編碼時(shí)的編碼格式需保持一致侍瑟,才能正常控制buffer大小。
3.結(jié)束語
該篇先介紹大致的編碼知識(shí)涨颜,計(jì)劃下一篇會(huì)詳細(xì)的介紹下把一個(gè)文件解碼后再重新編碼為指定格式费韭,最后合成為新的視頻文件,最近正在研究這個(gè)方向庭瑰,后續(xù)持續(xù)更新星持,感謝關(guān)注!