由于項目需要,需要將錄制好的全屏視頻剪切成長寬均為設備寬度的正方形。本來以為很簡單只需要改變錄制時分辨率即可,可惜發現蘋果提供的幾種分辨率均不能滿足需求,所以必須自己一點點查詢資料、嘗試完成需求,功夫不負有心,終于解決了問題,這里講遇到的坑分享給大家。
過程大致分為三步:
首先,先配置好錄制視頻的基礎
其次,在設備輸出代理方法中處理視頻
最后,保存視頻(具體怎么處理看情況定吧!)
第一、三步省略,直接說第二步。
這里用到主要的類以及相關庫: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;
}
忽略的第一部在這里附上可供參考的鏈接
第一步:錄制視頻 "星譜"的一邊文章 鏈接