MergeVideo

老生常谈:视频的合成

请勿转载~

ps: 只讲解其中的关键点和我自己在使用过程遇到的坑,如果有什么问题请在简书留言。

p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff}p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff; min-height: 13.0px}p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px 'PingFang SC'; color: #4bd157}p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #4bd157}span.s1 {font-variant-ligatures: no-common-ligatures}span.s2 {font-variant-ligatures: no-common-ligatures; color: #de38a6}span.s3 {font: 11.0px Menlo; font-variant-ligatures: no-common-ligatures; color: #ffffff}span.s4 {font: 11.0px Menlo; font-variant-ligatures: no-common-ligatures}span.s5 {font: 11.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures}span.s6 {font-variant-ligatures: no-common-ligatures; color: #ff4647}span.s7 {font-variant-ligatures: no-common-ligatures; color: #4bd157}span.s8 {font-variant-ligatures: no-common-ligatures; color: #ffffff}span.s9 {font-variant-ligatures: no-common-ligatures; color: #8b87ff}span.s10 {font: 11.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures; color: #4bd157}span.s11 {font: 11.0px 'PingFang SC'; font-variant-ligatures: no-common-ligatures; color: #ff4647}

- (IBAction)mergeVideo:(id)sender
{
    if (YES) {
        
        /* 合成视频套路就是下面几条,跟着走就行了,具体函数意思自行google
         1.不用说,肯定加载。用ASSET
         2.这里不考虑音轨,所以只获取video信息。用track 获取asset里的视频信息,一共两个track,一个track是你自己拍的视频,第二个track是特效视频,因为两个视频需要同时播放,所以起始时间相同,都是timezero,时长自然是你自己拍的视频时长。然后把两个track都放到mixComposition里。
         3.第三步就是最重要的了。instructionLayer,看字面意思也能看个七七八八了。架构图层,就是告诉系统,等下合成视频,视频大小,方向,等等。这个地方就是合成视频的核心。我们只需要更改透明度就行了,把特效track的透明度改一下,让他能显示底下你自己拍的视屏图层就行了。
         4.
        **/
        NSLog(@"First Asset = %@",firstAsset);
        
        // 1
        secondAsset = [AVAsset assetWithURL:videoURL];
        
        NSString *path = [[NSBundle mainBundle] pathForResource:@"rain" ofType:@"mp4"];
        NSLog(@"path is %@",path);
        //firstAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
        firstAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:path] options:nil];
        NSLog(@"second Asset = %@",secondAsset);
        NSLog(@"firstAsset==%f,%f",firstAsset.naturalSize.width,firstAsset.naturalSize.height);
        NSLog(@"secondAsset==%f,%f",secondAsset.naturalSize.width,secondAsset.naturalSize.height);
        
        
        //second Video
        
        //secondAsset = [AVAsset assetWithURL:videoTwoURL];
    }
    if (firstAsset&&secondAsset) {
        
        // 2.
        CGSize targetSize = CGSizeMake(640, 480);

        mixComposition = [[AVMutableComposition alloc] init];
        AVMutableCompositionTrack *firstTrack =
        [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                    preferredTrackID:kCMPersistentTrackID_Invalid];
        [firstTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,CMTimeAdd(firstAsset.duration, CMTimeMakeWithSeconds(5.0f, 600)))
                            ofTrack:[[firstAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
                             atTime:CMTimeMakeWithSeconds(0.0f, 30)
                              error:nil];
        
        [firstTrack scaleTimeRange:CMTimeRangeMake(kCMTimeZero, firstAsset.duration) toDuration:CMTimeAdd(firstAsset.duration, CMTimeMakeWithSeconds(5.0f, 600))];
        AVMutableCompositionTrack *secondTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
        

        [secondTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,secondAsset.duration)
                             ofTrack:[[secondAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
                              atTime:kCMTimeZero
                               error:nil];

        
        //CGAffineTransformMake(a,b,c,d,tx,ty) ad缩放 bc 旋转tx,ty位移

         //3.
        AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
        mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero,secondAsset.duration);
        
         //第一个视频的架构层
        
        AVMutableVideoCompositionLayerInstruction *firstlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:firstTrack];
        [firstlayerInstruction setOpacity:0 atTime:CMTimeAdd(firstAsset.duration, CMTimeMakeWithSeconds(5.0f, 600))];
        //[firstlayerInstruction setTransform:[self layerTrans:firstAsset withTargetSize:targetSize] atTime:kCMTimeZero];
//        [firstlayerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:CMTimeRangeMake(kCMTimeZero, firstAsset.duration)];
         // 第二个视频的架构层
        CGRect test = CGRectMake(0, 0, 300, 300);
        [firstlayerInstruction setCropRectangle:test atTime:kCMTimeZero];//展示整个layer中某一位置和大小的视图
        AVMutableVideoCompositionLayerInstruction *secondlayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:secondTrack];
       [secondlayerInstruction setTransform:[self layerTrans:secondAsset withTargetSize:targetSize] atTime:kCMTimeZero];
//        [secondlayerInstruction setOpacityRampFromStartOpacity:0.0 toEndOpacity:1.0 timeRange:CMTimeRangeMake(kCMTimeZero, CMTimeMake(600, 600))];
        
        
       
        
        // 这个地方你把数组顺序倒一下,视频上下位置也跟着变了。
        mainInstruction.layerInstructions = [NSArray arrayWithObjects:firstlayerInstruction,secondlayerInstruction, nil];
        mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, secondAsset.duration);
        
        mainComposition = [AVMutableVideoComposition videoComposition];
        mainComposition.instructions = [NSArray arrayWithObjects:mainInstruction,nil];
        mainComposition.frameDuration = CMTimeMake(1, 30);
        mainComposition.renderSize = CGSizeMake(640, 480);
        
        //  导出路径
        
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        
        NSString *documentsDirectory = [paths objectAtIndex:0];
        
        NSString *myPathDocs =  [documentsDirectory stringByAppendingPathComponent:
                                 [NSString stringWithFormat:@"mergeVideo.mov"]];
        
        NSFileManager *fileManager = [NSFileManager defaultManager];
        [fileManager removeItemAtPath:myPathDocs error:NULL];
        
        NSURL *url = [NSURL fileURLWithPath:myPathDocs];
        
//        NSLog(@"URL:-  %@", [url description]);
        
        //导出
        
        AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
        
        exporter.outputURL = url;
        
        exporter.outputFileType = AVFileTypeQuickTimeMovie;
        
        exporter.shouldOptimizeForNetworkUse = YES;
        
        exporter.videoComposition = mainComposition;
        
        NSLog(@"%.0f",mixComposition.duration.value/mixComposition.duration.timescale + 0.0f);
        
        AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:exporter.asset];
        playerItem.videoComposition = exporter.videoComposition;
        
        
        AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
        
        AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
        
        playerLayer.frame = self.view.layer.bounds;
        playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
        [self.view.layer addSublayer:playerLayer];
        
        [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(playItem:) userInfo:@{@"item":player} repeats:NO];
        
        [exporter exportAsynchronouslyWithCompletionHandler:^{
            
            dispatch_async(dispatch_get_main_queue(), ^{
                
                [self exportDidFinish:exporter];
                
            });
        }];
        
    }else {
        
     
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"出错!" message:@"选择视频"
                                                       delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
    }
    

}

上述代码里面包含了多视频合成以及背景音频的合成:
1.视频合成最主要的还是依靠AVMutableComposition,其属性AVMutableVideoComposition包含着视频片段的所有信息,AVMutableAudioMix则包含了所有音频片段的信息。

  1. 如上所示,如果只是简单的视频合成,加一段或者不加音频,则只需要依次创建AVMutableComposition,AVMutableVideoComposition,AVMutableCompositionTrack,AVMutableVideoCompositionLayerInstruction。
  2. 上述代码就是前一篇文正提到的N久远的代码,好像是个印度人写的(也包含了一些我我之前测试用的片段),反正仔细看也能很清楚的发现代码顺序很别扭。 不过其中有很多启发性质的方法,比如scaleTimeRange:CMTimeRangeMake,之类的,说明视频片段被加到AVMutableCompositionTrack上之后其实是可以伸缩的,仔细看文档还可以发现很多细分的方法,插入,删除之类的。
  3. AVMutableVideoCompositionLayerInstruction是和track对应的,其实两个可以理解为track-avplayer,layerInstruction--AVPlayerlayer,仔细去看layer文档,就能找到layer的一些简单变换,伸缩,透明度之类的。
  4. 值得一提的是,AVMutableVideoComposition中插入AVMutableCompositionTrack是有数量限制的,15个位上限。所以如果你想要多短视频合成的话,最好优先考虑清楚每个视频的表现逻辑,然后根据需要确定创建多少个track,最后按照时间以及变换规则对layer进行变换。
  5. AVAssetExportSession即导出,有多重格式可选择,不过貌似就只能选AVFileTypeQuickTimeMovie,如果你的源视频来自网络则需要把shouldOptimizeForNetworkUse设置为yes.
  6. 绝大多数的导出异常都是mainInstruction出现问题
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #ffffff}p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #00b1ff}span.s1 {font-variant-ligatures: no-common-ligatures}span.s2 {font-variant-ligatures: no-common-ligatures; color: #00b1ff}span.s3 {font-variant-ligatures: no-common-ligatures; color: #ffffff}

    NSURL *videoURL;
    AVURLAsset *firstAsset;
    AVURLAsset *secondAsset;
    AVMutableVideoComposition *mainComposition;
    AVMutableComposition *mixComposition;
    NSMutableArray * audioMixParams;
    NSURL * audioUrl;

算了,最后贴上github地址,里面包含了我自己测试用的音视频合成以及即时播放的代码,不过都是临时使用的,尤其是播放,并不一定会生效。
https://github.com/Thetiso/MergeVideo

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

推荐阅读更多精彩内容

  • 译者注:这里面的内容主要是分析mp4/3gp文件的层级结构,详细的介绍了各种不同的box的结构等,网上有一些参考资...
    HaloMartin阅读 2,654评论 0 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,116评论 25 707
  • 原文:AVFoundation Programming Guide 写在前面 简单翻译一下AVFoundation...
    朦胧1919阅读 1,602评论 0 1
  • 今天早上醒来之后有点累,直到38分才坐起来,可能是晚上睡晚了。 语文课很有趣,上得很开心,大家也很积极。但是数...
    叶儿花阅读 205评论 0 0
  • 2017.09.29 星期五 晴 农历八月初十 这两天宝宝女儿带回去的,我刚好可以轻松休闲两天。昨天燕姐跟冲叔就约...
    小幸福vs茹萍阅读 181评论 0 0