摘自:https://zhuanlan.zhihu.com/p/455196527
https://blog.csdn.net/weiwei9363/article/details/136412810
https://blog.csdn.net/lidec/article/details/107006760 Android MediaCodec+OpenGL視頻編解碼實(shí)踐筆記
MediaCodec處理的類(lèi)型
MediaCodec 支持處理三種數(shù)據(jù)類(lèi)型,分別是壓縮數(shù)據(jù)(compressed data)、原始音頻數(shù)據(jù)(raw audio data)、原始視頻數(shù)據(jù)(raw video data),可以使用 ByteBuffer 處理這三種數(shù)據(jù),也就是后文中提到的緩沖區(qū),對(duì)于原始視頻數(shù)據(jù),可以使用 Surface 來(lái)提高編解碼器性能,但是不能訪問(wèn)原始視頻數(shù)據(jù),但是可以通過(guò) ImageReader 訪問(wèn)原始視頻幀,通過(guò) Image 進(jìn)而獲取到與之對(duì)應(yīng)的 YUV 數(shù)據(jù)等其他信息。
壓縮緩沖區(qū):用于解碼器的輸入緩沖區(qū)和用于編碼器的輸出緩沖區(qū)會(huì)包含 MediaFormat 的 KEY_MIME 對(duì)應(yīng)類(lèi)型的壓縮數(shù)據(jù),對(duì)于視頻類(lèi)型,通常是單個(gè)壓縮視頻幀,對(duì)于音頻數(shù)據(jù),這通常是一個(gè)編碼的音頻段,通常包含幾毫秒的音頻,因格式類(lèi)型而定。
原始音頻緩沖區(qū):原始音頻緩沖區(qū)包含 PCM 音頻數(shù)據(jù)的整個(gè)幀,這是每一個(gè)通道按照通道順序的一個(gè)樣本,每個(gè) PCM 音頻樣本都是 16 位帶符號(hào)整數(shù)或浮點(diǎn)數(shù)(以本機(jī)字節(jié)順序),如果要使用浮點(diǎn) PCM 編碼的原始音頻緩沖區(qū),需要如下配置:
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
檢查 MediaFormat 中的浮點(diǎn) PCM 的方法如下:
static boolean isPcmFloat(MediaFormat format) {
return format.getInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
== AudioFormat.ENCODING_PCM_FLOAT;
}
原始視頻緩沖區(qū):在 ByteBuffer 模式下,視頻緩沖區(qū)根據(jù)其 MediaFormat 的 KEY_COLOR_FORMAT 設(shè)置的值進(jìn)行布局,可以從通過(guò) MediaCodecInfo 相關(guān)方法獲取設(shè)備受支持的顏色格式,視頻編解碼器可能支持三種顏色格式:
- native raw video format:原始原始視頻格式,由CodecCapabilities 的 COLOR_FormatSurface 常量標(biāo)記,可以與輸入或輸出Surface一起使用。
- flexible YUV buffers:靈活的 YUV 緩沖區(qū),如 CodecCapabilities 的 COLOR_FormatYUV420Flexible 常量對(duì)應(yīng)的顏色格式,可以通過(guò) getInput、OutputImage 等于與輸入、輸出 Surface 以及 ByteBuffer 模式一起使用。
- other specific formats:其他特定格式:通常僅在 ByteBuffer 模式下支持這些格式, 某些顏色格式是特定于供應(yīng)商的,其他在均在 CodecCapabilities 中定義。
自 Android 5.1 開(kāi)始,所有視頻編解碼器均支持靈活的 YUV 4:2:0 緩沖區(qū)。其中 MediaFormat#KEY_WIDTH 和 MediaFormat#KEY_HEIGHT 鍵指定視頻幀的大小,在大多數(shù)情況下,視頻僅占據(jù)視頻幀的一部分,具體表示如下:

需要使用以下鍵從輸出格式獲取原始輸出圖像的裁剪矩形,如果輸出格式中不存在這些鍵,則視頻將占據(jù)整個(gè)視頻幀,在使用任何 MediaFormat#KEY_ROTATION 之前,也就是在設(shè)置旋轉(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");
}
MediaCodec編解碼的流程
MediaCodec 首先獲取一個(gè)空的輸入緩沖區(qū),填充要編碼或解碼的數(shù)據(jù),再將填充數(shù)據(jù)的輸入緩沖區(qū)送到 MediaCodec 進(jìn)行處理,處理完數(shù)據(jù)后會(huì)釋放這個(gè)填充數(shù)據(jù)的輸入緩沖區(qū),最后獲取已經(jīng)編碼或解碼的輸出緩沖區(qū),使用完畢后釋放輸出緩沖區(qū),其編解碼的流程示意圖如下:

各個(gè)階段對(duì)應(yīng)的 API 如下:
// 獲取可用的輸入緩沖區(qū)的索引
public int dequeueInputBuffer (long timeoutUs)
// 獲取輸入緩沖區(qū)
public ByteBuffer getInputBuffer(int index)
// 將填滿數(shù)據(jù)的inputBuffer提交到編碼隊(duì)列
public final void queueInputBuffer(int index,int offset, int size, long presentationTimeUs, int flags)
// 獲取已成功編解碼的輸出緩沖區(qū)的索引
public final int dequeueOutputBuffer(BufferInfo info, long timeoutUs)
// 獲取輸出緩沖區(qū)
public ByteBuffer getOutputBuffer(int index)
// 釋放輸出緩沖區(qū)
public final void releaseOutputBuffer(int index, boolean render)
MediaCodec生命周期
MediaCodec 有三種狀態(tài),分別是執(zhí)行(Executing)、停止(Stopped)和釋放(Released),其中執(zhí)行和停止分別有三個(gè)子狀態(tài),執(zhí)行的三個(gè)字狀態(tài)分別是 Flushed、Running 和 Stream-of-Stream,停止的三個(gè)子狀態(tài)分別是 Uninitialized、Configured 和 Error,MediaCodec 生命周期示意圖如下:

如上圖所示,三種狀態(tài)的切換都是由 start、stop、reset、release 等觸發(fā),根據(jù) MediaCodec 處理數(shù)據(jù)方式的不同,其生命周期會(huì)略有不同,如在異步模式下 start 之后立即進(jìn)入 Running 子狀態(tài),如果已經(jīng)處于 Flushed 子狀態(tài),則需再次調(diào)用 start 進(jìn)入 Running 子狀態(tài),下面是各個(gè)子狀態(tài)切換對(duì)應(yīng)的關(guān)鍵 API 如下:
- 停止?fàn)顟B(tài)(Stopped)
// 創(chuàng)建MediaCodec進(jìn)入U(xiǎn)ninitialized子狀態(tài)
public static MediaCodec createByCodecName (String name)
public static MediaCodec createEncoderByType (String type)
public static MediaCodec createDecoderByType (String type)
// 配置MediaCodec進(jìn)入Configured子狀態(tài),crypto和descrambler會(huì)在后文中進(jìn)行說(shuō)明
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
public void configure(MediaFormat format, @Nullable Surface surface,int flags, MediaDescrambler descrambler)
// Error
// 編解碼過(guò)程中遇到錯(cuò)誤進(jìn)入Error子狀態(tài)
- 執(zhí)行狀態(tài)(Executing)
// start之后立即進(jìn)入Flushed子狀態(tài)
public final void start()
// 第一個(gè)輸入緩沖區(qū)出隊(duì)的時(shí)候進(jìn)入Running子狀態(tài)
public int dequeueInputBuffer (long timeoutUs)
// 輸入緩沖區(qū)與流結(jié)束標(biāo)記排隊(duì)時(shí),編解碼器將轉(zhuǎn)換為End-of-Stream子狀態(tài)
// 此時(shí)MediaCodec將不接受其他輸入緩沖區(qū),但會(huì)生成輸出緩沖區(qū)
public void queueInputBuffer (int index, int offset, int size, long presentationTimeUs, int flags)
- 釋放狀態(tài)(Released)
// 編解碼完成結(jié)束后釋放MediaCodec進(jìn)入釋放狀態(tài)(Released)
public void release ()
MediaCodec的創(chuàng)建
前面已經(jīng)提到過(guò)當(dāng)創(chuàng)建 MediaCodec 的時(shí)候進(jìn)入U(xiǎn)ninitialized 子狀態(tài),其創(chuàng)建方式如下:
// 創(chuàng)建MediaCodec
public static MediaCodec createByCodecName (String name)
public static MediaCodec createEncoderByType (String type)
public static MediaCodec createDecoderByType (String type)
MediaCodec初始化
創(chuàng)建 MediaCodec 之后進(jìn)入 Uninitialized 子狀態(tài),此時(shí)需要對(duì)其進(jìn)行一些設(shè)置如指定 MediaFormat。
如果使用的是異步處理數(shù)據(jù)的方式,在 configure 之前要設(shè)置 MediaCodec.Callback,關(guān)鍵 API 如下:
// 1. MediaFormat
// 創(chuàng)建MediaFormat
public static final MediaFormat createVideoFormat(String mime,int width,int height)
// 開(kāi)啟或關(guān)閉功能,具體參見(jiàn)MediaCodeInfo.CodecCapabilities
public void setFeatureEnabled(@NonNull String feature, boolean enabled)
// 參數(shù)設(shè)置
public final void setInteger(String name, int value)
// 2. setCallback
// 如果使用的是異步處理數(shù)據(jù)的方式,在configure 之前要設(shè)置 MediaCodec.Callback
public void setCallback (MediaCodec.Callback cb)
public void setCallback (MediaCodec.Callback cb, Handler handler)
// 3. 配置
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
public void configure(MediaFormat format, @Nullable Surface surface,int flags, MediaDescrambler descrambler)
上面 configure 配置中涉及到幾個(gè)參數(shù),其中 surface 表示解碼器要渲染的 Surface,flags 則是指定當(dāng)前編解碼器是作為編碼器還是解碼器來(lái)使用的,crypto 和 descrambler 都和解密有關(guān),比如某些 vip 視頻就需要特定的密鑰來(lái)配合解碼,只有用戶登錄校驗(yàn)后才會(huì)對(duì)視頻內(nèi)容進(jìn)行解密,要不然某些需要付費(fèi)才能觀看的視頻下載之后就能隨意傳播了,更多細(xì)節(jié)可以查看音視頻中的數(shù)字版權(quán)技術(shù)。
此外某些特定格式比如 AAC 音頻以及 MPEG4、H.264、H.265 視頻格式,這些格式包含一些用于 MediaCodec 的初始化特定的數(shù)據(jù),當(dāng)解碼處理這些壓縮格式時(shí),必須在 start 之后且在任何幀數(shù)據(jù)處理之前將這些特定數(shù)據(jù)提交給 MediaCodec,即在對(duì) queueInputBuffer 的調(diào)用中使用標(biāo)志 BUFFER_FLAG_CODEC_CONFIG 標(biāo)記此類(lèi)數(shù)據(jù),這些特定的數(shù)據(jù)也可以通過(guò) MediaFormat 設(shè)置 ByteBuffer 的方式進(jìn)行配置,如下
// csd-0、csd-1、csd-2同理
val bytes = byteArrayOf(0x00.toByte(), 0x01.toByte())
mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(bytes))
其中 csd-0、csd-1 這些鍵可以從 MediaExtractor#getTrackFormat 獲取的MediaFormat中獲取,這些特定的數(shù)據(jù)會(huì)在start 時(shí)自動(dòng)提交給 MediaCodec,無(wú)需直接提交此數(shù)據(jù),如果在輸出緩沖區(qū)或格式更改之前調(diào)用了 flush,則會(huì)丟失提交的特定數(shù)據(jù),就需要在 queueInputBuffer 的調(diào)用中使用標(biāo)志 BUFFER_FLAG_CODEC_CONFIG 標(biāo)記這類(lèi)數(shù)據(jù)。
Android 使用以下特定于編解碼器的數(shù)據(jù)緩沖區(qū),為了正確配置 MediaMuxer 軌道,還需要將它們?cè)O(shè)置為軌道格式,每個(gè)參數(shù)集和標(biāo)有(*)的編解碼器專(zhuān)用數(shù)據(jù)部分必須以“ \ x00 \ x00 \ x00 \ x01”的起始代碼開(kāi)頭,參考如下:

編碼器在收到這些信息后將會(huì)同樣輸出帶有BUFFER_FLAG_CODEC_CONFIG標(biāo)記的 outputbuffer,此時(shí)這些數(shù)據(jù)就是特定數(shù)據(jù),不是媒體數(shù)據(jù)。
MediaCodec數(shù)據(jù)處理方式
每個(gè)創(chuàng)建已經(jīng)創(chuàng)建的編解碼器都維護(hù)一組輸入緩沖區(qū),有兩種處理數(shù)據(jù)的方式,同步和異步方式,根據(jù) API 版本不同有所區(qū)別,在 API 21 也就是從 Android5.0 開(kāi)始,推薦使用 ButeBuffer 的方式進(jìn)行數(shù)據(jù)的處理,在此之前只能使用 ButeBuffer 數(shù)組的方式進(jìn)行數(shù)據(jù)的處理,如下:

MediaCodec,也就是編解碼器的數(shù)據(jù)處理,主要是獲取輸入、輸出緩沖區(qū)、提交數(shù)據(jù)給編解碼器、釋放輸出緩沖區(qū)這幾個(gè)過(guò)程,同步方式和異步方式的不同點(diǎn)在于輸入緩沖區(qū)和輸出緩沖區(qū)的其關(guān)鍵 API 如下:
// 獲取輸入緩沖區(qū)(同步)
public int dequeueInputBuffer (long timeoutUs)
public ByteBuffer getInputBuffer (int index)
// 獲取輸出緩沖區(qū)(同步)
public int dequeueOutputBuffer (MediaCodec.BufferInfo info, long timeoutUs)
public ByteBuffer getOutputBuffer (int index)
// 輸入、輸出緩沖區(qū)索引從MediaCodec.Callback的回調(diào)中獲取,在獲取對(duì)應(yīng)的輸入、輸出緩沖區(qū)(異步)
public void setCallback (MediaCodec.Callback cb)
public void setCallback (MediaCodec.Callback cb, Handler handler)
// 提交數(shù)據(jù)
public void queueInputBuffer (int index, int offset, int size, long presentationTimeUs, int flags)
public void queueSecureInputBuffer (int index, int offset, MediaCodec.CryptoInfo info, long presentationTimeUs, int flags)
// 釋放輸出緩沖區(qū)
public void releaseOutputBuffer (int index, boolean render)
public void releaseOutputBuffer (int index, long renderTimestampNs)
同步處理模式
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(…);
// 使用有效數(shù)據(jù)填充輸入緩沖區(qū)
…
codec.queueInputBuffer(inputBufferId, …);
}
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
// bufferFormat與outputFormat是相同的
// 輸出緩沖區(qū)已準(zhǔn)備后被處理或渲染了
…
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// 輸出格式改變,后續(xù)采用新格式,此時(shí)使用getOutputFormat()獲取新格式
// 如果使用getOutputFormat(outputBufferId)獲取特定緩沖區(qū)的格式,則無(wú)需監(jiān)聽(tīng)格式變化
outputFormat = codec.getOutputFormat(); // option B
}
}
codec.stop();
codec.release();
異步處理模式
只要設(shè)置callback,就等于使用了異步模式,此時(shí)一個(gè)線程queue數(shù)據(jù),而這個(gè)回調(diào)可以再單獨(dú)設(shè)置一個(gè)線程托管。我們?cè)诨卣{(diào)onOutputBufferAvailable中就可以得到編碼好的數(shù)據(jù)。
val mimeType = MediaFormat.MIMETYPE_VIDEO_AVC
val encoder = MediaCodec.createEncoderByType(encodeCodecName)
encoder.setCallback(object: MediaCodec.Callback(){
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
//
}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
//
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
//
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
//
}
})
MediaCodec類(lèi)中的setCallback()方法用于設(shè)置一個(gè)回調(diào)接口,這個(gè)接口將在編解碼操作的各個(gè)階段被調(diào)用。這個(gè)方法接收一個(gè)MediaCodec.Callback對(duì)象作為參數(shù)。
MediaCodec.Callback是一個(gè)抽象類(lèi),它定義了四個(gè)方法:
- onInputBufferAvailable(MediaCodec codec, int index):當(dāng)輸入緩沖區(qū)可用時(shí),此方法被調(diào)用。參數(shù)index指示了哪個(gè)輸入緩沖區(qū)已經(jīng)變得可用。
- onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info):當(dāng)輸出緩沖區(qū)可用時(shí),此方法被調(diào)用。參數(shù)index指示了哪個(gè)輸出緩沖區(qū)已經(jīng)變得可用,info包含了關(guān)于這個(gè)緩沖區(qū)的元數(shù)據(jù),如其包含的數(shù)據(jù)的大小,時(shí)間戳等。
- onError(MediaCodec codec, MediaCodec.CodecException e):當(dāng)編解碼器發(fā)生錯(cuò)誤時(shí),此方法被調(diào)用。參數(shù)e是一個(gè)MediaCodec.CodecException對(duì)象,包含了關(guān)于錯(cuò)誤的詳細(xì)信息。
- onOutputFormatChanged(MediaCodec codec, MediaFormat format):當(dāng)輸出格式發(fā)生變化時(shí),此方法被調(diào)用。參數(shù)format是一個(gè)MediaFormat對(duì)象,包含了新的輸出格式。
- muxer 何時(shí)啟動(dòng)
在啟動(dòng) muxer 之前我們需要明確知道 output format 的信息。
在使用MediaCodec進(jìn)行編碼時(shí),onOutputFormatChanged 方法會(huì)在開(kāi)始編碼后首次調(diào)用。這是因?yàn)樵陂_(kāi)始編碼后,MediaCodec 會(huì)根據(jù)你設(shè)置的參數(shù)(如分辨率、比特率等)來(lái)確定最終的輸出格式。一旦輸出格式確定,就會(huì)觸發(fā)onOutputFormatChanged方法。
這個(gè)方法的調(diào)用表示編碼器的輸出格式已經(jīng)準(zhǔn)備好,你可以獲取到這個(gè)新的輸出格式,并用它來(lái)配置你的MediaMuxer。這是必要的,因?yàn)镸ediaMuxer需要知道它正在混合的音頻和視頻的具體格式。
基于上述原因,在異步模式下我們可以在 onOutputFormatChanged 回調(diào)函數(shù)中啟動(dòng) muxer:
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
videoTrackIndex = muxer.addTrack(format)
muxer.start()
}
- 循環(huán)地編碼視頻幀
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
val pts = computePresentationTime(generateIndex)
// input eos
if(generateIndex == NUM_FRAMES)
{
codec.queueInputBuffer(index, 0, 0, pts, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
}else
{
val frameData = ByteArray(videoWidth * videoHeight * 3 / 2)
generateFrame(generateIndex, codec.inputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT), frameData)
val inputBuffer = codec.getInputBuffer(index)
inputBuffer.put(frameData)
codec.queueInputBuffer(index, 0, frameData.size, pts, 0)
generateIndex++
}
}
override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
// output eos
val isDone = (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
if(isDone)
{
outputEnd.set(true)
info.size = 0
}
if(info.size > 0){
val encodedData = codec.getOutputBuffer(index)
muxer.writeSampleData(videoTrackIndex, encodedData!!, info)
codec.releaseOutputBuffer(index, false)
}
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {
e.printStackTrace()
}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
//...
}
- val pts = computePresentationTime(generateIndex):這行代碼計(jì)算了當(dāng)前幀的顯示時(shí)間,通常是根據(jù)幀率和當(dāng)前幀的索引來(lái)計(jì)算的。
- if(generateIndex == NUM_FRAMES):這行代碼檢查是否已經(jīng)處理完所有的幀。如果是,那么就需要向編碼器發(fā)送一個(gè)表示輸入結(jié)束的標(biāo)志。
- codec.queueInputBuffer(index, 0, 0, pts, MediaCodec.BUFFER_FLAG_END_OF_STREAM):這行代碼向編碼器的輸入隊(duì)列中添加一個(gè)空的緩沖區(qū),并設(shè)置了一個(gè)表示輸入結(jié)束的標(biāo)志。這告訴編碼器不會(huì)有更多的數(shù)據(jù)輸入了。
- val frameData = ByteArray(videoWidth * videoHeight * 3 / 2):這行代碼創(chuàng)建了一個(gè)字節(jié)數(shù)組,用于存儲(chǔ)一幀的數(shù)據(jù)。這里假設(shè)的是YUV420格式的數(shù)據(jù),所以大小是寬度乘以高度的1.5倍。
- generateFrame(generateIndex, codec.inputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT), frameData):這行代碼生成了一幀的數(shù)據(jù)。
- val inputBuffer = codec.getInputBuffer(index):這行代碼獲取了編碼器的一個(gè)輸入緩沖區(qū)。
- inputBuffer.put(frameData):這行代碼將生成的幀數(shù)據(jù)放入輸入緩沖區(qū)。
- codec.queueInputBuffer(index, 0, frameData.size, pts, 0):這行代碼將填充了數(shù)據(jù)的輸入緩沖區(qū)添加到編碼器的輸入隊(duì)列中。
- generateIndex++:這行代碼將幀的索引加一,準(zhǔn)備處理下一幀的數(shù)據(jù)。
onOutputBufferAvailable 回調(diào)邏輯:
- val isDone = (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0:這行代碼檢查編碼器是否已經(jīng)處理完所有的輸入數(shù)據(jù)并生成了所有的輸出數(shù)據(jù)。如果是,那么isDone會(huì)被設(shè)置為true。
- if(isDone) {…}:這個(gè)if語(yǔ)句檢查是否已經(jīng)完成了所有的編碼工作。如果是,那么就設(shè)置outputEnd為true,表示輸出結(jié)束,并將info.size設(shè)置為0,表示沒(méi)有更多的輸出數(shù)據(jù)。
- if(info.size > 0){…}:這個(gè)if語(yǔ)句檢查是否有輸出數(shù)據(jù)。如果有,那么就處理這些數(shù)據(jù)。
- val encodedData = codec.getOutputBuffer(index):這行代碼獲取了編碼器的一個(gè)輸出緩沖區(qū),這個(gè)緩沖區(qū)包含了編碼后的數(shù)據(jù)。
- muxer.writeSampleData(videoTrackIndex, encodedData!!, info):這行代碼將編碼后的數(shù)據(jù)寫(xiě)入到媒體混合器中。這里的videoTrackIndex是視頻軌道的索引,encodedData是編碼后的數(shù)據(jù),info包含了這些數(shù)據(jù)的元信息,如顯示時(shí)間、大小等。
- codec.releaseOutputBuffer(index, false):這行代碼釋放了編碼器的輸出緩沖區(qū),讓編碼器可以繼續(xù)使用這個(gè)緩沖區(qū)來(lái)存儲(chǔ)新的輸出數(shù)據(jù)。這里的false表示不需要將這個(gè)緩沖區(qū)的數(shù)據(jù)顯示出來(lái),因?yàn)槲覀兪窃诰幋a數(shù)據(jù),而不是播放數(shù)據(jù)。
opengl編碼數(shù)據(jù)直接給到MediaCodec的方式
必須調(diào)用MediaCodec的 createInputSurface()方法,拿出MediaCodec內(nèi)部的Surface,這個(gè)Surface用于接收視頻幀數(shù)據(jù),具體操作就是以這個(gè)Surface為畫(huà)布,創(chuàng)建一個(gè)opengl環(huán)境,在這個(gè)opengl環(huán)境中做任何繪制都等于畫(huà)出了視頻畫(huà)面,別忘了調(diào)用eglSwapBuffers,這時(shí)就可以阻塞讀取MediaCodec,讀出的數(shù)據(jù)就是編碼好的視頻流。此時(shí)可以使用同步方式,也可以使用異步方式。
如果還有預(yù)覽的需求,此時(shí)可以在一個(gè)線程中創(chuàng)建兩個(gè)opengl環(huán)境,一個(gè)使用屏幕Surface,一個(gè)就是上面的MediaCodec的Surface,然后通過(guò)eglMakeCurrent切換環(huán)境進(jìn)行兩次繪制,同時(shí)進(jìn)行預(yù)覽和錄像。
編碼結(jié)束
當(dāng)要處理的數(shù)據(jù)結(jié)束時(shí)(End-of-stream),需要標(biāo)記流的結(jié)束,可以在最后一個(gè)有效的輸入緩沖區(qū)上使用 queueInputBuffer 提交數(shù)據(jù)的時(shí)候指定 flags 為 BUFFER_FLAG_END_OF_STREAM 標(biāo)記其結(jié)束,也可以在最后一個(gè)有效輸入緩沖區(qū)之后提交一個(gè)空的設(shè)置了流結(jié)束標(biāo)志的輸入緩沖區(qū)來(lái)標(biāo)記其結(jié)束,此時(shí)不能夠再提交輸入緩沖區(qū),除非編解碼器被 flush、stop、restart,輸出緩沖區(qū)繼續(xù)返回直到最終通過(guò)在 dequeueOutputBuffer 或通過(guò) Callback#onOutputBufferAvailable 返回的 BufferInfo 中指定相同的流結(jié)束標(biāo)志,最終通知輸出流結(jié)束為止。
如果使用了一個(gè)輸入 Surface 作為編解碼器的輸入,此時(shí)沒(méi)有可訪問(wèn)的輸入緩沖區(qū),輸入緩沖區(qū)會(huì)自動(dòng)從這個(gè) Surface 提交給編解碼器,相當(dāng)于省略了輸入的這個(gè)過(guò)程,這個(gè)輸入 Surface 可由 createInputSurface 方法創(chuàng)建,此時(shí)調(diào)用 signalEndOfInputStream 將發(fā)送流結(jié)束的信號(hào),調(diào)用后,輸入表面將立即停止向編解碼器提交數(shù)據(jù),關(guān)鍵 API 如下:
// 創(chuàng)建輸入Surface,需在configure之后、start之前調(diào)用
public Surface createInputSurface ()
// 設(shè)置輸入Surface
public void setInputSurface (Surface surface)
// 發(fā)送流結(jié)束的信號(hào)
public void signalEndOfInputStream ()
同理如果使用了輸出 Surface,則與之相關(guān)的輸出緩沖區(qū)的相關(guān)功能將會(huì)被代替,可以通過(guò) setOutputSurface 設(shè)置一個(gè) Surface 作為編解碼器的輸出,可以選擇是否在輸出 Surface 上渲染每一個(gè)輸出緩沖區(qū),關(guān)鍵 API 如下:
// 設(shè)置輸出Surface
public void setOutputSurface (Surface surface)
// false表示不渲染這個(gè)buffer,true表示使用默認(rèn)的時(shí)間戳渲染這個(gè)buffer
public void releaseOutputBuffer (int index, boolean render)
// 使用指定的時(shí)間戳渲染這個(gè)buffer
public void releaseOutputBuffer (int index, long renderTimestampNs)
MediaCodec的異常處理
關(guān)于 MediaCodec 使用過(guò)程中的異常處理,這里提一下 CodecException 異常,一般是由編解碼器內(nèi)部異常導(dǎo)致的,比如媒體內(nèi)容損壞、硬件故障、資源耗盡等,可以通過(guò)如下方法判斷以做進(jìn)一步的處理:
// true表示可以通過(guò)stop、configure、start來(lái)恢復(fù)
public boolean isRecoverable ()
// true表示暫時(shí)性問(wèn)題,編碼或解碼操作會(huì)在后續(xù)重試進(jìn)行
public boolean isTransient ()
如果 isRecoverable 和 isTransient 都是返回 false,則需要通過(guò) reset 或 release 操作釋放資源后重新工作,兩者不可能同時(shí)返回 true。關(guān)于 MediaCodec 的介紹到此為止。