AVFoundation - 创建视频过渡动画
1. 两个场景间过渡会使用一些类型的动画过渡效果, 诸如:渐隐, 溶解, 擦除等. 视频过渡的核心类是AVVideoComposition, 这个类对两个或者多哥视频轨道组合在一起的方法给出了一个总体描述. 他由一组时间范围和描述组合行为的介绍内容组成, 这些信息出现在组合资源内的任意时间点. 除了包含描述输入视频层的组合信息之外, 还提供了配置视频组合的渲染尺寸, 缩放和帧时长等属性. 视频组合的配置确定了委托对象处理时AVComposition的呈现方式. 这里的委托对象比如AVPlayer或AVAssetImageGenerator.
2. AVVideoComposition 是由一组AVVideoCompositionInstruction对象格式定义的指令组成的. 这个对象所提供的最关键的一段数据是组合对象时间轴内的时间范围信息. 这一时间范围是在某一组合形式出现时的时间范围. 要执行的组合特质是通过其layerInstructions集合定义的.
3. AVVideoCompositionLayerInstruction用于定义给视频轨道应用的模糊, 变形, 和裁剪效果. 它提供了一些方法用于在特定的时间点或在一个时间范围内对这些值就行修改. 在一段时间内对这些值进行操作可以让开发者创建出动态的过渡效果. 比如溶解和渐淡效果.
4. 部署视频布局, 要在剪辑中添加过渡, 首先需要将两个轨道的视频片段重新部署, 我们知道如何将多个音频轨道组合在一起, 视频情况也是一样的. 大多数情况下两个轨道就够了, 不过也可以加入过多的轨道, 需要注意的是添加多轨道会对性能产生负面影响. 首先创建包含两个视频轨道组合.
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *trackA = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackId:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *trackB = [composition addMutableTrackWithMediaType:AVMediaTypeVideo perferredTrackID:kCMPersistentTrackID_Invalid];
NSArray *videoTracks = @[trackA, trackB];
5. 实际组合中的所有视频剪辑都可以遵循相同的A-B模式, 当视频片段按照这种方式排列后, 视频片段前面或后面的空间都会添加一个空片段, 他们都是普通AVCompositionTrackSegment实例, 与视频是一样的, 不过他们不包含任何媒体.
NSArray *videoAssets = nil; //array of loaded AVAsset instances;
CMTime cursorTime = kCMTimeZero;
for (NSUInteger I = 0; I < videoAssets.cout; I++) {
NSUInteger trackIndex = I % 2;
AVMutableCompositionTrack *currentTrack = videoTracks[trackIndex];
AVAsset *asset = videoAssets[I];
AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
[currentTrack insertTimeRange:timeRange ofTrack:assetTrack atTime:cursorTime error:nil];
}
6. 定义重叠区域, 要在两个片段中应用视频过渡, 需要根据期望的过渡的持续时长来确定片段的重叠情况. 解决这一问题只需要对cursorTime稍加调整即可.
NSArray *videoAssets = nil;
CMTime cursorTime = kCMTimeZero;
CMTime transitionDuration = CMTimeMake(2, 1);
for (NSUInteger I = 0; I < videoAssets.count; I ++) {
NSUInteger trackIndex = I % 2;
NSMutableCompositionTrack *currentTrack = videoTracks[trackIndex];
AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
[currentTrack insertTimeRange:timeRange ofTrack:assetTrack atTime:cursorTime error:nil];
cursorTime = CMTimeAdd(cursorTime, timeRange.duration);
cursorTime = CMTimeSubtract(cursorTime, transitionDuration);
}
7. 计算通过和过渡的时间范围, AVVideoComposition是由AVVideoCompositionInstruction对象组成, 其中最重要的数据是时间范围, 他用来表示某种出现的组合方式和持续的时长. 需要计算两个类型的时间范围. 第一个通常被认为是通过时间范围, 第二个时间范围类型为过渡时间范围.
NSMutableArray *videoAssets =
CMTime cursorTime = kCMTimeZero;
CMTime transDuration = CMTimeMake(2,1);
NSMutableArray *passThroughTimeRanges = [NSMutableArray array];
NSMutableArray *transitionTimeRanges = [NSMutableArray array];
NSUInteger videoCount = [videoAssets count];
for (NSUInteger I = 0; I < videoCount; I ++) {
AVAsset *asset = videoAssets[I];
CMTimeRange timeRange = CMTimeRangeMake(cursorTime, asset.duration);
if (I > 0) {
timeRange.start = CMTimeAdd(timeRange.start, transDuration);
timeRange.duration = CMTimeSubstract(timeRange.duration, transDuration);
}
if (I + 1 < videoCount) {
timeRange.duration = CMTimeSubstract(timeRange.duration, transDuration);
}
[passThroughTimeRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
cursorTime = CMTimeAdd(cursorTime, asset.duration);
cursorTime = CMTimeSubstract(cursorTime, transDuration);
if (I + 1 < videoCount ){
timeRange = CMTimeRangeMake(cursorTime, transDuration);
NSValue *timeRangeValue = [NSValue valueWithCMTimeRange:timeRange];
[transitionTimeRanges addObject:timeRangeValue];
}
}
8. 创建组合和层指令, 创建AVVideoCompositionInstruction和AVVideoCompositionLayerInstruction实例. 提供视频组合所执行的指令.
NSMutableArray *compositionInstructions = [NSMutableArray array];
NSArray *tracks = [composition trackWithMediaType:AVMediaTypeVideo];
for (NSUInteger I = 0; I < passThroughTimeRanges.cout; I++) {
NSUInteger tackIndex = I % 2;
AVMutableCompositionTrack *currentTrack = tracks[trackIndex];
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = [passThroughTimeRanges[I] CMTimeRangeValue];
AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:currentTrack];
instraction.layerInstructions = @[layerInstruction];
[compositionInstructions addObject:instruction];
if (I < transitionTimeRanges.count) {
AVCompositionTrack *foregroundTrack = tracks[trackIndex];
AVCompositionTrack *backgroundTrack = tracks[1-trackIndex];
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoComposition videoCompositionInstruction];
CMTimeRange timeRange = [transitionTimeRanges[I] CMTimeRangeValue];
instruction.timeRange = timeRange;
AVMutableVideoCompositionLayerInstruction *fromLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:foregroundTrack];
AVMutableVideoCompositionLayerInstruction *toLayerInstruction = [AVMutableCompositionLayerInstructionWithAssetTrack:backgroundTrack];
instruction.layerInstructions = @[fromLayerInstruction, toLayerInstruction];
[compositionInstructions addObject:instruction];
}
}
9. 创建和配置AVVideoComposition实例
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.instructions = compositionInstructions; //instrucrtions属性用于设置组合指令, 这些指令向组合容器描述时间范围和执行组合的种类.
videoComposition.renderSize = CGSizeMake(12280, 720); //渲染尺寸. 这个尺寸对应组合总的视频原始大小.
videoComposition.frameDuration = CMTimeMake(1, 30); //设置帧率
videoComposition.renderSCale = 1.0f; //设置视频组合应用的缩放.
10. 创建视频组合的捷径, AVVideoComposition定义了一个便捷初始化方法videoCompositionWithPropertiesOfAssetm, 将AVComposition作为参数并创建一个AVVideoComposition, 这个方法创建一个带有配置AVVideoComposition对象.
instructions属性包含一组完整的基于组合视频轨道.(以及其中包含的片段空间布局)的组合和层指令.
renderSize 属性被设置为AVComposition对象的naturalSize, 或者如果没有设置, 则使用能够满足组合视频轨道中最大的视频维度和尺寸值.
11. 视频过渡摘抄
- (id)initWithComposition:(AVComposition *)composition videoComposion:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix {
self = [super init];
if (self) {
_composition = composition;
_videoComposition = videoComposition;
_audioMix = audioMix;
}
return self;
}
- (AVPlayerItem *)makePlayable {
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:[self.composition copy]];
playerItem.audioMix = self.audioMix;
playerItem.videoComposition = self.videoComposition;
return playerItem;
}
- (AVAssetExportSession *)makeExportable {
NSString *preset = AVAssetExportPresetHighestQuality;
AVAssetExportSession *session = [AVAssetExportSession exportSessionWithAsset:[self.composition copy]];
session.audioMix = self.audioMix;
session.videoComposition = self.videoComposition;
return session;
}
@implementation THTransitionCompositionBuilder
- (id)initWithTimeline:(THTimeline *)timeline {
self = [super init];
if (self) {
_timeline = timeline;
}
return self;
}
- (id <THComposition>)buildComposition {
self.composition = [AVMutableComposition composition];
[self buildCompositionTracks];
AVVideoComposition *videoComposition = [self buildVideoComposition];
AVAudioMix *audioMix = [self buildAudioMix];
return [[THTransitionComposition alloc] initWithComposition:self.composition videoComposition:videoComposition audioMix:audioMix];
}
- (void)buildCompositionTracks {
CMPersistentTrackID trackID = kCMPersistentTrackID_Invalid;
AVMutableCompositionTrack *compositionTrackA = [self.composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredID:trackID];
AVMutableCompositionTrack *compositionTrackB = [self.composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:trackID];
NSArray *videoTracks = @[compositionTrackA, compositionTrackB];
CMTime cursorTime = kCMTimeZero;
CMTime transitionDuration = kCMTimeZero;
if (!self.timeline.transitions) {
transitionDuration = 2;
}
NSArray *videos = self.timeline.videos;
for (NSUInteger I = 0; I < videos.count; I++) {
NSUInteger trackIndex = I % 2;
THVideoItem *item = videos[I];
AVMutableCompositionTrack *currentTrack = videoTracks[trackIndex];
AVAssetTrack *assetTrack = [[item.asset trackWithMediaType:AVMediaTypeVideo] firstObject];
[currentTrack insertTimeRange:item.timeRange ofTrack:assetTrack atTime:cursorTime error:nil];
cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration);
cursorTime = CMTimeSubtract(cursorTime, 2);
}
[self addCompositionTrackOfType:AVMediaTypeAudio withMediItems:self.timeline.voiceOvers];
NSArray *musicItems = self.timeline.musicItems;
self.musicTrack = [self addCompositionTrackOfType:AVMediaTypeAudio with];
MediaItems:musicItems];
}
- (AVVideoComposition *)buildVideoComposition {
AVVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:self.composition];
NSArray *transitionInstructions = [self transitionInstructionsInVideoComposition:videoComposition];
for (THTransitionInstructions *instructions in transitionInstructions) {
CMTimeRange timeRange = instruction.compositionInstruction.timeRange;
AVMutableVideoCompositionLayerInstruction *fromLayer = instructions.fromLayerInstruction;
THVideoTransitionType type = instructions.transition.type;
if (type == THVideoTransitionTypeDissolve) { //溶解
[fromLayer setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:timeRange];
}else if (type == THVideoTransitionTypePush) { //推入
}else if (type == THVideoTransitionTypeWipe) { //擦除
}
instructions.compositionInstruction.layerInstructions = @{fromLayer, toLayer};
}
return videoComposition;
}
@end
11. 提取过渡指令
- (NSArray *)transitionInstructionsInVideoComposition:(AVVideoComposition *)vc {
NSMutableArray *transitionInstructions = [NSMutableArray array];
int layerInstructionsIndex = 1;
NSArray *compositionInstructions = vc.instructions;
for (AVMutableVideoCompositionInstruction *vci in compositionInstructions ) {
if (vci.layerInstructions.count == 2) {
THTransitionInstructions *instructions = [[THTransitionInstructions alloc] init];
instructions.fromLayerInstruction = vci.layerInstructions[1-layerInstructionIndex];
intructions.toLayerInstruction = [vci.layerInstructions addObject:instructions];
layerInstructionIndex = layerInstructionIndex == 1 ? 0 : 1;
}
}
NSArray *transitions = self.timeline.transitions;
if (transitions == nil) {
return transitionInstructions;
}
for (NSUInteger I = 0; I < transitionInstructions.count; I++) {
THTransitionInstructions *tis = transitionInstructions[I];
tis.transition = self.timeline.transitions[I];
}
return transitionInstructions;
}