一个视频文件中包含了很多轨道(Track),比如一个或多个音频轨道,一个或多个视频轨道。我们可以对这些轨道中的数据进行各种操作(裁剪、拼接、旋转等)。
本次我用到的视频打点裁剪相关功能用到的类如下:
AVAsset:素材,比如出相册中取出的数据
AVAssetTrack:素材的轨道
AVMutableComposition:视频的工程文件(是AVAsset的子类,可以用来直接播放)
AVMutableCompositionTrack:工程文件对应的轨道
这里我的需求是要裁剪调一个视频素材的头尾,第一步需要获取到素材的视频轨道和音频轨道
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *audioAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
第二步,创建工程文件,将素材的轨道数据导入到工程文件的轨道中
AVMutableComposition *mutableComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *audioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *videoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo
preferredTrackID:kCMPersistentTrackID_Invalid];
NSError *errorAudio;
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:audioAssetTrack atTime:kCMTimeZero error:&errorAudio];
NSError *errorVideo;
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:&errorVideo];
第三步,计算需要裁剪的头和尾的range,然后将音频轨道和视频轨道都进行裁剪
CMTime start = CMTimeMakeWithSeconds(self.startTime, videoAsset.duration.timescale);
CMTime end = CMTimeMakeWithSeconds(self.stopTime - self.startTime, videoAsset.duration.timescale);
CMTime duration = CMTimeMakeWithSeconds(videoAsset.duration.value - self.stopTime, videoAsset.duration.timescale);
CMTimeRange range = CMTimeRangeMake(kCMTimeZero, start);
CMTimeRange range2 = CMTimeRangeMake(end, duration);
[videoTrack removeTimeRange: range];
[audioTrack removeTimeRange: range];
[videoTrack removeTimeRange: range2];
[audioTrack removeTimeRange: range2];
到这里,我们已经将工程文件中的视频进行了裁剪,我们可以直接使用AVPlayer进行播放看裁剪成功没有
AVPlayerViewController *playerController = [[AVPlayerViewController alloc] init];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:mutableComposition];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
playerController.player = player;
[self presentViewController:playerController animated:true completion:nil];
当然,更多的是需要使用AVAssetExportSession将工程文件转码导出,代码如下:
AVAssetExportSession* exportSession = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetPassthrough];
NSURL *fileUrl = [NSURL fileURLWithPath:self.tempPath];
exportSession.outputURL = fileUrl;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
[exportSession exportAsynchronouslyWithCompletionHandler:
^(void ) {
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(@"failed: %@", [[exportSession error] localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"canceled");
break;
default:
NSLog(@"success");
break;
}
}
];
至此,整个裁剪功能的主要代码已经完成。
遇到的坑
最初我参考别人的demo发现可以使用AVAssetExportSession的timeRange来实现裁剪。
CMTimeRange range = CMTimeRangeMake(start, duration);
self.exportSession.timeRange = range;
但是当裁剪的视频时长比较长时,这种方法在从头开始裁剪和裁剪视频后半部分在耗时上差别巨大。
比如我测试了30分钟的视频,我裁剪0-10分钟大概耗时8秒,但是裁剪20-30分钟耗时达到了惊人的60秒。我不清楚这个是因为底层来转码时选择转码范围的算法导致了这一现象还是其他什么原因。
最终我选择了使用上文中提到的切割轨道数据的方式基本做到了在30分钟的视频中切割10分钟,无论切割哪一段视频,都可以保持8秒完成切割和转码导出。
如有问题,请指正。