原文:https://mp.weixin.qq.com/s?__biz=MzU3NTA3MDU1OQ==&mid=2247484865&idx=1&sn=174b8ca702466e83e72c7115d91b06ea&chksm=fd298df1ca5e04e7b2df9dc9f21e5cfe3e910204c905d8605f648ce6f6404432a83ae52a23a3&scene=178&cur_album_id=1638784435628064770#rd
MediaCodec 支持處理三種數(shù)據(jù)類型,分別是壓縮數(shù)據(jù)(compressed data)、原始音頻數(shù)據(jù)(raw audio data)、原始視頻數(shù)據(jù)(raw video data),可以使用 ByteBuffer 處理這三種數(shù)據(jù),也就是后文中提到的緩沖區(qū), 可以使用 Surface 來提高編解碼器性能,可以通過 ImageReader 訪問原始視頻幀,不能直接訪問原始視頻數(shù)據(jù),通過 Image 進而獲取到與之對應的 YUV 數(shù)據(jù)等其他信息。
壓縮緩沖區(qū):用于解碼器的輸入緩沖區(qū)和用于編碼器的輸出緩沖區(qū)會包含 MediaFormat 的 KEY_MIME 對應類型的壓縮數(shù)據(jù),對于視頻類型,通常是單個壓縮視頻幀,對于音頻數(shù)據(jù),這通常是一個編碼的音頻段,通常包含幾毫秒的音頻,因格式類型而定。
原始視頻緩沖區(qū):
格式:在 ByteBuffer 模式下,視頻緩沖區(qū)根據(jù)其 MediaFormat 的 KEY_COLOR_FORMAT 設置的值進行布局,可以從通過 MediaCodecInfo 相關方法獲取設備受支持的顏色格式,視頻編解碼器可能支持三種顏色格式:
native raw video format:原始原始視頻格式,由CodecCapabilities 的 COLOR_FormatSurface 常量標記,可以與輸入或輸出Surface一起使用。
flexible YUV buffers:靈活的 YUV 緩沖區(qū),如 CodecCapabilities 的 COLOR_FormatYUV420Flexible 常量對應的顏色格式,可以通過 getInput、OutputImage 等于與輸入、輸出 Surface 以及 ByteBuffer 模式一起使用。
other specific formats:其他特定格式:通常僅在 ByteBuffer 模式下支持這些格式, 某些顏色格式是特定于供應商的,其他在均在 CodecCapabilities 中定義。
自 Android 5.1 開始,所有視頻編解碼器均支持靈活的 YUV 4:2:0 緩沖區(qū)。
視頻幀大小于緩沖區(qū)幀大小關系:
編解碼器的MediaFormat#KEY_WIDTH 和 MediaFormat#KEY_HEIGHT 鍵指定視頻幀的大小,在大多數(shù)情況下,視頻僅占據(jù)視頻幀的一部分,具體表示如下:
需要使用以下鍵從輸出格式獲取原始輸出圖像的裁剪矩形,如果輸出格式中不存在這些鍵,則視頻將占據(jù)整個視頻幀,在使用任何 MediaFormat#KEY_ROTATION 之前,也就是在設置旋轉之前,可以使用下面的方式計算視頻幀的大小,參考如下:
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ù),比如解碼視頻用的MediaExtractor.readSampleData(inputBuffer, 0)
再將填充數(shù)據(jù)的輸入緩沖區(qū)送到 MediaCodec
MediaCodec處理數(shù)據(jù),就是編碼或者解碼
MediaCodec處理完數(shù)據(jù)后將數(shù)據(jù)填充到緩沖區(qū),然后釋放輸入緩沖區(qū)
最后客戶端獲取已經(jīng)編碼或解碼的輸出緩沖區(qū),使用完畢后釋放輸出緩沖區(qū),
其編解碼的流程示意圖如下:
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),則需再次調用 start 進入 Running 子狀態(tài),下面是各個子狀態(tài)切換對應的關鍵 API 如下:
停止狀態(tài)(Stopped)
停止狀態(tài)(Stopped)
// 創(chuàng)建MediaCodec進入Uninitialized子狀態(tài)
public static MediaCodec createByCodecName (String name)
public static MediaCodec createEncoderByType (String type)
public static MediaCodec createDecoderByType (String type)
// 配置MediaCodec進入Configured子狀態(tài),crypto和descrambler會在后文中進行說明
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags)
public void configure(MediaFormat format, @Nullable Surface surface,int flags, MediaDescrambler descrambler)
// Error
// 編解碼過程中遇到錯誤進入Error子狀態(tài)
配置成功之后進入 執(zhí)行狀態(tài)(Executing) 然后start
// start之后立即進入Flushed子狀態(tài)
public final void start()
// 客戶端獲取第一個輸入緩沖區(qū)索引的時候進入Running子狀態(tài)
public int dequeueInputBuffer (long timeoutUs)
// 此時MediaCodec將不接受其他輸入緩沖區(qū),但會生成輸出緩沖區(qū)
// 將填滿數(shù)據(jù)的inputBuffer提交到編碼隊列 也是run
public void queueInputBuffer (int index, int offset, int size, long presentationTimeU)
// 遇到結束符,輸入緩沖區(qū)與流結束標記排隊時,編解碼器將轉換為End-of-Stream子狀態(tài)
釋放狀態(tài)(Released)
1// 編解碼完成結束后釋放MediaCodec進入釋放狀態(tài)(Released)
2public void release ()
MediaCodec的創(chuàng)建
前面已經(jī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}
當然 MediaCodecList 也提供了相應的獲取編解碼器的方法,如下:
1// 獲取指定格式的編碼器
2public String findEncoderForFormat (MediaFormat format)
3// 獲取指定格式的解碼器
4public String findDecoderForFormat (MediaFormat format)
5
對于上述方法中的參數(shù) MediaFormat 格式中不能包含任何幀率的設置,如果已經(jīng)設置了幀率需要將其清除再使用。
上面提到了 MediaCodecList,這里簡單說一下,使用 MediaCodecList 可以方便的列出當前設備支持的所有的編解碼器,創(chuàng)建 MediaCodec 的時候要選擇當前格式支持的編解碼器,也就是選擇的編解碼器需支持對應的 MediaFormat,每個編解碼器都被包裝成一個 MediaCodecInfo 對象,據(jù)此可以查看該編碼器的一些特性,比如是否支持硬件加速、是軟解還是硬解編解碼器等,常用的簡單如下:
1// 是否軟解
2public boolean isSoftwareOnly ()
3// 是Android平臺提供(false)還是廠商提供(true)的編解碼器
4public boolean isVendor ()
5// 是否支持硬件加速
6public boolean isHardwareAccelerated ()
7// 是編碼器還是解碼器
8public boolean isEncoder ()
9// 獲取當前編解碼器支持的合適
10public String[] getSupportedTypes ()
11// ...
軟解和硬解應該是音視頻開發(fā)中必須掌握的,當使用 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),此時需要對其進行一些設置如指定 MediaFormat、如果使用的是異步處理數(shù)據(jù)的方式,在 configure 之前要設置 MediaCodec.Callback,關鍵 API 如下:
1// 1. MediaFormat
2// 創(chuàng)建MediaFormat
3public static final MediaFormat createVideoFormat(String mime,int width,int height)
4// 開啟或關閉功能,具體參見MediaCodeInfo.CodecCapabilities
5public void setFeatureEnabled(@NonNull String feature, boolean enabled)
6// 參數(shù)設置
7public final void setInteger(String name, int value)
8
// 2. setCallback
10// 如果使用的是異步處理數(shù)據(jù)的方式,在configure 之前要設置 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)
- 此外某些特定格式比如 AAC 音頻以及 MPEG4、H.264、H.265 視頻格式,這些格式包含一些用于 MediaCodec 的初始化特定的數(shù)據(jù),當解碼處理這些壓縮格式時,必須在 start 之后且在任何幀數(shù)據(jù)處理之前將這些特定數(shù)據(jù)提交給 MediaCodec,即在對 queueInputBuffer 的調用中使用標志 BUFFER_FLAG_CODEC_CONFIG 標記此類數(shù)據(jù),這些特定的數(shù)據(jù)也可以通過 MediaFormat 設置 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ū)或格式更改之前調用了 flush,則會丟失提交的特定數(shù)據(jù),就需要在 queueInputBuffer 的調用中使用標志 BUFFER_FLAG_CODEC_CONFIG 標記這類數(shù)據(jù)。
Android 使用以下特定于編解碼器的數(shù)據(jù)緩沖區(qū),為了正確配置 MediaMuxer 軌道,還需要將它們設置為軌道格式,每個參數(shù)集和標有(*)的編解碼器專用數(shù)據(jù)部分必須以“ \ x00 \ x00 \ x00 \ x01”的起始代碼開頭,參考如下:
編碼器在收到這些信息后將會同樣輸出帶有BUFFER_FLAG_CODEC_CONFIG標記的 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ù)的處理。
MediaCodec,也就是編解碼器的數(shù)據(jù)處理,主要是獲取輸入、輸出緩沖區(qū)、提交數(shù)據(jù)給編解碼器、釋放輸出緩沖區(qū)這幾個過程,同步方式和異步方式的不同點在于輸入緩沖區(qū)和輸出緩沖區(qū)的其關鍵 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的回調中獲取,在獲取對應的輸入、輸出緩沖區(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
處理數(shù)據(jù)的結束
當要處理的數(shù)據(jù)結束時(End-of-stream),需要標記流的結束,可以在最后一個有效的輸入緩沖區(qū)上使用 queueInputBuffer 提交數(shù)據(jù)的時候指定 flags 為 BUFFER_FLAG_END_OF_STREAM 標記其結束,也可以在最后一個有效輸入緩沖區(qū)之后提交一個空的設置了流結束標志的輸入緩沖區(qū)來標記其結束,此時不能夠再提交輸入緩沖區(qū),除非編解碼器被 flush、stop、restart,輸出緩沖區(qū)繼續(xù)返回直到最終通過在 dequeueOutputBuffer 或通過 Callback#onOutputBufferAvailable 返回的 BufferInfo 中指定相同的流結束標志,最終通知輸出流結束為止。
如果使用了一個輸入 Surface 作為編解碼器的輸入,此時沒有可訪問的輸入緩沖區(qū),輸入緩沖區(qū)會自動從這個 Surface 提交給編解碼器,相當于省略了輸入的這個過程,這個輸入 Surface 可由 createInputSurface 方法創(chuàng)建,此時調用 signalEndOfInputStream 將發(fā)送流結束的信號,調用后,輸入表面將立即停止向編解碼器提交數(shù)據(jù),關鍵 API 如下:
1// 創(chuàng)建輸入Surface,需在configure之后、start之前調用
2public Surface createInputSurface ()
3// 設置輸入Surface
4public void setInputSurface (Surface surface)
5// 發(fā)送流結束的信號
6public void signalEndOfInputStream ()
7
同理如果使用了輸出 Surface,則與之相關的輸出緩沖區(qū)的相關功能將會被代替,可以通過 setOutputSurface 設置一個 Surface 作為編解碼器的輸出,可以選擇是否在輸出 Surface 上渲染每一個輸出緩沖區(qū),關鍵 API 如下:
1// 設置輸出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)
自適應播放支持
當 MediaCodec 作為視頻解碼器的時候,可以通過如下方式檢查解碼器是否支持自適應播放,也就是此時解碼器是否支持無縫的分辨率修改:
1// 是否支持某項功能,CodecCapabilities#FEATURE_AdaptivePlayback對應對應自適應播放支持
2public boolean isFeatureSupported (String name)
3
此時只有在將解碼器配置在 Surface 上解碼時,自適應播放的功能才會被激活,視頻解碼時當 strat 或 flush 調用后,只有關鍵幀(key-frame)才能完全獨立解碼,也就是通常說的 I 幀,其他幀都是據(jù)此來解碼的,不同格式對應關鍵幀如下:
圖片
不同的解碼器對自適應播放的支持能力不同,其 seek 操作后處理也是不同,這部分內容暫時留到后續(xù)具體實踐后再做整理。
seeking和自適應播放支持
無論是否支持并配置為自適應播放,視頻解碼器(以及一些壓縮數(shù)據(jù)的編碼器)在seek和格式更改的行為都不同??梢酝ㄟ^CodecCapatilities.isFeatureSupported(String)檢查解碼器是否支持自適應播放。視頻解碼器的自適應播放只有在將codec配置到Surface上時,才會被激活。
流邊界和關鍵幀
重要的是,在start()或者flush()后輸入數(shù)據(jù)要在合適的流邊界開始:第一幀必須是關鍵幀。
關鍵幀可以通過自身被完全解碼(大多數(shù)codec的I幀),并且關鍵幀之后沒有幀要顯示指的是關鍵幀之前的幀。
下表對不同視頻格式合適的關鍵幀進行了總結:
不支持自適應播放的解碼器(包括不解碼到Surface)
為了開始解碼與之前提交數(shù)據(jù)不相鄰的數(shù)據(jù),必須flush解碼器。由于所有的輸出buffer在flush時被立即撤銷,所以需要首先發(fā)送信號,等到end-of-stream標志時再flush。重要的是,刷新后的輸入數(shù)據(jù)在合適的流邊界/關鍵幀開始。
注意,提交數(shù)據(jù)的格式在flush后必須不能更改,flush() 不支持不連續(xù)的格式。對于這種情況,一個完整的stop() - configure(…) - start() 的循環(huán)的必要的。
同時注意,如果你在start()后flush codec太頻繁 - 一般來說,在首次output buffer或output format變更被接收到時 - 你需要重新提交codec-specific-data到codec??梢圆榭瓷厦骊P于 codec-specific-data 一塊的介紹。
支持并配置自適應播放的解碼器
為了開始解碼與之前提交數(shù)據(jù)不相鄰的數(shù)據(jù)(如在seek后),不需要flush解碼器。這樣,輸入數(shù)據(jù)是不連續(xù)的,必須從一個合適的流邊界/關鍵幀開始。
對于某些視頻格式,即H.264,H.265,VP8和VP9,也可以改變畫面大小或配置中間流。為此,須將整個新的codec-specific configuration data與關鍵幀一起打包到單個buffer(包括任何起始代碼)中,并將其作為常規(guī)輸入buffer提交。
在圖像大小更改發(fā)生之后及在返回新尺寸的任何幀之前,可以從dequeueOutputBuffer或onOutputFormatChanged回調中獲得INFO_OUTPUT_FORMAT_CHANGED返回值。
就像codec-specific configuration data一樣,在更改圖片大小后不久,調用flush()時要小心,如果沒有收到圖片尺寸更改的確認,需要重新請求圖片大小。
MediaCodec的異常處理
關于 MediaCodec 使用過程中的異常處理,這里提一下 CodecException 異常,一般是由編解碼器內部異常導致的,比如媒體內容損壞、硬件故障、資源耗盡等,可以通過如下方法判斷以做進一步的處理:
1// true表示可以通過stop、configure、start來恢復
2public boolean isRecoverable ()
3// true表示暫時性問題,編碼或解碼操作會在后續(xù)重試進行
4public boolean isTransient ()
如果 isRecoverable 和 isTransient 都是返回 false,則需要通過 reset 或 release 操作釋放資源后重新工作,兩者不可能同時返回 true。關于 MediaCodec 的介紹到此為止。
ImageReader
Imagee reader 類允許應用程序直接訪問渲染到 Surface 中的圖像數(shù)據(jù)。
多個 Android 媒體 API 類接受 Surface 對象作為渲染的目標,包括 MediaPlayer、 MediaCodec、 CameraDevice、 ImageWriter 和 RenderScript 分配。每個源使用的圖像大小和格式各不相同,應該在針對特定 API 的文檔中進行檢查。比如 mediaCodec.configure(videoFormat, imageReader.getSurface(), null, 0);
將imageReader的surface表面?zhèn)鬟f給解碼器,作為解碼數(shù)據(jù)輸出位置。
官方文檔寫的:有效的大小和格式取決于圖像數(shù)據(jù)的來源。意思應該是通過ImageReader設置的大小和格式無效??
圖像數(shù)據(jù)封裝在 Image 對象中,可以同時訪問多個這樣的對象,最多可以訪問 maxImages 個對象,maxImages構造函數(shù)參數(shù)指定,應該盡量小,減少內存消耗。
通過 Surface 發(fā)送到 ImageReader 的新映像將排隊,直到通過 acquireLatestImage ()或 acquirenextage ()調用訪問為止。由于內存限制,如果 image reader 無法獲取并釋放 Images,則圖像源最終會在嘗試將 Images 渲染到 Surface 時停止或丟棄 Images
從ImageReader的隊列中獲取下一幅圖像。如果沒有可用的新圖像,則返回null。
構造方法
newInstance
為所需大小、格式和消費者使用標志的圖像創(chuàng)建新的讀取器。
maxImages參數(shù)確定可以同時從ImageReader獲取的圖像對象的最大數(shù)量。請求更多緩沖區(qū)會占用更多內存,因此只使用用例所需的最小數(shù)量是很重要的。
有效的大小和格式取決于圖像數(shù)據(jù)的來源。
**format and usage flag組合描述消費者端點將如何使用緩沖區(qū)。**例如,如果應用程序打算將圖像發(fā)送到MediaCodec或MediaRecorder進行硬件視頻編碼,則格式和使用標志組合需要是私有和硬件緩沖#使用#視頻#編碼。當ImageReader對象以有效大小和此類格式/使用標志組合創(chuàng)建時,應用程序可以將圖像發(fā)送到ImageWriter,該ImageWriter使用MediaCodec或MediaRecorder提供的輸入面創(chuàng)建。
如果格式是私有的,則創(chuàng)建的ImageReader將生成應用程序無法直接訪問的圖像。應用程序仍然可以從該ImageReader獲取圖像,并將其發(fā)送到相機進行重新處理,或通過ImageWriter接口發(fā)送到MediaCodec/MediaRecorder進行硬件視頻編碼。但是,getPlanes()將為私有格式的圖像返回一個空數(shù)組。應用程序可以通過調用getImageFormat()檢查現(xiàn)有讀取器的格式。
與使用其他格式(如YUV_420_888)的圖像閱讀器相比,當應用程序不需要訪問圖像數(shù)據(jù)時,使用專用格式的圖像閱讀器更高效。
請注意,并非ImageReader支持所有格式和使用標志組合。下面是ImageReader支持的組合(假設消費者端點支持這種圖像消費,例如硬件視頻編碼)。
acquireNextImage()
建議使用acquireNextImage()進行批處理/后臺處理。不正確地使用此功能可能會導致圖像顯示的延遲越來越大,然后出現(xiàn)完全停滯,似乎沒有新圖像出現(xiàn)。
如果已使用acquireNextImage()或acquireLatestImage()獲取了最大值,則此操作將因引發(fā)非法狀態(tài)異常而失敗。特別是,如果acquireNextImage()或acquireLatestImage()調用的序列大于maxImages,而不調用圖像#介于兩者之間,則會耗盡底層隊列。此時,將拋出IllegalStateException,直到使用Image#close發(fā)布更多圖像。文章來源:http://www.zghlxwxcb.cn/news/detail-400732.html
getSurface
公共表面getSurface()
獲取一個可用于為該ImageReader生成圖像的表面。
在將有效的圖像數(shù)據(jù)渲染到此曲面之前,acquireNextImage()方法將返回null。只有一個源可以同時向該曲面生成數(shù)據(jù),盡管一旦第一個源與曲面斷開連接,同一個曲面可以通過不同的API重用。
請注意,保持此方法返回的曲面對象不足以防止其父ImageReader被回收。從這個意義上說,曲面就像是對提供它的ImageReader的弱引用。文章來源地址http://www.zghlxwxcb.cn/news/detail-400732.html
到了這里,關于Android音視頻-MediaCodec的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!