最近在做一个卡通配音的需求: 对卡通视频里面的对话进行配音, 然后再将所有录音与原视频合成, 输出一个配音视频.
在网上简单的搜索了相关资料学习后, 发现这个功能在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);
});
}
});
到这里就合成配音的过程就全部完成了.
有任何不对的地方,或者不明白的地方, 欢迎提出