1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        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)文章如下:

        下面將從以下幾個(gè)方面介紹 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 進(jìn)而獲取到與之對應(yīng)的 YUV 數(shù)據(jù)等其他信息。

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

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

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

        檢查 MediaFormat 中的浮點(diǎn) 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ū)的一個(gè)通道,可以使用以下代碼:

        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è)置的值進(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ù)視頻幀的一部分,具體表示如下:

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

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

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

        1//?獲取可用的輸入緩沖區(qū)的索引
        2public?int?dequeueInputBuffer?(long?timeoutUs)
        3//?獲取輸入緩沖區(qū)
        4public?ByteBuffer?getInputBuffer(int?index)
        5//?將填滿數(shù)據(jù)的inputBuffer提交到編碼隊(duì)列
        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í)行和停止分別有三個(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ù)方式的不同,其生命周期會略有不同,如在異步模式下 start 之后立即進(jìn)入 Running 子狀態(tài),如果已經(jīng)處于 Flushed 子狀態(tài),則需再次調(diào)用 start 進(jìn)入 Running 子狀態(tài),下面是各個(gè)子狀態(tài)切換對應(yīng)的關(guān)鍵 API 如下:
        • 停止?fàn)顟B(tài)(Stopped)

        1//?創(chuàng)建MediaCodec進(jìn)入U(xiǎn)ninitialized子狀態(tài)
        2public?static?MediaCodec?createByCodecName?(String?name)
        3public?static?MediaCodec?createEncoderByType?(String?type)
        4public?static?MediaCodec?createDecoderByType?(String?type)
        5//?配置MediaCodec進(jìn)入Configured子狀態(tài),crypto和descrambler會在后文中進(jìn)行說明
        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//?編解碼過程中遇到錯(cuò)誤進(jìn)入Error子狀態(tài)

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

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

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

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

        MediaCodec的創(chuàng)建

        前面已經(jīng)提到過當(dāng)創(chuàng)建 MediaCodec 的時(shí)候進(jìn)入U(xiǎn)ninitialized 子狀態(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 時(shí)可以借助 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 的時(shí)候要選擇當(dāng)前格式支持的編解碼器,也就是選擇的編解碼器需支持對應(yīng)的 MediaFormat,每個(gè)編解碼器都被包裝成一個(gè) 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 的時(shí)候不能說全是硬解,到底使用硬解還是軟解還是要看使用的編碼器,一般廠商提供的編解碼器都是硬解編解碼器,比如高通(qcom)等,一般如系統(tǒng)提供的則是軟解編解碼器,如帶有 android 字樣的編解碼器,下面是本人(MI 10 Pro)自己手機(jī)的部分編解碼器:

        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 之后進(jìn)入 Uninitialized 子狀態(tài),此時(shí)需要對其進(jìn)行一些設(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 配置中涉及到幾個(gè)參數(shù),其中 surface 表示解碼器要渲染的 Surface,flags 則是指定當(dāng)前編解碼器是作為編碼器還是解碼器來使用的,crypto 和 descrambler 都和解密有關(guān),比如某些 vip 視頻就需要特定的密鑰來配合解碼,只有用戶登錄校驗(yàn)后才會對視頻內(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,即在對 queueInputBuffer 的調(diào)用中使用標(biāo)志 BUFFER_FLAG_CODEC_CONFIG 標(biāo)記此類數(shù)據(jù),這些特定的數(shù)據(jù)也可以通過 MediaFormat 設(shè)置 ByteBuffer 的方式進(jìn)行配置,如下:

        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 時(shí)自動提交給 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è)置為軌道格式,每個(gè)參數(shù)集和標(biāo)有(*)的編解碼器專用數(shù)據(jù)部分必須以“ \ x00 \ x00 \ x00 \ x01”的起始代碼開頭,參考如下:

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

        MediaCodec,也就是編解碼器的數(shù)據(jù)處理,主要是獲取輸入、輸出緩沖區(qū)、提交數(shù)據(jù)給編解碼器、釋放輸出緩沖區(qū)這幾個(gè)過程,同步方式和異步方式的不同點(diǎn)在于輸入緩沖區(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ù)采用新格式,此時(shí)使用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ù)采用新格式,此時(shí)使用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é)束時(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ū)來標(biāo)記其結(jié)束,此時(shí)不能夠再提交輸入緩沖區(qū),除非編解碼器被 flush、stop、restart,輸出緩沖區(qū)繼續(xù)返回直到最終通過在 dequeueOutputBuffer 或通過 Callback#onOutputBufferAvailable 返回的 BufferInfo 中指定相同的流結(jié)束標(biāo)志,最終通知輸出流結(jié)束為止。
        如果使用了一個(gè)輸入 Surface 作為編解碼器的輸入,此時(shí)沒有可訪問的輸入緩沖區(qū),輸入緩沖區(qū)會自動從這個(gè) Surface 提交給編解碼器,相當(dāng)于省略了輸入的這個(gè)過程,這個(gè)輸入 Surface 可由 createInputSurface 方法創(chuàng)建,此時(shí)調(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è)置一個(gè) Surface 作為編解碼器的輸出,可以選擇是否在輸出 Surface 上渲染每一個(gè)輸出緩沖區(qū),關(guān)鍵 API 如下:
        1//?設(shè)置輸出Surface
        2public?void?setOutputSurface?(Surface?surface)
        3//?false表示不渲染這個(gè)buffer,true表示使用默認(rèn)的時(shí)間戳渲染這個(gè)buffer
        4public?void?releaseOutputBuffer?(int?index,?boolean?render)
        5//?使用指定的時(shí)間戳渲染這個(gè)buffer
        6public?void?releaseOutputBuffer?(int?index,?long?renderTimestampNs)
        7

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

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

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

        MediaCodec的異常處理

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

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


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

        推薦閱讀:

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

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

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

        NDK 學(xué)習(xí)進(jìn)階免費(fèi)視頻來了

        推薦幾個(gè)堪稱教科書級別的 Android 音視頻入門項(xiàng)目

        覺得不錯(cuò),點(diǎn)個(gè)在看唄~


        瀏覽 98
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产精品无码久久久免费 | 国产激情视频精品 | 精品人妻无码一区二区三区三级 | 无码黄色电影 | AV电影免费在线观看 | 三级免费毛片 | 婷婷五月丁香五月激情五月色播五月 | 水多多成人A片在线观看播放 | 亚洲欧洲天堂 | 99热自拍 |