iOS干货音视频处理

iOS 视频+配音/音频合成

2017-09-04  本文已影响40人  简书弧线

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

在网上简单的搜索了相关资料学习后, 发现这个功能在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);
            });
        }
    });

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

上一篇 下一篇

猜你喜欢

热点阅读