前言
最近項目中的很多功能都要使用到AVFoundation,現在將項目中的復雜功能進行拆分細化,并總結成小Demo記錄下來。
下面這個是圖片插入到視頻中,并導出一個完整視頻的例子。需求是將動畫視頻中的人物頭像替換成拍照獲得的真人頭像。代碼中的動畫效果進行簡化,具體情況自行添加處理。
整體流程
- 獲取視頻資源
AVURLAsset
。- 創建自定義合成對象
AVMutableComposition
,我定義它為可變組件。- 在可變組件中添加資源數據,也就是軌道
AVMutableCompositionTrack
(一般添加2中:音頻軌道和視頻軌道)- 創建視頻組件
AVMutableVideoComposition
,這個類是處理視頻中要編輯的東西。可以設定所需視頻的大小、規模以及幀的持續時間。以及管理并設置視頻組件的指令- 創建視頻組件的指令
AVMutableVideoCompositionInstruction
,這個類主要用于管理應用層的指令。- 創建視頻應用層的指令
AVMutableVideoCompositionLayerInstruction
用戶管理視頻框架應該如何被應用和組合,也就是說是子視頻在總視頻中出現和消失的時間、大小、動畫等。- 創建視頻導出會話對象
AVAssetExportSession
,主要是根據videoComposition
去創建一個新的視頻,并輸出到一個指定的文件路徑中去。
補充一點:由于4、5、6步驟直接的關聯性,要修改下創建順序 6->5->4;這樣去創建代碼更明晰。
以下是實現代碼
- (void)insertPictureWith:(NSString *)videPath outPath:(NSString *)outPath image:(UIImage *)image;
{
// 1. 獲取視頻資源`AVURLAsset`。
NSURL *videoURL = [NSURL fileURLWithPath:videPath];// 本地文件
AVAsset *videoAsset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
if (!videoAsset) {
return;
}
CMTime durationTime = videoAsset.duration;//視頻的時長
// 2. 創建自定義合成對象`AVMutableComposition`,我定義它為可變組件。
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
// 3. 在可變組件中添加資源數據,也就是軌道`AVMutableCompositionTrack`(一般添加2中:音頻軌道和視頻軌道)
// - 視頻軌道
AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
preferredTrackID:kCMPersistentTrackID_Invalid];
NSArray *videoAssetTraks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];
if (videoAssetTraks.count == 0) {
return;
}
AVAssetTrack *videoAssetTrack1 = [videoAssetTraks firstObject];
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, durationTime)
ofTrack:videoAssetTrack1
atTime:kCMTimeZero
error:nil];
// - 音頻軌道
AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
NSArray *audioAssetTraks = [videoAsset tracksWithMediaType:AVMediaTypeAudio];
if (audioAssetTraks.count == 0) {
return;
}
AVAssetTrack *audioAssetTrack = [audioAssetTraks firstObject];
[audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, durationTime)
ofTrack:audioAssetTrack
atTime:kCMTimeZero
error:nil];
// 6. 創建視頻應用層的指令`AVMutableVideoCompositionLayerInstruction` 用戶管理視頻框架應該如何被應用和組合,也就是說是子視頻在總視頻中出現和消失的時間、大小、動畫等。
AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
// - 設置視頻層級的一些屬性
[videolayerInstruction setTransform:videoAssetTrack1.preferredTransform atTime:kCMTimeZero];
// 5. 創建視頻組件的指令`AVMutableVideoCompositionInstruction`,這個類主要用于管理應用層的指令。
AVMutableVideoCompositionInstruction *mainCompositionIns = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mainCompositionIns.timeRange = CMTimeRangeMake(kCMTimeZero, durationTime);// 設置視頻軌道的時間范圍
mainCompositionIns.layerInstructions = [NSArray arrayWithObjects:videolayerInstruction,nil];
// 4. 創建視頻組件`AVMutableVideoComposition`,這個類是處理視頻中要編輯的東西。可以設定所需視頻的大小、規模以及幀的持續時間。以及管理并設置視頻組件的指令
AVMutableVideoComposition *mainComposition = [AVMutableVideoComposition videoComposition];
CGSize videoSize = videoAssetTrack1.naturalSize;
mainComposition.renderSize = videoSize;
mainComposition.instructions = [NSArray arrayWithObject:mainCompositionIns];
mainComposition.frameDuration = CMTimeMake(1, 30); // FPS 幀
// --- 插入圖片
CALayer *animLayer = [CALayer layer];
[animLayer setContents:(id)[image CGImage]];
animLayer.frame = CGRectMake(0, 0, 150, 150);
NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(videoSize.width, videoSize.height/2)];
NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(0, videoSize.height)];
NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(videoSize.width, 0)];
NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(0, videoSize.height/2)];
NSValue *value6 = [NSValue valueWithCGPoint:CGPointMake(videoSize.width, videoSize.height)];
NSValue *value7 = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
CAKeyframeAnimation *positionAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnim.values = @[value1,value2,value3,value4,value5,value6,value7];
positionAnim.duration = CMTimeGetSeconds(durationTime);
positionAnim.beginTime = AVCoreAnimationBeginTimeAtZero;
positionAnim.fillMode = kCAFillModeForwards;
positionAnim.removedOnCompletion = NO;
[animLayer addAnimation:positionAnim forKey:@"move"];
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
videoLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
[parentLayer addSublayer:videoLayer];
[parentLayer addSublayer:animLayer];
mainComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
// 7. 創建視頻導出會話對象`AVAssetExportSession`,主要是根據`videoComposition`去創建一個新的視頻,并輸出到一個指定的文件路徑中去。
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mixComposition
presetName:AVAssetExportPresetHighestQuality];
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMake(durationTime.value - 200, durationTime.timescale));
exporter.outputURL = [NSURL fileURLWithPath:outPath];
exporter.shouldOptimizeForNetworkUse = YES;
exporter.videoComposition = mainComposition;
[exporter exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
// 返回主線
[self.activityIndicator stopAnimating];
if (exporter.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"合成成功");
self.hintLabel.text = @"合成狀態提示:合成成功!!!!";
}else {
NSLog(@"合成失敗 ---- -%@",exporter.error);
self.hintLabel.text = @"合成狀態提示:合成失敗!!!!";
}
});
}];
}
// Storyboard關聯過來的方法
- (IBAction)playVideoButtonAction:(id)sender {
NSString *videoPath = [[NSBundle mainBundle] pathForResource:@"TaoLeSi" ofType:@"mp4"];
NSString *outPath = @"/Users/cgtiger130/Desktop/taolesi_insetPIC.mov";
[self.activityIndicator startAnimating];
self.hintLabel.text = @"合成狀態提示: 正在合成";
[self insertPictureWith:videoPath outPath:outPath image:[UIImage imageNamed:@"paopao.png"]];
}
合成效果
再次強調圖片中的內容不是上述代碼的運行效果,只是比代碼中的動畫效果更復雜一些。
視頻合成測試2.gif
參考文檔
AVFoundation Programming Guide(官方文檔翻譯)完整版中英對照
視頻特效制作:如何給視頻添加邊框、水印、動畫以及3D效果