两个独立的视频拼接起来之后颇有可能会出现衔接处过于生硬的问题,此时就须要给视频添加过渡效果,这一效果须要用到 AVVideoComposition 及其子类 AVMutableVideoComposition。数组
AVMutableVideoComposition 是过渡效果实现的核心,它可以表示多个视频轨道的合并,同时表达合并的方式,也就是过渡效果,同时提供了配置视频组合的渲染尺寸、缩放、帧时长等。AVMutableVideoComposition 由一组 AVMutableVideoCompositionInstruction 组成,AVMutableVideoCompositionInstruction 定义了时间范围信息,以及每一帧的层级,也就是 AVMutableVideoCompositionLayerInstruction,AVMutableVideoCompositionLayerInstruction 用于真正实现各种模糊、变形和裁剪效果。ide
总结一下就是:布局
而 AVMutableVideoComposition 就能够被提供给 AVPlayerItem、AVAssetExportSession、AVAssetReaderVideoCompositionOutput 和 AVAssetImageGenerator 使用了,可是要注意的是,与 AVAudioMix 相似,AVMutableVideoComposition 并不能与 AVComposition 关联,这一点致使在编辑和传递 AVMutableVideoComposition 过程当中,须要时时考虑附带 AVMutableVideoComposition 参数。动画
AVVideoComposition 与 AVComposition 没有关系。ui
实现过渡效果的基本步骤能够分为spa
因为 AVMutableVideoCompositionLayerInstruction 是与视频轨道绑定的,所以在处理过渡效果时,须要在不一样轨道之间处理,常见的方式是交错放置多个视频,造成以下形式的视频布局code
段1 | 段2 | 段3 |
---|---|---|
视频A | 视频C | |
视频B |
能够用一个视频轨道数组来表达多个轨道。同时,为了实现过渡效果,两个相邻的视频,如视频 A 和 B 之间,应当在时间轴上有重叠区域,所以须要对时间轴进行以下区分orm
视频 A | AB 过渡区 | 视频 B | BC 过渡区 | 视频 C |
---|
这样的划分也须要记录下来,因此最终合并媒体的步骤以下视频
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 轨道
复制代码
遍历资源过程当中,首先须要将视频轨道和音频轨道加入到 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 索引行为,所以目前是不能播放多个视频轨道的。
如今咱们有了两个视频轨道,一个表示独立区的时间数组,一个表示过渡区的时间数组,接下来须要在每个过渡区里定义具体的过渡动画,并将全部
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 自己支持三种过渡动画效果
[frontLayerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:[transitionRanges[idx] CMTimeRangeValue]];
[backLayerInstruction setOpacityRampFromStartOpacity:0.0 toEndOpacity:1.0 timeRange:[transitionRanges[idx] CMTimeRangeValue]];
复制代码
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]];
复制代码
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;
复制代码
这里定义了四个主要属性
固然还能够用快捷方式来获取一个 AVMutableVideoComposition
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:composition];
复制代码
这个方法所配置的属性以下所示
生成了 AVMutableVideoComposition 之后就能够直接用于播放或导出了。
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:[composition copy]];
item.videoComposition = videoComposition;
复制代码