前言
MediaCodec大坑絕對(duì)是大坑,坑的很直溜侠草。
本系列是參考 [奇卓社]的文章翰撑,喜歡的小伙伴可以直接去看[奇卓社]。
參考文章
MediaCodec是什么匠题?
?? 從API 16(Android 4.1)開始拯坟,Android提供了MediaCodec類以便開發(fā)者更加靈活的處理音視頻的編解碼,MediaCodec類可以訪問底層媒體編解碼器框架(StageFright或openMAX),即編碼器/解碼器組件韭山。這是Android low-level多媒體支持基礎(chǔ)設(shè)施的一部分(通常與MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack.一起使用))郁季。
MediaCodec 大綱
一圖勝千言::
廣義而言,編解碼器處理輸入數(shù)據(jù)以生成輸出數(shù)據(jù)掠哥。
它異步處理數(shù)據(jù)巩踏,并使用一組輸入和輸出緩沖區(qū)。
在一個(gè)簡(jiǎn)單的級(jí)別上续搀,您請(qǐng)求(或接收)一個(gè)空的輸入緩沖區(qū)塞琼,將其填充數(shù)據(jù)并將其發(fā)送到編解碼器進(jìn)行處理。
編解碼器用完了數(shù)據(jù)并將其轉(zhuǎn)換為空的輸出緩沖區(qū)之一禁舷。
最后彪杉,您請(qǐng)求(或接收)已填充的輸出緩沖區(qū),使用其內(nèi)容并將其釋放回編解碼器牵咙。
數(shù)據(jù)類型
編解碼器對(duì)三種數(shù)據(jù)進(jìn)行操作:compressed data
壓縮數(shù)據(jù)(即為經(jīng)過H264. H265等編碼的視頻數(shù)據(jù)或AAC等編碼的音頻數(shù)據(jù))派近,raw audio data
原始音頻數(shù)據(jù)和raw video data
原始視頻數(shù)據(jù)。
三種類型的數(shù)據(jù)均可以利用ByteBuffers進(jìn)行處理洁桌,但是對(duì)于原始視頻數(shù)據(jù)應(yīng)提供一個(gè)Surface以提高編解碼器的性能渴丸。
Surface直接使用本地視頻數(shù)據(jù)緩存(native video buffers)
,而沒有映射或復(fù)制數(shù)據(jù)到ByteBuffers另凌,因此谱轨,這種方式會(huì)更加高效。在使用Surface的時(shí)候吠谢,通常不能直接訪問原始視頻數(shù)據(jù)土童,但是可以使用ImageReader類來訪問非安全的解碼(原始)視頻幀。這仍然比使用ByteBuffers更加高效工坊,因?yàn)橐恍┍镜鼐彺妫╪ative buffer)可以被映射到 direct ByteBuffers献汗。當(dāng)使用ByteBuffer模式敢订,你可以利用Image類和getInput/OutputImage(int)方法來訪問到原始視頻數(shù)據(jù)幀。
Compressed Buffers 壓縮緩沖區(qū)
輸入緩沖區(qū)(用于解碼器)和輸出緩沖區(qū)(用于編碼器)根據(jù)MediaFormat#KEY_MIME
包含壓縮數(shù)據(jù)罢吃。
對(duì)于視頻類型楚午,通常是單個(gè)壓縮視頻幀。
對(duì)于音頻數(shù)據(jù)刃麸,這通常是一個(gè)訪問單元(一個(gè)編碼的音頻段醒叁,通常包含幾毫秒的音頻,這由格式類型決定)泊业,但是由于緩沖區(qū)中可能包含多個(gè)編碼的訪問單元把沼,因此這一要求稍微有所放松。
無論哪種情況吁伺,緩沖區(qū)都不會(huì)在任意字節(jié)邊界處開始或結(jié)束饮睬,而不會(huì)在幀/訪問單元邊界處開始或結(jié)束,除非使用BUFFER_FLAG_PARTIAL_FRAME
對(duì)其進(jìn)行了標(biāo)記篮奄。
Raw Audio Buffers 原始音頻緩沖區(qū)
原始的音頻數(shù)據(jù)緩存包含完整的PCM(脈沖編碼調(diào)制)音頻數(shù)據(jù)幀捆愁,這是每一個(gè)通道按照通道順序的一個(gè)樣本。
每一個(gè)PCM音頻樣本都是一個(gè)按照本機(jī)字節(jié)順序的16位帶符號(hào)整數(shù)或浮點(diǎn)數(shù)(16 bit signed integer or a float, in native byte order.)窟却。
使用浮點(diǎn)PCM編碼的原始音頻緩沖區(qū) :僅當(dāng)在MediaCodec configure(…)期間將MediaFormat的MediaFormat#KEY_PCM_ENCODING
設(shè)置為AudioFormat#ENCODING_PCM_FLOAT
并由解碼器的getOutputFormat()
或編碼器的getInputFormat()
確認(rèn)時(shí)昼丑,才可以使用浮點(diǎn)PCM編碼的原始音頻緩沖區(qū)。
檢查MediaFormat中的float PCM的示例方法如下:
static boolean isPcmFloat(MediaFormat format) {
return format.getInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
== AudioFormat.ENCODING_PCM_FLOAT;
}
為了在短數(shù)組中提取包含16位帶符號(hào)整數(shù)音頻數(shù)據(jù)的緩沖區(qū)的一個(gè)通道夸赫,可以使用以下代碼:
// Assumes the buffer PCM encoding is 16 bit. 假定緩沖區(qū)PCM編碼為16位菩帝。
short[] getSamplesForChannel(MediaCodec codec, int bufferId, int channelIx) {
ByteBuffer outputBuffer = codec.getOutputBuffer(bufferId);
MediaFormat format = codec.getOutputFormat(bufferId);
ShortBuffer samples = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
int numChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
if (channelIx < 0 || channelIx >= numChannels) {
return null;
}
short[] res = new short[samples.remaining() / numChannels];
for (int i = 0; i < res.length; ++i) {
res[i] = samples.get(i * numChannels + channelIx);
}
return res;
}
Raw Video Buffers 原始視頻緩沖區(qū)
在ByteBuffer模式下,視頻緩沖區(qū)根據(jù)他們的MediaFormat#KEY_COLOR_FORMAT
進(jìn)行布局茬腿。
你可以通過調(diào)用getCodecInfo().MediaCodecInfo#getCapabilitiesForType.CodecCapabilities#colorFormats
方法獲得編解碼器支持的顏色格式數(shù)組呼奢。
視頻編解碼器可以支持三種類型的顏色格式:
-
native raw video format 原始原始視頻格式:由
CodecCapabilities#COLOR_FormatSurface
標(biāo)記,可以與輸入或輸出Surface一起使用切平。 -
flexible YUV buffers (such as CodecCapabilities#COLOR_FormatYUV420Flexible) 靈活的YUV緩沖區(qū): 利用一個(gè)輸入或輸出Surface握础,或者在ByteBuffer模式下,可以通過調(diào)用
getInput/OutputImage(int)
方法使用這些格式悴品。 -
other, specific formats 其他特定格式:通常只在ByteBuffer模式下被支持禀综。有些顏色格式是特定供應(yīng)商指定的。其他的一些被定義在
MediaCodecInfo.CodecCapabilities
中苔严。這些顏色格式同 flexible format相似菇存,你仍然可以使用getInput/OutputImage(int)
方法(API 21)。
從Android 5.1(API 22)開始邦蜜,所有的視頻編解碼器都支持靈活的YUV4:2:0緩存(flexible YUV420 buffers)。
在支持Build.VERSION_CODES.LOLLIPOP
和Image
之前悼沈,您需要使用MediaFormat#KEY_STRIDE和MediaFormat#KEY_SLICE_HEIGHT
輸出格式值來了解原始輸出緩沖區(qū)的布局贱迟。
MediaFormat#KEY_WIDTH
和MediaFormat#KEY_HEIGHT
鍵指定視頻幀的大小絮供; 但是衣吠,在大多數(shù)情況下,視頻(圖片)僅占據(jù)視頻幀的一部分壤靶。
這由“裁剪矩形”表示缚俏。 這由“crop rectangle 剪裁矩形”
表示。
您需要使用以下鍵從輸出格式獲取原始輸出圖像的裁剪矩形贮乳。如果不存在這些鍵忧换,則視頻將占據(jù)整個(gè)視頻幀。在應(yīng)用任何MediaFormat#KEY_ROTATION
之前向拆,應(yīng)在輸出幀的上下文中了解裁剪矩形亚茬。
Format | Key Type | Description |
---|---|---|
"crop-left" | Integer | The left-coordinate (x) of the crop rectangle |
"crop-top" | Integer | The top-coordinate (y) of the crop rectangle |
"crop-right" | Integer | The right-coordinate (x) MINUS 1 of the crop rectangle |
"crop-bottom" | Integer | The bottom-coordinate (y) MINUS 1 of the crop rectangle |
The right and bottom coordinates can be understood as the coordinates of the right-most valid column/bottom-most valid row of the cropped output image.
右坐標(biāo)和底坐標(biāo)可以理解為裁剪后的輸出圖像的最右邊的有效列/最下面的有效行的坐標(biāo)。
旋轉(zhuǎn)前視頻幀的大小可以這樣計(jì)算:
MediaFormat format = decoder.getOutputFormat(…);
int width = format.getInteger(MediaFormat.KEY_WIDTH);
if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
}
int height = format.getInteger(MediaFormat.KEY_HEIGHT);
if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
}
生命周期
在編解碼器的生命周期內(nèi)有三種理論狀態(tài):停止態(tài)-Stopped浓恳、執(zhí)行態(tài)-Executing丽蝎、釋放態(tài)-Released牍白。
- 停止?fàn)顟B(tài)(Stopped)包括了三種子狀態(tài):未初始化(Uninitialized)、配置(Configured)、錯(cuò)誤(Error)痕寓。
-
執(zhí)行狀態(tài)(Executing)在概念上會(huì)經(jīng)歷三種子狀態(tài):刷新(Flushed)、運(yùn)行(Running)柬甥、流結(jié)束(End-of-Stream)灶轰。
一圖勝千言::
生命周期
當(dāng)你使用任意一種工廠方法(factory methods)創(chuàng)建了一個(gè)編解碼器,此時(shí)編解碼器處于未初始化狀態(tài)(Uninitialized)疑务。
首先沾凄,你需要使用configure(…)方法對(duì)編解碼器進(jìn)行配置,這將使編解碼器轉(zhuǎn)為配置狀態(tài)(Configured)知允。
然后調(diào)用start()方法使其轉(zhuǎn)入執(zhí)行狀態(tài)(Executing)撒蟀。在這種狀態(tài)下你可以通過上述的緩存隊(duì)列操作處理數(shù)據(jù)。執(zhí)行狀態(tài)(Executing)包含三個(gè)子狀態(tài): 刷新(Flushed)温鸽、運(yùn)行( Running) 以及流結(jié)束(End-of-Stream)保屯。
在調(diào)用start()方法后編解碼器立即進(jìn)入刷新子狀態(tài)(Flushed),此時(shí)編解碼器會(huì)擁有所有的緩存涤垫。
一旦第一個(gè)輸入緩存(input buffer)被移出隊(duì)列姑尺,編解碼器就轉(zhuǎn)入運(yùn)行子狀態(tài)(Running),編解碼器的大部分生命周期會(huì)在此狀態(tài)下度過蝠猬。
當(dāng)你將一個(gè)帶有end-of-stream 標(biāo)記的輸入緩存入隊(duì)列時(shí)切蟋,編解碼器將轉(zhuǎn)入流結(jié)束子狀態(tài)(End-of-Stream)。在這種狀態(tài)下榆芦,編解碼器不再接收新的輸入緩存柄粹,但它仍然產(chǎn)生輸出緩存(output buffers)直到end-of- stream標(biāo)記到達(dá)輸出端(直到在輸出端達(dá)到流結(jié)束為止)喘鸟。你可以在執(zhí)行狀態(tài)(Executing)下的任何時(shí)候通過調(diào)用flush()方法使編解碼器重新返回到刷新子狀態(tài)(Flushed)。通過調(diào)用stop()方法使編解碼器返回到未初始化狀態(tài)(Uninitialized)驻右,此時(shí)這個(gè)編解碼器可以再次重新配置 什黑。當(dāng)你使用完編解碼器后,你必須調(diào)用release()方法釋放其資源堪夭。
在極少情況下編解碼器會(huì)遇到錯(cuò)誤并進(jìn)入錯(cuò)誤狀態(tài)(Error)愕把。這個(gè)錯(cuò)誤可能是在隊(duì)列操作時(shí)返回一個(gè)錯(cuò)誤的值或者有時(shí)候產(chǎn)生了一個(gè)異常導(dǎo)致的。通過調(diào)用reset()方法使編解碼器再次可用森爽。你可以在任何狀態(tài)調(diào)用reset()方法使編解碼器返回到未初始化狀態(tài)(Uninitialized)恨豁。否則,調(diào)用 release()方法進(jìn)入最終的Released狀態(tài)拗秘。