AVFoundation 視頻過渡效果

兩個獨立的視頻拼接起來之後頗有可能會出現銜接處過於生硬的問題,此時就須要給視頻添加過渡效果,這一效果須要用到 AVVideoComposition 及其子類 AVMutableVideoComposition。數組

AVMutableVideoComposition 是過渡效果實現的核心,它可以表示多個視頻軌道的合併,同時表達合併的方式,也就是過渡效果,同時提供了配置視頻組合的渲染尺寸、縮放、幀時長等。AVMutableVideoComposition 由一組 AVMutableVideoCompositionInstruction 組成,AVMutableVideoCompositionInstruction 定義了時間範圍信息,以及每一幀的層級,也就是 AVMutableVideoCompositionLayerInstruction,AVMutableVideoCompositionLayerInstruction 用於真正實現各種模糊、變形和裁剪效果。ide

總結一下就是:佈局

  • AVMutableVideoCompositionLayerInstruction 負責執行具體的過渡動畫
  • AVMutableVideoCompositionInstruction 負責管理過渡動畫在什麼時候執行
  • AVMutableVideoComposition 表明最終修改過的視頻組合對象

而 AVMutableVideoComposition 就能夠被提供給 AVPlayerItem、AVAssetExportSession、AVAssetReaderVideoCompositionOutput 和 AVAssetImageGenerator 使用了,可是要注意的是,與 AVAudioMix 相似,AVMutableVideoComposition 並不能與 AVComposition 關聯,這一點致使在編輯和傳遞 AVMutableVideoComposition 過程當中,須要時時考慮附帶 AVMutableVideoComposition 參數。動畫

AVVideoComposition 與 AVComposition 沒有關係。ui

實現過渡效果的基本步驟能夠分爲spa

  • 合併視頻和音頻,生成多視頻軌道的 AVMutableComposition
  • 對視頻過渡區域,生成過渡動畫
  • 組裝 AVMutableVideoComposition,提供給 AVPlayerItem 或 AVAssetExportSession 使用

1. 合併媒體

因爲 AVMutableVideoCompositionLayerInstruction 是與視頻軌道綁定的,所以在處理過渡效果時,須要在不一樣軌道之間處理,常見的方式是交錯放置多個視頻,造成以下形式的視頻佈局code

段1 段2 段3
視頻A 視頻C
視頻B

能夠用一個視頻軌道數組來表達多個軌道。同時,爲了實現過渡效果,兩個相鄰的視頻,如視頻 A 和 B 之間,應當在時間軸上有重疊區域,所以須要對時間軸進行以下區分orm

視頻 A AB 過渡區 視頻 B BC 過渡區 視頻 C

這樣的劃分也須要記錄下來,因此最終合併媒體的步驟以下視頻

1.1 初始化相關對象

AVMutableComposition *composition = [AVMutableComposition composition];
                    __block CMTime cursor = kCMTimeZero;
                    CMTime transitionTime = CMTimeMake(2, 1); // 過渡時間
                    NSMutableArray *passRanges = [NSMutableArray array];// 視頻獨立區時間數組
                    NSMutableArray *transitionRanges = [NSMutableArray array]; // 過渡區時間數組
                    
                    AVMutableCompositionTrack *videoCompositionTrackA = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
                    AVMutableCompositionTrack *videoCompositionTrackB = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
                    NSArray *videoTracks = @[videoCompositionTrackA, videoCompositionTrackB]; // 生成 AB 軌道
複製代碼

1.2 遍歷資源

遍歷資源過程當中,首先須要將視頻軌道和音頻軌道加入到 AVMutableComposition 中,其次須要更獨立區時間數組和過渡區時間數組對象

// 視頻軌道
                        AVMutableCompositionTrack *videoCompositionTrack = videoTracks[idx % 2];
                        AVAssetTrack *videoTrack = [[targetAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
                        [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, targetAsset.duration) ofTrack:videoTrack atTime:cursor error:nil];
                        
                        // 音頻軌道
                        AVMutableCompositionTrack *audioCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
                        AVAssetTrack *audioTracck = [[targetAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
                        [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, targetAsset.duration) ofTrack:audioTracck atTime:cursor error:nil];
                        
                        CMTimeRange timeRange = CMTimeRangeMake(cursor, targetAsset.duration);
                        // 去除每個視頻的頭部過渡區
                        if (idx > 0) { // 第一個視頻只須要裁剪尾部過渡區,不須要裁剪頭部過渡區
                            timeRange.start = CMTimeAdd(timeRange.start, transitionTime);
                            timeRange.duration = CMTimeSubtract(timeRange.duration, transitionTime);
                        }
                        // 去除每個視頻的尾部過渡區
                        if (idx + 1 < mediaAssets.count) { // 末尾視頻沒有尾部過渡區,其餘視頻還須要去除尾部過渡區
                            timeRange.duration = CMTimeSubtract(timeRange.duration, transitionTime);
                        }
                        [passRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
                        
                        cursor = CMTimeAdd(cursor, targetAsset.duration);
                        cursor = CMTimeSubtract(cursor, transitionTime);
                        
                        if (idx + 1 < mediaAssets.count) { // 末尾一個視頻沒有尾部過渡區
                            timeRange = CMTimeRangeMake(cursor, transitionTime);
                            [transitionRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
                        }
複製代碼

這裏咱們將視頻錯開放入了兩個視頻軌道里,要注意因爲視頻軌道內具備 z 索引行爲,所以目前是不能播放多個視頻軌道的。

2. 過渡動畫

如今咱們有了兩個視頻軌道,一個表示獨立區的時間數組,一個表示過渡區的時間數組,接下來須要在每個過渡區裏定義具體的過渡動畫,並將全部

NSMutableArray *compositionInstructions = [NSMutableArray array];
                    NSArray *tracks = [composition tracksWithMediaType:AVMediaTypeVideo];
                    [passRanges enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                        NSUInteger trackIndex = idx % 2;
                        
                        AVMutableCompositionTrack *currentTrack = tracks[trackIndex]; // 取出對應軌道
                        AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
                        instruction.timeRange = [obj CMTimeRangeValue];// 取出獨立分區的 duration
                        AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:currentTrack];// 取出當前軌道的 layerInstruction
                        instruction.layerInstructions = @[layerInstruction];
                        [compositionInstructions addObject:instruction];// 將 AVMutableVideoCompositionInstruction 加入到數組裏
                        
                        if (idx < transitionRanges.count) { // 過渡區處理
                            AVCompositionTrack *foregroundTrack = tracks[trackIndex];//當前的track
                            AVCompositionTrack *backgroundTrack = tracks[1 - trackIndex];// 下一個 track
                            AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
                            instruction.timeRange = [transitionRanges[idx] CMTimeRangeValue];
                            AVMutableVideoCompositionLayerInstruction *frontLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:foregroundTrack];// 取出當前軌道的 layerInstruction
                            AVMutableVideoCompositionLayerInstruction *backLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:backgroundTrack];// 取出下一個軌道的 layerInstruction
                            
                            // 實際過渡動畫的定義
                            
                            instruction.layerInstructions = @[frontLayerInstruction, backLayerInstruction];
                            [compositionInstructions addObject:instruction];
                        }
                    }];
複製代碼

要注意,compositionInstructions 數組必須按順序組裝 AVMutableVideoCompositionInstruction 對象。

AVMutableVideoCompositionLayerInstruction 自己支持三種過渡動畫效果

  • opacity 透明度變化、溶解效果
[frontLayerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:[transitionRanges[idx] CMTimeRangeValue]];
                            [backLayerInstruction setOpacityRampFromStartOpacity:0.0 toEndOpacity:1.0 timeRange:[transitionRanges[idx] CMTimeRangeValue]];
複製代碼
  • Transform 矩陣變換、推入效果
CGAffineTransform identityTransform = CGAffineTransformIdentity;
                            CGFloat videoWidth = 1280.f;
                            CGAffineTransform from = CGAffineTransformMakeTranslation(-videoWidth, 0);
                            CGAffineTransform to = CGAffineTransformMakeTranslation(videoWidth, 0.0);
                            [frontLayerInstruction setTransformRampFromStartTransform:identityTransform toEndTransform:from timeRange:[transitionRanges[idx] CMTimeRangeValue]];
                            [backLayerInstruction setTransformRampFromStartTransform:to toEndTransform:identityTransform timeRange:[transitionRanges[idx] CMTimeRangeValue]];
複製代碼
  • CropRectangle 裁剪區域、擦除效果
CGFloat videoWidth = 1280.f;
                            CGFloat videoHeight = 720.f;

                            CGRect startRect = CGRectMake(0.0f, 0.0f, videoWidth, videoHeight);
                            CGRect endRect = CGRectMake(0.0f, 0.0f, videoWidth, 0.0f);

                            [frontLayerInstruction setCropRectangleRampFromStartCropRectangle:startRect toEndCropRectangle:endRect timeRange:[transitionRanges[idx] CMTimeRangeValue]];
複製代碼

組裝媒體

得到了裝有 AVMutableVideoCompositionInstruction 的 compositionInstructions 數組後,就能夠組裝 AVMutableVideoComposition 了

AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
                    videoComposition.instructions = [compositionInstructions copy];
                    videoComposition.renderSize = CGSizeMake(1280.f, 720.f);
                    videoComposition.frameDuration = CMTimeMake(1, 30);
                    videoComposition.renderScale = 1.0;
複製代碼

這裏定義了四個主要屬性

  • instructions 屬性用於設置全部組合指令,也就是 AVMutableVideoCompositionInstruction
  • renderSize 定義渲染尺寸
  • frameDuration 定義有效幀率,30 FPS 的幀率對應 frameDuration 爲 1/30
  • renderScale 定義視頻組合的縮放值

固然還能夠用快捷方式來獲取一個 AVMutableVideoComposition

AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:composition];
複製代碼

這個方法所配置的屬性以下所示

  • instructions 屬性包含一組完整的基於組合視頻軌道的組合和層指令
  • renderSize 被設置爲 AVComposition 的 naturalSize,若是沒有,則設置爲可以知足最大視頻維度的尺寸值
  • frameDuration 設置爲組合視頻軌道的最大 nominalFrameRate 的值,若是都爲 0 則設置爲 1/30
  • renderScale 設置爲 1.0

生成了 AVMutableVideoComposition 之後就能夠直接用於播放或導出了。

AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:[composition copy]];
item.videoComposition = videoComposition;
複製代碼
相關文章
相關標籤/搜索