目錄
- 概述
- 支持的數(shù)據(jù)類型
- 使用MediaCodec的編解碼流程
- MediaCodec生命周期
- MediaCodec API簡介
- 同步和異步API的使用流程
- 示例程序
參考
- [1] developer.android/reference//MediaCodec
- [2] bigflake/Android MediaCodec stuff
- [3] 瞎貓/初識(shí)MediaCodec
1. 概述
推薦以官方文檔[1]作為主要的參考问慎,其中有詳細(xì)的介紹杯缺。
MediaCodec是Android提供的用于對(duì)音視頻進(jìn)行編解碼的類,它通過訪問底層的codec來實(shí)現(xiàn)編解碼的功能舔庶。是Android media基礎(chǔ)框架的一部分,通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface和AudioTrack 一起使用错忱。
歷史:
- Android 4.1, API16倘待,MediaCodec的首個(gè)版本。
- Android 4.3, API18膝昆,擴(kuò)展了一種通過 Surface 提供輸入的方法(createInputSurface)丸边,這樣允許輸入來自于攝像頭的預(yù)覽或者是經(jīng)過OpenGL ES渲染叠必。
- Android 5.0, API21,引入了“異步模式”妹窖。
強(qiáng)烈建議從示例代碼開始了解MediaCodec纬朝,而不是試圖從文檔把它搞清楚篮绿。
2. 支持的數(shù)據(jù)類型
編解碼器支持的數(shù)據(jù)類型: 壓縮的音視頻據(jù)侈玄、原始音頻數(shù)據(jù)和原始視頻數(shù)據(jù)揪罕。
- 數(shù)據(jù)通過ByteBuffers類來表示郊愧。
- 可以設(shè)置Surface來獲取/呈現(xiàn)原始的視頻數(shù)據(jù)玷犹,Surface使用本地的視頻buffer杀餐,不需要進(jìn)行ByteBuffers拷貝广恢∈硎螅可以讓編解碼器的效率更高嫉沽。
- 通常在使用Surface的時(shí)候辟犀,無法訪問原始的視頻數(shù)據(jù),但是可以使用ImageReader訪問解碼后的原始視頻幀绸硕。在使用ByteBuffer的模式下堂竟,可以使用Image類和getInput/OutputImage(int)獲取原始視頻幀。
壓縮數(shù)據(jù):
- MediaFormat#KEY_MIME格式類型玻佩。
- 對(duì)于視頻類型出嘹,通常是一個(gè)單獨(dú)的壓縮視頻幀。
- 對(duì)于音頻數(shù)據(jù)夺蛇,通常是一個(gè)單獨(dú)的訪問單元(一個(gè)編碼的音頻段通常包含由格式類型決定的幾毫秒的音頻)疚漆,但是這個(gè)要求稍微寬松一些,因?yàn)橐粋€(gè)buffer可能包含多個(gè)編碼的音頻訪問單元刁赦。
- 在這兩種情況下娶聘,buffer都不會(huì)在任意字節(jié)邊界上開始或結(jié)束,而是在幀/訪問單元邊界上開始或結(jié)束甚脉,除非它們被BUFFER_FLAG_PARTIAL_FRAME標(biāo)記丸升。
原始音頻buffer
原始音頻buffer包含PCM音頻數(shù)據(jù)的整個(gè)幀,這是每個(gè)通道按通道順序的一個(gè)樣本牺氨。每個(gè)樣本都是一個(gè) AudioFormat#ENCODING_PCM_16BIT狡耻。
原始視頻buffer
在ByteBuffer模式下,視頻buffer根據(jù)它們的MediaFormat#KEY_COLOR_FORMAT進(jìn)行布局猴凹∫恼可以從getCodecInfo(). MediaCodecInfo.getCapabilitiesForType.CodecCapability.colorFormats獲取支持的顏色格式。視頻編解碼器可以支持三種顏色格式:
- native raw video format: CodecCapabilities.COLOR_FormatSurface郊霎,可以與輸入/輸出的Surface一起使用沼头。
- flexible YUV buffers 例如CodecCapabilities.COLOR_FormatYUV420Flexible, 可以使用getInput/OutputImage(int)與輸入/輸出Surface一起使用,也可以在ByteBuffer模式下使用进倍。
- other, specific formats: 通常只支持ByteBuffer模式土至。有些顏色格式是廠商特有的,其他定義在CodecCapabilities猾昆。對(duì)于等價(jià)于flexible格式的顏色格式陶因,可以使用getInput/OutputImage(int)。
從Build.VERSION_CODES.LOLLIPOP_MR1.開始垂蜗,所有視頻編解碼器都支持flexible的YUV 4:2:0 buffer楷扬。
3. 使用MediaCodec的編解碼流程
一些編解碼器對(duì)于它們的buffer要求是比較特殊的,比如內(nèi)存對(duì)齊或是有特定的最小最大限制么抗,為了適應(yīng)廣泛的可能性毅否,buffer分配是由編解碼器實(shí)現(xiàn)的亚铁。
- 這看起來和“零拷貝”原則是相悖的蝇刀,但大部分情況發(fā)生拷貝的幾率是比較小的,因?yàn)榫幗獯a器并不需要復(fù)制或調(diào)整這些數(shù)據(jù)來滿足要求徘溢,而且大多數(shù)情況可以直接使用buffer吞琐,比如直接從磁盤或網(wǎng)絡(luò)讀取數(shù)據(jù)到buffer中,不需要復(fù)制然爆。
MediaCodec采用異步方式處理數(shù)據(jù)站粟,并且使用了一組輸入輸出buffer(ByteBuffer)。
- 使用者從MediaCodec請(qǐng)求一個(gè)空的輸入buffer(ByteBuffer)曾雕,填充滿數(shù)據(jù)后將它傳遞給MediaCodec處理奴烙。
- MediaCodec處理完這些數(shù)據(jù)并將處理結(jié)果輸出至一個(gè)空的輸出buffer(ByteBuffer)中。
- 使用者從MediaCodec獲取輸出buffer的數(shù)據(jù)剖张,消耗掉里面的數(shù)據(jù)切诀,使用完輸出buffer的數(shù)據(jù)之后,將其釋放回編解碼器搔弄。
流程如下圖所示:
4. MediaCodec的生命周期
MediaCodec的生命周期有三種狀態(tài):Stopped幅虑、Executing、Released顾犹。
- Stopped倒庵,包含三種子狀態(tài):Uninitialized、Configured炫刷、Error擎宝。
-
Executing,包含三種子狀態(tài):Flushed浑玛、Running绍申、End-of-Stream。
Stopped的三種子狀態(tài):
- Uninitialized:當(dāng)創(chuàng)建了一個(gè)MediaCodec對(duì)象锄奢,此時(shí)處于Uninitialized狀態(tài)失晴【缒澹可以在任何狀態(tài)調(diào)用reset()方法使MediaCodec返回到Uninitialized狀態(tài)。
- Configured:使用configure(…)方法對(duì)MediaCodec進(jìn)行配置轉(zhuǎn)為Configured狀態(tài)涂屁。
- Error:MediaCodec遇到錯(cuò)誤時(shí)進(jìn)入Error狀態(tài)书在。錯(cuò)誤可能是在隊(duì)列操作時(shí)返回的錯(cuò)誤或者異常導(dǎo)致的。
Executing的三種子狀態(tài):
- Flushed:在調(diào)用start()方法后MediaCodec立即進(jìn)入Flushed子狀態(tài)拆又,此時(shí)MediaCodec會(huì)擁有所有的緩存儒旬。可以在Executing狀態(tài)的任何時(shí)候通過調(diào)用flush()方法返回到Flushed子狀態(tài)帖族。
- Running:一旦第一個(gè)輸入緩存(input buffer)被移出隊(duì)列栈源,MediaCodec就轉(zhuǎn)入Running子狀態(tài),這種狀態(tài)占據(jù)了MediaCodec的大部分生命周期竖般。通過調(diào)用stop()方法轉(zhuǎn)移到Uninitialized狀態(tài)甚垦。
- End-of-Stream:將一個(gè)帶有end-of-stream標(biāo)記的輸入buffer入隊(duì)列時(shí),MediaCodec將轉(zhuǎn)入End-of-Stream子狀態(tài)涣雕。在這種狀態(tài)下艰亮,MediaCodec不再接收之后的輸入buffer,但它仍然產(chǎn)生輸出buffer直到end-of-stream標(biāo)記輸出挣郭。
Released
- 當(dāng)使用完MediaCodec后迄埃,必須調(diào)用release()方法釋放其資源。調(diào)用 release()方法進(jìn)入最終的Released狀態(tài)兑障。
5. MediaCodec API簡介
上面MediaCodec的生命周期的圖中包含了MediaCodec一些主要的方法侄非,下面對(duì)
MediaCodec 主要的API做一個(gè)介紹:
- MediaCodec創(chuàng)建:
- createDecoderByType/createEncoderByType:根據(jù)特定MIME類型(如"video/avc")創(chuàng)建codec。
- createByCodecName:知道組件的確切名稱(如OMX.google.mp3.decoder)的時(shí)候流译,根據(jù)組件名創(chuàng)建codec逞怨。使用MediaCodecList可以獲取組件的名稱。
- configure:配置解碼器或者編碼器先蒋。
- start:成功配置組件后調(diào)用start骇钦。
-
buffer處理的接口:
- dequeueInputBuffer:從輸入流隊(duì)列中取數(shù)據(jù)進(jìn)行編碼操作。
- queueInputBuffer:輸入流入隊(duì)列竞漾。
- dequeueOutputBuffer:從輸出隊(duì)列中取出編碼操作之后的數(shù)據(jù)眯搭。
- releaseOutputBuffer:處理完成,釋放ByteBuffer數(shù)據(jù)业岁。
- getInputBuffers:獲取需要編碼數(shù)據(jù)的輸入流隊(duì)列鳞仙,返回的是一個(gè)ByteBuffer數(shù)組。
- getOutputBuffers:獲取編解碼之后的數(shù)據(jù)輸出流隊(duì)列笔时,返回的是一個(gè)ByteBuffer數(shù)組棍好。
- flush:清空的輸入和輸出端口。
- stop:終止decode/encode會(huì)話
- release:釋放編解碼器實(shí)例使用的資源。
5.1 MediaCodec創(chuàng)建
MediaCodec的一個(gè)實(shí)例處理一種特定類型的數(shù)據(jù)(例如MP3音頻或H.264視頻)借笙,進(jìn)行編碼或解碼操作扒怖。
MediaCodec創(chuàng)建:
- 可以使用MediaCodecList為特定的媒體格式創(chuàng)建一個(gè)MediaCodec。
- 可以從MediaExtractor#getTrackFormat獲得track的格式业稼。
- 使用MediaFormat#setFeatureEnabled注入想要添加的任何特性盗痒。
- 然后調(diào)用MediaCodecList#findDecoderForFormat來獲取能夠處理該特定媒體格式的編解碼器的名稱。
- 最后低散,使用createByCodecName(字符串)創(chuàng)建編解碼器俯邓。
- 還可以使用createDecoder/EncoderByType(java.lang.String)為特定MIME類型創(chuàng)建首選的編解碼器。但是熔号,這不能用于注入特性稽鞭,并且可能會(huì)創(chuàng)建一個(gè)不能處理特定媒體格式的編解碼器。
5.2 configure
配置codec引镊。
public void configure(
MediaFormat format,
Surface surface, MediaCrypto crypto, int flags);
-
MediaFormat format
:輸入數(shù)據(jù)的格式(解碼器)或輸出數(shù)據(jù)的所需格式(編碼器)朦蕴。傳null等同于傳遞MediaFormat#MediaFormat作為空的MediaFormat。 -
Surface surface
:指定Surface祠乃,用于解碼器輸出的渲染梦重。如果編解碼器不生成原始視頻輸出(例如兑燥,不是視頻解碼器)和/或想配置解碼器輸出ByteBuffer亮瓷,則傳null。 -
MediaCrypto crypto
:指定一個(gè)crypto對(duì)象降瞳,用于對(duì)媒體數(shù)據(jù)進(jìn)行安全解密嘱支。對(duì)于非安全的編解碼器,傳null挣饥。 -
int flags
:當(dāng)組件是編碼器時(shí)除师,flags指定為常量CONFIGURE_FLAG_ENCODE。
MediaFormat:封裝描述媒體數(shù)據(jù)格式的信息(包括音頻或視頻)扔枫,以及可選的特性元數(shù)據(jù)汛聚。
- 媒體數(shù)據(jù)的格式指定為key/value對(duì)。key是字符串短荐。值可以integer倚舀、long、float忍宋、String或ByteBuffer痕貌。
- 特性元數(shù)據(jù)被指定為string/boolean對(duì)。
5.3 dequeueInputBuffer
public final int dequeueInputBuffer(long timeoutUs)
- 返回用于填充有效數(shù)據(jù)的輸入buffer的索引糠排,如果當(dāng)前沒有可用的buffer舵稠,則返回-1。
-
long timeoutUs
:等待可用的輸入buffer的時(shí)間。- 如果timeoutUs == 0哺徊,則立即返回室琢。
- 如果timeoutUs < 0,則無限期等待可用的輸入buffer落追。
- 如果timeoutUs > 0研乒,則等待“timeoutUs”微秒。
5.4 queueInputBuffer
在指定索引處填充輸入buffer后淋硝,使用queueInputBuffer將buffer提交給組件雹熬。
特定于codec的數(shù)據(jù)
- 許多codec要求實(shí)際壓縮的數(shù)據(jù)流之前必須有“特定于codec的數(shù)據(jù)”,即用于初始化codec的設(shè)置數(shù)據(jù)谣膳,如
- AVC視頻中的PPS/SPS竿报。
- vorbis音頻中的code tables。
public native final void queueInputBuffer(
int index,
int offset, int size, long presentationTimeUs, int flags)
-
int index
:以前調(diào)用dequeueInputBuffer(long)返回的輸入buffer的索引继谚。 -
int offset
:數(shù)據(jù)開始時(shí)輸入buffer中的字節(jié)偏移量烈菌。 -
int size
:有效輸入數(shù)據(jù)的字節(jié)數(shù)。 -
long presentationTimeUs
:此buffer的PTS(以微秒為單位)花履。 -
int flags
:一個(gè)由BUFFER_FLAG_CODEC_CONFIG和BUFFER_FLAG_END_OF_STREAM標(biāo)志組成的位掩碼芽世。雖然沒有被禁止,但是大多數(shù)codec并不對(duì)輸入buffer使用BUFFER_FLAG_KEY_FRAME標(biāo)志诡壁。- BUFFER_FLAG_END_OF_STREAM:用于指示這是輸入數(shù)據(jù)的最后一部分济瓢。
- BUFFER_FLAG_CODEC_CONFIG:通過指定這個(gè)標(biāo)志,可以在start()或flush()之后直接提交特定于codec的數(shù)據(jù)buffer妹卿。但是旺矾,如果您使用包含這些密鑰的媒體格式配置編解碼器,它們將在啟動(dòng)后由MediaCodec直接自動(dòng)提交夺克。因此箕宙,不建議使用BUFFER_FLAG_CODEC_CONFIG標(biāo)志,只建議高級(jí)用戶使用铺纽。
5.5 dequeueOutputBuffer
從MediaCodec獲取輸出buffer柬帕。
public final int dequeueOutputBuffer(
@NonNull BufferInfo info, long timeoutUs)
- 返回值:已成功解碼的輸出buffer的索引或INFO_*常量之一(INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED 或 INFO_OUTPUT_BUFFERS_CHANGED)。
- 返回INFO_TRY_AGAIN_LATER而timeoutUs指定為了非負(fù)值狡门,表示超時(shí)了陷寝。
- 返回INFO_OUTPUT_FORMAT_CHANGED表示輸出格式已更改,后續(xù)數(shù)據(jù)將遵循新格式融撞。
-
BufferInfo info
:輸出buffer的metadata盼铁。 -
long timeoutUs
:含義同dequeueInputBuffer中的timeoutUs參數(shù)。
BufferInfo
public final static class BufferInfo {
public void set(
int newOffset, int newSize, long newTimeUs, int newFlags);
public int offset;
public int size;
public long presentationTimeUs;
public int flags;
};
- offset:buffer中數(shù)據(jù)的起始偏移量尝偎。
- 注意設(shè)備之間的offset是不一致的饶火。在一些設(shè)備上鹏控,offset是相對(duì)裁剪矩形的左上角像素,而在大多數(shù)設(shè)備上肤寝,offset是相對(duì)整個(gè)幀的左上角像素当辐。
- size:buffer中的數(shù)據(jù)量(以字節(jié)為單位)。如果是0則表示buffer中沒有數(shù)據(jù)鲤看,可以丟棄缘揪。0大小的buffer的唯一用途是攜帶流結(jié)束標(biāo)記。
- presentationTimeUs:buffer的PTS(以微秒為單位)义桂。來源于相應(yīng)輸入buffer一起傳入的PTS找筝。對(duì)于大小為0的buffer,應(yīng)該忽略這個(gè)值慷吊。
- flags:與buffer關(guān)聯(lián)的標(biāo)識(shí)信息袖裕,flags包含如下取值:
- BUFFER_FLAG_KEY_FRAME:buffer包含關(guān)鍵幀的數(shù)據(jù)。
- BUFFER_FLAG_CODEC_CONFIG:buffer包含編解碼器初始化/編解碼器特定的數(shù)據(jù)溉瓶,而不是媒體數(shù)據(jù)急鳄。
- BUFFER_FLAG_END_OF_STREAM:標(biāo)志著流的結(jié)束,即在此之后沒有buffer可用堰酿,除非后面跟著flush疾宏。
- BUFFER_FLAG_PARTIAL_FRAME:buffer只包含幀的一部分,解碼器應(yīng)該對(duì)數(shù)據(jù)進(jìn)行批處理触创,直到在解碼幀之前出現(xiàn)沒有該標(biāo)志的buffer為止坎藐。
public static final int BUFFER_FLAG_KEY_FRAME = 1;
public static final int BUFFER_FLAG_CODEC_CONFIG = 2;
public static final int BUFFER_FLAG_END_OF_STREAM = 4;
public static final int BUFFER_FLAG_PARTIAL_FRAME = 8;
5.6 releaseOutputBuffer
使用此方法將輸出buffer返回給codec或?qū)⑵滗秩驹谳敵鰏urface。
public void releaseOutputBuffer (int index,
boolean render)
-
boolean render
:如果在配置codec時(shí)指定了一個(gè)有效的surface嗅榕,則傳遞true會(huì)將此輸出buffer在surface上渲染顺饮。一旦不再使用buffer,該surface將把buffer釋放回codec凌那。
6. 同步和異步API的使用流程
6.1 同步API的使用流程
- 創(chuàng)建并配置MediaCodec對(duì)象。
- 循環(huán)直到完成:
- 如果輸入buffer準(zhǔn)備好了:
- 讀取一段輸入吟逝,將其填充到輸入buffer中
- 如果輸出buffer準(zhǔn)備好了:
- 從輸出buffer中獲取數(shù)據(jù)進(jìn)行處理帽蝶。
- 處理完畢后,release MediaCodec 對(duì)象块攒。
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();
6.2 異步API的使用流程
在Android 5.0, API21励稳,引入了“異步模式”。
- 創(chuàng)建并配置MediaCodec對(duì)象囱井。
- 給MediaCodec對(duì)象設(shè)置回調(diào)MediaCodec.Callback
- 在onInputBufferAvailable回調(diào)中:
- 讀取一段輸入驹尼,將其填充到輸入buffer中
- 在onOutputBufferAvailable回調(diào)中:
- 從輸出buffer中獲取數(shù)據(jù)進(jìn)行處理。
- 處理完畢后庞呕,release MediaCodec 對(duì)象新翎。
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();
7. 示例程序
Android源碼中的CTS部分給出了很多可以關(guān)于Media編解碼的Demo[2]程帕,可以去參考和學(xué)習(xí)。
7.1 設(shè)備支持的解碼器
MediaCodecList可用于獲取設(shè)備支持的編解碼器的名字地啰、能力愁拭,以查找合適的編解碼器。
以下是獲取編解碼器組件名稱的示例代碼:
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);//REGULAR_CODECS參考api說明
MediaCodecInfo[] codecs = list.getCodecInfos();
Log.d(TAG, "Decoders: ");
for (MediaCodecInfo codec : codecs) {
if (codec.isEncoder())
continue;
Log.d(TAG, codec.getName());
}
Log.d(TAG, "Encoders: ");
for (MediaCodecInfo codec : codecs) {
if (codec.isEncoder())
Log.d(TAG, codec.getName());
}
不同設(shè)備支持的類型不同亏吝,下面本機(jī)獲取到編解碼器組件的名稱:
Decoders:
OMX.google.mp3.decoder
OMX.google.amrnb.decoder
OMX.google.amrwb.decoder
OMX.google.aac.decoder
OMX.google.g711.alaw.decoder
OMX.google.g711.mlaw.decoder
OMX.google.vorbis.decoder
OMX.google.opus.decoder
OMX.google.raw.decoder
OMX.google.gsm.decoder
OMX.qcom.video.decoder.avc
OMX.qcom.video.decoder.mpeg4
OMX.qcom.video.decoder.mpeg2
OMX.qcom.video.decoder.h263
OMX.qcom.video.decoder.vc1
OMX.qcom.video.decoder.divx
OMX.qcom.video.decoder.divx311
OMX.qcom.video.decoder.divx4
OMX.qcom.video.decoder.vp8
OMX.qcom.video.decoder.vp9
OMX.qcom.video.decoder.hevc
OMX.ffmpeg.dsd.decoder
OMX.ffmpeg.dts.decoder
OMX.ffmpeg.adpcm.decoder
OMX.qti.audio.decoder.flac
OMX.google.mpeg4.decoder
OMX.google.h263.decoder
OMX.google.h264.decoder
OMX.google.hevc.decoder
OMX.google.vp8.decoder
OMX.google.vp9.decoder
Encoders:
OMX.google.aac.encoder
OMX.google.amrnb.encoder
OMX.google.amrwb.encoder
OMX.google.flac.encoder
OMX.qcom.video.encoder.avc
OMX.qcom.video.encoder.mpeg4
OMX.qcom.video.encoder.h263
OMX.qcom.video.encoder.vp8
OMX.qcom.video.encoder.hevc
OMX.google.h263.encoder
OMX.google.h264.encoder
OMX.google.mpeg4.encoder
OMX.google.vp8.encoder
- 以
OMX.google.
開頭的通常是軟件編解碼器岭埠。 - 以
OMX.[硬件廠商名稱]
開頭的通常是硬件編解碼器。
5.2 AAC解碼為PCM的示例
public class AACToPCM {
private static final String TAG = "AACToPCM";
public static final int ERROR_INPUT_INVALID = 100;
public static final int ERROR_OUTPUT_FAILED = 200;
public static final int ERROR_OPEN_CODEC = 300;
public static final int OK = 0;
private static final int TIMEOUT_USEC = 0;
private MediaExtractor mExtractor;
private MediaFormat mFormat;
private MediaCodec mDecoder;
private FileOutputStream mFos;
private ByteBuffer[] mInputBuffers;
private ByteBuffer[] mOutputBuffers;
private boolean mDecodeEnd;
public AACToPCM() {}
private int checkPath(String path) {
if (path == null || path.isEmpty()) {
Log.d(TAG, "invalid path, path is empty");
return ERROR_INPUT_INVALID;
}
File file = new File(path);
if (!file.isFile()) {
Log.d(TAG, "path is not a file, path:" + path);
return ERROR_INPUT_INVALID;
} else if (!file.exists()) {
Log.d(TAG, "file not exists, path:" + path);
return ERROR_INPUT_INVALID;
} else {
Log.d(TAG, "path is a file, path:" + path);
}
return OK;
}
public int decodeAACToPCM(String audioPath, String pcmPath) {
int ret;
if (OK != (ret = openInput(audioPath))) {
return ret;
}
if (OK != (ret = openOutput(pcmPath))) {
return ret;
}
if (OK != (ret = openCodec(mFormat))) {
return ret;
}
mDecodeEnd = false;
while (!mDecodeEnd) {
if (OK != (ret = decode(mDecoder, mExtractor))) {
Log.d(TAG, "decode failed, ret=" + ret);
break;
}
}
close();
return ret;
}
private int decode(MediaCodec codec, MediaExtractor extractor) {
Log.d(TAG, "decode");
int inputIndex = codec.dequeueInputBuffer(TIMEOUT_USEC);
if (inputIndex >= 0) {
ByteBuffer inputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
inputBuffer = codec.getInputBuffer(inputIndex);
} else {
inputBuffer = mInputBuffers[inputIndex];
}
inputBuffer.clear();
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {//read end
codec.queueInputBuffer(inputIndex, 0, 0, 0L,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
codec.queueInputBuffer(inputIndex, 0, sampleSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {//TIMEOUT
Log.d(TAG, "INFO_TRY_AGAIN_LATER");//TODO how to declare this info
return OK;
} else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
Log.d(TAG, "output format changed");
return OK;
} else if (outputIndex < 0) {
Log.d(TAG, "outputIndex=" + outputIndex);
return OK;
} else {
ByteBuffer outputBuffer;
if (Build.VERSION.SDK_INT >= 21) {
outputBuffer = codec.getOutputBuffer(outputIndex);
} else {
outputBuffer = mOutputBuffers[outputIndex];
}
byte[] buffer = new byte[bufferInfo.size];
outputBuffer.get(buffer);
try {
Log.d(TAG, "output write, size="+ bufferInfo.size);
mFos.write(buffer);
mFos.flush();
} catch (IOException e) {
e.printStackTrace();
return ERROR_OUTPUT_FAILED;
}
codec.releaseOutputBuffer(outputIndex, false);
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
mDecodeEnd = true;
}
}
return OK;
}
private int openCodec(MediaFormat format) {
Log.d(TAG, "openCodec, format mime:" + format.getString(MediaFormat.KEY_MIME));
try {
mDecoder = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
} catch (IOException e) {
e.printStackTrace();
return ERROR_OPEN_CODEC;
}
mDecoder.configure(format, null, null, 0);
mDecoder.start();
if (Build.VERSION.SDK_INT < 21) {
mInputBuffers = mDecoder.getInputBuffers();
mOutputBuffers = mDecoder.getOutputBuffers();
}
return OK;
}
private int openInput(String audioPath) {
Log.d(TAG, "openInput audioPath:" + audioPath);
int ret;
if (OK != (ret = checkPath(audioPath))) {
return ret;
}
mExtractor = new MediaExtractor();
int audioTrack = -1;
boolean hasAudio = false;
try {
mExtractor.setDataSource(audioPath);
for (int i = 0; i < mExtractor.getTrackCount(); ++i) {
MediaFormat format = mExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
Log.d(TAG, "mime=" + mime);
if (mime.startsWith("audio/")) {
audioTrack = i;
hasAudio = true;
mFormat = format;
break;
}
}
if (!hasAudio) {
Log.d(TAG, "input contain no audio");
return ERROR_INPUT_INVALID;
}
mExtractor.selectTrack(audioTrack);
} catch (IOException e) {
return ERROR_INPUT_INVALID;
}
return OK;
}
private int openOutput(String outputPath) {
Log.d(TAG, "openOutput outputPath:" + outputPath);
try {
mFos = new FileOutputStream(outputPath);
} catch (IOException e) {
return ERROR_OUTPUT_FAILED;
}
return OK;
}
private void close() {
mExtractor.release();
mDecoder.stop();
mDecoder.release();
try {
mFos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}