iOS 视频+配音/音频合成

最近在做一个卡通配音的需求: 对卡通视频里面的对话进行配音, 然后再将所有录音与原视频合成, 输出一个配音视频.

在网上简单的搜索了相关资料学习后, 发现这个功能在iOS中实现比较简单, 不需要太复杂的逻辑和代码, 就可以实现.

视频配音的步骤:

1.准备好素材:需要配音的视频.mp4,背景音乐.mp3

2.根据视频内容进行对话录音

3.使用AVAssetExportSession类进行视频/音频合成, 输出一个全新的MP4文件

第一步: 准备工作, 获取视频和背景音乐文件路径, 以及最总合成输出路径

    // 声音来源
    NSURL *audioInputUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"背景音乐" ofType:@"mp3"]];
    // 视频来源
    NSURL *videoInputUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"视频" ofType:@"mp4"]];
    // 自定义视频输出地址
    NSString *outPutFilePath = [self getMixVideoOutputPath];
    // 合成之前, 先删除输出路径存在的同名MP4文件, 否则无法合成
    // 这样是因为, 我在合成的过程中发现, 如果输出路径上已经存在了, 名字一模一样的MP4文件, 那么合成的过程将会跳过, 也就是说它不会进行覆盖操作, 所以会导致合成失败
    NSURL *outputFileUrl = [NSURL fileURLWithPath:outPutFilePath];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager removeItemAtPath:outPutFilePath error:nil];

第二步: 将视频和背景音乐加入合成轨道

    // 设置时间起点为0, 这个参数在下面会用到
    CMTime nextClistartTime = kCMTimeZero;
    // 创建可变的音视频组合(这个就是用来处理轨道的类)
    AVMutableComposition *comosition = [AVMutableComposition composition];

处理视频轨道

    // 视频采集
    AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoInputUrl options:nil];
    // 视频时间范围
    CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
    // 视频通道 枚举 kCMPersistentTrackID_Invalid = 0
    // 这一行代码, 需要注意的是AVMediaTypeVideo这个参数, 这里表明的是, 将视频中的视频轨道抽离处理.因为一般一个视频包含了2个轨道:视频轨道 和 音频轨道.
    // 我们在这里把视频轨道从视频抽离出来, 拿到的视频轨道是无声的, 方便我们之后配音用
        AVMutableCompositionTrack *videoTrack = [comosition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    // 视频采集通道
    AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
    // 把采集轨道数据加入到可变轨道之中
    // insertTimeRange: 插入的视频范围(即视频本身的哪一段需要插入进去)
    // ofTrack:插入的轨道
    // tTime:插入的时间点(在合成的视频的时间点)
    [videoTrack insertTimeRange:videoTimeRange ofTrack:videoAssetTrack atTime:nextClistartTime error:nil];

处理背景音乐轨道

    // 声音采集
    AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:audioInputUrl options:nil];
    // 因为我使用的背景音乐时长和视频是一致的, 所以这里直接写了audioTimeRange = videoTimeRange
    CMTimeRange audioTimeRange = videoTimeRange;
    // 音频通道采集
    // 这里的处理和上面视频的处理是一样的道理, 把音频轨道AVMediaTypeAudio从视频抽离出来
    AVMutableCompositionTrack *audioTrack = [comosition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    // 音频采集通道
    AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
    // 加入合成轨道之中
    [audioTrack insertTimeRange:audioTimeRange ofTrack:audioAssetTrack atTime:CMTimeMakeWithSeconds(0, videoAsset.duration.timescale) error:nil];

处理录音轨道

    // 将所有录音逐个合成, 因为有很多个录音, 所以用for循环逐个插入
    for (NSInteger index = 0 ; index < self.item.items.count; index ++) {
        // 录音采集        AVURLAsset *recordAsset = [[AVURLAsset alloc] initWithURL:[self getRecordFilePathWithIndex:index] options:nil];
        // 插入范围
        CMTimeRange recordRange = CMTimeRangeMake(kCMTimeZero, recordAsset.duration);;
        // 创建录音轨道
        AVMutableCompositionTrack *recordTrack = [comosition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
        AVAssetTrack *recordAssetTrack = [[recordAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
        //--------以下可以忽略-----------
        // 下面这段是获取录音插入时间点的计算, 需要自己定, 因为我的录音时间点包含早模型中, 所以需要在这里截取, 你们可以忽略
        DubbingItemsItem *item = self.item.items[index];
        NSRange startRange = [item.ftime rangeOfString:@":"];
        NSString *startMinStr = [item.ftime substringToIndex:startRange.location];
        NSString *startSecStr = [item.ftime substringFromIndex:startRange.location + 1];
        // 插入时间
        float startTime = [startMinStr floatValue] * 60 + [startSecStr floatValue];
        //--------以上可以忽略-----------
        [recordTrack insertTimeRange:recordRange ofTrack:recordAssetTrack atTime:CMTimeMakeWithSeconds(startTime, videoAsset.duration.timescale) error:nil];
    }

最后合成视频

    // 创建一个输出
    AVAssetExportSession *assetExport = [[AVAssetExportSession alloc] initWithAsset:comosition presetName:AVAssetExportPresetMediumQuality];
    // 输出类型, 这里有输出类型有很多中, AVFileTypeQuickTimeMovie/AVFileTypeMPEG4等等,说明支持很多种输出类型
    assetExport.outputFileType = AVFileTypeMPEG4;
    // 输出地址
    assetExport.outputURL = outputFileUrl;
    // 对视频进行优化, 具体不太明白什么意思, 文档里面说"indicates that the output file should be optimized for network use, e.g. that a QuickTime movie file should support "fast start"
    // 我才应该是为了让输出的视频,具备一些功能, 例如使用"QuickTime"这个软件可以"快速启动"这个视频. 大概这个意思
    assetExport.shouldOptimizeForNetworkUse = YES;
    
    // 在这里可以显示一个loading图, 等待合成完毕, 一般需要几秒钟
    
   [assetExport exportAsynchronouslyWithCompletionHandler:^{
        //NSLog(@"%zd", [assetExport status]);
        // 如果导出的状态为完成
        if ([assetExport status] == AVAssetExportSessionStatusCompleted) {
            // 回到主线程
            dispatch_async(dispatch_get_main_queue(), ^{
                // 调用播放方法
                NSLog(@"合成完毕:%@", outputFileUrl);
            });
        }
        
        if ([assetExport status] == AVAssetExportSessionStatusCancelled) {
            // 取消合成
            // 进度条归0, 处理UI
        }
        
        if ([assetExport status] == AVAssetExportSessionStatusFailed) {
            // 合成失败
            dispatch_sync(dispatch_get_main_queue(), ^{
                //  显示失败提示, 进度条归0
            });
        }
    }];
    
    // 进度条处理 
//  在模拟器上, 进度条会有显示, 因为合成速度慢
// 在真机上, 测试时发现 1分钟内的视频, 几乎是瞬间合成完毕, 所以进度条几乎没起到作用
//2018-3-29更新
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        while (assetExport.status == AVAssetExportSessionStatusExporting) {
            dispatch_sync(dispatch_get_main_queue(), ^{
                //NSLog(@"当前压缩进度:%f",assetExport.progress);
            });
        }
    });

到这里就合成配音的过程就全部完成了.
有任何不对的地方,或者不明白的地方, 欢迎提出

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

推荐阅读更多精彩内容