1. 前言
在iOS中,AVFoundation是一個集視頻播放、播放緩存、視頻轉碼、圖層混合、混音、變調、變速等諸多功能的多媒體庫,在iOS短視頻SDK中,使用到了
AVFoundation的硬解和播放模塊,以下將介紹短視頻SDK中對這些模塊的應用實踐和遇到的問題以及解決方案。
2. 基本概念
- 解碼:將壓縮數據還原為未壓縮數據,關于利用VideoToolbox硬解H.264可以參考這篇文章;
- 編碼:將原始數據進行壓縮生成另一種格式;
- 轉碼:將已壓縮的視頻碼流轉換成另一種視頻碼流;
3. 問題及解決方案
在AVFoundation中提供了多層可用于播放的組件,例如AVPlayerViewController和AVPlayer,這些系統組件可以滿足視頻的基本播放功能,在項目中,我們采用了AVPlayer進行播放預覽,但使用中遇到不少問題,后改用AVAssetReader做解碼,對解碼后的數據進行處理后做預覽。下面介紹播放預覽中系統組件的一些使用注意事項。
3.1 MediaToolBox使用注意事項
在使用AVPlayer做音頻變調、混響的預覽時,用到了MTAudioProcessingTap
,該類的所有回調是以函數指針存放于結構體中:
typedef struct {
int version;
void* CM_NULLABLE clientInfo;
MTAudioProcessingTapInitCallback CM_NULLABLE init;
MTAudioProcessingTapFinalizeCallback CM_NULLABLE finalize;
MTAudioProcessingTapPrepareCallback CM_NULLABLE prepare;
MTAudioProcessingTapUnprepareCallback CM_NULLABLE unprepare;
MTAudioProcessingTapProcessCallback CM_NONNULL process;
} MTAudioProcessingTapCallbacks;
OC與C函數交互時,我們會在clientInfo
變量中存放OC對象,在C語言函數的回調方法里使用__bridge
的方式獲取OC對象。
當OC對象釋放時,MTAudioProcessingTap
的callback
才返回,在回調的C函數里面獲取到的clientInfo
就是野指針,crash就產生了。
在clientInfo
指向的對象被釋放時,需要保存已釋放的狀態,在回調里首先檢查該狀態,判斷當前對象是否釋放,以避免造成野指針訪問。
3.2 AVAssetReader使用注意事項
AVAssetReader可用于讀取AVAsset媒體資源的軌道數據,支持解碼、格式轉換、mix等操作。但注意事項也不少:
-
AVAssetReader不可重復調用
startReading
,當出現fail或complete狀態后也不能重復調用; - AVAssetReader做解碼的時候,切換后臺/來電會失去GPU權限,造成解碼失敗,AVAssetReader也變成fail狀態。異常打斷結束后,需要重啟reader,并確定reader重啟成功,否則需要retry;
-
AVAssetReader啟動后調用
seek
時,并不會很精準seek
到目標點,一般會比指定的時間早幾幀(AVPlayer的精準seek,也有同樣的問題),需要記錄seek
的目標時間點,如果seek
后讀取出的buffer攜帶的 pts比seek
的目標時間小,需要拋棄該數據; -
AVAssetReaderOutput不可重復添加,也不可在AssetReader調用
startReading
后添加; -
AVAssetReaderOutput不可在未添加前調用
copyNextSampleBuffer
; -
AVAssetReader釋放資源時,需要調用
cancelReading
來釋放 AVAsset資源,否則會出現fetch
不到該資源的問題; - AVAssetReader對文件視頻首幀非關鍵幀的視頻會解碼失敗,這說明AVAssetReader對文件格式要求很嚴格,不夠魯棒;
- AVAssetReader不支持m3u8文件,回出現讀取不到軌道信息的情況,如果需要解析HLS視頻,需要使用FFMpeg進行解封裝和VideoToolBox解碼;
- AVAssetReader內部創建了解碼器和緩存列表,但解碼器數量是有限制的(同AVPlayerItem)。
當然AVAssetReader 只做demux,不做解碼工作時可以避免上述一些問題,但需要自行使用VideoToolBox進行硬解,pixel format轉換也得單獨處理。
3.3 AudioQueue使用注意事項
AudioQueue可進行音頻播放,開播前會預緩存一定數量的buffer數據。在allocate buffer
時,需要設置buffer的大小,該大小需要根據audio data format來設置,正常播放沒有問題,但播放速度非1.0的情況下,buffer太小時或太大,都會有異常的問題,需要考慮的有mBytesPerFrame
、mChannelsPerFrame
以及mSampleRate
。
而解碼后得到的音頻frame buffer中采樣數并不固定,當多音頻播放時,需要考慮是否存在audio data format變化的問題。
當然,每次切換音頻,重啟AudioQueue也是一種方案。
AudioQueue的數據獲取采用的是pull
模式。在
AudioQueueOutputCallback
的回調中,需要Enqueue
待緩存的AudioQueueBufferRef
。
當Enqueue
的時候,可能會觸發AudioQueueStop
或者AudioQueueDispose
,盡管inImmediate
設置為true
,也會造成假死一段時間,需要在 AudioQueueOutputCallback
的回調函數中先檢查狀態是否需要停止,如果為正常狀態,則Enqueue buffer
,否則flush
掉當前 AudioQueue的數據。
4. 結語
以上是iOS短視頻使用到的AVFoundation組件時遇到的問題,在 金山云多媒體SDK中硬編直接使用VideoToolBox做編碼,避免了一些AVAssetWriter的問題,此處未做贅述。
以上是遇到的一些問題,歡迎指正。