VideoAudioCompositionDemo
iOS 音频视频图像合成那点事
Install / Use
/learn @gao211326/VideoAudioCompositionDemoREADME
VideoAudioCompositionDemo
iOS 音频视频图像合成那点事
人而无信不知其可
前言
很久很久没有写点什么了,只因为最近事情太多了,这几天终于闲下来了,趁此机会,记录下几个月前写的一个关于视频音频图片合成方面的一个小例子
入场
先来看看实现的大概功能吧~

由于其它功能不好制作gif,这里就先展示一个简单的水印图片
下面就让我们一点点来分析分析
需要了解什么
先来看一个关系图,字写的丑,将就着看吧....
看着上面的图,是有点凌乱的感觉,下面我们就一点点来剥开。
代码实现
根据需要实现的功能,我这里建了一个类,来分别实现不同的功能
@interface VideoAudioComposition : NSObject
/**
合成后的名字
*/
@property (nonatomic,copy) NSString *compositionName;
/**
合成类型
*/
@property (nonatomic,assign) CompositionType compositionType;
/**
转换后的格式
*/
@property (nonatomic, copy) AVFileType outputFileType;
/**
进度block
*/
@property (nonatomic,copy)CompositionProgress progressBlock;
/**
视频音频合成
@param videoUrl 视频地址
@param videoTimeRange 截取时间
@param audioUrl 音频地址
@param audioTimeRange 截取时间
@param successBlcok 成功回调
*/
- (void)compositionVideoUrl:(NSURL *)videoUrl videoTimeRange:(CMTimeRange)videoTimeRange audioUrl:(NSURL *)audioUrl audioTimeRange:(CMTimeRange)audioTimeRange success:(SuccessBlcok)successBlcok;
/**
视频和视频合成
@param videoUrl 视频地址
@param videoTimeRange 截取时间
@param mergeVideoUrl 视频地址
@param mergeVideoTimeRange 截取时间
@param successBlcok 成功回调
*/
- (void)compositionVideoUrl:(NSURL *)videoUrl videoTimeRange:(CMTimeRange)videoTimeRange mergeVideoUrl:(NSURL *)mergeVideoUrl mergeVideoTimeRange:(CMTimeRange)mergeVideoTimeRange success:(SuccessBlcok)successBlcok;
/**
多个音频合成
@param audios 音频地址
@param timeRanges 截取时间(数组可为空:默认视为音频的起止时间,若不为空,则必须传入与audios数量相等的time)CMTimeRangeMake(kCMTimeZero, kCMTimeZero) 默认为起止时间
@param successBlcok 成功回调
*/
- (void)compositionAudios:(NSArray <NSURL*>*)audios timeRanges:(NSArray<NSValue *> *)timeRanges success:(SuccessBlcok)successBlcok;
/**
多个视频合成
@param videos 视频地址
@param timeRanges 截取时间(数组可为空:默认视为视频的起止时间,若不为空,则必须传入与audios数量相等的time)CMTimeRangeMake(kCMTimeZero, kCMTimeZero) 默认为起止时间
@param successBlcok 成功回调
*/
- (void)compositionVideos:(NSArray <NSURL*>*)videos timeRanges:(NSArray<NSValue *> *)timeRanges success:(SuccessBlcok)successBlcok;
@end
实现代码
- (NSString *)compositionPath
{
return [GLFolderManager createCacheFilePath:kCompositionPath];
}
- (void)compositionVideoUrl:(NSURL *)videoUrl videoTimeRange:(CMTimeRange)videoTimeRange audioUrl:(NSURL *)audioUrl audioTimeRange:(CMTimeRange)audioTimeRange success:(SuccessBlcok)successBlcok
{
NSCAssert(_compositionName.length > 0, @"请输入转换后的名字");
NSString *outPutFilePath = [[self compositionPath] stringByAppendingPathComponent:_compositionName];
//存在该文件
if ([GLFolderManager fileExistsAtPath:outPutFilePath]) {
[GLFolderManager clearCachesWithFilePath:outPutFilePath];
}
// 创建可变的音视频组合
AVMutableComposition *composition = [AVMutableComposition composition];
// 音频通道
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 视频通道 枚举 kCMPersistentTrackID_Invalid = 0
AVMutableCompositionTrack *videoTrack = nil;
// 视频采集
AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
videoTimeRange = [self fitTimeRange:videoTimeRange avUrlAsset:videoAsset];
// 音频采集
AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:audioUrl options:nil];
audioTimeRange = [self fitTimeRange:audioTimeRange avUrlAsset:audioAsset];
if (_compositionType == VideoAudioToVideo) {
//以视频时间为标准 若视频时间小于音频时间 则让音频时间和视频时间保持一致
if (CMTimeCompare(videoTimeRange.duration,audioTimeRange.duration))
{
audioTimeRange.duration = videoTimeRange.duration;
}
//在测试中发现 VideoAudioToAudio如果不用 视频通道 就不要去创建 否则会失败
videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
}
// 音频采集通道
AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 加入合成轨道之中
[audioTrack insertTimeRange:audioTimeRange ofTrack:audioAssetTrack atTime:kCMTimeZero error:nil];
switch (_compositionType) {
case VideoAudioToAudio:
{
// 音频采集通道
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 把采集轨道数据加入到可变轨道之中
[audioTrack insertTimeRange:videoTimeRange ofTrack:videoAssetTrack atTime:audioTimeRange.duration error:nil];
}
break;
case VideoAudioToVideo:{
// 视频采集通道
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
// 把采集轨道数据加入到可变轨道之中
[videoTrack insertTimeRange:videoTimeRange ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
}
break;
default:
break;
}
[self composition:composition storePath:outPutFilePath success:successBlcok];
}
- (void)compositionVideoUrl:(NSURL *)videoUrl videoTimeRange:(CMTimeRange)videoTimeRange mergeVideoUrl:(NSURL *)mergeVideoUrl mergeVideoTimeRange:(CMTimeRange)mergeVideoTimeRange success:(SuccessBlcok)successBlcok
{
switch (_compositionType) {
case VideoToVideo:
{
NSArray *timeRanges = [NSArray arrayWithObjects:[NSValue valueWithCMTimeRange:videoTimeRange],[NSValue valueWithCMTimeRange:mergeVideoTimeRange] ,nil];
[self compositionVideos:@[videoUrl,mergeVideoUrl] timeRanges:timeRanges success:successBlcok];
}
break;
case VideoToAudio:{
NSArray *timeRanges = [NSArray arrayWithObjects:[NSValue valueWithCMTimeRange:videoTimeRange],[NSValue valueWithCMTimeRange:mergeVideoTimeRange] ,nil];
[self compositionAudios:@[videoUrl,mergeVideoUrl] timeRanges:timeRanges success:successBlcok];
}
break;
default:
break;
}
}
- (void)compositionVideos:(NSArray<NSURL *> *)videos timeRanges:(NSArray<NSValue *> *)timeRanges success:(SuccessBlcok)successBlcok
{
[self compositionMedia:videos timeRanges:timeRanges type:0 success:successBlcok];
}
- (void)compositionAudios:(NSArray<NSURL *> *)audios timeRanges:(NSArray<NSValue *> *)timeRanges success:(SuccessBlcok)successBlcok
{
[self compositionMedia:audios timeRanges:timeRanges type:1 success:successBlcok];
}
#pragma mark == private method
- (void)compositionMedia:(NSArray<NSURL *> *)media timeRanges:(NSArray<NSValue *> *)timeRanges type:(NSInteger)type success:(SuccessBlcok)successBlcok
{
NSCAssert(_compositionName.length > 0, @"请输入转换后的名字");
NSCAssert((timeRanges.count == 0 || timeRanges.count == media.count), @"请输入正确的timeRange");
NSString *outPutFilePath = [[self compositionPath] stringByAppendingPathComponent:_compositionName];
//存在该文件
if ([GLFolderManager fileExistsAtPath:outPutFilePath]) {
[GLFolderManager clearCachesWithFilePath:outPutFilePath];
}
// 创建可变的音视频组合
AVMutableComposition *composition = [AVMutableComposition composition];
if (type == 0) {
// 视频通道
AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// 音频通道
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
CMTime atTime = kCMTimeZero;
for (int i = 0;i < media.count;i ++) {
NSURL *url = media[i];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, kCMTimeZero);
if (timeRanges.count > 0) {
timeRange = [timeRanges[i] CMTimeRangeValue];
}
// 视频采集
AVURLAsset *videoAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
timeRange = [self fitTimeRange:timeRange avUrlAsset:videoAsset];
// 视频采集通道
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
// 把采集轨道数据加入到可变轨道之中
[videoTrack insertTimeRange:timeRange ofTrack:videoAssetTrack atTime:atTime error:nil];
// 音频采集通道
AVAssetTrack *audioAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 加入合成轨道之中
[audioTrack insertTimeRange:timeRange ofTrack:audioAssetTrack atTime:atTime error:nil];
atTime = CMTimeAdd(atTime, timeRange.duration);
}
}else{
// 音频通道
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
CMTime atTime = kCMTimeZero;
for (int i = 0;i < media.count;i ++) {
NSURL *url = media[i];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, kCMTimeZero);
if (timeRanges.count > 0) {
timeRange = [timeRanges[i] CMTimeRangeValue];
}
// 音频采集
AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
timeRange = [self fitTimeRange:timeRange avUrlAsset:audioAsset];
// 音频采集通道
AVAssetTrack *audioAssetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
// 加入合成轨道之中
[audioTrack insertTimeRange:timeRange ofTrack:audioAssetTrack atTime:atTime error:nil];
atTime = CMTimeAdd(atTime, timeRange.duration);
}
