MediaCodec使用步驟

摘自: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視頻編解碼實踐筆記

MediaCodec處理的類型

MediaCodec 支持處理三種數(shù)據(jù)類型,分別是壓縮數(shù)據(jù)(compressed data)、原始音頻數(shù)據(jù)(raw audio data)、原始視頻數(shù)據(jù)(raw video data),可以使用 ByteBuffer 處理這三種數(shù)據(jù),也就是后文中提到的緩沖區(qū),對于原始視頻數(shù)據(jù),可以使用 Surface 來提高編解碼器性能,但是不能訪問原始視頻數(shù)據(jù),但是可以通過 ImageReader 訪問原始視頻幀,通過 Image 進(jìn)而獲取到與之對應(yīng)的 YUV 數(shù)據(jù)等其他信息。

壓縮緩沖區(qū):用于解碼器的輸入緩沖區(qū)和用于編碼器的輸出緩沖區(qū)會包含 MediaFormat 的 KEY_MIME 對應(yīng)類型的壓縮數(shù)據(jù),對于視頻類型,通常是單個壓縮視頻幀,對于音頻數(shù)據(jù),這通常是一個編碼的音頻段,通常包含幾毫秒的音頻,因格式類型而定。

原始音頻緩沖區(qū):原始音頻緩沖區(qū)包含 PCM 音頻數(shù)據(jù)的整個幀,這是每一個通道按照通道順序的一個樣本,每個 PCM 音頻樣本都是 16 位帶符號整數(shù)或浮點數(shù)(以本機字節(jié)順序),如果要使用浮點 PCM 編碼的原始音頻緩沖區(qū),需要如下配置:

mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);

檢查 MediaFormat 中的浮點 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)行布局,可以從通過 MediaCodecInfo 相關(guān)方法獲取設(shè)備受支持的顏色格式,視頻編解碼器可能支持三種顏色格式:

  • native raw video format:原始原始視頻格式,由CodecCapabilities 的 COLOR_FormatSurface 常量標(biāo)記,可以與輸入或輸出Surface一起使用。
  • flexible YUV buffers:靈活的 YUV 緩沖區(qū),如 CodecCapabilities 的 COLOR_FormatYUV420Flexible 常量對應(yīng)的顏色格式,可以通過 getInput、OutputImage 等于與輸入、輸出 Surface 以及 ByteBuffer 模式一起使用。
  • other specific formats:其他特定格式:通常僅在 ByteBuffer 模式下支持這些格式, 某些顏色格式是特定于供應(yīng)商的,其他在均在 CodecCapabilities 中定義。

自 Android 5.1 開始,所有視頻編解碼器均支持靈活的 YUV 4:2:0 緩沖區(qū)。其中 MediaFormat#KEY_WIDTH 和 MediaFormat#KEY_HEIGHT 鍵指定視頻幀的大小,在大多數(shù)情況下,視頻僅占據(jù)視頻幀的一部分,具體表示如下:


image.png

需要使用以下鍵從輸出格式獲取原始輸出圖像的裁剪矩形,如果輸出格式中不存在這些鍵,則視頻將占據(jù)整個視頻幀,在使用任何 MediaFormat#KEY_ROTATION 之前,也就是在設(shè)置旋轉(zhuǎn)之前,可以使用下面的方式計算視頻幀的大小,參考如下:

 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 首先獲取一個空的輸入緩沖區(qū),填充要編碼或解碼的數(shù)據(jù),再將填充數(shù)據(jù)的輸入緩沖區(qū)送到 MediaCodec 進(jìn)行處理,處理完數(shù)據(jù)后會釋放這個填充數(shù)據(jù)的輸入緩沖區(qū),最后獲取已經(jīng)編碼或解碼的輸出緩沖區(qū),使用完畢后釋放輸出緩沖區(qū),其編解碼的流程示意圖如下:

image.png

各個階段對應(yīng)的 API 如下:

// 獲取可用的輸入緩沖區(qū)的索引
 public int dequeueInputBuffer (long timeoutUs)
 // 獲取輸入緩沖區(qū)
 public ByteBuffer getInputBuffer(int index)
 // 將填滿數(shù)據(jù)的inputBuffer提交到編碼隊列
 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í)行和停止分別有三個子狀態(tài),執(zhí)行的三個字狀態(tài)分別是 Flushed、Running 和 Stream-of-Stream,停止的三個子狀態(tài)分別是 Uninitialized、Configured 和 Error,MediaCodec 生命周期示意圖如下:


image.png

如上圖所示,三種狀態(tài)的切換都是由 start、stop、reset、release 等觸發(fā),根據(jù) MediaCodec 處理數(shù)據(jù)方式的不同,其生命周期會略有不同,如在異步模式下 start 之后立即進(jìn)入 Running 子狀態(tài),如果已經(jīng)處于 Flushed 子狀態(tài),則需再次調(diào)用 start 進(jìn)入 Running 子狀態(tài),下面是各個子狀態(tài)切換對應(yīng)的關(guān)鍵 API 如下:

  • 停止?fàn)顟B(tài)(Stopped)
// 創(chuàng)建MediaCodec進(jìn)入Uninitialized子狀態(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會在后文中進(jìn)行說明
 public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
 public void configure(MediaFormat format, @Nullable Surface surface,int flags, MediaDescrambler descrambler)
 // Error
 // 編解碼過程中遇到錯誤進(jìn)入Error子狀態(tài)
  • 執(zhí)行狀態(tài)(Executing)
// start之后立即進(jìn)入Flushed子狀態(tài)
public final void start()
// 第一個輸入緩沖區(qū)出隊的時候進(jìn)入Running子狀態(tài)
public int dequeueInputBuffer (long timeoutUs)
// 輸入緩沖區(qū)與流結(jié)束標(biāo)記排隊時,編解碼器將轉(zhuǎn)換為End-of-Stream子狀態(tài)
// 此時MediaCodec將不接受其他輸入緩沖區(qū),但會生成輸出緩沖區(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)提到過當(dāng)創(chuàng)建 MediaCodec 的時候進(jìn)入Uninitialized 子狀態(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),此時需要對其進(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)
 // 開啟或關(guā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 配置中涉及到幾個參數(shù),其中 surface 表示解碼器要渲染的 Surface,flags 則是指定當(dāng)前編解碼器是作為編碼器還是解碼器來使用的,crypto 和 descrambler 都和解密有關(guān),比如某些 vip 視頻就需要特定的密鑰來配合解碼,只有用戶登錄校驗后才會對視頻內(nèi)容進(jìn)行解密,要不然某些需要付費才能觀看的視頻下載之后就能隨意傳播了,更多細(xì)節(jié)可以查看音視頻中的數(shù)字版權(quán)技術(shù)。

此外某些特定格式比如 AAC 音頻以及 MPEG4、H.264、H.265 視頻格式,這些格式包含一些用于 MediaCodec 的初始化特定的數(shù)據(jù),當(dāng)解碼處理這些壓縮格式時,必須在 start 之后且在任何幀數(shù)據(jù)處理之前將這些特定數(shù)據(jù)提交給 MediaCodec,即在對 queueInputBuffer 的調(diào)用中使用標(biāo)志 BUFFER_FLAG_CODEC_CONFIG 標(biāo)記此類數(shù)據(jù),這些特定的數(shù)據(jù)也可以通過 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ù)會在start 時自動提交給 MediaCodec,無需直接提交此數(shù)據(jù),如果在輸出緩沖區(qū)或格式更改之前調(diào)用了 flush,則會丟失提交的特定數(shù)據(jù),就需要在 queueInputBuffer 的調(diào)用中使用標(biāo)志 BUFFER_FLAG_CODEC_CONFIG 標(biāo)記這類數(shù)據(jù)。

Android 使用以下特定于編解碼器的數(shù)據(jù)緩沖區(qū),為了正確配置 MediaMuxer 軌道,還需要將它們設(shè)置為軌道格式,每個參數(shù)集和標(biāo)有(*)的編解碼器專用數(shù)據(jù)部分必須以“ \ x00 \ x00 \ x00 \ x01”的起始代碼開頭,參考如下:


image.png

編碼器在收到這些信息后將會同樣輸出帶有BUFFER_FLAG_CODEC_CONFIG標(biāo)記的 outputbuffer,此時這些數(shù)據(jù)就是特定數(shù)據(jù),不是媒體數(shù)據(jù)。

MediaCodec數(shù)據(jù)處理方式

每個創(chuàng)建已經(jīng)創(chuàng)建的編解碼器都維護一組輸入緩沖區(qū),有兩種處理數(shù)據(jù)的方式,同步和異步方式,根據(jù) API 版本不同有所區(qū)別,在 API 21 也就是從 Android5.0 開始,推薦使用 ButeBuffer 的方式進(jìn)行數(shù)據(jù)的處理,在此之前只能使用 ButeBuffer 數(shù)組的方式進(jìn)行數(shù)據(jù)的處理,如下:


image.png

MediaCodec,也就是編解碼器的數(shù)據(jù)處理,主要是獲取輸入、輸出緩沖區(qū)、提交數(shù)據(jù)給編解碼器、釋放輸出緩沖區(qū)這幾個過程,同步方式和異步方式的不同點在于輸入緩沖區(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)中獲取,在獲取對應(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ù)采用新格式,此時使用getOutputFormat()獲取新格式
      // 如果使用getOutputFormat(outputBufferId)獲取特定緩沖區(qū)的格式,則無需監(jiān)聽格式變化
      outputFormat = codec.getOutputFormat(); // option B
  }
 }
 codec.stop();
 codec.release();
異步處理模式

只要設(shè)置callback,就等于使用了異步模式,此時一個線程queue數(shù)據(jù),而這個回調(diào)可以再單獨設(shè)置一個線程托管。我們在回調(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類中的setCallback()方法用于設(shè)置一個回調(diào)接口,這個接口將在編解碼操作的各個階段被調(diào)用。這個方法接收一個MediaCodec.Callback對象作為參數(shù)。

MediaCodec.Callback是一個抽象類,它定義了四個方法:

  1. onInputBufferAvailable(MediaCodec codec, int index):當(dāng)輸入緩沖區(qū)可用時,此方法被調(diào)用。參數(shù)index指示了哪個輸入緩沖區(qū)已經(jīng)變得可用。
  2. onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info):當(dāng)輸出緩沖區(qū)可用時,此方法被調(diào)用。參數(shù)index指示了哪個輸出緩沖區(qū)已經(jīng)變得可用,info包含了關(guān)于這個緩沖區(qū)的元數(shù)據(jù),如其包含的數(shù)據(jù)的大小,時間戳等。
  3. onError(MediaCodec codec, MediaCodec.CodecException e):當(dāng)編解碼器發(fā)生錯誤時,此方法被調(diào)用。參數(shù)e是一個MediaCodec.CodecException對象,包含了關(guān)于錯誤的詳細(xì)信息。
  4. onOutputFormatChanged(MediaCodec codec, MediaFormat format):當(dāng)輸出格式發(fā)生變化時,此方法被調(diào)用。參數(shù)format是一個MediaFormat對象,包含了新的輸出格式。
  • muxer 何時啟動
    在啟動 muxer 之前我們需要明確知道 output format 的信息。

在使用MediaCodec進(jìn)行編碼時,onOutputFormatChanged 方法會在開始編碼后首次調(diào)用。這是因為在開始編碼后,MediaCodec 會根據(jù)你設(shè)置的參數(shù)(如分辨率、比特率等)來確定最終的輸出格式。一旦輸出格式確定,就會觸發(fā)onOutputFormatChanged方法。

這個方法的調(diào)用表示編碼器的輸出格式已經(jīng)準(zhǔn)備好,你可以獲取到這個新的輸出格式,并用它來配置你的MediaMuxer。這是必要的,因為MediaMuxer需要知道它正在混合的音頻和視頻的具體格式。

基于上述原因,在異步模式下我們可以在 onOutputFormatChanged 回調(diào)函數(shù)中啟動 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) {
    //...
}

  1. val pts = computePresentationTime(generateIndex):這行代碼計算了當(dāng)前幀的顯示時間,通常是根據(jù)幀率和當(dāng)前幀的索引來計算的。
  2. if(generateIndex == NUM_FRAMES):這行代碼檢查是否已經(jīng)處理完所有的幀。如果是,那么就需要向編碼器發(fā)送一個表示輸入結(jié)束的標(biāo)志。
  3. codec.queueInputBuffer(index, 0, 0, pts, MediaCodec.BUFFER_FLAG_END_OF_STREAM):這行代碼向編碼器的輸入隊列中添加一個空的緩沖區(qū),并設(shè)置了一個表示輸入結(jié)束的標(biāo)志。這告訴編碼器不會有更多的數(shù)據(jù)輸入了。
  4. val frameData = ByteArray(videoWidth * videoHeight * 3 / 2):這行代碼創(chuàng)建了一個字節(jié)數(shù)組,用于存儲一幀的數(shù)據(jù)。這里假設(shè)的是YUV420格式的數(shù)據(jù),所以大小是寬度乘以高度的1.5倍。
  5. generateFrame(generateIndex, codec.inputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT), frameData):這行代碼生成了一幀的數(shù)據(jù)。
  6. val inputBuffer = codec.getInputBuffer(index):這行代碼獲取了編碼器的一個輸入緩沖區(qū)。
  7. inputBuffer.put(frameData):這行代碼將生成的幀數(shù)據(jù)放入輸入緩沖區(qū)。
  8. codec.queueInputBuffer(index, 0, frameData.size, pts, 0):這行代碼將填充了數(shù)據(jù)的輸入緩沖區(qū)添加到編碼器的輸入隊列中。
  9. generateIndex++:這行代碼將幀的索引加一,準(zhǔn)備處理下一幀的數(shù)據(jù)。

onOutputBufferAvailable 回調(diào)邏輯:

  1. val isDone = (info.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0:這行代碼檢查編碼器是否已經(jīng)處理完所有的輸入數(shù)據(jù)并生成了所有的輸出數(shù)據(jù)。如果是,那么isDone會被設(shè)置為true。
  2. if(isDone) {…}:這個if語句檢查是否已經(jīng)完成了所有的編碼工作。如果是,那么就設(shè)置outputEnd為true,表示輸出結(jié)束,并將info.size設(shè)置為0,表示沒有更多的輸出數(shù)據(jù)。
  3. if(info.size > 0){…}:這個if語句檢查是否有輸出數(shù)據(jù)。如果有,那么就處理這些數(shù)據(jù)。
  4. val encodedData = codec.getOutputBuffer(index):這行代碼獲取了編碼器的一個輸出緩沖區(qū),這個緩沖區(qū)包含了編碼后的數(shù)據(jù)。
  5. muxer.writeSampleData(videoTrackIndex, encodedData!!, info):這行代碼將編碼后的數(shù)據(jù)寫入到媒體混合器中。這里的videoTrackIndex是視頻軌道的索引,encodedData是編碼后的數(shù)據(jù),info包含了這些數(shù)據(jù)的元信息,如顯示時間、大小等。
  6. codec.releaseOutputBuffer(index, false):這行代碼釋放了編碼器的輸出緩沖區(qū),讓編碼器可以繼續(xù)使用這個緩沖區(qū)來存儲新的輸出數(shù)據(jù)。這里的false表示不需要將這個緩沖區(qū)的數(shù)據(jù)顯示出來,因為我們是在編碼數(shù)據(jù),而不是播放數(shù)據(jù)。

opengl編碼數(shù)據(jù)直接給到MediaCodec的方式

必須調(diào)用MediaCodec的 createInputSurface()方法,拿出MediaCodec內(nèi)部的Surface,這個Surface用于接收視頻幀數(shù)據(jù),具體操作就是以這個Surface為畫布,創(chuàng)建一個opengl環(huán)境,在這個opengl環(huán)境中做任何繪制都等于畫出了視頻畫面,別忘了調(diào)用eglSwapBuffers,這時就可以阻塞讀取MediaCodec,讀出的數(shù)據(jù)就是編碼好的視頻流。此時可以使用同步方式,也可以使用異步方式。
如果還有預(yù)覽的需求,此時可以在一個線程中創(chuàng)建兩個opengl環(huán)境,一個使用屏幕Surface,一個就是上面的MediaCodec的Surface,然后通過eglMakeCurrent切換環(huán)境進(jìn)行兩次繪制,同時進(jìn)行預(yù)覽和錄像。

編碼結(jié)束

當(dāng)要處理的數(shù)據(jù)結(jié)束時(End-of-stream),需要標(biāo)記流的結(jié)束,可以在最后一個有效的輸入緩沖區(qū)上使用 queueInputBuffer 提交數(shù)據(jù)的時候指定 flags 為 BUFFER_FLAG_END_OF_STREAM 標(biāo)記其結(jié)束,也可以在最后一個有效輸入緩沖區(qū)之后提交一個空的設(shè)置了流結(jié)束標(biāo)志的輸入緩沖區(qū)來標(biāo)記其結(jié)束,此時不能夠再提交輸入緩沖區(qū),除非編解碼器被 flush、stop、restart,輸出緩沖區(qū)繼續(xù)返回直到最終通過在 dequeueOutputBuffer 或通過 Callback#onOutputBufferAvailable 返回的 BufferInfo 中指定相同的流結(jié)束標(biāo)志,最終通知輸出流結(jié)束為止。

如果使用了一個輸入 Surface 作為編解碼器的輸入,此時沒有可訪問的輸入緩沖區(qū),輸入緩沖區(qū)會自動從這個 Surface 提交給編解碼器,相當(dāng)于省略了輸入的這個過程,這個輸入 Surface 可由 createInputSurface 方法創(chuàng)建,此時調(diào)用 signalEndOfInputStream 將發(fā)送流結(jié)束的信號,調(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é)束的信號
public void signalEndOfInputStream ()

同理如果使用了輸出 Surface,則與之相關(guān)的輸出緩沖區(qū)的相關(guān)功能將會被代替,可以通過 setOutputSurface 設(shè)置一個 Surface 作為編解碼器的輸出,可以選擇是否在輸出 Surface 上渲染每一個輸出緩沖區(qū),關(guān)鍵 API 如下:

// 設(shè)置輸出Surface
public void setOutputSurface (Surface surface)
// false表示不渲染這個buffer,true表示使用默認(rèn)的時間戳渲染這個buffer
public void releaseOutputBuffer (int index, boolean render)
// 使用指定的時間戳渲染這個buffer
public void releaseOutputBuffer (int index, long renderTimestampNs)

MediaCodec的異常處理

關(guān)于 MediaCodec 使用過程中的異常處理,這里提一下 CodecException 異常,一般是由編解碼器內(nèi)部異常導(dǎo)致的,比如媒體內(nèi)容損壞、硬件故障、資源耗盡等,可以通過如下方法判斷以做進(jìn)一步的處理:

// true表示可以通過stop、configure、start來恢復(fù)
public boolean isRecoverable ()
// true表示暫時性問題,編碼或解碼操作會在后續(xù)重試進(jìn)行
public boolean isTransient ()

如果 isRecoverable 和 isTransient 都是返回 false,則需要通過 reset 或 release 操作釋放資源后重新工作,兩者不可能同時返回 true。關(guān)于 MediaCodec 的介紹到此為止。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 MediaCodec前面已經(jīng)做了簡介,那么這一篇就是使用了。 參考文章 官方MediaCodec Andro...
    yzzCool閱讀 2,407評論 0 2
  • 最近公司要求提供一個支持 Android 硬件轉(zhuǎn)碼的底層庫,所以自己從頭去看了 MediaCodec 相關(guān)的知識,...
    GeorgeMR閱讀 24,274評論 2 20
  • 簡介 MediaCodec是 Android media 基礎(chǔ)框架的一部分,通常和 MediaExtractor[...
    JackyWu15閱讀 3,392評論 0 4
  • 前言 MediaCodec大坑絕對是大坑,坑的很直溜。本系列是參考 [奇卓社]的文章,喜歡的小伙伴可以直接去看[奇...
    yzzCool閱讀 4,238評論 0 3
  • MediaCodec的官方文檔 一、Android MediaCodec簡單介紹 Android中可以使用Medi...
    黃海佳閱讀 6,334評論 1 17

友情鏈接更多精彩內(nèi)容