兩個獨立的視頻拼接起來以后很有可能會出現(xiàn)銜接處過于生硬的問題,此時就需要給視頻添加過渡效果鳞仙,這一效果需要用到 AVVideoComposition 及其子類 AVMutableVideoComposition君躺。
AVMutableVideoComposition 是過渡效果實現(xiàn)的核心峭判,它能夠表示多個視頻軌道的合并,同時表達(dá)合并的方式棕叫,也就是過渡效果林螃,同時提供了配置視頻組合的渲染尺寸、縮放俺泣、幀時長等疗认。AVMutableVideoComposition 由一組 AVMutableVideoCompositionInstruction 組成,AVMutableVideoCompositionInstruction 定義了時間范圍信息伏钠,以及每一幀的層級横漏,也就是 AVMutableVideoCompositionLayerInstruction,AVMutableVideoCompositionLayerInstruction 用于真正實現(xiàn)各類模糊熟掂、變形和裁剪效果缎浇。
總結(jié)一下就是:
- AVMutableVideoCompositionLayerInstruction 負(fù)責(zé)執(zhí)行具體的過渡動畫
- AVMutableVideoCompositionInstruction 負(fù)責(zé)管理過渡動畫在何時執(zhí)行
- AVMutableVideoComposition 代表最終修改過的視頻組合對象
而 AVMutableVideoComposition 就可以被提供給 AVPlayerItem、AVAssetExportSession赴肚、AVAssetReaderVideoCompositionOutput 和 AVAssetImageGenerator 使用了素跺,但是要注意的是二蓝,與 AVAudioMix 類似,AVMutableVideoComposition 并不能與 AVComposition 關(guān)聯(lián)指厌,這一點導(dǎo)致在編輯和傳遞 AVMutableVideoComposition 過程中刊愚,需要時時考慮附帶 AVMutableVideoComposition 參數(shù)。
AVVideoComposition 與 AVComposition 沒有關(guān)系仑乌。
實現(xiàn)過渡效果的基本步驟可以分為
- 合并視頻和音頻,生成多視頻軌道的 AVMutableComposition
- 對視頻過渡區(qū)域琴锭,生成過渡動畫
- 組裝 AVMutableVideoComposition晰甚,提供給 AVPlayerItem 或 AVAssetExportSession 使用
1. 合并媒體
由于 AVMutableVideoCompositionLayerInstruction 是與視頻軌道綁定的,因此在處理過渡效果時决帖,需要在不同軌道之間處理厕九,常見的方式是交錯放置多個視頻,形成如下形式的視頻布局
段1 | 段2 | 段3 |
---|---|---|
視頻A | 視頻C | |
視頻B |
可以用一個視頻軌道數(shù)組來表達(dá)多個軌道地回。同時扁远,為了實現(xiàn)過渡效果,兩個相鄰的視頻刻像,如視頻 A 和 B 之間畅买,應(yīng)當(dāng)在時間軸上有重疊區(qū)域,因此需要對時間軸進(jìn)行如下區(qū)分
視頻 A | AB 過渡區(qū) | 視頻 B | BC 過渡區(qū) | 視頻 C |
---|
這樣的劃分也需要記錄下來细睡,所以最終合并媒體的步驟如下
1.1 初始化相關(guān)對象
AVMutableComposition *composition = [AVMutableComposition composition];
__block CMTime cursor = kCMTimeZero;
CMTime transitionTime = CMTimeMake(2, 1); // 過渡時間
NSMutableArray *passRanges = [NSMutableArray array];// 視頻獨立區(qū)時間數(shù)組
NSMutableArray *transitionRanges = [NSMutableArray array]; // 過渡區(qū)時間數(shù)組
AVMutableCompositionTrack *videoCompositionTrackA = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *videoCompositionTrackB = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
NSArray *videoTracks = @[videoCompositionTrackA, videoCompositionTrackB]; // 生成 AB 軌道
1.2 遍歷資源
遍歷資源過程中谷羞,首先需要將視頻軌道和音頻軌道加入到 AVMutableComposition 中,其次需要更獨立區(qū)時間數(shù)組和過渡區(qū)時間數(shù)組
// 視頻軌道
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);
// 去除每一個視頻的頭部過渡區(qū)
if (idx > 0) { // 第一個視頻只需要裁剪尾部過渡區(qū)溜徙,不需要裁剪頭部過渡區(qū)
timeRange.start = CMTimeAdd(timeRange.start, transitionTime);
timeRange.duration = CMTimeSubtract(timeRange.duration, transitionTime);
}
// 去除每一個視頻的尾部過渡區(qū)
if (idx + 1 < mediaAssets.count) { // 末尾視頻沒有尾部過渡區(qū)湃缎,其他視頻還需要去除尾部過渡區(qū)
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) { // 末尾一個視頻沒有尾部過渡區(qū)
timeRange = CMTimeRangeMake(cursor, transitionTime);
[transitionRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
}
這里我們將視頻錯開放入了兩個視頻軌道里,要注意由于視頻軌道內(nèi)具有 z 索引行為蠢壹,因此目前是不能播放多個視頻軌道的嗓违。
2. 過渡動畫
現(xiàn)在我們有了兩個視頻軌道,一個表示獨立區(qū)的時間數(shù)組图贸,一個表示過渡區(qū)的時間數(shù)組蹂季,接下來需要在每一個過渡區(qū)里定義具體的過渡動畫,并將所有
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]; // 取出對應(yīng)軌道
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = [obj CMTimeRangeValue];// 取出獨立分區(qū)的 duration
AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:currentTrack];// 取出當(dāng)前軌道的 layerInstruction
instruction.layerInstructions = @[layerInstruction];
[compositionInstructions addObject:instruction];// 將 AVMutableVideoCompositionInstruction 加入到數(shù)組里
if (idx < transitionRanges.count) { // 過渡區(qū)處理
AVCompositionTrack *foregroundTrack = tracks[trackIndex];//當(dāng)前的track
AVCompositionTrack *backgroundTrack = tracks[1 - trackIndex];// 下一個 track
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = [transitionRanges[idx] CMTimeRangeValue];
AVMutableVideoCompositionLayerInstruction *frontLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:foregroundTrack];// 取出當(dāng)前軌道的 layerInstruction
AVMutableVideoCompositionLayerInstruction *backLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:backgroundTrack];// 取出下一個軌道的 layerInstruction
// 實際過渡動畫的定義
instruction.layerInstructions = @[frontLayerInstruction, backLayerInstruction];
[compositionInstructions addObject:instruction];
}
}];
要注意疏日,compositionInstructions 數(shù)組必須按順序組裝 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 裁剪區(qū)域父能、擦除效果
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 數(shù)組后,就可以組裝 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 屬性用于設(shè)置所有組合指令净神,也就是 AVMutableVideoCompositionInstruction
- renderSize 定義渲染尺寸
- frameDuration 定義有效幀率何吝,30 FPS 的幀率對應(yīng) frameDuration 為 1/30
- renderScale 定義視頻組合的縮放值
當(dāng)然還可以用快捷方式來獲取一個 AVMutableVideoComposition
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:composition];
這個方法所配置的屬性如下所示
- instructions 屬性包含一組完整的基于組合視頻軌道的組合和層指令
- renderSize 被設(shè)置為 AVComposition 的 naturalSize溉委,如果沒有,則設(shè)置為能夠滿足最大視頻維度的尺寸值
- frameDuration 設(shè)置為組合視頻軌道的最大 nominalFrameRate 的值爱榕,如果都為 0 則設(shè)置為 1/30
- renderScale 設(shè)置為 1.0
生成了 AVMutableVideoComposition 以后就可以直接用于播放或?qū)С隽恕?/p>
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:[composition copy]];
item.videoComposition = videoComposition;