AVFoundation實戰之視頻合成-插入圖片和動畫

前言

最近項目中的很多功能都要使用到AVFoundation,現在將項目中的復雜功能進行拆分細化,并總結成小Demo記錄下來。

下面這個是圖片插入到視頻中,并導出一個完整視頻的例子。需求是將動畫視頻中的人物頭像替換成拍照獲得的真人頭像。代碼中的動畫效果進行簡化,具體情況自行添加處理。

整體流程

  1. 獲取視頻資源AVURLAsset
  2. 創建自定義合成對象AVMutableComposition,我定義它為可變組件。
  3. 在可變組件中添加資源數據,也就是軌道AVMutableCompositionTrack(一般添加2中:音頻軌道和視頻軌道)
  4. 創建視頻組件AVMutableVideoComposition,這個類是處理視頻中要編輯的東西。可以設定所需視頻的大小、規模以及幀的持續時間。以及管理并設置視頻組件的指令
  5. 創建視頻組件的指令AVMutableVideoCompositionInstruction,這個類主要用于管理應用層的指令。
  6. 創建視頻應用層的指令AVMutableVideoCompositionLayerInstruction 用戶管理視頻框架應該如何被應用和組合,也就是說是子視頻在總視頻中出現和消失的時間、大小、動畫等。
  7. 創建視頻導出會話對象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效果

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容