MediaCodec 可以用來獲得安卓底層的多媒體編碼,可以用來編碼和解碼,它是安卓 low-level 多媒體基礎(chǔ)框架的重要組成部分。那為什么不選擇FFmpeg來做視頻編解碼,由于在處理的過程中速率太慢,且需要在解碼后快速展示,所以選擇MediaCodec。通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface和AudioTrack 一起使用。

MediaCodec大致來說處理輸入數(shù)據(jù)以生成輸出數(shù)據(jù)。 它異步處理數(shù)據(jù),并使用一組輸入和輸出緩沖區(qū)。 生成一個空的輸入緩沖區(qū),將其填充數(shù)據(jù)并將其發(fā)送到編解碼器進行處理。 編解碼器用完了數(shù)據(jù)并將其轉(zhuǎn)換為空的輸出緩沖區(qū)之一。 將已填充的輸出緩沖區(qū)給用戶,最后將其釋放回編解碼器。
一、數(shù)據(jù)類型
編解碼器支持的數(shù)據(jù)類型: 壓縮的音視頻據(jù)、原始音頻數(shù)據(jù)和原始視頻數(shù)據(jù)。
- 數(shù)據(jù)通過ByteBuffers類來表示。
- 可以設(shè)置Surface來獲取/呈現(xiàn)原始的視頻數(shù)據(jù),Surface使用本地的視頻buffer,不需要進行ByteBuffers拷貝??梢宰尵幗獯a器的效率更高。
- 通常在使用Surface的時候,無法訪問原始的視頻數(shù)據(jù),但是可以使用ImageReader訪問解碼后的原始視頻幀。在使用ByteBuffer的模式下,可以使用Image類和getInput/OutputImage(int)獲取原始視頻幀。
壓縮數(shù)據(jù):
- MediaFormat#KEY_MIME格式類型。
- 對于視頻類型,通常是一個單獨的壓縮視頻幀。
- 對于音頻數(shù)據(jù),通常是一個單獨的訪問單元(一個編碼的音頻段通常包含由格式類型決定的幾毫秒的音頻),但是這個要求稍微寬松一些,因為一個buffer可能包含多個編碼的音頻訪問單元。
- 在這兩種情況下,buffer都不會在任意字節(jié)邊界上開始或結(jié)束,而是在幀/訪問單元邊界上開始或結(jié)束,除非它們被BUFFER_FLAG_PARTIAL_FRAME標記。
原始音頻buffers
原始音頻buffer包含PCM音頻數(shù)據(jù)的整個幀,這是每個通道按通道順序的一個樣本。每個樣本都是一個 AudioFormat#ENCODING_PCM_16BIT。
原始視頻buffers
在ByteBuffer模式下,視頻buffer根據(jù)它們的MediaFormat#KEY_COLOR_FORMAT進行布局??梢詮膅etCodecInfo(). 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。對于等價于flexible格式的顏色格式,可以使用getInput/OutputImage(int)。
從Build.VERSION_CODES.LOLLIPOP_MR1.開始,所有視頻編解碼器都支持flexible的YUV 4:2:0 buffer。
二、MediaCodec API簡介
MediaCodec 主要的API做一個介紹:
-
MediaCodec創(chuàng)建:
- createDecoderByType/createEncoderByType:根據(jù)特定MIME類型(如"video/avc")創(chuàng)建codec。
- createByCodecName:知道組件的確切名稱(如OMX.google.mp3.decoder)的時候,根據(jù)組件名創(chuàng)建codec。使用MediaCodecList可以獲取組件的名稱。
configure:配置解碼器或者編碼器。
start:成功配置組件后調(diào)用start。
-
buffer處理的接口:
- dequeueInputBuffer:從輸入流隊列中取數(shù)據(jù)進行編碼操作。
- queueInputBuffer:輸入流入隊列。
- dequeueOutputBuffer:從輸出隊列中取出編碼操作之后的數(shù)據(jù)。
- releaseOutputBuffer:處理完成,釋放ByteBuffer數(shù)據(jù)。
- getInputBuffers:獲取需要編碼數(shù)據(jù)的輸入流隊列,返回的是一個ByteBuffer數(shù)組。
- getOutputBuffers:獲取編解碼之后的數(shù)據(jù)輸出流隊列,返回的是一個ByteBuffer數(shù)組。
flush:清空的輸入和輸出端口。
stop:終止decode/encode會話
release:釋放編解碼器實例使用的資源。
三、使用流程
- MediaCodec創(chuàng)建
- configure傳入配置數(shù)據(jù)
- MediaCodec.start()啟動轉(zhuǎn)碼
- dequeueInputBuffer填充數(shù)據(jù)
- getOutputBuffer獲取ByteBuffer數(shù)據(jù)
- releaseOutputBuffer釋放數(shù)據(jù)
MediaCodec創(chuàng)建
- 可以使用MediaCodecList為特定的媒體格式創(chuàng)建一個MediaCodec。
- 可以從MediaExtractor#getTrackFormat獲得track的格式。
- 使用MediaFormat#setFeatureEnabled注入想要添加的任何特性。
- 然后調(diào)用MediaCodecList#findDecoderForFormat來獲取能夠處理該特定媒體格式的編解碼器的名稱。
- 最后,使用createByCodecName(字符串)創(chuàng)建編解碼器。
- 還可以使用createDecoder/EncoderByType(java.lang.String)為特定MIME類型創(chuàng)建首選的編解碼器。但是,這不能用于注入特性,并且可能會創(chuàng)建一個不能處理特定媒體格式的編解碼器。
configure傳入配置數(shù)據(jù)
在創(chuàng)建好 MediaCodec 之后,需要對其進行設(shè)置,這樣 MediaCodec 的狀態(tài)就可以由 uninitialized 變成 configured
public void configure(
@Nullable MediaFormat format,
@Nullable Surface surface, @Nullable MediaCrypto crypto,
@ConfigureFlag int flags) {
configure(format, surface, crypto, null, flags);
}
public void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
@ConfigureFlag int flags, @Nullable MediaDescrambler descrambler) {
configure(format, surface, null,
descrambler != null ? descrambler.getBinder() : null, flags);
}
MediaFormat format:輸入數(shù)據(jù)的格式(解碼器)或輸出數(shù)據(jù)的所需格式(編碼器)。傳null等同于傳遞MediaFormat#MediaFormat作為空的MediaFormat。
Surface surface:指定Surface,用于解碼器輸出的渲染。如果編解碼器不生成原始視頻輸出(例如,不是視頻解碼器)和/或想配置解碼器輸出ByteBuffer,則傳null。
MediaCrypto crypto:指定一個crypto對象,用于對媒體數(shù)據(jù)進行安全解密。對于非安全的編解碼器,傳null。
int flags:當組件是編碼器時,flags指定為常量CONFIGURE_FLAG_ENCODE。
MediaFormat, 如果某些參數(shù)沒有設(shè)置的話,會導致 MediaCodec 拋出 IllegalStateException.
Video 所必須的 Format Setting
| Encoder | Decoder | |
|---|---|---|
| KEY_MIME | ?? | ?? |
| KEY_BIT_RATE | ?? | ? |
| KEY_WIDTH | ?? | ?? |
| KEY_HEIGHT | ?? | ?? |
| KEY_COLOR_FORMAT | ?? | ? |
| KYE_FRAME_RATE | ?? | ? |
| KEY_I_FRAME_INTERVAL | ?? | ? |
Audio 所必須的 Format Setting
| Encoder | Decoder | |
|---|---|---|
| KEY_MIME | ?? | ?? |
| KEY_BIT_RATE | ?? | ? |
| KEY_CHANNEL_COUNT | ?? | ?? |
| KEY_SAMPLE_RATE | ?? | ?? |
//視頻格式
// 類型(avc高級編碼 h264) 編碼出的寬、高
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
//參數(shù)配置
// 1500kbs碼率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1500_000);
//幀率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
//關(guān)鍵幀間隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 20);
//顏色格式(RGB\YUV)
//從surface當中回去
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
//編碼器
mMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
//將參數(shù)配置給編碼器
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
MediaCodec.start
mMediaCodec.start();
dequeueInputBuffer
//結(jié)合MediaMuxer去使用
//在初始化的時候調(diào)用
//封裝器 復用器
// 一個 mp4 的封裝器 將h.264 通過它寫出到文件就可以了
mMediaMuxer = new MediaMuxer(mPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
//輸出緩沖區(qū)
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//等待10 ms
int status = mMediaCodec.dequeueOutputBuffer(bufferInfo, 10_000);
//讓我們重試 1、需要更多數(shù)據(jù) 2、可能還沒編碼為完(需要更多時間)
if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
} else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
//開始編碼 就會調(diào)用一次
MediaFormat outputFormat = mMediaCodec.getOutputFormat();
//配置封裝器
// 增加一路指定格式的媒體流 視頻
index = mMediaMuxer.addTrack(outputFormat);
mMediaMuxer.start();
} else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
//忽略
} else {
//成功 取出一個有效的輸出
ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(status);
//如果獲取的ByteBuffer 是配置信息 ,不需要寫出到mp4
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
bufferInfo.size = 0;
}
if (bufferInfo.size != 0) {
//mSpeed是一個倍數(shù)默認是1
bufferInfo.presentationTimeUs = (long) (bufferInfo.presentationTimeUs / mSpeed);
//寫到mp4
//根據(jù)偏移定位
outputBuffer.position(bufferInfo.offset);
//ByteBuffer 可讀寫總長度
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
//寫出
mMediaMuxer.writeSampleData(index, outputBuffer, bufferInfo);
}
}
| status | 說明 |
|---|---|
| MediaCodec.INFO_TRY_AGAIN_LATER | 1、需要更多數(shù)據(jù) 2、可能還沒編碼為完(需要更多時間) |
| MediaCodec.INFO_OUTPUT_FORMAT_CHANGED | 開始編碼 就會調(diào)用一次 |
| MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED | 這個方法過時了,忽略 |
getOutputBuffer
//成功 取出一個有效的輸出
ByteBuffer outputBuffer = mMediaCodec.getOutputBuffer(status);
使用此方法將輸出buffer返回給codec或?qū)⑵滗秩驹谳敵鰏urface。
releaseOutputBuffer
public void releaseOutputBuffer (int index,
boolean render)
-
boolean render:如果在配置codec時指定了一個有效的surface,則傳遞true會將此輸出buffer在surface上渲染。一旦不再使用buffer,該surface將把buffer釋放回codec。
總結(jié)
學完了MediaCodec和MediaMuxer我們可以結(jié)合使用,做一個視頻錄制的小功能,也可以加上OpenGL一起,主要是利用MediaCodec.createInputSurface();獲得一個Surface,就會自動編碼 inputSurface 中的圖像,交給虛擬屏幕 通過opengl 將預覽的紋理 繪制到這一個虛擬屏幕中