<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android原生編解碼接口MediaCodec詳解

          共 5801字,需瀏覽 12分鐘

           ·

          2021-02-23 10:52

          PS:有些想法可以先開始,慢慢完善才是好的選擇。

          MediaCodec 是 Android 中的編解碼器組件,用來訪問底層提供的編解碼器,通常與 MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface 和 AudioTrack 一起使用,MediaCodec 幾乎是 Android 播放器硬解碼的標(biāo)配,但是具體使用的是軟編解碼器還是硬解編解碼器,還是和 MediaCodec 的配置相關(guān),音視頻相關(guān)文章如下:

          下面將從以下幾個方面介紹 MediaCodec,主要內(nèi)容如下

          1. MediaCodec處理的類型

          2. MediaCodec編解碼的流程

          3. MediaCodec生命周期

          4. MediaCodec的創(chuàng)建

          5. MediaCodec的初始化

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

          7. 自適應(yīng)播放支持

          8. MediaCodec的異常處理

          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 進而獲取到與之對應(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ū),需要如下配置:

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

          檢查 MediaFormat 中的浮點 PCM 的方法如下:

          1?static?boolean?isPcmFloat(MediaFormat?format)?{
          2??return?format.getInteger(MediaFormat.KEY_PCM_ENCODING,?AudioFormat.ENCODING_PCM_16BIT)
          3??????==?AudioFormat.ENCODING_PCM_FLOAT;
          4?}

          提取包含 16 位帶符號整數(shù)音頻數(shù)據(jù)的緩沖區(qū)的一個通道,可以使用以下代碼:

          1//?Assumes?the?buffer?PCM?encoding?is?16?bit.
          2short[]?getSamplesForChannel(MediaCodec?codec,?int?bufferId,?int?channelIx)?{
          3??????ByteBuffer?outputBuffer?=?codec.getOutputBuffer(bufferId);
          4??????MediaFormat?format?=?codec.getOutputFormat(bufferId);
          5??????ShortBuffer?samples?=?outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer();
          6??????int?numChannels?=?format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
          7??????if?(channelIx?0?||?channelIx?>=?numChannels)?{
          8????????return?null;
          9??????}
          10?????short[]?res?=?new?short[samples.remaining()?/?numChannels];
          11??????for?(int?i?=?0;?i?12????????res[i]?=?samples.get(i?*?numChannels?+?channelIx);
          13??????}
          14??????return?res;
          15}

          原始視頻緩沖區(qū):在 ByteBuffer 模式下,視頻緩沖區(qū)根據(jù)其 MediaFormat 的 KEY_COLOR_FORMAT 設(shè)置的值進行布局,可以從通過 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ù)視頻幀的一部分,具體表示如下:

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

          1?MediaFormat?format?=?decoder.getOutputFormat(…);
          2?int?width?=?format.getInteger(MediaFormat.KEY_WIDTH);
          3?if?(format.containsKey("crop-left")?&&?format.containsKey("crop-right"))?{
          4????width?=?format.getInteger("crop-right")?+?1?-?format.getInteger("crop-left");
          5?}
          6?int?height?=?format.getInteger(MediaFormat.KEY_HEIGHT);
          7?if?(format.containsKey("crop-top")?&&?format.containsKey("crop-bottom"))?{
          8????height?=?format.getInteger("crop-bottom")?+?1?-?format.getInteger("crop-top");
          9?}

          MediaCodec編解碼的流程

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

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

          1//?獲取可用的輸入緩沖區(qū)的索引
          2public?int?dequeueInputBuffer?(long?timeoutUs)
          3//?獲取輸入緩沖區(qū)
          4public?ByteBuffer?getInputBuffer(int?index)
          5//?將填滿數(shù)據(jù)的inputBuffer提交到編碼隊列
          6public?final?void?queueInputBuffer(int?index,int?offset,?int?size,?long?presentationTimeUs,?int?flags)
          7//?獲取已成功編解碼的輸出緩沖區(qū)的索引
          8public?final?int?dequeueOutputBuffer(BufferInfo?info,?long?timeoutUs)
          9//?獲取輸出緩沖區(qū)
          10public?ByteBuffer?getOutputBuffer(int?index)
          11//?釋放輸出緩沖區(qū)
          12public?final?void?releaseOutputBuffer(int?index,?boolean?render)?
          13

          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 生命周期示意圖如下:

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

          1//?創(chuàng)建MediaCodec進入Uninitialized子狀態(tài)
          2public?static?MediaCodec?createByCodecName?(String?name)
          3public?static?MediaCodec?createEncoderByType?(String?type)
          4public?static?MediaCodec?createDecoderByType?(String?type)
          5//?配置MediaCodec進入Configured子狀態(tài),crypto和descrambler會在后文中進行說明
          6public?void?configure(MediaFormat?format,?Surface?surface,?MediaCrypto?crypto,?int?flags)
          7public?void?configure(MediaFormat?format,?@Nullable?Surface?surface,int?flags,?MediaDescrambler?descrambler)
          8//?Error
          9//?編解碼過程中遇到錯誤進入Error子狀態(tài)

          • 執(zhí)行狀態(tài)(Executing)

          1//?start之后立即進入Flushed子狀態(tài)
          2public?final?void?start()
          3//?第一個輸入緩沖區(qū)出隊的時候進入Running子狀態(tài)
          4public?int?dequeueInputBuffer?(long?timeoutUs)
          5//?輸入緩沖區(qū)與流結(jié)束標(biāo)記排隊時,編解碼器將轉(zhuǎn)換為End-of-Stream子狀態(tài)
          6//?此時MediaCodec將不接受其他輸入緩沖區(qū),但會生成輸出緩沖區(qū)
          7public?void?queueInputBuffer?(int?index,?int?offset,?int?size,?long?presentationTimeUs,?int?flags)

          • 釋放狀態(tài)(Released)

          1//?編解碼完成結(jié)束后釋放MediaCodec進入釋放狀態(tài)(Released)
          2public?void?release?()

          MediaCodec的創(chuàng)建

          前面已經(jīng)提到過當(dāng)創(chuàng)建 MediaCodec 的時候進入Uninitialized 子狀態(tài),其創(chuàng)建方式如下:

          1//?創(chuàng)建MediaCodec
          2public?static?MediaCodec?createByCodecName?(String?name)
          3public?static?MediaCodec?createEncoderByType?(String?type)
          4public?static?MediaCodec?createDecoderByType?(String?type)
          5

          使用 createByCodecName 時可以借助 MediaCodecList 獲取支持的編解碼器,下面是獲取指定 MIME 類型的編碼器:

          1/**
          2?*?查詢指定MIME類型的編碼器
          3?*/

          4fun?selectCodec(mimeType:?String):?MediaCodecInfo??{
          5????val?mediaCodecList?=?MediaCodecList(MediaCodecList.REGULAR_CODECS)
          6????val?codeInfos?=?mediaCodecList.codecInfos
          7????for?(codeInfo?in?codeInfos)?{
          8????????if?(!codeInfo.isEncoder)?continue
          9????????val?types?=?codeInfo.supportedTypes
          10????????for?(type?in?types)?{
          11????????????if?(type.equals(mimeType,?true))?{
          12????????????????return?codeInfo
          13????????????}
          14????????}
          15????}
          16????return?null
          17}

          當(dāng)然 MediaCodecList 也提供了相應(yīng)的獲取編解碼器的方法,如下:

          1//?獲取指定格式的編碼器
          2public?String?findEncoderForFormat?(MediaFormat?format)
          3//?獲取指定格式的解碼器
          4public?String?findDecoderForFormat?(MediaFormat?format)
          5

          對于上述方法中的參數(shù) MediaFormat 格式中不能包含任何幀率的設(shè)置,如果已經(jīng)設(shè)置了幀率需要將其清除再使用。

          上面提到了 MediaCodecList,這里簡單說一下,使用 MediaCodecList 可以方便的列出當(dāng)前設(shè)備支持的所有的編解碼器,創(chuàng)建 MediaCodec 的時候要選擇當(dāng)前格式支持的編解碼器,也就是選擇的編解碼器需支持對應(yīng)的 MediaFormat,每個編解碼器都被包裝成一個 MediaCodecInfo 對象,據(jù)此可以查看該編碼器的一些特性,比如是否支持硬件加速、是軟解還是硬解編解碼器等,常用的簡單如下:

          1//?是否軟解
          2public?boolean?isSoftwareOnly?()
          3//?是Android平臺提供(false)還是廠商提供(true)的編解碼器
          4public?boolean?isVendor?()
          5//?是否支持硬件加速
          6public?boolean?isHardwareAccelerated?()
          7//?是編碼器還是解碼器
          8public?boolean?isEncoder?()
          9//?獲取當(dāng)前編解碼器支持的合適
          10public?String[]?getSupportedTypes?()
          11//?...

          軟解和硬解應(yīng)該是音視頻開發(fā)中必須掌握的,當(dāng)使用 MediaCodec 的時候不能說全是硬解,到底使用硬解還是軟解還是要看使用的編碼器,一般廠商提供的編解碼器都是硬解編解碼器,比如高通(qcom)等,一般如系統(tǒng)提供的則是軟解編解碼器,如帶有 android 字樣的編解碼器,下面是本人(MI 10 Pro)自己手機的部分編解碼器:

          1//?硬解編解碼器
          2OMX.qcom.video.encoder.heic
          3OMX.qcom.video.decoder.avc
          4OMX.qcom.video.decoder.avc.secure
          5OMX.qcom.video.decoder.mpeg2
          6OMX.google.gsm.decoder
          7OMX.qti.video.decoder.h263sw
          8c2.qti.avc.decoder
          9...
          10//?軟解編解碼器
          11c2.android.aac.decoder
          12c2.android.aac.decoder
          13c2.android.aac.encoder
          14c2.android.aac.encoder
          15c2.android.amrnb.decoder
          16c2.android.amrnb.decoder
          17...

          MediaCodec初始化

          創(chuàng)建 MediaCodec 之后進入 Uninitialized 子狀態(tài),此時需要對其進行一些設(shè)置如指定 MediaFormat、如果使用的是異步處理數(shù)據(jù)的方式,在 configure 之前要設(shè)置 MediaCodec.Callback,關(guān)鍵 API 如下:

          1//?1.?MediaFormat
          2//?創(chuàng)建MediaFormat
          3public?static?final?MediaFormat?createVideoFormat(String?mime,int?width,int?height)
          4//?開啟或關(guān)閉功能,具體參見MediaCodeInfo.CodecCapabilities
          5public?void?setFeatureEnabled(@NonNull?String?feature,?boolean?enabled)
          6//?參數(shù)設(shè)置
          7public?final?void?setInteger(String?name,?int?value)
          8
          9//?2.?setCallback
          10//?如果使用的是異步處理數(shù)據(jù)的方式,在configure?之前要設(shè)置?MediaCodec.Callback
          11public?void?setCallback?(MediaCodec.Callback?cb)
          12public?void?setCallback?(MediaCodec.Callback?cb,?Handler?handler)
          13
          14//?3.?配置
          15public?void?configure(MediaFormat?format,?Surface?surface,?MediaCrypto?crypto,?int?flags)
          16public?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)容進行解密,要不然某些需要付費才能觀看的視頻下載之后就能隨意傳播了,更多細節(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 的方式進行配置,如下:

          1//?csd-0、csd-1、csd-2同理
          2val?bytes?=?byteArrayOf(0x00.toByte(),?0x01.toByte())
          3mediaFormat.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”的起始代碼開頭,參考如下:

          編碼器在收到這些信息后將會同樣輸出帶有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 的方式進行數(shù)據(jù)的處理,在此之前只能使用 ButeBuffer ?數(shù)組的方式進行數(shù)據(jù)的處理,如下:

          MediaCodec,也就是編解碼器的數(shù)據(jù)處理,主要是獲取輸入、輸出緩沖區(qū)、提交數(shù)據(jù)給編解碼器、釋放輸出緩沖區(qū)這幾個過程,同步方式和異步方式的不同點在于輸入緩沖區(qū)和輸出緩沖區(qū)的其關(guān)鍵 API 如下:

          1//?獲取輸入緩沖區(qū)(同步)
          2public?int?dequeueInputBuffer?(long?timeoutUs)
          3public?ByteBuffer?getInputBuffer?(int?index)
          4//?獲取輸出緩沖區(qū)(同步)
          5public?int?dequeueOutputBuffer?(MediaCodec.BufferInfo?info,?long?timeoutUs)
          6public?ByteBuffer?getOutputBuffer?(int?index)
          7//?輸入、輸出緩沖區(qū)索引從MediaCodec.Callback的回調(diào)中獲取,在獲取對應(yīng)的輸入、輸出緩沖區(qū)(異步)
          8public?void?setCallback?(MediaCodec.Callback?cb)
          9public?void?setCallback?(MediaCodec.Callback?cb,?Handler?handler)
          10//?提交數(shù)據(jù)
          11public?void?queueInputBuffer?(int?index,?int?offset,?int?size,?long?presentationTimeUs,?int?flags)
          12public?void?queueSecureInputBuffer?(int?index,?int?offset,?MediaCodec.CryptoInfo?info,?long?presentationTimeUs,?int?flags)
          13//?釋放輸出緩沖區(qū)
          14public?void?releaseOutputBuffer?(int?index,?boolean?render)
          15public?void?releaseOutputBuffer?(int?index,?long?renderTimestampNs)
          16

          下面主要介紹介紹適用于 Android 5.0 之后的 ButeBuffer 的方式,

          Android 5.0 開始 Deprecated 了 ButeBuffer 數(shù)組的方式,官網(wǎng)上提到 ButeBuffer 相較 ButeBuffer 數(shù)組的方式做了一定優(yōu)化,故在設(shè)備滿足條件的情況下盡量使用 ButeBuffer 對應(yīng)的 API,且推薦使用異步模式處理數(shù)據(jù),同步和異步處理方式代碼參考如下:
          • 同步處理模式
          1MediaCodec?codec?=?MediaCodec.createByCodecName(name);
          2?codec.configure(format,?…);
          3?MediaFormat?outputFormat?=?codec.getOutputFormat();?//?option?B
          4?codec.start();
          5?for?(;;)?{
          6??int?inputBufferId?=?codec.dequeueInputBuffer(timeoutUs);
          7??if?(inputBufferId?>=?0)?{
          8????ByteBuffer?inputBuffer?=?codec.getInputBuffer(…);
          9????//?使用有效數(shù)據(jù)填充輸入緩沖區(qū)
          10????…
          11????codec.queueInputBuffer(inputBufferId,?…);
          12??}
          13??int?outputBufferId?=?codec.dequeueOutputBuffer(…);
          14??if?(outputBufferId?>=?0)?{
          15????ByteBuffer?outputBuffer?=?codec.getOutputBuffer(outputBufferId);
          16????MediaFormat?bufferFormat?=?codec.getOutputFormat(outputBufferId);?//?option?A
          17????//?bufferFormat與outputFormat是相同的
          18????//?輸出緩沖區(qū)已準(zhǔn)備后被處理或渲染了
          19????…
          20????codec.releaseOutputBuffer(outputBufferId,?…);
          21??}?else?if?(outputBufferId?==?MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)?{
          22????//?輸出格式改變,后續(xù)采用新格式,此時使用getOutputFormat()獲取新格式
          23????//?如果使用getOutputFormat(outputBufferId)獲取特定緩沖區(qū)的格式,則無需監(jiān)聽格式變化
          24????outputFormat?=?codec.getOutputFormat();?//?option?B
          25??}
          26?}
          27?codec.stop();
          28?codec.release();
          具體可以參考上一篇文章中的案例:Camera2、MediaCodec錄制mp4
          • 異步處理模式
          1MediaCodec?codec?=?MediaCodec.createByCodecName(name);
          2?codec.configure(format,?…);
          3?MediaFormat?outputFormat?=?codec.getOutputFormat();?//?option?B
          4?codec.start();
          5?for?(;;)?{
          6??int?inputBufferId?=?codec.dequeueInputBuffer(timeoutUs);
          7??if?(inputBufferId?>=?0)?{
          8????ByteBuffer?inputBuffer?=?codec.getInputBuffer(…);
          9????//?使用有效數(shù)據(jù)填充輸入緩沖區(qū)
          10????…
          11????codec.queueInputBuffer(inputBufferId,?…);
          12??}
          13??int?outputBufferId?=?codec.dequeueOutputBuffer(…);
          14??if?(outputBufferId?>=?0)?{
          15????ByteBuffer?outputBuffer?=?codec.getOutputBuffer(outputBufferId);
          16????MediaFormat?bufferFormat?=?codec.getOutputFormat(outputBufferId);?//?option?A
          17????//?bufferFormat與outputFormat是相同的
          18????//?輸出緩沖區(qū)已準(zhǔn)備后被處理或渲染了
          19????…
          20????codec.releaseOutputBuffer(outputBufferId,?…);
          21??}?else?if?(outputBufferId?==?MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)?{
          22????//?輸出格式改變,后續(xù)采用新格式,此時使用getOutputFormat()獲取新格式
          23????//?如果使用getOutputFormat(outputBufferId)獲取特定緩沖區(qū)的格式,則無需監(jiān)聽格式變化
          24????outputFormat?=?codec.getOutputFormat();?//?option?B
          25??}
          26?}
          27?codec.stop();
          28?codec.release();
          當(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 如下:
          1//?創(chuàng)建輸入Surface,需在configure之后、start之前調(diào)用
          2public?Surface?createInputSurface?()
          3//?設(shè)置輸入Surface
          4public?void?setInputSurface?(Surface?surface)
          5//?發(fā)送流結(jié)束的信號
          6public?void?signalEndOfInputStream?()
          7

          同理如果使用了輸出 Surface,則與之相關(guān)的輸出緩沖區(qū)的相關(guān)功能將會被代替,可以通過 setOutputSurface 設(shè)置一個 Surface 作為編解碼器的輸出,可以選擇是否在輸出 Surface 上渲染每一個輸出緩沖區(qū),關(guān)鍵 API 如下:
          1//?設(shè)置輸出Surface
          2public?void?setOutputSurface?(Surface?surface)
          3//?false表示不渲染這個buffer,true表示使用默認的時間戳渲染這個buffer
          4public?void?releaseOutputBuffer?(int?index,?boolean?render)
          5//?使用指定的時間戳渲染這個buffer
          6public?void?releaseOutputBuffer?(int?index,?long?renderTimestampNs)
          7

          自適應(yīng)播放支持

          當(dāng) MediaCodec 作為視頻解碼器的時候,可以通過如下方式檢查解碼器是否支持自適應(yīng)播放,也就是此時解碼器是否支持無縫的分辨率修改:
          1//?是否支持某項功能,CodecCapabilities#FEATURE_AdaptivePlayback對應(yīng)對應(yīng)自適應(yīng)播放支持
          2public?boolean?isFeatureSupported?(String?name)
          3

          此時只有在將解碼器配置在 Surface 上解碼時,自適應(yīng)播放的功能才會被激活,視頻解碼時當(dāng) strat 或 flush 調(diào)用后,只有關(guān)鍵幀(key-frame)才能完全獨立解碼,也就是通常說的 I 幀,其他幀都是據(jù)此來解碼的,不同格式對應(yīng)關(guān)鍵幀如下:
          不同的解碼器對自適應(yīng)播放的支持能力不同,其 seek 操作后處理也是不同,這部分內(nèi)容暫時留到后續(xù)具體實踐后再做整理。

          MediaCodec的異常處理

          關(guān)于 MediaCodec 使用過程中的異常處理,這里提一下 CodecException 異常,一般是由編解碼器內(nèi)部異常導(dǎo)致的,比如媒體內(nèi)容損壞、硬件故障、資源耗盡等,可以通過如下方法判斷以做進一步的處理:
          1//?true表示可以通過stop、configure、start來恢復(fù)
          2public?boolean?isRecoverable?()
          3//?true表示暫時性問題,編碼或解碼操作會在后續(xù)重試進行
          4public?boolean?isTransient?()

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


          技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。

          推薦閱讀:

          音視頻面試基礎(chǔ)題

          OpenGL ES 學(xué)習(xí)資源分享

          開通專輯 | 細數(shù)那些年寫過的技術(shù)文章專輯

          NDK 學(xué)習(xí)進階免費視頻來了

          推薦幾個堪稱教科書級別的 Android 音視頻入門項目

          覺得不錯,點個在看唄~


          瀏覽 98
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色黄视频在线观看 | AV天堂区 | 国产伦子伦一级A片免费看老牛 | 四虎黄色网址 | 五月天婷婷丁香网 |