AVFoundation框架解析(六)—— 視頻音頻的合成(一)

版本記錄

版本號 時間
V1.0 2017.08.30

前言

AVFoundation框架是ios中很重要的框架,所有與視頻音頻相關的軟硬件控制都在這個框架里面,接下來這幾篇就主要對這個框架進行介紹和講解。感興趣的可以看我上幾篇。
1. AVFoundation框架解析(一)—— 基本概覽
2. AVFoundation框架解析(二)—— 實現視頻預覽錄制保存到相冊
3. AVFoundation框架解析(三)—— 幾個關鍵問題之關于框架的深度概括
4. AVFoundation框架解析(四)—— 幾個關鍵問題之AVFoundation探索(一)
5. AVFoundation框架解析(五)—— 幾個關鍵問題之AVFoundation探索(二)

AVFoundation 編輯

AVFoundation框架提供了一組功能豐富的類,以便于編輯視聽資源。 AVFoundation的編輯API的核心是組合。 組合僅僅是來自一個或多個不同媒體資產的軌道的集合。 AVMutableComposition類提供了一個用于插入和刪除軌道以及管理其時間順序的接口。 下圖顯示了如何將新組合從現有資產的組合中拼湊起來形成新的資產。 如果您想要做的是將多個資產合并到單個文件中,那么就是您需要的細節。 如果您想在合成的軌道上執行任何自定義音頻或視頻處理,則需要分別包含音頻混合或視頻合成。

使用AVMutableAudioMix類,您可以對合成中的音軌執行自定義音頻處理,如下圖所示。 目前,您可以指定最大音量或設置音軌的音量斜坡。

您可以使用AVMutableVideoComposition類直接與組合中的視頻軌進行編輯,如下圖所示。 使用單個視頻構圖,您可以為輸出視頻指定所需的渲染大小和縮放以及幀持續時間。 通過視頻作品的指示(由AVMutableVideoCompositionInstruction類表示),您可以修改視頻的背景顏色并應用圖層說明。 這些層指令(由AVMutableVideoCompositionLayerInstruction類表示)可用于將轉換,變換斜坡,不透明度和不透明度斜坡應用于組合中的視頻軌道。 視頻構圖類還使您能夠使用animationTool屬性將Core Animation框架的效果引入到視頻中。

要將您的合成與音頻混合和視頻合成相結合,請使用AVAssetExportSession對象,如下圖所示。 您可以使用您的合成初始化導出會話,然后分別將音頻混合和視頻構圖分配給audioMixvideoComposition屬性。


Creating a Composition

要創建自己的組合,可以使用AVMutableComposition類。 要向組合添加媒體數據,您必須添加一個或多個由AVMutableCompositionTrack類表示的組合軌道。 最簡單的情況是創建一個具有一個視頻軌道和一個音軌的可變組合:

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
// Create the video composition track.
AVMutableCompositionTrack *mutableCompositionVideoTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// Create the audio composition track.
AVMutableCompositionTrack *mutableCompositionAudioTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

1. Options for Initializing a Composition Track

在組合中添加新曲目時,您必須同時提供媒體類型和曲目ID。 雖然音頻和視頻是最常用的媒體類型,但您也可以指定其他媒體類型,如AVMediaTypeSubtitleAVMediaTypeText

與某些視聽數據相關聯的每個軌道都具有稱為軌跡ID的唯一標識符。 如果您指定kCMPersistentTrackID_Invalid作為首選軌道ID,則會為您自動生成唯一標識符并與軌道相關聯。


Adding Audiovisual Data to a Composition

一旦您有一個或多個軌道的合成,您可以開始將您的媒體數據添加到相應的軌道上。 要將媒體數據添加到合成軌道,您需要訪問媒體數據所在的AVAsset對象。 您可以使用可變組合軌道界面將同一底層介質類型的多個軌道放在同一軌道上。 以下示例說明如何按順序將兩個不同的視頻資源軌道添加到相同的構圖軌道中:

// You can retrieve AVAssets from a number of places, like the camera roll for example.
AVAsset *videoAsset = <#AVAsset with at least one video track#>;
AVAsset *anotherVideoAsset = <#another AVAsset with at least one video track#>;

// Get the first video track from each asset.
AVAssetTrack *videoAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *anotherVideoAssetTrack = [[anotherVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

// Add them both to the composition.
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,videoAssetTrack.timeRange.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:nil];
[mutableCompositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero,anotherVideoAssetTrack.timeRange.duration) ofTrack:anotherVideoAssetTrack atTime:videoAssetTrack.timeRange.duration error:nil];

1. Retrieving Compatible Composition Tracks

在可能的情況下,每個媒體類型應該只有一個合成軌跡。 兼容資產軌道的統一導致資源使用量最少。 當連續呈現媒體數據時,您應該將相同類型的任何媒體數據放在同一組合軌上。 您可以查詢可變的合成,以確定是否有任何合成軌道與您所需的素材資源軌道兼容:

AVMutableCompositionTrack *compatibleCompositionTrack = [mutableComposition mutableTrackCompatibleWithTrack:<#the AVAssetTrack you want to insert#>];
if (compatibleCompositionTrack) {
    // Implementation continues.
}

將多個視頻片段放置在相同的合成軌道上可能會導致視頻片段之間的轉換丟幀,特別是在嵌入式設備上。 為您的視頻片段選擇合成軌道的數量完全取決于您的app的設計及其預期平臺。


Generating a Volume Ramp

單個AVMutableAudioMix對象可以對您單獨組成的所有音軌執行自定義音頻處理。 您可以使用audioMix類方法創建音頻混合,并使用AVMutableAudioMixInputParameters類的實例將音頻混合與組合中的特定軌道相關聯。 可以使用音頻混合來改變音軌的音量。 以下示例顯示如何在特定音軌上設置音量斜坡,以在組合的持續時間內慢慢淡出音頻:

AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];

// Create the audio mix input parameters object.
AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:mutableCompositionAudioTrack];

// Set the volume ramp to slowly fade the audio out over the duration of the composition.
[mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:CMTimeRangeMake(kCMTimeZero, mutableComposition.duration)];

// Attach the input parameters to the audio mix.
mutableAudioMix.inputParameters = @[mixParameters];

Performing Custom Video Processing

與音頻混合一樣,您只需要一個AVMutableVideoComposition對象來對您的組合視頻軌道執行所有自定義視頻處理。 使用視頻構圖,您可以直接為構圖視頻軌道設置適當的渲染大小,縮放比例和幀速率。

1. Changing the Composition’s Background Color

所有視頻構圖還必須包含至少包含一個視頻構圖指令的AVVideoCompositionInstruction對象數組。 您可以使用AVMutableVideoCompositionInstruction類創建自己的視頻構圖說明。 使用視頻合成指令,您可以修改構圖的背景顏色,指定是否需要后處理或應用圖層指令。

以下示例說明如何創建一個視頻合成指令,將整個構圖的背景顏色更改為紅色。

AVMutableVideoCompositionInstruction *mutableVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mutableVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition.duration);
mutableVideoCompositionInstruction.backgroundColor = [[UIColor redColor] CGColor];

2. Applying Opacity Ramps

視頻合成指令也可用于應用視頻合成圖層指令。 AVMutableVideoCompositionLayerInstruction對象可以對組合中的某個視頻軌道應用變換,變換斜坡,不透明度和不透明度斜坡。 視頻合成指令的layerInstructions數組中的圖層指令的順序決定了在構圖指令的持續時間內,來自源軌道的視頻幀應如何分層和組合。 以下代碼片段顯示如何設置不透明度斜坡以在轉換到第二個視頻之前緩慢淡出組合中的第一個視頻:

AVAsset *firstVideoAssetTrack = <#AVAssetTrack representing the first video segment played in the composition#>;
AVAsset *secondVideoAssetTrack = <#AVAssetTrack representing the second video segment played in the composition#>;

// Create the first video composition instruction.
AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

// Set its time range to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);

// Create the layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];

// Create the opacity ramp to fade out the first video track over its entire duration.
[firstVideoLayerInstruction setOpacityRampFromStartOpacity:1.f toEndOpacity:0.f timeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration)];

// Create the second video composition instruction so that the second video track isn't transparent.
AVMutableVideoCompositionInstruction *secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

// Set its time range to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));

// Create the second layer instruction and associate it with the composition video track.
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:mutableCompositionVideoTrack];

// Attach the first layer instruction to the first video composition instruction.
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];

// Attach the second layer instruction to the second video composition instruction.
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];

// Attach both of the video composition instructions to the video composition.
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

3. Incorporating Core Animation Effects

視頻構圖可以通過animationTool屬性添加核心動畫的功能。 通過這個動畫工具,您可以完成水印視頻和添加標題或動畫疊加等任務。 核心動畫可以以兩種不同的方式與視頻合成一起使用:您可以添加一個核心動畫圖層作為自己的個人作品軌跡,或者您可以將Core Animation效果(使用核心動畫圖層)渲染到您的作曲中的視頻幀中。 以下代碼通過向視頻的中心添加水印來顯示后一個選項:

CALayer *watermarkLayer = <#CALayer representing your desired watermark image#>;
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
videoLayer.frame = CGRectMake(0, 0, mutableVideoComposition.renderSize.width, mutableVideoComposition.renderSize.height);
[parentLayer addSublayer:videoLayer];
watermarkLayer.position = CGPointMake(mutableVideoComposition.renderSize.width/2, mutableVideoComposition.renderSize.height/4);
[parentLayer addSublayer:watermarkLayer];
mutableVideoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

Putting It All Together: Combining Multiple Assets and Saving the Result to the Camera Roll

這個簡短的代碼示例說明了如何組合兩個視頻資產軌道和音頻資產軌道來創建單個視頻文件。 它顯示如何:

  • 創建一個AVMutableComposition對象并添加多個AVMutableCompositionTrack對象
  • AVAssetTrack對象的時間范圍添加到兼容的組合軌道
  • 檢查視頻資產軌道的preferredTransform屬性以確定視頻的方向
  • 使用AVMutableVideoCompositionLayerInstruction對象將變換應用到合成中的視頻軌道
  • 為視頻構圖的renderSizeframeDuration屬性設置適當的值
  • 導出到視頻文件時,使用組合與視頻合成
  • 將視頻文件保存到相機膠卷

要關注最相關的代碼,這個例子省略了一個完整的應用程序的幾個方面,如內存管理和錯誤處理。 要使用AVFoundation,您將有足夠的經驗與Cocoa推斷丟失的部分。

1. Creating the Composition

要從單獨的資源組合軌道,您可以使用AVMutableComposition對象。 創建構圖并添加一個音頻和一個視頻軌道。

AVMutableComposition *mutableComposition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioCompositionTrack = [mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

2. Adding the Assets

一個空的組合對你來說沒有用。 將兩個視頻資產軌道和音頻資產軌道添加到組合。

AVAssetTrack *firstVideoAssetTrack = [[firstVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVAssetTrack *secondVideoAssetTrack = [[secondVideoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration) ofTrack:firstVideoAssetTrack atTime:kCMTimeZero error:nil];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, secondVideoAssetTrack.timeRange.duration) ofTrack:secondVideoAssetTrack atTime:firstVideoAssetTrack.timeRange.duration error:nil];
[audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration)) ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:nil];

這里還要注意:這假設您有兩個資產包含至少一個視頻軌道,每個資產包含至少一個音軌。 可以從相機膠卷中檢索視頻,并且可以從音樂庫或視頻本身檢索音軌。

3. Checking the Video Orientations

將視頻和音軌添加到組合中后,您需要確保兩個視頻軌道的方向正確。 默認情況下,所有視頻軌道都假定為橫向模式。 如果您的視頻軌道以縱向模式拍攝,導出時視頻將無法正確定向。 同樣,如果您嘗試將以縱向模式拍攝的視頻與橫向模式下的視頻拍攝相結合,導出會話將無法完成。

BOOL isFirstVideoPortrait = NO;
CGAffineTransform firstTransform = firstVideoAssetTrack.preferredTransform;
// Check the first video track's preferred transform to determine if it was recorded in portrait mode.
if (firstTransform.a == 0 && firstTransform.d == 0 && (firstTransform.b == 1.0 || firstTransform.b == -1.0) && (firstTransform.c == 1.0 || firstTransform.c == -1.0)) {
    isFirstVideoPortrait = YES;
}
BOOL isSecondVideoPortrait = NO;
CGAffineTransform secondTransform = secondVideoAssetTrack.preferredTransform;
// Check the second video track's preferred transform to determine if it was recorded in portrait mode.
if (secondTransform.a == 0 && secondTransform.d == 0 && (secondTransform.b == 1.0 || secondTransform.b == -1.0) && (secondTransform.c == 1.0 || secondTransform.c == -1.0)) {
    isSecondVideoPortrait = YES;
}
if ((isFirstVideoAssetPortrait && !isSecondVideoAssetPortrait) || (!isFirstVideoAssetPortrait && isSecondVideoAssetPortrait)) {
    UIAlertView *incompatibleVideoOrientationAlert = [[UIAlertView alloc] initWithTitle:@"Error!" message:@"Cannot combine a video shot in portrait mode with a video shot in landscape mode." delegate:self cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
    [incompatibleVideoOrientationAlert show];
    return;
}

4. Applying the Video Composition Layer Instructions

一旦您知道視頻片段具有兼容的方向,您可以對每個視頻片段應用必要的圖層指令,并將這些圖層指令添加到視頻構圖。

AVMutableVideoCompositionInstruction *firstVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

// Set the time range of the first instruction to span the duration of the first video track.
firstVideoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, firstVideoAssetTrack.timeRange.duration);
AVMutableVideoCompositionInstruction * secondVideoCompositionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];

// Set the time range of the second instruction to span the duration of the second video track.
secondVideoCompositionInstruction.timeRange = CMTimeRangeMake(firstVideoAssetTrack.timeRange.duration, CMTimeAdd(firstVideoAssetTrack.timeRange.duration, secondVideoAssetTrack.timeRange.duration));
AVMutableVideoCompositionLayerInstruction *firstVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];

// Set the transform of the first layer instruction to the preferred transform of the first video track.
[firstVideoLayerInstruction setTransform:firstTransform atTime:kCMTimeZero];
AVMutableVideoCompositionLayerInstruction *secondVideoLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompositionTrack];

// Set the transform of the second layer instruction to the preferred transform of the second video track.
[secondVideoLayerInstruction setTransform:secondTransform atTime:firstVideoAssetTrack.timeRange.duration];
firstVideoCompositionInstruction.layerInstructions = @[firstVideoLayerInstruction];
secondVideoCompositionInstruction.layerInstructions = @[secondVideoLayerInstruction];
AVMutableVideoComposition *mutableVideoComposition = [AVMutableVideoComposition videoComposition];
mutableVideoComposition.instructions = @[firstVideoCompositionInstruction, secondVideoCompositionInstruction];

所有AVAssetTrack對象都有一個preferredTransform屬性,其中包含該資產軌道的方向信息。 每當資產軌跡顯示在屏幕上時,就會應用此轉換。 在以前的代碼中,層指令的變換設置為資產軌道的變換,以便在調整渲染大小后,新構圖中的視頻會正確顯示。

5. Setting the Render Size and Frame Duration

要完成視頻方向修復,您必須相應地調整renderSize屬性。 您還應為frameDuration屬性選擇合適的值,例如1/30秒(或每秒30幀)。 默認情況下,renderScale屬性設置為1.0,適用于此組合。

CGSize naturalSizeFirst, naturalSizeSecond;
// If the first video asset was shot in portrait mode, then so was the second one if we made it here.
if (isFirstVideoAssetPortrait) {
// Invert the width and height for the video tracks to ensure that they display properly.
    naturalSizeFirst = CGSizeMake(firstVideoAssetTrack.naturalSize.height, firstVideoAssetTrack.naturalSize.width);
    naturalSizeSecond = CGSizeMake(secondVideoAssetTrack.naturalSize.height, secondVideoAssetTrack.naturalSize.width);
}
else {
// If the videos weren't shot in portrait mode, we can just use their natural sizes.
    naturalSizeFirst = firstVideoAssetTrack.naturalSize;
    naturalSizeSecond = secondVideoAssetTrack.naturalSize;
}
float renderWidth, renderHeight;
// Set the renderWidth and renderHeight to the max of the two videos widths and heights.
if (naturalSizeFirst.width > naturalSizeSecond.width) {
    renderWidth = naturalSizeFirst.width;
}
else {
    renderWidth = naturalSizeSecond.width;
}
if (naturalSizeFirst.height > naturalSizeSecond.height) {
    renderHeight = naturalSizeFirst.height;
}
else {
    renderHeight = naturalSizeSecond.height;
}
mutableVideoComposition.renderSize = CGSizeMake(renderWidth, renderHeight);
// Set the frame duration to an appropriate value (i.e. 30 frames per second for video).
mutableVideoComposition.frameDuration = CMTimeMake(1,30);

6. Exporting the Composition and Saving it to the Camera Roll

此過程的最后一步包括將整個組合導出到單個視頻文件中,并將該視頻保存到相機卷。 您可以使用AVAssetExportSession對象來創建新的視頻文件,并將其傳遞給輸出文件所需的URL。 然后,您可以使用ALAssetsLibrary類將生成的視頻文件保存到相機膠卷。

// Create a static date formatter so we only have to initialize it once.
static NSDateFormatter *kDateFormatter;
if (!kDateFormatter) {
    kDateFormatter = [[NSDateFormatter alloc] init];
    kDateFormatter.dateStyle = NSDateFormatterMediumStyle;
    kDateFormatter.timeStyle = NSDateFormatterShortStyle;
}

// Create the export session with the composition and set the preset to the highest quality.
AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:mutableComposition presetName:AVAssetExportPresetHighestQuality];

// Set the desired output URL for the file created by the export process.
exporter.outputURL = [[[[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:@YES error:nil] URLByAppendingPathComponent:[kDateFormatter stringFromDate:[NSDate date]]] URLByAppendingPathExtension:CFBridgingRelease(UTTypeCopyPreferredTagWithClass((CFStringRef)AVFileTypeQuickTimeMovie, kUTTagClassFilenameExtension))];

// Set the output file type to be a QuickTime movie.
exporter.outputFileType = AVFileTypeQuickTimeMovie;
exporter.shouldOptimizeForNetworkUse = YES;
exporter.videoComposition = mutableVideoComposition;

// Asynchronously export the composition to a video file and save this file to the camera roll once export completes.
[exporter exportAsynchronouslyWithCompletionHandler:^{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (exporter.status == AVAssetExportSessionStatusCompleted) {
            ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
            if ([assetsLibrary videoAtPathIsCompatibleWithSavedPhotosAlbum:exporter.outputURL]) {
                [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:exporter.outputURL completionBlock:NULL];
            }
        }
    });
}];

后記

有關視頻音頻合成的原理理論就這么多,后期會給大家具體工程上的演示,希望對大家有所幫助,謝謝~~~

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

推薦閱讀更多精彩內容