AVFoundation 视频与动画图层

1,718 阅读2分钟

在编辑视频的过程中,时常有场景需要对视频增添更多个性化的内容,如字幕、水印或者表情等,此时需要对视频增加一个动画叠加层,在实现上来讲就需要结合 AVFoundation 和 CoreAnimation 一同使用。

在 AVFoundation 中使用 CoreAnimation 和在 iOS 其他地方使用 CoreAnimation 一样,但是最大的差别在于时间模型。正常使用 CoreAnimation 的时候,时间模型取决于系统主机,但是视频动画有其自己的时间线,同时需要支持停止、暂停、回退或快进等效果,所以不能直接用系统主机的时间模型向一个视频中添加基于时间的动画。

针对这一特性,AVFoundation 在两个场景下提供了两个工具实现此效果

  • 需要在播放视频时加入 CoreAnimation,提供 AVSynchronizedLayer 类
  • 需要在导出视频时加入 CoreAnimation,提供 AVVideoCompositionCoreAnimationTool

不清楚为什么需要两个类实现同一个效果,这样做会导致在实现带有 CoreAnimation 的 layer 时,要保证 layer 的坐标与尺寸保持一致会非常麻烦。

1. AVSynchronizedLayer 与播放

首先实现一个简单的 CALayer 来展示一段字符串组成的字幕

    CGFloat fontSize = 32.0f;
    UIFont *font = [UIFont fontWithName:@"GillSans-Bold" size:fontSize];
    NSDictionary *attrs = @{NSFontAttributeName : font, NSForegroundColorAttributeName : (id)[UIColor whiteColor].CGColor};
    NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"hello yasic" attributes:attrs];
    CGSize textSize = [@"hello yasic" sizeWithAttributes:attrs];
    CATextLayer *layer = [CATextLayer layer];
    layer.opacity = 0.0;
    layer.string = string;
    layer.frame = CGRectMake((SCREEN_WIDTH - textSize.width)/2.0, (SCREEN_HEIGHT - textSize.height)/2.0, textSize.width, textSize.height);
    layer.backgroundColor = [UIColor clearColor].CGColor;

然后对这个 layer 加上 CoreAnimation

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
    animation.values = @[@0.0f, @1.0f, @1.0f, @0.0f];
    animation.keyTimes = @[@0.0f, @0.3f, @0.7f, @1.0f];
    animation.beginTime = CMTimeGetSeconds(CMTimeMake(1, 1));
    animation.duration = 5.0f;
    animation.removedOnCompletion = NO;
    [layer addAnimation:animation forKey:@"opacity"];

这里一定要设置起始时间,如果要表示影片片头,不能用 0.0 来赋值 beginTime,因为 CoreAnimation 会将 0.0 的 beginTime 转为 CACurrentMediaTime(),所以要用 AVCoreAnimationBeginTimeAtZero 来代替。

另外,需要注意要将 CoreAnimation 的 removedOnCompletion 属性设置为 NO,否则在播放过程中动画执行一次后就从图层树上移除了,之后就不会出现动画了。

CoreAnimation 提供了 CAAnimationGroup 来组合多个动画,但是书中建议在视频动画中避免使用 CAAnimationGroup。

最终将这个 layer 加入到 AVSynchronizedLayer 上

    AVSynchronizedLayer *syncLayer = [AVSynchronizedLayer synchronizedLayerWithPlayerItem:self.avPlayerItem];
    [syncLayer addSublayer:[self makeTextLayer]];
    syncLayer.frame = CGRectMake(0, 0, 1280, 720);
    [self.layer addSublayer:syncLayer];

可以看到 AVSynchronizedLayer 是与 AVPlayerItem 绑定的,这样就能实现时间同步了。

2. AVVideoCompositionCoreAnimationTool 与导出

导出时也需要创建 CALayer

CALayer *animationLayer = [CALayer layer];
animationLayer.frame = CGRectMake(0, 0, 1280.f, 720.f);
CALayer *videoLayer = [CALayer layer];
videoLayer.frame = CGRectMake(0, 0, 1280.f, 720.f);                            
[animationLayer addSublayer:videoLayer];
[animationLayer addSublayer:[self makeTextLayer]];
animationLayer.geometryFlipped = YES; // 避免错位现象
AVVideoCompositionCoreAnimationTool *animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:animationLayer];

得到 animationTool 后将其绑定到 AVMutableVideoComposition 上,即可用于导出了。

AVMutableVideoComposition *finalVideocomposition = [videoComposition copy];
finalVideocomposition.animationTool = animationTool;