由于项目需要,需要将录制好的全屏视频剪切成长宽均为设备宽度的正方形。本来以为很简单只需要改变录制时分辨率即可,可惜发现苹果提供的几种分辨率均不能满足需求,所以必须自己一点点查询资料、尝试完成需求,功夫不负有心,终于解决了问题,这里讲遇到的坑分享给大家。
过程大致分为三步:
首先,先配置好录制视频的基础
其次,在设备输出代理方法中处理视频
最后,保存视频(具体怎么处理看情况定吧!)
第一、三步省略,直接说第二步。
这里用到主要的类以及相关库:AVFoundation
AVAsset:素材库里的素材;
AVAssetTrack:素材的轨道;
AVMutableComposition :一个用来合成视频的“合成器”;
AVMutableCompositionTrack :“合成器”中的对媒体轨道,有音频轨、视频轨等,里面可以插入各种对应的素材;
AVMutableVideoCompositionLayerInstruction:视频轨道中的一个视频,可以缩放、旋转等;
AVMutableVideoCompositionInstruction:一个视频轨道,包含了这个轨道上的所有视频素材;
AVMutableVideoComposition:管理所有视频轨道,可以决定最终视频的尺寸,裁剪需要在这里进行;
AVAssetExportSession:配置渲染参数并渲染。
AVAsset
根据官方文档中对它的描述大致上可以把它理解成:某定时长视频资源信息数据模型,包括时长、音频轨道、视频轨道、文本、字幕等信息。
注:基于这是一个定时长视频资源本质,即使我们成功初始化AVAsset或其子类实例,我们也不可能立刻拿到我们想要的信息。原因是AVAsset是同步返回信息的,为了用户体验良好避免阻塞,我们可以在子线程获取信息,但是更推荐大家的是在使用过程中注册监听自己感兴趣的属性值变化来获取信息。
AVPlayerItem
管理着视频资源播放状态、以及可以被KVO监测的属性,错误信息等,注意的是一般情况下,监听和接受监听执行的代码在主线程上。
- (void)croppedVideo:(NSURL*)videoUrl{
// 1 — 源视频地址
AVAsset *videoAsset = [AVAsset assetWithURL:videoUrl];
// 2 - 创建AVMutableComposition实例.
AVMutableComposition *mixComposition = [AVMutableComposition composition];
// 3 - 视频、音频通道
AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *audioAssertTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
// 3.0 这块是裁剪,这里必须插入音频数据,否则没有声音
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [videoAsset duration])
ofTrack:videoAssetTrack
atTime:kCMTimeZero
error:nil];
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [videoAsset duration])
ofTrack:audioAssertTrack
atTime:kCMTimeZero
error:nil];
// 3.1 AVMutableVideoCompositionInstruction 视频轨道中的一个视频,可以缩放、旋转等
AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
// 3.2 AVMutableVideoCompositionLayerInstruction 一个视频轨道,包含了这个轨道上的所有视频素材
AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
BOOL isVideoAssetPortrait_ = NO;
CGAffineTransform videoTransform = videoAssetTrack.preferredTransform;
if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
isVideoAssetPortrait_ = YES;
}
if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
isVideoAssetPortrait_ = YES;
}
[videolayerInstruction setTransform:videoAssetTrack.preferredTransform atTime:kCMTimeZero];
[videolayerInstruction setOpacity:0.0 atTime:videoAsset.duration];
// 3.3 - Add instructions
mainInstruction.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil];
// AVMutableVideoComposition:管理所有视频轨道,可以决定最终视频的尺寸,裁剪需要在这里进行
AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
CGSize naturalSize;
if(isVideoAssetPortrait_){
naturalSize = CGSizeMake(videoAssetTrack.naturalSize.height, videoAssetTrack.naturalSize.width);
} else {
naturalSize = videoAssetTrack.naturalSize;
}
float renderWidth, renderHeight;
renderWidth = naturalSize.width;
renderHeight = naturalSize.height;
float value = renderWidth>renderHeight?renderHeight:renderWidth;
mainCompositionInst.renderSize = CGSizeMake(value, value);
mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];
mainCompositionInst.frameDuration = CMTimeMake(1, 30);
// 4 - Get path
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
[[NSFileManager defaultManager] removeItemAtPath:JYTempDataPathWithComponent(kCroppedVideoName) error:nil];
exporter.outputURL = [NSURL fileURLWithPath:JYTempDataPathWithComponent(kCroppedVideoName)];
exporter.outputFileType = AVFileTypeMPEG4;
exporter.shouldOptimizeForNetworkUse = YES;
exporter.videoComposition = mainCompositionInst;
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (exporter.status == AVAssetExportSessionStatusCompleted) {
_outputFileUrl = [exporter.outputURL copy];
if (self.videoCompletionBlock) {
self.videoCompletionBlock();
self.videoCompletionBlock = nil;
}
}
});
}];
}
static inline NSString *JYTempDataPathWithComponent(NSString *component) {
if (!component || !component.length) return nil;
NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:component];
return fullPath;
}
忽略的第一部在这里附上可供参考的链接
第一步:录制视频 "星谱"的一边文章 链接