AVFoundation实战之视频合成-插入图片和动画

前言

最近项目中的很多功能都要使用到AVFoundation,现在将项目中的复杂功能进行拆分细化,并总结成小Demo记录下来。

下面这个是图片插入到视频中,并导出一个完整视频的例子。需求是将动画视频中的人物头像替换成拍照获得的真人头像。代码中的动画效果进行简化,具体情况自行添加处理。

整体流程

  1. 获取视频资源AVURLAsset
  2. 创建自定义合成对象AVMutableComposition,我定义它为可变组件。
  3. 在可变组件中添加资源数据,也就是轨道AVMutableCompositionTrack(一般添加2中:音频轨道和视频轨道)
  4. 创建视频组件AVMutableVideoComposition,这个类是处理视频中要编辑的东西。可以设定所需视频的大小、规模以及帧的持续时间。以及管理并设置视频组件的指令
  5. 创建视频组件的指令AVMutableVideoCompositionInstruction,这个类主要用于管理应用层的指令。
  6. 创建视频应用层的指令AVMutableVideoCompositionLayerInstruction 用户管理视频框架应该如何被应用和组合,也就是说是子视频在总视频中出现和消失的时间、大小、动画等。
  7. 创建视频导出会话对象AVAssetExportSession,主要是根据videoComposition去创建一个新的视频,并输出到一个指定的文件路径中去。
    补充一点:由于4、5、6步骤直接的关联性,要修改下创建顺序 6->5->4;这样去创建代码更明晰。

以下是实现代码


- (void)insertPictureWith:(NSString *)videPath outPath:(NSString *)outPath image:(UIImage *)image;
{
    // 1. 获取视频资源`AVURLAsset`。
    NSURL *videoURL = [NSURL fileURLWithPath:videPath];// 本地文件
    AVAsset *videoAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
    
    if (!videoAsset) {
        return;
    }
    CMTime durationTime = videoAsset.duration;//视频的时长
    
    // 2. 创建自定义合成对象`AVMutableComposition`,我定义它为可变组件。
    AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
    
    
    // 3. 在可变组件中添加资源数据,也就是轨道`AVMutableCompositionTrack`(一般添加2中:音频轨道和视频轨道)
    // - 视频轨道
    AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                        preferredTrackID:kCMPersistentTrackID_Invalid];
    NSArray *videoAssetTraks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];
    if (videoAssetTraks.count == 0) {
        return;
    }
    AVAssetTrack *videoAssetTrack1 = [videoAssetTraks firstObject];
    
    [videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, durationTime)
                        ofTrack:videoAssetTrack1
                         atTime:kCMTimeZero
                          error:nil];
    // - 音频轨道
    AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio
                                                                        preferredTrackID:kCMPersistentTrackID_Invalid];
    NSArray *audioAssetTraks = [videoAsset tracksWithMediaType:AVMediaTypeAudio];
    if (audioAssetTraks.count == 0) {
        return;
    }
    AVAssetTrack *audioAssetTrack = [audioAssetTraks firstObject];
    [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, durationTime)
                        ofTrack:audioAssetTrack
                         atTime:kCMTimeZero
                          error:nil];
    
    
    // 6. 创建视频应用层的指令`AVMutableVideoCompositionLayerInstruction` 用户管理视频框架应该如何被应用和组合,也就是说是子视频在总视频中出现和消失的时间、大小、动画等。
    AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
    // - 设置视频层级的一些属性
    [videolayerInstruction setTransform:videoAssetTrack1.preferredTransform atTime:kCMTimeZero];
    
    
    // 5. 创建视频组件的指令`AVMutableVideoCompositionInstruction`,这个类主要用于管理应用层的指令。
    AVMutableVideoCompositionInstruction *mainCompositionIns = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    mainCompositionIns.timeRange = CMTimeRangeMake(kCMTimeZero, durationTime);// 设置视频轨道的时间范围
    mainCompositionIns.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil];
    
    // 4. 创建视频组件`AVMutableVideoComposition`,这个类是处理视频中要编辑的东西。可以设定所需视频的大小、规模以及帧的持续时间。以及管理并设置视频组件的指令
    AVMutableVideoComposition *mainComposition = [AVMutableVideoComposition videoComposition];
    CGSize videoSize = videoAssetTrack1.naturalSize;
    mainComposition.renderSize = videoSize;
    mainComposition.instructions = [NSArray arrayWithObject:mainCompositionIns];
    mainComposition.frameDuration = CMTimeMake(1, 30); // FPS 帧
    
    
    // --- 插入图片
    CALayer *animLayer = [CALayer layer];
    
    [animLayer setContents:(id)[image CGImage]];
    animLayer.frame = CGRectMake(0, 0, 150, 150);
    
    NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
    NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(videoSize.width, videoSize.height/2)];
    NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(0, videoSize.height)];
    NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(videoSize.width, 0)];
    NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(0, videoSize.height/2)];
    NSValue *value6 = [NSValue valueWithCGPoint:CGPointMake(videoSize.width, videoSize.height)];
    NSValue *value7 = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
    
    CAKeyframeAnimation *positionAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    positionAnim.values = @[value1,value2,value3,value4,value5,value6,value7];
    positionAnim.duration = CMTimeGetSeconds(durationTime);
    positionAnim.beginTime = AVCoreAnimationBeginTimeAtZero;
    positionAnim.fillMode = kCAFillModeForwards;
    positionAnim.removedOnCompletion = NO;
    
    [animLayer addAnimation:positionAnim forKey:@"move"];
    
    
    CALayer *parentLayer = [CALayer layer];
    CALayer *videoLayer = [CALayer layer];
    parentLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
    videoLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
    [parentLayer addSublayer:videoLayer];
    [parentLayer addSublayer:animLayer];
    
    
    mainComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
    
    
    // 7. 创建视频导出会话对象`AVAssetExportSession`,主要是根据`videoComposition`去创建一个新的视频,并输出到一个指定的文件路径中去。
    AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
                                                                      presetName:AVAssetExportPresetHighestQuality];
    exporter.outputFileType = AVFileTypeQuickTimeMovie;
    exporter.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMake(durationTime.value - 200, durationTime.timescale));
    exporter.outputURL = [NSURL fileURLWithPath:outPath];
    exporter.shouldOptimizeForNetworkUse = YES;
    exporter.videoComposition = mainComposition;
    
    [exporter exportAsynchronouslyWithCompletionHandler:^{
        dispatch_async(dispatch_get_main_queue(), ^{
            
            // 返回主线
            [self.activityIndicator stopAnimating];
            
            if (exporter.status == AVAssetExportSessionStatusCompleted) {
                NSLog(@"合成成功");
                self.hintLabel.text = @"合成状态提示:合成成功!!!!";
            }else {
                NSLog(@"合成失败 ---- -%@",exporter.error);
                self.hintLabel.text = @"合成状态提示:合成失败!!!!";
            }
            
            
            
        });
    }];
    
    
    
    
}

// Storyboard关联过来的方法
- (IBAction)playVideoButtonAction:(id)sender {
    
    NSString *videoPath = [[NSBundle mainBundle] pathForResource:@"TaoLeSi" ofType:@"mp4"];
    
    NSString *outPath = @"/Users/cgtiger130/Desktop/taolesi_insetPIC.mov";
    
    [self.activityIndicator startAnimating];
    
    self.hintLabel.text = @"合成状态提示: 正在合成";
    
    [self insertPictureWith:videoPath outPath:outPath image:[UIImage imageNamed:@"paopao.png"]];
    
}


合成效果

再次强调图片中的内容不是上述代码的运行效果,只是比代码中的动画效果更复杂一些。


视频合成测试2.gif

参考文档

AVFoundation Programming Guide(官方文档翻译)完整版中英对照
视频特效制作:如何给视频添加边框、水印、动画以及3D效果

你可能感兴趣的:(AVFoundation实战之视频合成-插入图片和动画)