AVFoundation 小結
概述
AVFoundation 是 Objective-C 中創建及編輯視聽媒體文件的幾個框架之一,其提供了檢查、創建、編輯或重新編碼媒體文件的接口,也使得從設備獲取的視頻實時數據可操縱。但是,通常情況,簡單的播放或者錄像,直接使用 AVKit 框架或者 UIImagePickerController 類即可。另外,值得注意的是,在 AVFoundation 框架中使用的基本數據結構,如時間相關的或描述媒體數據的數據結構都聲明在 CoreMedia 框架中。
-
AVFoundation 框架包含視頻相關的接口以及音頻相關的接口,與音頻相關的類有 AVAudioPlayer、AVAudioRecorder、AVAudioSession。
-
AVFoundation 框架中最基本的類是 AVAsset ,它是一個或者多個媒體數據的集合,描述的是整個集合的屬性,如標題、時長、大小等,並且沒有特定的數據格式。集合的每一個媒體數據都是統一的數據類型,稱之為 track。簡單的情況是一種數據是音頻數據,一種是視頻數據,而較復雜的情況是一種數據交織着音頻和視頻數據,並且 AVAsset 是可能有元數據的。
另外,需要明白的是在 AVFoundation 中,初始化了 asset 及 track 后,並不意味着資源已經可用,因為若資源本身並不攜帶自身信息時,那么系統需要自己計算相關信息,這個過程會阻塞線程,所以應該使用異步方式進行獲取資源信息后的操作。
-
AVFoundation 提供了豐富的方法來管理視聽資源的播放,為了支持這些方法,它將描述 asset 的狀態與 asset 本身分離,這就使得在同一個時刻,以不同的方式播放同一個 asset 中的不同的媒體數據變得可能。對於 asset 的狀態是由 player 管理的,而 asset 中的 track 的狀態是由 player tracker 管理的。使用這兩個狀態管理對象,可以實現諸如設置 asset 中視頻部分的大小、設置音頻的混合參數及與視頻的合成或者將 asset 中的某些媒體數據置為不可用。
另外,還可以通過 player 將輸出定位到 Core Animation 層中,或通過播放隊列設置 player 集合的播放順序。
-
AVFoundation 提供了多種方法來創建 asset ,可以簡單的重編碼已經存在的 asset ,這個過程可以使用 export session 或者使用 asset reader 和 asset writer 。
-
若要生成視頻的縮略圖,可以使用 asset 初始化一個 AVAssetImageGenerator 實例對象,它會使用默認可用的視頻 tracks 來生成圖片。
-
AVFoundation 中可以使用 compositions 將多個媒體數據(video/audio tracks)合成為一個 asset ,這個過程中,可以添加或移除 tracks ,調整它們的順序,或者設置音頻的音量和變化坡度,視頻容量等屬性。這些媒體數據的集合保存在內存中,直到使用 export session 將它導出到本地文件中。另外,還可以使用 asset writer 創建 asset 。
-
使用 capture session 協調從設備(如相機、麥克風)輸入的數據和輸出目標(如視頻文件)。可以為 session 設置多個輸入和輸出,即使它正在工作,還可以通過它停止數據的流動。另外,還可以使用 preview layer 將相機記錄的影像實時展示給用戶。
-
在 AVFoundation 中的回調處理並不保證回調任務在某個特定的線程或隊列中執行,其遵循兩個原則,UI 相關的操作在主線程中執行,其他回調需要為其指定調用的隊列。
基本類
AVAsset
創建 AVAsset 或其子類 AVURLAsset 時,需要提供資源的位置,方法如下:
NSURL *url = <#視聽資源的 URL ,可以是本地文件地址,也可以是網頁媒體鏈接#>; AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
上述方法的第二個參數是創建對象時的選擇項,其中可能包含的選擇項如下:
- AVURLAssetPreferPreciseDurationAndTimingKey 是否需要資源的准確時長,及訪問資源各個准確的時間點
- AVURLAssetReferenceRestrictionsKey 鏈接其他資源的約束
- AVURLAssetHTTPCookiesKey 添加資源能夠訪問的 HTTP cookies
- AVURLAssetAllowsCellularAccessKey 是否能夠使用蜂窩網絡
創建並初始化一個 AVAsset 實例對象后,並不意味着該對象的所有屬性都可以獲取使用了,因為其中的一些屬性需要額外的計算才能夠得到,那么當獲取這些屬性時,可能會阻塞當前線程,所以需要異步獲取這些屬性。
AVAsset 與 AVAssetTrack 都遵循 AVAsynchronousKeyValueLoading 協議,這個協議中有以下兩個方法:
//獲取指定屬性的狀態 - (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError; //異步加載指定的屬性集合 - (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;
通常,我們使用上述第二個方法異步加載想要的屬性,而后在加載完成的回調 block 中使用第一個方法判斷屬性是否加載成功,然后訪問想要的屬性,執行自己的操作,如下代碼:
NSURL *url = <#資源路徑#>; AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil]; NSArray *keys = @[@"duration",@"tracks"]; [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() { NSError *error = nil; AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"tracks" error:&error]; //根據相應的屬性狀態進行對應的處理 switch (tracksStatus) { case AVKeyValueStatusUnknown: //TODO break; case AVKeyValueStatusLoading: //TODO break; case AVKeyValueStatusLoaded: //TODO break; case AVKeyValueStatusFailed: //TODO break; case AVKeyValueStatusCancelled: //TODO break; } }];
AVAssetImageGenerator
使用 AVAssetImageGenerator 生成視頻資源的縮略圖,使用 AVAsset 對象創建 AVAssetImageGenerator 對象,可以使用類方法或實例方法,如下:
+ (instancetype)assetImageGeneratorWithAsset:(AVAsset *)asset; - (instancetype)initWithAsset:(AVAsset *)asset NS_DESIGNATED_INITIALIZER;
當然,在此之前,最好調用 AVAsset 中的方法 - (NSArray<AVAssetTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;
來判斷 asset 中是否有可視媒體數據。如果有,那么再創建 AVAssetImageGenerator 對象,而后再調用下面的方法,來獲取一張或多張圖片。
//獲取一張圖片,requestedTime 指定要獲取視頻中哪個時刻的圖片,actualTime 返回圖片實際是視頻的哪個時刻,outError 返回錯誤信息 - (nullable CGImageRef)copyCGImageAtTime:(CMTime)requestedTime actualTime:(nullable CMTime *)actualTime error:(NSError * _Nullable * _Nullable)outError CF_RETURNS_RETAINED; //獲取多張圖片,每一次圖片生成后,都會調用一次 handler - (void)generateCGImagesAsynchronouslyForTimes:(NSArray<NSValue *> *)requestedTimes completionHandler:(AVAssetImageGeneratorCompletionHandler)handler; //上述 handler 的類型如下,回調中的參數有圖片的請求時刻和實際時刻,圖片,狀態(成功、失敗、取消),錯誤信息 typedef void (^AVAssetImageGeneratorCompletionHandler)(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error);
AVAssetExportSession
使用 AVAssetExportSession 類對視頻進行裁剪及轉碼,即將一個 AVAsset 類實例修改后保存為另一個 AVAsset 類實例,最后保存到文件中。
在修改資源之前,為避免不兼容帶來的錯誤,可以先調用下面的方法,檢查預設置是否合理。
//獲取與 asset 兼容的預設置 + (NSArray<NSString *> *)exportPresetsCompatibleWithAsset:(AVAsset *)asset; //判斷提供的預設置和輸出的文件類型是否與 asset 相兼容 + (void)determineCompatibilityOfExportPreset:(NSString *)presetName withAsset:(AVAsset *)asset outputFileType:(nullable NSString *)outputFileType completionHandler:(void (^)(BOOL compatible))handler NS_AVAILABLE(10_9, 6_0);
除了設置文件類型外,還可以設置文件的大小、時長、范圍等屬性,一切准備就緒后,調用方法:
- (void)exportAsynchronouslyWithCompletionHandler:(void (^)(void))handler;
進行文件的導出,導出結束后,會調用 handler 回調,在回調中應該檢查 AVAssetExportSession 的 status 屬性查看導出是否成功,若指定的文件保存地址在沙盒外,或在導出的過程中有電話打入都會導致文件保存失敗,如下例程:
- (void)exportVideo:(NSURL *)url { AVAsset *anAsset = [AVAsset assetWithURL:url]; [AVAssetExportSession determineCompatibilityOfExportPreset:AVAssetExportPresetHighestQuality withAsset:anAsset outputFileType:AVFileTypeMPEG4 completionHandler:^(BOOL compatible) { if (compatible){ AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:anAsset presetName:AVAssetExportPresetHighestQuality]; exportSession.outputFileType = AVFileTypeMPEG4; CMTime start = CMTimeMakeWithSeconds(1.0, 600); CMTime duration = CMTimeMakeWithSeconds(3.0, 600); CMTimeRange range = CMTimeRangeMake(start, duration); exportSession.timeRange = range; [exportSession exportAsynchronouslyWithCompletionHandler:^{ switch ([exportSession status]) { case AVAssetExportSessionStatusCompleted: NSLog(@"completed"); break; case AVAssetExportSessionStatusFailed: NSLog(@"failed"); break; case AVAssetExportSessionStatusCancelled: NSLog(@"canceled"); break; default: break; } }]; } }]; }
媒體資源播放
使用一個 AVPlayer 類實例可以管理一個 asset 資源,但是它的屬性 currentItem 才是 asset 的實際管理者。currentItem 是 AVPlayerItem 類的實例,而它的屬性 tracks 包含着的 AVPlayerItemTracker 實例對應着 asset 中的各個 track 。
那么,為了控制 asset 的播放,可以使用 AVPlayer 類,在播放的過程中,可以使用 AVPlayerItem 實例管理整個 asset 的狀態,使用 AVPlayerItemTracker 對象管理 asset 中每個 track 的狀態。另外,還可以使用 AVPlayerLayer 類來顯示播放的內容。
所以,在創建 AVPlayer 實例對象時,除了可以直接傳遞資源文件的路徑進行創建外,還可以傳遞 AVPlayerItem 的實例對象,如下方法:
+ (instancetype)playerWithURL:(NSURL *)URL; + (instancetype)playerWithPlayerItem:(nullable AVPlayerItem *)item; - (instancetype)initWithURL:(NSURL *)URL; - (instancetype)initWithPlayerItem:(nullable AVPlayerItem *)item;
創建后,並不是可以直接使用,還要對它的狀態進行檢查,只有 status 的值為 AVPlayerStatusReadyToPlay 時,才能進行播放,所以這里需要使用 KVO 模式對該狀態進行監控,以決定何時可以進行播放。
若要管理多個資源的播放,則應使用 AVPlayer 的子類 AVQueuePlayer ,這個子類擁有的多個 AVPlayerItem 同各個資源相對應。
不同類型的 asset
對於播放不同類型的資源,需要進行的准備工作有所不同,這主要取決於資源的來源。資源數據可能來自本地設備上文件的讀取,也可能來自網絡上數據流。
對於本地文件,可以使用文件地址創建 AVAsset 對象,而后使用該對象創建 AVPlayerItem 對象,最后將這個 item 對象與 AVPlayer 對象相關聯。之后,便是等待 status 的狀態變為 AVPlayerStatusReadyToPlay ,便可以進行播放了。
對於網絡數據的播放,不能使用地址創建 AVAsset 對象了,而是直接創建 AVPlayerItem 對象,將其同 AVPlayer 對象相關聯,當 status 狀態變為 AVPlayerStatusReadyToPlay 后,AVAsset 和 AVAssetTrack 對象將由 item 對象創建。
播放控制
通過調用 player 的 play 、pause 、setRate: 方法,可以控制 item 的播放,這些方法都會改變 player 的屬性 rate 的值,該值為 1 表示 item 按正常速率播放,為 0 表示 item 暫停播放,0~1 表示低速播放,大於 1 表示高速播放,小於 0 表示從后向前播放。
item 的屬性 timeControlStatus 的值表示當前 item 的狀態,有下面 3 個值:
- AVPlayerTimeControlStatusPaused 暫停
- AVPlayerTimeControlStatusPlaying 播放
- AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate 等待按指定速率播放狀態,該狀態是當 rate 的值設置為非 0 值時,而 item 因某些原因還無法播放的情況,而無法播放的原因,可依通過 item 的 reasonForWaitingToPlay 屬性值查看。
item 的屬性 actionAtItemEnd 的值表示當前 item 播放結束后的動作,有下面 3 個值:
- AVPlayerActionAtItemEndAdvance 只適用於 AVQueuePlayer 類,表示播放隊列中的下一個 item
- AVPlayerActionAtItemEndPause 表示暫停
- AVPlayerActionAtItemEndNone 表示無操作,當前 item 的 currentTime 屬性值仍然按 rate 的值改變
item 的 currentTime 屬性值表示當前 item 的播放時間,可以調用下面的方法指定 item 從何處進行播放。
//第二個方法能夠進行更准確的跳轉,但是需要進行額外的計算
- (void)seekToDate:(NSDate *)date; - (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter; (tolerance: 公差,前后公差) //這兩個方法傳入了一個回調,當一個時間跳轉請求被新的請求或其他操作打斷時,回調也會被執行但是此時 finished 參數值為 NO - (void)seekToTime:(CMTime)time completionHandler:(void (^)(BOOL finished))completionHandler NS_AVAILABLE(10_7, 5_0); - (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL finished))completionHandler NS_AVAILABLE(10_7, 5_0);
使用 AVQueuePlayer 管理多個 item 的播放,仍然可以通過調用 play 開始依次播放 item,調用 advanceToNextItem 方法播放下一個 item ,還可以通過下面的方法添加或移除 item 。
- (BOOL)canInsertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem; - (void)insertItem:(AVPlayerItem *)item afterItem:(nullable AVPlayerItem *)afterItem; - (void)removeItem:(AVPlayerItem *)item; - (void)removeAllItems;
可以使用下面的方法監聽播放時間的變化,需要強引用這兩個方法返回的監聽者。
- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block; - (id)addBoundaryTimeObserverForTimes:(NSArray<NSValue *> *)times queue:(nullable dispatch_queue_t)queue usingBlock:(void (^)(void))block;
用上面的方法每注冊一個監聽者,就需要對應的使用下面的方法進行注銷,並且在注銷之前,要確保沒有 block 被執行。
- (void)removeTimeObserver:(id)observer;
當 item 播放結束后,再次調用 player 的方法 play 不會使 item 重新播放,要實現重播,可以注冊一個 AVPlayerItemDidPlayToEndTimeNotification 通知,當接收到這個通知時,可以調 seekToTime: 方法,傳入 kCMTimeZero 參數,將 player 的播放時間重置。
媒體資源編輯基本類
AVFoundation 框架中提供了豐富的接口用於視聽資源的編輯,其中的關鍵是 composition ,它將不同的 asset 相結合並形成一個新的 asset 。使用 AVMutableComposition 類可以增刪 asset 來將指定的 asset 集合到一起。除此之外,若想將集合到一起的視聽資源以自定義的方式進行播放,需要使用 AVMutableAudioMix 和 AVMutableVideoComposition類對其中的資源進行協調管理。最終要使用 AVAssetExportSession 類將編輯的內容保存到文件中。
AVComposition
同 AVAsset 擁有多個 AVAssetTrack 一樣,作為子類的 AVComposition 也擁有多個 AVCompositionTrack ,而 AVCompositionTrack 是 AVAssetTrack 的子類。所以,AVComposition 實例對象是多個 track 的集合,真正描述媒體屬性的是 AVCompositionTrack 實例對象。而 AVCompositionTrack 又是媒體數據片段的集合,這些數據片段由 AVCompositionTrackSegment 類進行描述。
該類的相關屬性和方法如下:
//獲取 composition 中包含的 tracks @property (nonatomic, readonly) NSArray<AVCompositionTrack *> *tracks; //獲取 composition 中可視媒體資源播放時在屏幕上顯示的大小 @property (nonatomic, readonly) CGSize naturalSize; //獲取 composition 生成 asset 時的指定配置 @property (nonatomic, readonly, copy) NSDictionary<NSString *, id> *URLAssetInitializationOptions NS_AVAILABLE(10_11, 9_0); //根據不同的參數,獲取 composition 中的 track - (nullable AVCompositionTrack *)trackWithTrackID:(CMPersistentTrackID)trackID; - (NSArray<AVCompositionTrack *> *)tracksWithMediaType:(NSString *)mediaType; - (NSArray<AVCompositionTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;
值得注意的是 AVComposition 類中並沒有提供初始化方法,一般我們使用它的子類 AVMutableComposition ,進行各種操作后,再生成 AVComposition 實例以供查詢,如下例程:
AVMutableComposition *mutableComposition = [AVMutableComposition composition]; //進行添加資源等操作 <#····#> //使用可變的 composition 生成一個不可變的 composition 以供使用 AVComposition *composition = [myMutableComposition copy]; AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:composition];
AVMutableComposition
AVMutableComposition 是 AVComposition 的子類,其包含的 tracks 則是 AVCompositionTrack 的子類 AVMutableCompositionTrack 。
AVMutableComposition 中提供了兩個類方法用來獲取一個空的 AVMutableComposition 實例對象。
+ (instancetype)composition;
+ (instancetype)compositionWithURLAssetInitializationOptions:(nullable NSDictionary<NSString *, id> *)URLAssetInitializationOptions NS_AVAILABLE(10_11, 9_0);
對整個 composition 中的 tracks 的修改方法如下:
//將指定時間段的 asset 中的所有的 tracks 添加到 composition 中 startTime 處 //該方法可能會在 composition 中添加新的 track 以便 asset 中 timeRange 范圍中的所有 tracks 都添加到 composition 中 - (BOOL)insertTimeRange:(CMTimeRange)timeRange ofAsset:(AVAsset *)asset atTime:(CMTime)startTime error:(NSError * _Nullable * _Nullable)outError; //向 composition 中的所有 tracks 添加空的時間范圍 - (void)insertEmptyTimeRange:(CMTimeRange)timeRange; //從 composition 的所有 tracks 中刪除一段時間,該操作不會刪除 track ,而是會刪除與該時間段相交的 track segment - (void)removeTimeRange:(CMTimeRange)timeRange; //改變 composition 中的所有的 tracks 的指定時間范圍的時長,該操作會改變 asset 的播放速度 - (void)scaleTimeRange:(CMTimeRange)timeRange toDuration:(CMTime)duration;
從 composition 中獲取 track 或向其中添加/移除 track 方法如下:
//向 composition 中添加一個空的 track ,並且指定媒體資源類型及 trackID 屬性值 //若提供的參數 preferredTrackID 無效或為 kCMPersistentTrackID_Invalid ,那么唯一的 trackID 會自動生成 - (AVMutableCompositionTrack *)addMutableTrackWithMediaType:(NSString *)mediaType preferredTrackID:(CMPersistentTrackID)preferredTrackID; //從 composition 中刪除一個指定的 track - (void)removeTrack:(AVCompositionTrack *)track; //獲取一個與 asset track 相兼容的 composition track //為了更好的性能,composition track 的數量應保持最小,這個數量與必需並行播放的媒體數據段數量以及媒體數據的類型相關 //對於能夠線性執行且類型相同的媒體數據應使用同一個 composition track ,即使這些數據來自不同的 asset - (nullable AVMutableCompositionTrack *)mutableTrackCompatibleWithTrack:(AVAssetTrack *)track;
AVMutableComposition 中也提供了過濾 AVMutableCompositionTrack 的接口
- (nullable AVMutableCompositionTrack *)trackWithTrackID:(CMPersistentTrackID)trackID; - (NSArray<AVMutableCompositionTrack *> *)tracksWithMediaType:(NSString *)mediaType; - (NSArray<AVMutableCompositionTrack *> *)tracksWithMediaCharacteristic:(NSString *)mediaCharacteristic;
AVCompositionTrack
AVCompositionTrack 類同其父類 AVAssetTrack 一樣是媒體資源的管理者,它實際是媒體資源數據的集合,它的屬性 segments 是 AVCompositionTrackSegment 類的實例對象集合,每個對象描述一個媒體數據片段。類 AVCompositionTrack 並不常用,通常使用的是它的子類 AVMutableCompositionTrack 。
AVMutableCompositionTrack
AVMutableCompositionTrack 中提供的屬性如下:
//沒有外部數值指定時,媒體1秒鍾時間的粒度 @property (nonatomic) CMTimeScale naturalTimeScale; //當前 track 相關聯的語言編碼 @property (nonatomic, copy, nullable) NSString *languageCode; //當前 track 相關聯的額外語言編碼 @property (nonatomic, copy, nullable) NSString *extendedLanguageTag; //對於可顯示的媒體數據應優先選擇的仿射變換設置,默認值為 CGAffineTransformIdentity @property (nonatomic) CGAffineTransform preferredTransform; //應優先選擇的音量,默認值為 1 @property (nonatomic) float preferredVolume; //當前track 所包含的所有的媒體數據片段,對於這些片段,它們構成了 track 的完整時間線, //所以他們的時間線不可以重疊,並且第一個數據片段的時間從 kCMTimeZero 開始,依次往后的時間必須連續不間斷、不重疊 @property (nonatomic, copy, null_resettable) NSArray<AVCompositionTrackSegment *> *segments;
當我們獲取了一個 AVMutableCompositionTrack 實例對象后,便可以通過以下方法對其進行添加或移除數據片段
//將已存在的資源文件指定時間范圍的媒體數據插入到當前 composition 的指定時間處
//如果 startTime 為 kCMTimeInvalid 值,那么數據被添加到 composition 的最后 - (BOOL)insertTimeRange:(CMTimeRange)timeRange ofTrack:(AVAssetTrack *)track atTime:(CMTime)startTime error:(NSError * _Nullable * _Nullable)outError; //這個方法與上述方法類似,只是可以批量操作,但是注意提供的時間范圍不能重疊 - (BOOL)insertTimeRanges:(NSArray<NSValue *> *)timeRanges ofTracks:(NSArray<AVAssetTrack *> *)tracks atTime:(CMTime)startTime error:(NSError * _Nullable * _Nullable)outError NS_AVAILABLE(10_8, 5_0); //插入一個沒有媒體數據的時間段,當這個范圍之前的媒體資源播放結束后,不會立刻播放之后的媒體數據,而是會靜默一段時間 - (void)insertEmptyTimeRange:(CMTimeRange)timeRange; //移除一段時間范圍的媒體數據,該方法不會導致該 track 從 composition 中移除,只是移除與時間范圍相交的數據片段 - (void)removeTimeRange:(CMTimeRange)timeRange; //改變某個時間范圍內的時間的時長,實質是改變了媒體數據的播放速率 //其速率是原時長與現時長的比值,總之,媒體數據是要按時長播放的 - (void)scaleTimeRange:(CMTimeRange)timeRange toDuration:(CMTime)duration; //判斷數據片段的時間線是否重疊 - (BOOL)validateTrackSegments:(NSArray<AVCompositionTrackSegment *> *)trackSegments error:(NSError * _Nullable * _Nullable)outError;
AVAssetTrackSegment
媒體資源 AVAsset 中的集合 AVAssetTrack 管理着單條時間線上的媒體數據片段,而每個數據片段則由 AVAssetTrackSegment 類進行描述。
AVAssetTrackSegment 有兩個屬性
-
timeMapping 描述的是數據片段在整個媒體文件中所處的時間范圍
timeMapping 是一個結構體,擁有兩個成員,對於編輯中的媒體數據片段,它們分別表示數據在源文件中的位置和目標文件中的位置 typedef struct { CMTimeRange source; CMTimeRange target; } CMTimeMapping;
- empty 描述該數據片段是否為空,如果為空,其 timeMapping.source.start 為 kCMTimeInvalid
AVCompositionTrackSegment
在編輯媒體文件時,在描述數據時,使用的是 AVAssetTrackSegment 的子類 AVCompositionTrackSegment ,她的主要屬性和方法如下:
//判斷數據片段是否為空,若為空 timeMapping.target 可為有效值,其他為未定義值 @property (nonatomic, readonly, getter=isEmpty) BOOL empty; //片段數據所處的文件的地址 @property (nonatomic, readonly, nullable) NSURL *sourceURL; //片段數據所處文件的描述 asset track 的 ID @property (nonatomic, readonly) CMPersistentTrackID sourceTrackID; //創建對象,提供了數據片段所在的文件、文件的描述 asset track 的 ID 、源文件中的數據時間范圍、目標文件中所處的時間范圍 //sourceTimeRange 與 targetTimeRange 的時間長度如果不一致,那么播放的速率會改變 + (instancetype)compositionTrackSegmentWithURL:(NSURL *)URL trackID:(CMPersistentTrackID)trackID sourceTimeRange:(CMTimeRange)sourceTimeRange targetTimeRange:(CMTimeRange)targetTimeRange; - (instancetype)initWithURL:(NSURL *)URL trackID:(CMPersistentTrackID)trackID sourceTimeRange:(CMTimeRange)sourceTimeRange targetTimeRange:(CMTimeRange)targetTimeRange NS_DESIGNATED_INITIALIZER; //創建僅有時間范圍而無實際媒體數據的實例 + (instancetype)compositionTrackSegmentWithTimeRange:(CMTimeRange)timeRange; - (instancetype)initWithTimeRange:(CMTimeRange)timeRange NS_DESIGNATED_INITIALIZER;
音頻的自定義播放
要在媒體資源播放的過程中實現音頻的自定義播放,需要用 AVMutableAudioMix 對不同的音頻進行編輯。這個類的實例對象的屬性 inputParameters 是音量描述對象的集合,每個對象都是對一個 audio track 的音量變化的描述,如下示例:
AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
AVMutableAudioMixInputParameters *mixParameters1 = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:compositionAudioTrack1];
[mixParameters1 setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration/2)]; [mixParameters1 setVolumeRampFromStartVolume:0.f toEndVolume:1.f timeRange:CMTimeRangeMake(mutableComposition.duration/2, mutableComposition.duration)]; AVMutableAudioMixInputParameters *mixParameters2 = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:compositionAudioTrack2]; [mixParameters2 setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)]; mutableAudioMix.inputParameters = @[mixParameters1, mixParameters2];
AVAudioMix
該類中有一個屬性 inputParameters ,它是 AVAudioMixInputParameters 實例對象的集合,每個實例都是對音頻播放方式的描述。可見,AVAudioMix 並不直接改變音頻播放的方式,其只是存儲了音頻播放的方式。
AVMutableAudioMix
AVMutableAudioMix 是 AVAudioMix 的子類,它的方法 audioMix 返回一個 inputParameters 屬性為空的實例。
AVAudioMixInputParameters
這個類是音量變化的描述類,它同一個音頻的 track 相關聯,並設置音量隨時間變化的算法,其獲取音量變化的方法如下:
//獲取的音量變化范圍 timeRange 應包含指定的時刻 time 否則最終返回 NO //startVolume 獲取音量開始變化時的初始音量 //endVolume 獲取音量變化結束時的音量 //timeRang 是實際音量變化的范圍,它應該包含指定的 time - (BOOL)getVolumeRampForTime:(CMTime)time startVolume:(nullable float *)startVolume endVolume:(nullable float *)endVolume timeRange:(nullable CMTimeRange *)timeRange;
AVMutableAudioMixInputParameters
AVMutableAudioMixInputParameters 是 AVAudioMixInputParameters 的子類,它提供了直接設置某個時刻或時間段的音量的方法。
//根據提供的 track 創建一個實例,此時的音量描述數據為空 + (instancetype)audioMixInputParametersWithTrack:(nullable AVAssetTrack *)track; //創建一個實例,此時的音量變化描述是空的,且 trackID 為 kCMPersistentTrackID_Invalid + (instancetype)audioMixInputParameters; //設置某個時間范圍內的初始音量及結束音量 - (void)setVolumeRampFromStartVolume:(float)startVolume toEndVolume:(float)endVolume timeRange:(CMTimeRange)timeRange; //設置某個時刻的音量 - (void)setVolume:(float)volume atTime:(CMTime)time;
視頻的自定義播放
同音頻的自定義播放一樣,要實現視頻的自定義播放,僅僅將視頻資源集合到一起是不夠的,需要使用 AVMutableVideoComposition 類來定義不同的視頻資源在不同的時間范圍內的播放方式。
AVVideoComposition
AVVideoComposition 是 AVMutableVideoComposition 的父類,它的主要屬性和方法如下:
//該類的構造類,提供自定義的構造類時,提供的類要遵守 AVVideoCompositing 協議 @property (nonatomic, readonly, nullable) Class<AVVideoCompositing> customVideoCompositorClass NS_AVAILABLE(10_9, 7_0); //視頻每一幀的刷新時間 @property (nonatomic, readonly) CMTime frameDuration; //視頻顯示時的大小范圍 @property (nonatomic, readonly) CGSize renderSize; //視頻顯示范圍大小的縮放比例(僅僅對 iOS 有效) @property (nonatomic, readonly) float renderScale; //描述視頻集合中具體視頻播放方式信息的集合,其是遵循 AVVideoCompositionInstruction 協議的類實例對象 //這些視頻播放信息構成一個完整的時間線,不能重疊,不能間斷,並且在數組中的順序即為相應視頻的播放順序 @property (nonatomic, readonly, copy) NSArray<id <AVVideoCompositionInstruction>> *instructions; //用於組合視頻幀與動態圖層的 Core Animation 的工具對象,可以為 nil @property (nonatomic, readonly, retain, nullable) AVVideoCompositionCoreAnimationTool *animationTool; //直接使用一個 asset 創建一個實例,創建的實例的各個屬性會根據 asset 中的所有的 video tracks 的屬性進行計算並適配,所以在調用該方法之前,確保 asset 中的屬性已經加載 //返回的實例對象的屬性 instructions 中的對象會對應每個 asset 中的 track 中屬性要求 //返回的實例對象的屬性 frameDuration 的值是 asset 中 所有 track 的 nominalFrameRate 屬性值最大的,如果這些值都為 0 ,默認為 30fps //返回的實例對象的屬性 renderSize 的值是 asset 的 naturalSize 屬性值,如果 asset 是 AVComposition 類的實例。否則,renderSize 的值將包含每個 track 的 naturalSize 屬性值 + (AVVideoComposition *)videoCompositionWithPropertiesOfAsset:(AVAsset *)asset NS_AVAILABLE(10_9, 6_0); //這三個屬性設置了渲染幀時的顏色空間、矩陣、顏色轉換函數,可能的值都在 AVVideoSetting.h 文件中定義 @property (nonatomic, readonly, nullable) NSString *colorPrimaries NS_AVAILABLE(10_12, 10_0); @property (nonatomic, readonly, nullable) NSString *colorYCbCrMatrix NS_AVAILABLE(10_12, 10_0); @property (nonatomic, readonly, nullable) NSString *colorTransferFunction NS_AVAILABLE(10_12, 10_0); //該方法返回一個實例,它指定的 block 會對 asset 中每一個有效的 track 的每一幀進行渲染得到 CIImage 實例對象 //在 block 中進行每一幀的渲染,成功后應調用 request 的方法 finishWithImage:context: 並將得到的 CIImage 對象作為參數 //若是渲染失敗,則應調用 finishWithError: 方法並傳遞錯誤信息 + (AVVideoComposition *)videoCompositionWithAsset:(AVAsset *)asset applyingCIFiltersWithHandler:(void (^)(AVAsynchronousCIImageFilteringRequest *request))applier NS_AVAILABLE(10_11, 9_0);
AVMutableVideoComposition
AVMutableVideoComposition 是 AVVideoComposition 的可變子類,它繼承父類的屬性可以改變,並且新增了下面的創建方法。
//這個方法創建的實例對象的屬性的值都是 nil 或 0,但是它的屬性都是可以進行修改的 + (AVMutableVideoComposition *)videoComposition;
AVVideoCompositionInstruction
在上述的兩個類中,真正包含有視頻播放方式信息的是 instructions 屬性,這個集合中的對象都遵循 AVVideoCompositionInstruction 協議,若不使用自定義的類,那么可以使用 AVFoundation 框架中的 AVVideoCompositionInstruction 類。
該類的相關屬性如下:
//表示該 instruction 生效的時間范圍 @property (nonatomic, readonly) CMTimeRange timeRange; //指定當前時間段的 composition 的背景色 //如果沒有指定,那么使用默認的黑色 //如果渲染的像素沒有透明度通道,那么這個顏色也會忽略透明度 @property (nonatomic, readonly, retain, nullable) __attribute__((NSObject)) CGColorRef backgroundColor; //AVVideoCompositionLayerInstruction 類實例對象的集合,描述各個視頻資源幀的層級及組合關系 //按這個數組的順序,第一個顯示在第一層,第二個在第一層下面顯示,以此類推 @property (nonatomic, readonly, copy) NSArray<AVVideoCompositionLayerInstruction *> *layerInstructions; //表明該時間段的視頻幀是否需要后期處理 //若為 NO,后期圖層的處理將跳過該時間段,這樣能夠提高效率 //為 YES 則按默認操作處理(參考 AVVideoCompositionCoreAnimationTool 類) @property (nonatomic, readonly) BOOL enablePostProcessing; //當前 instruction 中需要進行幀組合的所有的 track ID 的集合,由屬性 layerInstructions 計算得到 @property (nonatomic, readonly) NSArray<NSValue *> *requiredSourceTrackIDs NS_AVAILABLE(10_9, 7_0); //如果當前的 instruction 在該時間段內的視頻幀組合后,實質得到的是某個源視頻的幀,那么就返回這個視頻資源的 ID @property (nonatomic, readonly) CMPersistentTrackID passthroughTrackID NS_AVAILABLE(10_9, 7_0);
AVMutableVideoCompositionInstruction
AVMutableVideoCompositionInstruction 是 AVVideoCompositionInstruction 的子類,其繼承的父類的屬性可進行修改,並且提供了創建屬性值為 nil 或無效的實例的方法。
+ (instancetype)videoCompositionInstruction;
AVVideoCompositionLayerInstruction
AVVideoCompositionLayerInstruction 是對給定的視頻資源的不同播放方式進行描述的類,通過下面的方法,可以獲取仿射變化、透明度變化、裁剪區域變化的梯度信息。
//獲取包含指定時間的仿射變化梯度信息 //startTransform、endTransform 用來接收變化過程的起始值與結束值 //timeRange 用來接收變化的持續時間范圍 //返回值表示指定的時間 time 是否在變化時間 timeRange 內 - (BOOL)getTransformRampForTime:(CMTime)time startTransform:(nullable CGAffineTransform *)startTransform endTransform:(nullable CGAffineTransform *)endTransform timeRange:(nullable CMTimeRange *)timeRange; //獲取包含指定時間的透明度變化梯度信息 //startOpacity、endOpacity 用來接收透明度變化過程的起始值與結束值 //timeRange 用來接收變化的持續時間范圍 //返回值表示指定的時間 time 是否在變化時間 timeRange 內 - (BOOL)getOpacityRampForTime:(CMTime)time startOpacity:(nullable float *)startOpacity endOpacity:(nullable float *)endOpacity timeRange:(nullable CMTimeRange *)timeRange; //獲取包含指定時間的裁剪區域的變化梯度信息 //startCropRectangle、endCropRectangle 用來接收變化過程的起始值與結束值 //timeRange 用來接收變化的持續時間范圍 //返回值表示指定的時間 time 是否在變化時間 timeRange 內 - (BOOL)getCropRectangleRampForTime:(CMTime)time startCropRectangle:(nullable CGRect *)startCropRectangle endCropRectangle:(nullable CGRect *)endCropRectangle timeRange:(nullable CMTimeRange *)timeRange NS_AVAILABLE(10_9, 7_0);
AVMutableVideoCompositionLayerInstruction
AVMutableVideoCompositionLayerInstruction 是 AVVideoCompositionLayerInstruction 的子類,它可以改變 composition 中的 track 資源播放時的仿射變化、裁剪區域、透明度等信息。
相比於父類,該子類還提供了創建實例的方法:
//這兩個方法的區別在於,前者返回的實例對象的屬性 trackID 的值是 track 的 trackID 值 //而第二個方法的返回的實例對象的屬性 trackID 的值為 kCMPersistentTrackID_Invalid + (instancetype)videoCompositionLayerInstructionWithAssetTrack:(AVAssetTrack *)track; + (instancetype)videoCompositionLayerInstruction;
該類的屬性表示 instruction 所作用的 track 的 ID
@property (nonatomic, assign) CMPersistentTrackID trackID;
設置了 trackID 后,通過下面的方法,進行剃度信息的設置:
//設置視頻中幀的仿射變化信息
//指定了變化的時間范圍、起始值和結束值,其中坐標系的原點為左上角,向下向右為正方向
- (void)setTransformRampFromStartTransform:(CGAffineTransform)startTransform toEndTransform:(CGAffineTransform)endTransform timeRange:(CMTimeRange)timeRange; //設置 instruction 的 timeRange 范圍內指定時間的仿射變換,該值會一直保持,直到被再次設置 - (void)setTransform:(CGAffineTransform)transform atTime:(CMTime)time; //設置透明度的梯度信息,提供的透明度初始值和結束值應在0~1之間 //變化的過程是線形的 - (void)setOpacityRampFromStartOpacity:(float)startOpacity toEndOpacity:(float)endOpacity timeRange:(CMTimeRange)timeRange; //設置指定時間的透明度,該透明度會一直持續到下一個值被設置 - (void)setOpacity:(float)opacity atTime:(CMTime)time; //設置裁剪矩形的變化信息 - (void)setCropRectangleRampFromStartCropRectangle:(CGRect)startCropRectangle toEndCropRectangle:(CGRect)endCropRectangle timeRange:(CMTimeRange)timeRange NS_AVAILABLE(10_9, 7_0); //設置指定時間的裁剪矩形 - (void)setCropRectangle:(CGRect)cropRectangle atTime:(CMTime)time NS_AVAILABLE(10_9, 7_0);
AVVideoCompositionCoreAnimationTool
在自定義視頻播放時,可能需要添加水印、標題或者其他的動畫效果,需要使用該類。該類通常用來協調離線視頻中圖層與動畫圖層的組合(如使用 AVAssetExportSession 和 AVAssetReader 、AVAssetReader 類導出視頻文件或讀取視頻文件時),而若是在線實時的視頻播放,應使用 AVSynchronizedLayer 類來同步視頻的播放與動畫的效果。
在使用該類時,注意動畫在整個視頻的時間線上均可以被修改,所以,動畫的開始時間應該設置為 AVCoreAnimationBeginTimeAtZero ,這個值其實比 0 大,屬性值 removedOnCompletion 應該置為 NO,以防當動畫執行結束后被移除,並且不應使用與任何的 UIView 相關聯的圖層。
作為視頻組合的后期處理工具類,主要方法如下:
//向視頻組合中添加一個動畫圖層,這個圖層不能在任何圖層樹中 //提供的參數 trackID 應由方法 [AVAsset unusedTrackID] 得到,它不與任何視頻資源的 trackID 相關 //AVVideoCompositionInstruction 的屬性 layerInstructions 包含的 AVVideoCompositionLayerInstruction 實例對象中應該有 //該 trackID 一致的 AVVideoCompositionLayerInstruction 實例對象,並且為性能考慮,不應使用該對象設置 transform 的變化 //在 iOS 中,CALayer 作為 UIView 的背景圖層,其內容的是否能夠翻轉,由方法 contentsAreFlipped 決定(如果所有的圖層包括子圖層,該方法返回的值為 YES 的個數為奇數個,表示可以圖層中內容可以垂直翻轉) //所以這里的 layer 若用來設置 UIView 的 layer 屬性,或作為其中的子圖層,其屬性值 geometryFlipped 應設置為 YES ,這樣則能夠保持是否能夠翻轉的結果一致 + (instancetype)videoCompositionCoreAnimationToolWithAdditionalLayer:(CALayer *)layer asTrackID:(CMPersistentTrackID)trackID; //將放在圖層 videoLayer 中的組合視頻幀同動畫圖層 animationLayer 中的內容一起進行渲染,得到最終的視頻幀 //通常,videoLayer 是 animationLayer 的子圖層,而 animationLayer 則不在任何圖層樹中 + (instancetype)videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:(CALayer *)videoLayer inLayer:(CALayer *)animationLayer; //復制 videoLayers 中的每一個圖層,與 animationLayer一起渲染得到最中的幀 ////通常,videoLayers 中的圖層都在 animationLayer 的圖層樹中,而 animationLayer 則不屬於任何圖層樹 + (instancetype)videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayers:(NSArray<CALayer *> *)videoLayers inLayer:(CALayer *)animationLayer NS_AVAILABLE(10_9, 7_0);
AVVideoCompositionValidationHandling
當我們經過編輯后得到一個視頻資源 asset ,並且為該資源設置了自定義播放信息 video composition ,需要驗證對於這個 asset 而言,video composition 是否有效,可以調用 AVVideoComposition 的校驗方法。
/*
@param asset
設置第一個參數的校驗內容,設置 nil 忽略這些校驗
1. 該方法可以校驗 AVVideoComposition 的屬性 instructions 是否符合要求 2. 校驗 instructions 中的每個 AVVideoCompositionInstruction 對象的 layerInstructions 屬性中的 每一個 AVVideoCompositionLayerInstruction 對象 trackID 值是否對應 asset 中 track 的 ID 或 AVVideoComposition 的 animationTool 實例 3. 校驗時間 asset 的時長是否與 instructions 中的時間范圍相悖 @param timeRange 設置第二個參數的校驗內容 1. 校驗 instructions 的所有的時間范圍是否在提供的 timeRange 的范圍內, 若要忽略該校驗,可以傳參數 CMTimeRangeMake(kCMTimeZero, kCMTimePositiveInfinity) @param validationDelegate 設置遵循 AVVideoCompositionValidationHandling 協議的代理類,用來處理校驗過程中的報錯,可以為 nil */ - (BOOL)isValidForAsset:(nullable AVAsset *)asset timeRange:(CMTimeRange)timeRange validationDelegate:(nullable id<AVVideoCompositionValidationHandling>)validationDelegate NS_AVAILABLE(10_8, 5_0);
設置的代理對象要遵循協議 AVVideoCompositionValidationHandling ,該對象在實現下面的協議方法時,若修改了傳遞的 composition 參數,上面的校驗方法則會拋出異常。
該協議提供了以下回調方法,所有方法的返回值用來確定是否繼續進行校驗以獲取更多的錯誤。
//報告 videoComposition 中有無效的值 - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidValueForKey:(NSString *)key NS_AVAILABLE(10_8, 5_0); //報告 videoComposition 中有時間段沒有相對應的 instruction - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingEmptyTimeRange:(CMTimeRange)timeRange NS_AVAILABLE(10_8, 5_0); //報告 videoComposition 中的 instructions 中 timeRange 無效的實例對象 //可能是 timeRange 本身為 CMTIMERANGE_IS_INVALID //或者是該時間段同上一個的 instruction 的 timeRange 重疊 //也可能是其開始時間比上一個的 instruction 的 timeRange 的開始時間要早 - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidTimeRangeInInstruction:(id<AVVideoCompositionInstruction>)videoCompositionInstruction NS_AVAILABLE(10_8, 5_0); //報告 videoComposition 中的 layer instruction 同調用校驗方法時指定的 asset 中 track 的 trackID 不一致 //也不與 composition 使用的 animationTool 的trackID 一致 - (BOOL)videoComposition:(AVVideoComposition *)videoComposition shouldContinueValidatingAfterFindingInvalidTrackIDInInstruction:(id<AVVideoCompositionInstruction>)videoCompositionInstruction layerInstruction:(AVVideoCompositionLayerInstruction *)layerInstruction asset:(AVAsset *)asset NS_AVAILABLE(10_8, 5_0);
例程
下面的例子給出了將兩個視頻資源和一個音頻資源編輯組合為一個資源文件的步驟。
- 要組合多個視聽資源,需要先創建一個 AVMutableComposition 實例對象,用來組合資源。
-
然后向 composition 中添加 AVMutableCompositionTrack 實例對象,為性能考慮,對於非同時播放且相同類型的資源,應使用一個 AVMutableCompositionTrack 實例對象,所以這里添加一個視頻類型的 composition track 和一個音頻類型的 composition track 即可。
AVMutableComposition *mutableComposition = [AVMutableComposition composition]; AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid]; AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
-
獲得了擁有 composition track 的 composition 后,下一步就是將具體的視聽資源添加到組合中。
AVURLAsset *firstVideoAsset = [AVURLAsset URLAssetWithURL:firstVideoUrl options:nil]; AVURLAsset *secondVideoAsset = [AVURLAsset URLAssetWithURL:secondVideoUrl options:nil]; AVURLAsset *audioAsset = [AVURLAsset URLAssetWithURL:audioUrl options:nil]; //獲取視聽資源中的第一個 asset track AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]; AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0]; //第一個視頻插入的時間點是 kCMTimeZero [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil]; //第二個視頻插入的時間點是第一個視頻結束的時間 [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil]; //音頻的持續時間是兩個視頻時間的總和 CMTime videoTotalDuration = CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration); [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoTotalDuration) ofTrack: atTime:kCMTimeZero error:nil];
-
檢查視頻的鏡頭是否是橫向模式,組合時,video track 總是被認為是橫向模式,如果待組合的 video track 是縱向模式,那么最終的視頻顯示將不符合預想,而且無法將橫向模式和縱向模式的的視頻組合到一起。
//判斷第一個視頻的模式 BOOL isFirstVideoPortrait = NO; CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform; if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) { isFirstVideoPortrait = YES; } //判斷第二個視頻的模式 BOOL isSecondVideoPortrait = NO; CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform; if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) { isSecondVideoPortrait = YES; } //判斷兩個視頻的模式是否一致 if ((isFirstVideoAssetPortrait && !isSecondVideoAssetPortrait) || (!isFirstVideoAssetPortrait && isSecondVideoAssetPortrait)) { UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil]; [incompatibleVideoOrientationAlert show]; return; }
-
當每個視頻的方向是兼容的,那么可以對每個視頻的圖層進行必要的調整。
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration); AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction]; secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration); AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack]; [firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero]; AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack]; [secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration]; firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction]; secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction]; AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition]; mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];
-
檢查視頻方向的兼容性之后,需要調整視頻組合渲染區域的大小,設置視頻幀的刷新頻率,以兼容每一個視頻的播放。
//獲取視頻的原播放區域 CGSize naturalSizeFirst, naturalSizeSecond; if (isFirstVideoAssetPortrait) { naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width); naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width); } else { naturalSizeFirst = firstVideoAssetTrack.naturalSize; naturalSizeSecond = secondVideoAssetTrack.naturalSize; } //設置的渲染區域要能包含兩個視頻的播放區域 float renderWidth, renderHeight; if (naturalSizeFirst.width > naturalSizeSecond.width) { renderWidth = naturalSizeFirst.width; } else { renderWidth = naturalSizeSecond.width; } if (naturalSizeFirst.height > naturalSizeSecond.height) { renderHeight = naturalSizeFirst.height; } else { renderHeight = naturalSizeSecond.height; } mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight); //設置幀每一秒刷新30次 mutableVideoComposition.frameDuration = CMTimeMake(1,30);
-
最后將組合的視聽資源導出到一個單獨的文件中並保存到資源庫。
static NSDateFormatter *kDateFormatter; if (!kDateFormatter) { kDateFormatter = [[NSDateFormatter alloc] init]; kDateFormatter.dateStyle = NSDateFormatterMediumStyle; kDateFormatter.timeStyle = NSDateFormatterShortStyle; } AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality]; exporter.outputURL = [[[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:@YES error:nil] URLByAppendingPathComponent:[kDateFormatter stringFromDate:[NSDate date]]] URLByAppendingPathExtension:CFBridgingRelease(UTTypeCopyPreferredTagWithClass((CFStringRef)AVFileTypeQuickTimeMovie, kUTTagClassFilenameExtension))]; exporter.outputFileType = AVFileTypeQuickTimeMovie; exporter.shouldOptimizeForNetworkUse = YES; exporter.videoComposition = mutableVideoComposition; //異步導出 [exporter exportAsynchronouslyWithCompletionHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (exporter.status == AVAssetExportSessionStatusCompleted) { //保存文件到媒體庫 ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init]; if ([assetsLibrary videoAtPathIsCompatibleWithSavedPhotosAlbum:exporter.outputURL]) { [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:exporter.outputURL completionBlock:NULL]; } } }); }];
媒體資源捕獲
通過麥克風、攝像機等設備,可以捕獲外界的聲音和影像。要處理設備捕獲的數據,需要使用 AVCaptureDevice 類描述設備,使用 AVCaptureInput 配置數據從設備的輸入,使用 AVCaptureOutput 類管理數據到文件的寫入,而數據的輸入到寫出,需要使用 AVCaptureSession 類進行協調。此外,可以使用 AVCaptureVideoPreviewLayer 類顯示相機正在拍攝的畫面。
一個設備可以有多個輸入,使用 AVCaptureInputPort 類描述這些輸入,用 AVCaptureConnection 類描述具體類型的輸入與輸出的關系,可以實現更精細的數據處理。
AVCaptureSession
AVCaptureSession 是捕獲視聽數據的核心類,它協調數據的輸入和輸出。創建一個 AVCaptureSession 類的對象時,可以指定最終得到的視聽數據的質量,當然這個質量與設備也有關系,通常在設置之前,可以調用方法判斷 session 是否支持要設置的質量。
AVCaptureSession 類實例可設置的數據質量有 AVCaptureSessionPresetHigh 、AVCaptureSessionPresetMedium 、AVCaptureSessionPresetLow 、AVCaptureSessionPreset320x240 等。在進行設置之前,可以調用 AVCaptureSession 中的方法進行校驗。
- (BOOL)canSetSessionPreset:(NSString*)preset;
設置好對象后,可調用下面的方法,添加、移除輸入、輸出。
- (BOOL)canAddInput:(AVCaptureInput *)input; - (void)addInput:(AVCaptureInput *)input; - (void)removeInput:(AVCaptureInput *)input; - (BOOL)canAddOutput:(AVCaptureOutput *)output; - (void)addOutput:(AVCaptureOutput *)output; - (void)removeOutput:(AVCaptureOutput *)output; - (void)addInputWithNoConnections:(AVCaptureInput *)input NS_AVAILABLE(10_7, 8_0); - (void)addOutputWithNoConnections:(AVCaptureOutput *)output NS_AVAILABLE(10_7, 8_0); - (BOOL)canAddConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 8_0); - (void)addConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 8_0); - (void)removeConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 8_0);
開始執行 session 或者結束執行,調用下面的方法。
- (void)startRunning; - (void)stopRunning;
對於正在執行中的 session ,要對其進行改變,所作出的改變,應放在下面兩個方法之間。
- (void)beginConfiguration; - (void)commitConfiguration;
AVCaptureSession 開始執行、結束執行、執行過程中出錯或被打斷時,都會發出通知,通過注冊下面的通知,可以獲取我們感興趣的信息。
- AVCaptureSessionRuntimeErrorNotification 通過 AVCaptureSessionErrorKey 可以獲取出錯的原因
- AVCaptureSessionDidStartRunningNotification 開始 session
- AVCaptureSessionDidStopRunningNotification 結束 session
- AVCaptureSessionWasInterruptedNotification 通過 AVCaptureSessionInterruptionReasonKey 可以獲取被打斷的原因
- AVCaptureSessionInterruptionEndedNotification 打斷結束,session 重新開始
AVCaptureDevice
AVCaptureDevice 是用來描述設備屬性的類,要捕獲視聽數據,需要獲取相應的設備,使用該類獲取有效的設備資源。這個設備資源列表是隨時變動的,其在變動時,會發送 AVCaptureDeviceWasConnectedNotification 或 AVCaptureDeviceWasDisconnectedNotification 通知,以告知有設備連接或斷開。
在獲取設備之前,要先確定要獲取的設備的類型 AVCaptureDeviceType ,設備的位置 AVCaptureDevicePosition ,也可以通過要獲取的媒體數據類型進行設備的選擇。
獲取設備后,可以保存它的唯一標識、模型標識、名稱等信息,以待下次用來獲取設備。
+ (NSArray *)devices; + (NSArray *)devicesWithMediaType:(NSString *)mediaType; + (AVCaptureDevice *)defaultDeviceWithMediaType:(NSString *)mediaType; + (AVCaptureDevice *)deviceWithUniqueID:(NSString *)deviceUniqueID; @property(nonatomic, readonly) NSString *uniqueID; @property(nonatomic, readonly) NSString *modelID; @property(nonatomic, readonly) NSString *localizedName; //校驗獲得的設備能否提供相應的媒體數據類型 - (BOOL)hasMediaType:(NSString *)mediaType; //校驗獲得的設備能否支持相應的配置 - (BOOL)supportsAVCaptureSessionPreset:(NSString *)preset;
獲取一個設備后,可以通過修改它的屬性來滿足自己的需要。
- flashMode 閃光燈的模式(AVCaptureFlashModeOff 、AVCaptureFlashModeOn 、AVCaptureFlashModeAuto)
- torchMode 手電筒的模式(AVCaptureTorchModeOff 、AVCaptureTorchModeOn 、AVCaptureTorchModeAuto)
- torchLevel 手電筒的亮度(0~1)
- focusMode 聚焦模式(AVCaptureFocusModeLocked 、AVCaptureFocusModeAutoFocus 、AVCaptureFocusModeContinuousAutoFocus)
- exposureMode 曝光模式(AVCaptureExposureModeLocked 、AVCaptureExposureModeAutoExpose 、AVCaptureExposureModeContinuousAutoExposure 、AVCaptureExposureModeCustom)
- whiteBalanceMode 白平衡模式(AVCaptureWhiteBalanceModeLocked 、AVCaptureWhiteBalanceModeAutoWhiteBalance 、AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance)
在修改這些屬性時,應先判斷當前設備是否支持要設置的屬性值,並且所有的屬性修改都要放在下面兩個方法之間,以保證屬性能夠被正確設置。
- (BOOL)lockForConfiguration:(NSError **)outError; - (void)unlockForConfiguration;
在調用硬件設備之前,應先判斷應用是否擁有相應的權限,其權限分為以下幾種:
- AVAuthorizationStatusNotDetermined 未定義
- AVAuthorizationStatusRestricted 無權限(因某些原因,系統拒絕權限)
- AVAuthorizationStatusDenied 無權限(用戶拒絕)
- AVAuthorizationStatusAuthorized 有權限
//校驗權限 + (AVAuthorizationStatus)authorizationStatusForMediaType:(NSString *)mediaType NS_AVAILABLE_IOS(7_0); //請求權限,handler 處理會在任意線程中執行,所以需要在主線程中執行的處理由用戶負責指定 + (void)requestAccessForMediaType:(NSString *)mediaType completionHandler:(void (^)(BOOL granted))handler NS_AVAILABLE_IOS(7_0);
AVCaptureDeviceInput
AVCaptureDeviceInput 是 AVCaptureInput 的子類,使用一個 AVCaptureDevice 類實例創建該類的實例,其管理設備的輸入。
在創建了實例對象后,將其添加到 session 中。
NSError *error; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; if (input && [session canAddInput:input]) { [captureSession addInput:captureDeviceInput]; }
AVCaptureOutput
AVCaptureOutput 是一個抽象類,通常使用的是它的子類。
- AVCaptureMovieFileOutput 用來生成一個影視文件
- AVCaptureVideoDataOutput 用來處理輸入的視頻的幀
- AVCaptureAudioDataOutput 用來處理音頻數據
- AVCaptureStillImageOutput 用來獲取圖片
在創建了具體的子類后,將它添加到 session 中。
AVCaptureMovieFileOutput *movieOutput = [[AVCaptureMovieFileOutput alloc] init]; if ([session canAddOutput:movieOutput]) { [session addOutput:movieOutput]; }
AVCaptureFileOutput
AVCaptureFileOutput 是 AVCaptureOutput 的子類,是 AVCaptureMovieFileOutput 、AVCaptureAudioFileOutput 的父類。這個類中定義了文件輸出時的地址、時長、容量等屬性。
//當前記錄的數據的文件的地址 @property(nonatomic, readonly) NSURL *outputFileURL; //開始文件的記錄,指定文件的地址,以及記錄過程中或結束時要通知的代理對象 //指定的 outputFileURL 必需是有效的且沒有文件占用 - (void)startRecordingToOutputFileURL:(NSURL*)outputFileURL recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)delegate; //該方法可以停止數據向文件中寫入 //如果要停止一個文件的寫入轉而指定另一個文件的寫入,不應調用該方法,只需直接調用上面的方法 //當因該方法的調用、出錯、或寫入文件的變更導致當前文件開始停止寫入時,最后傳入的緩存數據仍會在后台被寫入 //無論何時,要使用文件,都需要等指定的代理對象被告知文件的寫入已經結束之后進行 - (void)stopRecording; //判斷當前是否有數據被寫入文件 @property(nonatomic, readonly, getter=isRecording) BOOL recording; //表示到目前為止,當前文件已經記錄了多長時間 @property(nonatomic, readonly) CMTime recordedDuration; //表示到目前為止,當前文件已經記錄了多少個字節 @property(nonatomic, readonly) int64_t recordedFileSize; /** 下面三個值對文件的記錄進行了限制,若果達到限制,則會在回調方法 captureOutput:didFinishRecordingToOutputFileAtURL:fromConnections:error: 中傳遞相應的錯誤 */ //表示當前文件能夠記錄的最長時間,kCMTimeInvalid 表示無時間限制 @property(nonatomic) CMTime maxRecordedDuration; //表示當前文件能夠記錄的最大字節數,0 表示無大小限制 @property(nonatomic) int64_t maxRecordedFileSize; //表示記錄當前文件時需要保留的最小字節數 @property(nonatomic) int64_t minFreeDiskSpaceLimit; //在 Mac OS X 系統下,通過指定遵循 AVCaptureFileOutputDelegate 協議的代理對象,來實現緩存數據的精確記錄 @property(nonatomic, assign) id<AVCaptureFileOutputDelegate> delegate NS_AVAILABLE(10_7, NA); /** 在 Mac OS X 系統下,這個屬性和方法可以判斷記錄是否停止,以及控制數據向文件中的停止寫入和重新開始寫入 */ @property(nonatomic, readonly, getter=isRecordingPaused) BOOL recordingPaused NS_AVAILABLE(10_7, NA); - (void)pauseRecording NS_AVAILABLE(10_7, NA); - (void)resumeRecording NS_AVAILABLE(10_7, NA);
AVCaptureFileOutputRecordingDelegate
AVCaptureFileOutputRecordingDelegate 是文件記錄過程中需要用到的協議,它通常的作用是告知代理對象文件記錄結束了。
//這個代理方法是遵循該協議的代理對象必須要實現的方法
//每一個文件記錄請求,最終都會調用這個方法,即使沒有數據成功寫入文件
//當 error 返回時,文件也可能成功保存了,應檢查 error 中的 AVErrorRecordingSuccessfullyFinishedKey 信息,查看具體錯誤 - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error; //當數據寫入文件后調用,如果數據寫入失敗,該方法可能不會被調用 - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections; /** 在 Mac OS X 系統下,當文件的記錄被暫停或重新開始,會調用下面的方法,如果記錄被終止,不會調用下面的方法 */ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didPauseRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections NS_AVAILABLE(10_7, NA); - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didResumeRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections NS_AVAILABLE(10_7, NA); //在 Mac OS X 系統下,當記錄將被停止,無論是主動的還是被動的,都會調用下面的方法 - (void)captureOutput:(AVCaptureFileOutput *)captureOutput willFinishRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections error:(NSError *)error NS_AVAILABLE(10_7, NA);
AVCaptureFileOutputDelegate
AVCaptureFileOutputDelegate 這個協議只用於 Mac OS X 系統下,它給了客戶端精准操控數據的機會。
/** 在 Mac OS X 10.8 系統之前,實現代理方法 captureOutput:didOutputSampleBuffer:fromConnection: 后便可以在該方法中實現數據記錄的准確開始或結束,而要實現在任一一個畫面幀處開始或停止數據的記錄,要對每收到的 幀數據進行預先處理,這個過程消耗電能、產生熱量、占用 CPU 資源,所以在 Mac OS X 10.8 及其之后的系統,提供了 下面的代理方法,來確定客戶端需不需要隨時進行記錄的開始或停止。 如果這個方法返回 NO ,對數據記錄的設置將在開啟記錄之后進行。 */ - (BOOL)captureOutputShouldProvideSampleAccurateRecordingStart:(AVCaptureOutput *)captureOutput NS_AVAILABLE(10_8, NA); /** 如果上面的方法返回了 YES ,那么客戶端便可以使用下面的方法對每一個視頻幀數據或音頻數據進行操作 為了提高性能,緩存池中的緩存變量的內存通常會被復用,如果長時間使用緩存變量,那么新的緩存數據無法復制到 相應的內存中便會被廢棄,所以若需要長時間使用緩存數據 sampleBuffer ,應復制一份,使其本身能夠被系統復用 */ - (void)captureOutput:(AVCaptureFileOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, NA);
AVCaptureMovieFileOutput
AVCaptureMovieFileOutput 是 AVCaptureFileOutput 的子類,它實現了在 AVCaptureFileOutput 中聲明的視頻數據記錄方法,並且可以設置數據的格式、寫入元數據、編碼方式等屬性。
//如果視頻數據按片段寫入,該值指定片段的時長,默認值是 10 秒 //該值為 kCMTimeInvalid 表示不使用片段對視頻進行記錄,這樣視頻只能一次寫入,不能被打斷 //改變該值不影響當前正在寫入的片段時長 @property(nonatomic) CMTime movieFragmentInterval; //向文件中添加的 AVMetadataItem 類元數據 @property(nonatomic, copy) NSArray *metadata; //獲取記錄 connection 中數據時,使用的設置 - (NSDictionary *)outputSettingsForConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 10_0); //設置記錄 connection 中數據時,使用的設置 AVVideoSettings.h //outputSettings 為空,表示在將 connection 中的數據寫入文件之前,其格式不做改變 //outputSettings 為 nil 時,其數據格式將由 session preset 決定 - (void)setOutputSettings:(NSDictionary *)outputSettings forConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 10_0); //在 iOS 系統下,獲取有效的編碼格式,作為 AVVideoCodecKey 的值,使用上面的方法進行設置 @property(nonatomic, readonly) NSArray *availableVideoCodecTypes NS_AVAILABLE_IOS(10_0); //設置文件記錄過程中,是否創建一個元數據對 connection 的 videoOrientation 和 videoMirrored 屬性變化進行跟蹤記錄 //connection 的屬性 mediaType 的值必需是 AVMediaTypeVideo //該值的設置只在記錄開始之前有效,開始記錄之后改變該值無效果 - (void)setRecordsVideoOrientationAndMirroringChanges:(BOOL)doRecordChanges asMetadataTrackForConnection:(AVCaptureConnection *)connection NS_AVAILABLE_IOS(9_0); //判斷該類實例對象是否會在記錄的過程中創建一個 timed metadata track 記錄 connection 的 videoOrientation 和 videoMirrored 屬性變化情況 - (BOOL)recordsVideoOrientationAndMirroringChangesAsMetadataTrackForConnection:(AVCaptureConnection *)connection NS_AVAILABLE_IOS(9_0);
AVCaptureAudioFileOutput
AVCaptureAudioFileOutput 是 AVCaptureFileOutput 的子類,該類用於將媒體數據記錄為一個音頻文件。
//返回該類支持的音頻文件類型 + (NSArray *)availableOutputFileTypes; //開始記錄音頻文件 - (void)startRecordingToOutputFileURL:(NSURL*)outputFileURL outputFileType:(NSString *)fileType recordingDelegate:(id<AVCaptureFileOutputRecordingDelegate>)delegate; //要寫入音頻文件中的元數據 AVMetadataItem 集合 @property(nonatomic, copy) NSArray *metadata; //寫入的音頻文件的設置 AVAudioSettings.h @property(nonatomic, copy) NSDictionary *audioSettings;
AVCaptureVideoDataOutput
AVCaptureVideoDataOutput 是 AVCaptureOutput 的子類,該類可以用來處理捕獲的每一個視頻幀數據。創建一個該類的實例對象后,要調用下面的方法設置一個代理對象,及調用代理對象所實現的協議方法的隊列。
- (void)setSampleBufferDelegate:(id<AVCaptureVideoDataOutputSampleBufferDelegate>)sampleBufferDelegate queue:(dispatch_queue_t)sampleBufferCallbackQueue;
指定的隊列 sampleBufferCallbackQueue 必需是串行隊列以保證傳遞的幀是按記錄時間先后傳遞的。
//設置輸出的視頻要進行怎樣的格式處理 //設置為空([NSDictionary dictionary])表示不改變輸入時的視頻格式 //設置為 nil 表示未壓縮格式 @property(nonatomic, copy) NSDictionary *videoSettings; //獲取 kCVPixelBufferPixelFormatTypeKey 的有效值 @property(nonatomic, readonly) NSArray *availableVideoCVPixelFormatTypes NS_AVAILABLE(10_7, 5_0); //獲取 AVVideoCodecKey 的有效值 @property(nonatomic, readonly) NSArray *availableVideoCodecTypes NS_AVAILABLE(10_7, 5_0); //表示當回調隊列阻塞時,是否立刻丟棄新接收的幀數據 @property(nonatomic) BOOL alwaysDiscardsLateVideoFrames;
AVCaptureVideoDataOutputSampleBufferDelegate
該協議用來處理接收的每一個幀數據,或者提示客戶端有幀數據被丟棄。
//接收到一個幀數據時,在指定的串行隊列中調用該方法,攜帶幀數據並包含有其他幀信息
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection; //丟棄一個幀數據時,在指定的串行隊列中調用該方法,sampleBuffer 只攜帶幀信息,具體幀數據並未攜帶 - (void)captureOutput:(AVCaptureOutput *)captureOutput didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection NS_AVAILABLE(10_7, 6_0);
AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer 是 CALayer 的子類,使用該類可以實現捕獲視頻的顯示。使用一個 session 創建一個該類對象,而后將該類對象插入到圖層樹中,從而顯示捕獲的視頻。
//創建方法 + (instancetype)layerWithSession:(AVCaptureSession *)session; - (instancetype)initWithSession:(AVCaptureSession *)session;
修改 AVCaptureVideoPreviewLayer 的屬性 videoGravity 值,可以選擇顯示捕獲視頻時的界面大小變化方式,它有以下可選值:
- AVLayerVideoGravityResize 默認值,直接鋪滿屏幕,及時畫面變形
- AVLayerVideoGravityResizeAspect 保持畫面的橫縱比,不鋪滿屏幕,多余的空間顯示黑色
- AVLayerVideoGravityResizeAspectFill 保持畫面的橫縱比,鋪滿屏幕,多余的畫面進行裁剪
AVCaptureAudioDataOutput
AVCaptureAudioDataOutput 是 AVCaptureOutput 的子類,該類可以處理接收到的音頻數據。同 AVCaptureVideoDataOutput 類似,該類也提供了一個方法,用於設置代理對象,以及調用代理對象實現的協議方法時的隊列。
- (void)setSampleBufferDelegate:(id<AVCaptureAudioDataOutputSampleBufferDelegate>)sampleBufferDelegate queue:(dispatch_queue_t)sampleBufferCallbackQueue;
AVCaptureAudioDataOutputSampleBufferDelegate
該協議提供了一個方法,用來實現對音頻數據的接收處理。
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
對於每個設備,其支持播放或捕獲的媒體資源都不相同,通過 AVCaptureDeviceFormat 類可以獲取相關信息。
視聽資源讀寫
對媒體數據資源進行簡單的轉碼或裁剪,使用 AVAssetExportSession 類便足夠了,但是更深層次的修改媒體資源,便需要用到 AVAssetReader 類和 AVAssetWriter 類。
AVAssetReader 只能與一個資源 asset 相關聯,且不能用來讀取實時數據,在開始讀取數據之前,需要為 reader 添加 AVAssetReaderOutput 的實例對象。這個實例對象描述的是待讀取的數據資源來源類型,通常使用 AVAssetReaderAudioMixOutput 、AVAssetReaderTrackOutput 、AVAssetReaderVideoCompositionOutput 三種子類。
AVAssetWriter 可以將來自多個數據源的數據以指定的格式寫入到一個指定的文件中,且其只能對應一個文件。在寫文件之前,需要用每一個 AVAssetWriterInput 類實例對象來描述相應的數據源。每一個 AVAssetWriterInput 實例對象接收的數據都應是 CMSampleBufferRef 類型的變量。如果使用 AVAssetWriterInputPixelBufferAdaptor 類也可以直接將 CVPixelBufferRef 類型的變量數據添加到 writer input 中。
AVAssetReader 與 AVAssetWriter 結合起來使用,便可以對讀取的數據進行相應的編輯修改,而后寫入到一個文件中並保存。
AVAssetReader
使用該類讀取媒體資源,其提供的初始化方法與一個 asset 相關聯。
//對於提供的參數 asset ,如果是可被修改的,那么在開始讀取操作后,對其進行了修改,之后的讀取操作都是無效的 + (nullable instancetype)assetReaderWithAsset:(AVAsset *)asset error:(NSError * _Nullable * _Nullable)outError; - (nullable instancetype)initWithAsset:(AVAsset *)asset error:(NSError * _Nullable * _Nullable)outError NS_DESIGNATED_INITIALIZER; //當前讀取操作的狀態,可取值有 AVAssetReaderStatusUnknown 、AVAssetReaderStatusReading 、 AVAssetReaderStatusCompleted 、AVAssetReaderStatusFailed 、AVAssetReaderStatusCancelled @property (readonly) AVAssetReaderStatus status; //當 status 的值為 AVAssetReaderStatusFailed 時,描述錯誤信息 @property (readonly, nullable) NSError *error; //限制可讀取的資源的時間范圍 @property (nonatomic) CMTimeRange timeRange; //判斷能否添加該數據源 - (BOOL)canAddOutput:(AVAssetReaderOutput *)output; //添加數據源 - (void)addOutput:(AVAssetReaderOutput *)output; //開始讀取 - (BOOL)startReading; //結束讀取 - (void)cancelReading;
AVAssetReaderOutput
AVAssetReaderOutput 是用來描述待讀取的數據的抽象類,讀取資源時,應創建該類的對象,並添加到相應的 AVAssetReader 實例對象中去。
//獲取的媒體數據的類型 @property (nonatomic, readonly) NSString *mediaType; //是否拷貝緩存中的數據到客戶端,默認 YES ,客戶端可以隨意修改數據,但是為優化性能,通常設為 NO @property (nonatomic) BOOL alwaysCopiesSampleData NS_AVAILABLE(10_8, 5_0); //同步獲取下一個緩存數據,使用返回的數據結束后,應使用 CFRelease 函數將其釋放 //當錯誤或沒有數據可讀取時,返回 NULL ,返回空后,應檢查相關聯的 reader 的狀態 - (nullable CMSampleBufferRef)copyNextSampleBuffer CF_RETURNS_RETAINED; //是否支持重新設置數據的讀取時間范圍,即能否修改 reader 的 timeRange 屬性 @property (nonatomic) BOOL supportsRandomAccess NS_AVAILABLE(10_10, 8_0); //設置重新讀取的時間范圍,這個時間范圍集合中的每一個時間范圍的開始時間必需是增長的且各個時間范圍不能重疊 //應在 reader 調用 copyNextSampleBuffer 方法返回 NULL 之后才可調用 - (void)resetForReadingTimeRanges:(NSArray<NSValue *> *)timeRanges NS_AVAILABLE(10_10, 8_0); //該方法調用后,上面的方法即不可再調用,同時 reader 的狀態也不會被阻止變為 AVAssetReaderStatusCompleted 了 - (void)markConfigurationAsFinal NS_AVAILABLE(10_10, 8_0);
AVAssetReaderTrackOutput
AVAssetReaderTrackOutput 是 AVAssetReaderOutput 的子類,它用來描述待讀取的數據來自 asset track ,在讀取前,還可以對數據的格式進行修改。
//初始化方法,參數中指定了 track 和 媒體的格式 //指定的 track 應在 reader 的 asset 中 + (instancetype)assetReaderTrackOutputWithTrack:(AVAssetTrack *)track outputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings; - (instancetype)initWithTrack:(AVAssetTrack *)track outputSettings:(nullable NSDictionary<NSString *, id> *)outputSettings NS_DESIGNATED_INITIALIZER; //指定音頻處理時的算法 @property (nonatomic, copy) NSString *audioTimePitchAlgorithm NS_AVAILABLE(10_9, 7_0);
AVAssetReaderAudioMixOutput
AVAssetReaderAudioMixOutput 是 AVAssetReaderOutput 的子類,它用來描述待讀取的數據來自音頻組合數據。創建該類實例對象提供的參數 audioTracks 集合中的每一個 asset track 都屬於相應的 reader 中的 asset 實例對象,且類型為 AVMediaTypeAudio 。
參數 audioSettings 給出了音頻數據的格式設置。
+ (instancetype)assetReaderAudioMixOutputWithAudioTracks:(NSArray<AVAssetTrack *> *)audioTracks audioSettings:(nullable NSDictionary<NSString *, id> *)audioSettings; - (instancetype)initWithAudioTracks:(NSArray<AVAssetTrack *> *)audioTracks audioSettings:(nullable NSDictionary<NSString *, id> *)audioSettings NS_DESIGNATED_INITIALIZER;
此外,該類的 audioMix 屬性,描述了從多個 track 中讀取的音頻的音量變化情況。
@property (nonatomic, copy, nullable) AVAudioMix *audioMix;
AVAssetReaderVideoCompositionOutput
AVAssetReaderVideoCompositionOutput 是 AVAssetReaderOutput 的子類,該類用來表示要讀取的類是組合的視頻數據。
同 AVAssetReaderAudioMixOutput 類似,該類也提供了兩個創建實例的方法,需要提供的參數的 videoTracks 集合中每一個 track 都是
與 reader 相關聯的 asset 中的 track 。
+ (instancetype)assetReaderVideoCompositionOutputWithVideoTracks:(NSArray<AVAssetTrack *> *)videoTracks videoSettings:(nullable NSDictionary<NSString *, id> *)videoSettings; - (instancetype)initWithVideoTracks:(NSArray<AVAssetTrack *> *)videoTracks videoSettings:(nullable NSDictionary<NSString *, id> *)videoSettings NS_DESIGNATED_INITIALIZER;
該類的屬性 videoComposition 同樣描述了每個 track 的幀的顯示方式。
@property (nonatomic, copy, nullable) AVVideoComposition *videoComposition;
使用 AVOutputSettingsAssistant 類可以獲取簡單的編碼設置