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效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容