一、常用的類
1、AVVideoComposition
對(duì)兩個(gè)或多個(gè)視頻軌道組合在一起的方法給出了總體描述。由一組時(shí)間范圍和描述組合行為的介紹內(nèi)容組成,這些信息出現(xiàn)在組合資源內(nèi)的任意時(shí)間點(diǎn)。
除了包含描述輸入視頻層組合的信息之外,還提供了配置視頻組合的渲染尺寸、縮放和幀時(shí)長(zhǎng)等屬性。視頻組合配置確定了委托對(duì)象處理時(shí)AVComposition的呈現(xiàn)方式。這里的委托對(duì)象比如AVPlayer或AVAssetImageGenerator。
AVVideoComposition并不是AVComposition的子類,沒(méi)有直接關(guān)聯(lián)。在視頻播放、淡出或處理時(shí)會(huì)用AVVideoComposition來(lái)控制資源視頻軌道的視頻組合行為。
2、AVVideoCompositionInstruction
AVVideoComposition是由一組AVVideoCompositionInstruction對(duì)象格式定義的指令組成的。這個(gè)對(duì)象所提供的最關(guān)鍵的一段數(shù)據(jù)是組合對(duì)象時(shí)間軸內(nèi)的時(shí)間范圍信息,這一時(shí)間范圍是在某一組合形式出現(xiàn)時(shí)的時(shí)間范文,要執(zhí)行的組合特質(zhì)是通過(guò)其layerInstructions集合定義的。
3、AVVideoCompositionLayerInstruction
用于定義對(duì)給定視頻軌道應(yīng)用的模糊、變形和裁剪效果。
它提供了一些方法用于在特定的時(shí)間點(diǎn)火災(zāi)一個(gè)時(shí)間范圍內(nèi)對(duì)這些值進(jìn)修修改。在一段時(shí)間內(nèi)對(duì)這些值應(yīng)用漸變操作可以讓開發(fā)者創(chuàng)建出動(dòng)態(tài)的過(guò)渡效果,比如溶解和漸淡效果。
與所有AVFoundation媒體編輯類一樣,視頻組合API具有不可變和可變兩種形式。不可變超類形式使用于客戶端對(duì)象,比如AVAssetExportSession,不過(guò)當(dāng)創(chuàng)建自己的視頻組合應(yīng)用程序時(shí),應(yīng)該使用不可變子類。
AVVideoComposition并不直接和AVComposition相關(guān)。相反,這些對(duì)象和雷系A(chǔ)VPlayerItem的客戶端相關(guān)聯(lián),在播放組合或進(jìn)行其他處理時(shí)使用這些對(duì)象。
不將AVComposition與輸出行為強(qiáng)耦合,可以再播放、導(dǎo)出或處理視頻時(shí)更靈活地確定如何使用這些行為。
二、概念理解
1、部署視頻布局
要在剪輯間添加過(guò)過(guò)渡,首先需要將兩個(gè)軌道間的視頻片段重新部署。大多數(shù)情況下兩個(gè)軌道就足夠,為滿足一些特殊需求加入更多軌道也是可以的,同時(shí)添加過(guò)多的軌道會(huì)對(duì)性能產(chǎn)生負(fù)面影響。
// 創(chuàng)建新的可變組合,添加兩個(gè)AVMediaTypeVideo類型的組合軌道
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *trackA = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *trackB = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
// 將組合軌道添加到一個(gè)NSArray中備用。
NSArray *videoTracks = @[trackA,trackB];
安排好所需軌道之后,需要錯(cuò)開部署兩個(gè)軌道的視頻布局(A-B交錯(cuò)模式)。當(dāng)視頻按照這種方式排列后,視頻片段前面或后面的空間都會(huì)添加一個(gè)空片段。都是普通的AVCompositionTrackSegment實(shí)例,與視頻一樣,不過(guò)不包含任何媒體。
// 視頻剪輯
NSArray *videoAssets ;
CMTime cursorTime = kCMTimeZero;
for (NSUInteger i = 0; i < videoAssets.count; i ++) {
//通過(guò)i%2來(lái)計(jì)算A-B模式下目標(biāo)軌道的索引號(hào)
NSUInteger trackIndex = i % 2;
AVMutableCompositionTrack *currentTrack = videoTracks[trackIndex];
AVAsset *asset = videoAssets[i];
AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
// 將視頻軌道從當(dāng)前資源中提取出來(lái),插入到currentTrack中
[currentTrack insertTimeRange:timeRange ofTrack:assetTrack atTime:cursorTime error:nil];
cursorTime = CMTimeAdd(cursorTime, timeRange.duration);
}
這個(gè)時(shí)候已經(jīng)采用交錯(cuò)方式排列這些軌道,但是每個(gè)片段都還緊接著上一篇段的片尾,兩個(gè)可見(jiàn)剪輯間沒(méi)有任何空間,還無(wú)法將他們組合在一起。
2、定義重疊區(qū)域
要在兩個(gè)片段中應(yīng)用視頻過(guò)度,需要根據(jù)期望的過(guò)度持續(xù)時(shí)長(zhǎng)來(lái)確定片段的重疊情況。
設(shè)置添加一個(gè)重疊時(shí)間來(lái)處理。
NSArray *videoAssets ;
CMTime cursorTime = kCMTimeZero;
// 定義一個(gè)2秒的CMTIme 作為期望的過(guò)渡持續(xù)時(shí)長(zhǎng)。
CMTime transitionDuration = CMTimeMake(2, 1);
for (NSUInteger i = 0; i < videoAssets.count; i ++) {
//通過(guò)i%2來(lái)計(jì)算A-B模式下目標(biāo)軌道的索引號(hào)
NSUInteger trackIndex = i % 2;
AVMutableCompositionTrack *currentTrack = videoTracks[trackIndex];
AVAsset *asset = videoAssets[i];
AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
// 將視頻軌道從當(dāng)前資源中提取出來(lái),插入到currentTrack中
[currentTrack insertTimeRange:timeRange ofTrack:assetTrack atTime:cursorTime error:nil];
cursorTime = CMTimeAdd(cursorTime, timeRange.duration);
// 減去過(guò)渡時(shí)長(zhǎng),過(guò)渡時(shí)長(zhǎng)適度偏移下一個(gè)插入點(diǎn)
cursorTime = CMTimeSubtract(cursorTime, transitionDuration);
}
此時(shí)的組合可以播放,但是視頻軌道怒又內(nèi)在的z索引行為,第一個(gè)視頻軌道和第二個(gè)之前,第二個(gè)在第三個(gè)之前,一次類推。
在這種情況下播放組合,會(huì)發(fā)現(xiàn)第一、第二段視頻什么都沒(méi)有,最后會(huì)看到最前面軌道中所包含的第三個(gè)片段。在看到第二個(gè)軌道的內(nèi)容前,需要定義時(shí)間范圍并向組合方法解釋這兩個(gè)軌道應(yīng)該如何組合。
創(chuàng)建重疊區(qū)域時(shí),已經(jīng)將視頻時(shí)間軸減少了(videoCount-1)*transitionDuration的大小。如果組合中有額外的軌道,則需要將它們的時(shí)長(zhǎng)相應(yīng)的縮短以滿足新的視頻時(shí)間軸。
3、計(jì)算通過(guò)和過(guò)渡的時(shí)間范圍
AVVideoComposition由一組AVVideoCompositionInstruction對(duì)象組成。其中最重要的數(shù)據(jù)是時(shí)間范圍,用來(lái)表示某種出現(xiàn)的組合方式持續(xù)的時(shí)長(zhǎng)。在開始創(chuàng)建AVVideoCompositionInstruction實(shí)例前,首先需要為組合對(duì)象計(jì)算一系列時(shí)間范圍。
需要計(jì)算兩個(gè)類型的時(shí)間范圍。第一個(gè)通常被認(rèn)為是通過(guò)(pass-through)時(shí)間范圍,在這個(gè)時(shí)間范圍內(nèi)希望一個(gè)軌道的所有幀序列都在不與其他軌道進(jìn)行混合的情況下通過(guò)某一區(qū)域。
第二個(gè)時(shí)間范圍類型是過(guò)渡(transition)時(shí)間范圍。定義了在組合中視頻片段重疊的區(qū)域,并在時(shí)間軸上標(biāo)記處應(yīng)用過(guò)渡效果的區(qū)域。
可以通過(guò)多種方式計(jì)算這些時(shí)間范圍,一種很好的通用方法。
NSArray *videoAssets ;
CMTime cursorTime = kCMTimeZero;
// 定義一個(gè)2秒的CMTIme 作為期望的過(guò)渡持續(xù)時(shí)長(zhǎng)。
CMTime transitionDuration = CMTimeMake(2, 1);
NSMutableArray *passThroughTiemRanges = [NSMutableArray array];
NSMutableArray *transitionTimeRanges = [NSMutableArray array];
NSUInteger videoCount = [videoAssets count];
for (NSUInteger i = 0; i < videoCount; i ++) {
AVAsset *asset = videoAssets[i];
CMTimeRange timeRange = CMTimeRangeMake(cursorTime, asset.duration);
if (i > 0) {
timeRange.start = CMTimeAdd(timeRange.start, transitionDuration);
timeRange.duration = CMTimeSubtract(timeRange.duration, transitionDuration);
}
if (i + 1 < videoCount) {
timeRange.duration = CMTimeSubtract(timeRange.duration, transitionDuration);
}
[passThroughTiemRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
cursorTime = CMTimeAdd(cursorTime, asset.duration);
cursorTime = CMTimeSubtract(cursorTime, transitionDuration);
if (i + 1 < videoCount) {
timeRange = CMTimeRangeMake(cursorTime, transitionDuration);
NSValue *timeRangeValue = [NSValue valueWithCMTimeRange:timeRange];
[transitionTimeRanges addObject:timeRangeValue];
}
}
創(chuàng)建所需要的通過(guò)和過(guò)渡時(shí)間范圍被認(rèn)為是整個(gè)處理過(guò)程中最重要的一步操作,他并不是最難解決的問(wèn)題,不過(guò)容易出錯(cuò)。進(jìn)行這一步處理的時(shí)候,有兩個(gè)關(guān)鍵點(diǎn):
- 1、計(jì)算的時(shí)間范圍必須沒(méi)有任何空隙或重疊。必須是緊接上一個(gè)片段之后按需排列的最新時(shí)間范圍。
- 2、計(jì)算必須考慮組合對(duì)象持續(xù)時(shí)間。如果組合中還包含額外的軌道,就需要使它們遵循目前的視頻時(shí)間軸,或者根據(jù)他們的持續(xù)時(shí)長(zhǎng)擴(kuò)展最終的時(shí)間范圍。
如果沒(méi)有注意到這兩點(diǎn),組合對(duì)象仍可播放,但視頻內(nèi)容不會(huì)被渲染,只會(huì)顯示黑屏。
Apple Developer Center中AVCompositionDebugViewer可以幫助呈現(xiàn)組合。
4、創(chuàng)建組合和層指令
創(chuàng)建AVVideoCompositionInstruction和AVVideoCompositionLayerInstruction示例,提供視頻組合方法所執(zhí)行的指令。
NSMutableArray *compositionInstructions = [NSMutableArray array];
//遍歷所有的通過(guò)時(shí)間范圍
for (NSUInteger i = 0; i > passThroughTiemRanges.count; i ++) {
// 循環(huán)在連個(gè)需要?jiǎng)?chuàng)建所需指令的視頻軌道間前后切換。
NSInteger trackIndex = i % 2;
AVMutableCompositionTrack *currentTrack = videoTracks[trackIndex];
// 創(chuàng)建AVMutableVideoCompositionInstruction實(shí)例,設(shè)置當(dāng)前通過(guò)時(shí)間范圍作為timeRange
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = [passThroughTiemRanges[i] CMTimeRangeValue];
// 為活動(dòng)組合創(chuàng)建一個(gè)新的AVMutableVideoCompositionLayerInstruction
AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:currentTrack];
// 添加到數(shù)組
instruction.layerInstructions = @[layerInstruction];
// 設(shè)置為組合指令的layerInstructions屬性。
[compositionInstructions addObject:instruction];
//組合的通過(guò)時(shí)間范圍區(qū)域只需要一個(gè)與要呈現(xiàn)視頻幀的軌道相關(guān)的單獨(dú)層指令。
if (i < transitionTimeRanges.count) {
//要?jiǎng)?chuàng)建過(guò)渡時(shí)間的指令,需要得到前一個(gè)軌道(過(guò)渡前的軌道)的引用和后一個(gè)軌道(過(guò)渡后的軌道)的引用
//確保軌道的引用始終順序正確。
AVCompositionTrack *foregroundTrack = videoTracks[trackIndex];
AVCompositionTrack *backgroundTrack = videoTracks[1-trackIndex];
//創(chuàng)建一個(gè)AVMutableVideoCompositionInstruction實(shí)例
AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
//設(shè)置當(dāng)前過(guò)渡時(shí)間范圍為它的timeRange屬性。
CMTimeRange timeRange = [transitionTimeRanges[i] CMTimeRangeValue];
instruction.timeRange = timeRange;
// 為每個(gè)軌道創(chuàng)建一個(gè)AVMutableVideoCompositionLayerInstruction實(shí)例
// 在這些層指令上定義從一個(gè)場(chǎng)景到另一個(gè)場(chǎng)景的過(guò)渡效果
AVMutableVideoCompositionLayerInstruction *fromLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:foregroundTrack];
AVMutableVideoCompositionLayerInstruction *toLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:backgroundTrack];
// 將兩個(gè)層指令添加到數(shù)組,并設(shè)置他們作為當(dāng)前組合指令的layerInstructions屬性
// 這一數(shù)組中的元素進(jìn)行排序非常重要,因?yàn)樗x了組合輸出中視頻圖層的z軸順序。
instruction.layerInstructions = @[fromLayerInstruction,toLayerInstruction];
[compositionInstructions addObject:instruction];
}
}
5、創(chuàng)建和配置AVVideoComposition
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.instructions = compositionInstructions;
// 定義組合應(yīng)該被渲染尺寸。對(duì)應(yīng)于組合中視頻原始大小720p-1280*720,1080p-1920*1080
videoComposition.renderSize = CGSizeMake(1280.f, 720.f);
// 設(shè)置有效幀率
videoComposition.frameDuration = CMTimeMake(1, 30);
// 視頻組合應(yīng)用的縮放
videoComposition.renderScale = 1.0f;
6、創(chuàng)建視頻組合的捷徑
// 創(chuàng)建一個(gè)基礎(chǔ)的AVMutableVideoComposition
AVMutableVideoComposition *co = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:composition];
包含基礎(chǔ)配置:
- instructions 屬性包含一組完整的基于組合視頻軌道(以及其中包含的片段空間布局)的組合和層指令
- renderSize 屬性被設(shè)置為AVComposition對(duì)象的naturalSize,如果沒(méi)有設(shè)置,則使用能夠滿足組合視頻軌道中最大視頻緯度的尺寸值。
- frameDuration 設(shè)置為組合視頻軌道中最大nominalFrameRate的值。如果所有軌道的nominalFrameRate值都為0,則frameDuration設(shè)置層默認(rèn)值1/30秒(30FPS)。
- renderScale 始終設(shè)置為1.0
三、過(guò)渡效果
1、溶解
[fromLayer setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:timeRange];
// 對(duì)fromLayer對(duì)象設(shè)置一個(gè)模糊漸變
2、推出
//定義個(gè)對(duì)于輸入視頻層的變換。
//CGAffineTransForm可以修改層的轉(zhuǎn)化、旋轉(zhuǎn)、縮放。對(duì)層應(yīng)用一個(gè)漸變的變化可以衍生出很多效果。
CGAffineTransform identityTransform = CGAffineTransformIdentity;
CGFloat videoWidth = videoCompostion.renderSize.width;
// 這里通過(guò)轉(zhuǎn)化(translation)轉(zhuǎn)換,修改圖層的x,y坐標(biāo)。
CGAffineTransform fromDestTransform = CGAffineTransformMakeTranslation(-videoWidth, 0);
CGAffineTransform toStartTransform = CGAffineTransformMakeTranslation(videoWidth, 0);
// 設(shè)置fromLayer的漸變效果,初始變換設(shè)置為identityTransform,終點(diǎn)變換為fromDestTransform
[fromLayer setTransformRampFromStartTransform:identityTransform toEndTransform:fromDestTransform timeRange:timeRange];
[toLayer setTransformRampFromStartTransform:toStartTransform toEndTransform:identityTransform timeRange:timeRange];
3、擦除
CGFloat videoWidth = videoCompostion.renderSize.width;
CGFloat videoHeight = videoCompostion.renderSize.height;
CGRect startRect = CGRectMake(0, 0, videoWidth, videoHeight);
CGRect endRect = CGRectMake(0, videoHeight, videoWidth, 0);
[fromLayer setCropRectangleRampFromStartCropRectangle:startRect toEndCropRectangle:endRect timeRange:timeRange];
完整代碼.m
#import "THTransitionCompositionBuilder.h"
#import "THVideoItem.h"
#import "THAudioItem.h"
#import "THVolumeAutomation.h"
#import "THTransitionComposition.h"
#import "THTransitionInstructions.h"
#import "THFunctions.h"
@interface THTransitionCompositionBuilder ()
@property (strong, nonatomic) THTimeline *timeline;
@property (strong, nonatomic) AVMutableComposition *composition;
@property (weak, nonatomic) AVMutableCompositionTrack *musicTrack;
@end
@implementation THTransitionCompositionBuilder
- (id)initWithTimeline:(THTimeline *)timeline {
self = [super init];
if (self) {
_timeline = timeline;
}
return self;
}
- (id <THComposition>)buildComposition {
self.composition = [AVMutableComposition composition];
[self buildCompositionTracks];
AVVideoComposition *videoComposition = [self buildVideoComposition];
AVAudioMix *audioMix = [self buildAudioMix];
return [[THTransitionComposition alloc] initWithComposition:self.composition
videoComposition:videoComposition
audioMix:audioMix];
}
- (void)buildCompositionTracks {
CMPersistentTrackID trackID = kCMPersistentTrackID_Invalid;
// 創(chuàng)建兩個(gè)AVMediaTypeVideo類型的軌道,提供所需的A-B軌道排列
AVMutableCompositionTrack *compositionTrackA = [self.composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:trackID];
AVMutableCompositionTrack *compositionTrackB = [self.composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:trackID];
NSArray *videoTracks = @[compositionTrackA,compositionTrackB];
CMTime cursorTime = kCMTimeZero;
CMTime transitionDuration = kCMTimeZero;
if (!THIsEmpty(self.timeline.transitions)) {
//判斷transitions是否被填充,即視頻過(guò)渡是否被設(shè)置為激活。
//如果激活過(guò)渡效果,設(shè)置常量值
transitionDuration = THDefaultTransitionDuration;
}
NSArray *videos = self.timeline.videos;
for (NSUInteger i = 0; i < videos.count; i ++) {
NSUInteger trackIndex = i%2;
THVideoItem *item = videos[i];
// 確定要插入的目標(biāo)軌道
AVMutableCompositionTrack *currentTrack = videoTracks[trackIndex];
AVAssetTrack *assetTrack = [[item.asset tracksWithMediaType:AVMediaTypeVideo] firstObject];
[currentTrack insertTimeRange:item.timeRange ofTrack:assetTrack atTime:cursorTime error:nil];
cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration);
cursorTime = CMTimeSubtract(cursorTime, transitionDuration);
}
//添加畫外音和音樂(lè)軌道
[self addCompositionTrackOfType:AVMediaTypeAudio withMediaItems:self.timeline.voiceOvers];
NSArray *musicItems = self.timeline.musicItems;
self.musicTrack = [self addCompositionTrackOfType:AVMediaTypeVideo withMediaItems:musicItems];
}
- (AVVideoComposition *)buildVideoComposition {
// 創(chuàng)建一個(gè)新的AVVideoComposition實(shí)例,自動(dòng)創(chuàng)建所需的組合對(duì)象和層指令
// 設(shè)置renderSize,renderScale和frameDuration屬性為相應(yīng)的值。
AVVideoComposition *videoCompostion = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:self.composition];
// 從AVVideoComposition中提取相關(guān)的層指令,可以應(yīng)用視頻過(guò)渡效果。
// 返回一個(gè)THTransitionInstructions對(duì)象數(shù)組
NSArray *transitionInstructions = [self transitionInstructionsInVideoComposition:videoCompostion];
for (THTransitionInstructions *instructions in transitionInstructions) {
CMTimeRange timeRange = instructions.compositionInstruction.timeRange;
AVMutableVideoCompositionLayerInstruction *fromLayer = instructions.fromLayerInstruction;
AVMutableVideoCompositionLayerInstruction *toLayer = instructions.toLayerInstruction;
THVideoTransitionType type = instructions.transition.type;
if (type == THVideoTransitionTypeDissolve) { // 溶解過(guò)渡效果
[fromLayer setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:timeRange];
// 對(duì)fromLayer對(duì)象設(shè)置一個(gè)模糊漸變
} else if (type == THVideoTransitionTypePush) { //推入過(guò)渡效果
//定義個(gè)對(duì)于輸入視頻層的變換。
//CGAffineTransForm可以修改層的轉(zhuǎn)化、旋轉(zhuǎn)、縮放。對(duì)層應(yīng)用一個(gè)漸變的變化可以衍生出很多效果。
CGAffineTransform identityTransform = CGAffineTransformIdentity;
CGFloat videoWidth = videoCompostion.renderSize.width;
// 這里通過(guò)轉(zhuǎn)化(translation)轉(zhuǎn)換,修改圖層的x,y坐標(biāo)。
CGAffineTransform fromDestTransform = CGAffineTransformMakeTranslation(-videoWidth, 0);
CGAffineTransform toStartTransform = CGAffineTransformMakeTranslation(videoWidth, 0);
// 設(shè)置fromLayer的漸變效果,初始變換設(shè)置為identityTransform,終點(diǎn)變換為fromDestTransform
[fromLayer setTransformRampFromStartTransform:identityTransform toEndTransform:fromDestTransform timeRange:timeRange];
[toLayer setTransformRampFromStartTransform:toStartTransform toEndTransform:identityTransform timeRange:timeRange];
} else if (type == THVideoTransitionTypeWipe) { //擦除過(guò)渡效果
CGFloat videoWidth = videoCompostion.renderSize.width;
CGFloat videoHeight = videoCompostion.renderSize.height;
CGRect startRect = CGRectMake(0, 0, videoWidth, videoHeight);
CGRect endRect = CGRectMake(0, videoHeight, videoWidth, 0);
[fromLayer setCropRectangleRampFromStartCropRectangle:startRect toEndCropRectangle:endRect timeRange:timeRange];
}
// 配合組合指令的layerInstructions,按顯示的順序傳遞指令,確保正確應(yīng)用視頻過(guò)渡效果
instructions.compositionInstruction.layerInstructions = @[fromLayer,toLayer];
}
return videoCompostion;
}
// Extract the composition and layer instructions out of the
// prebuilt AVVideoComposition. Make the association between the instructions
// and the THVideoTransition the user configured in the timeline.
- (NSArray *)transitionInstructionsInVideoComposition:(AVVideoComposition *)vc {
NSMutableArray *transitionInstructions = [NSMutableArray array];
int layerInstructionIndex = 1;
NSArray *compositionInstructions = vc.instructions;
// 遍歷從AVVideoComposition中得到的AVVideoCompositionInstruction對(duì)象
for (AVMutableVideoCompositionInstruction *vci in compositionInstructions) {
//只關(guān)心包含兩個(gè)層指令的組合指令,表示這個(gè)指令定義了組合中的過(guò)渡區(qū)域。
if (vci.layerInstructions.count == 2) {
THTransitionInstructions *instructions = [[THTransitionInstructions alloc] init];
instructions.compositionInstruction = vci;
// 自動(dòng)創(chuàng)建的層指令通常保存于layerInstructions數(shù)組
//第一個(gè)軌道的層指令作為數(shù)組的第一個(gè)元素,接下來(lái)是第二個(gè)軌道的層指令
//
instructions.fromLayerInstruction = [vci.layerInstructions[1-layerInstructionIndex] mutableCopy];
instructions.toLayerInstruction = [vci.layerInstructions[layerInstructionIndex] mutableCopy];
[transitionInstructions addObject:instructions];
layerInstructionIndex = layerInstructionIndex == 1 ? 0:1;
}
}
NSArray *transitions = self.timeline.transitions;
if (THIsEmpty(transitions)) {
//禁用了過(guò)渡
return transitionInstructions;
}
// 如果過(guò)渡已啟用,遍歷transitionInstructions,并將用戶選定的THVideoTransition對(duì)象和它進(jìn)行關(guān)聯(lián)。
for (NSUInteger i = 0; i < transitionInstructions.count; i ++) {
THTransitionInstructions *tis = transitionInstructions[i];
tis.transition = self.timeline.transitions[i];
}
return transitionInstructions;
}
- (AVMutableCompositionTrack *)addCompositionTrackOfType:(NSString *)mediaType
withMediaItems:(NSArray *)mediaItems {
AVMutableCompositionTrack *compositionTrack = nil;
if (!THIsEmpty(mediaItems)) {
compositionTrack =
[self.composition addMutableTrackWithMediaType:mediaType
preferredTrackID:kCMPersistentTrackID_Invalid];
CMTime cursorTime = kCMTimeZero;
for (THMediaItem *item in mediaItems) {
if (CMTIME_COMPARE_INLINE(item.startTimeInTimeline, !=, kCMTimeInvalid)) {
cursorTime = item.startTimeInTimeline;
}
AVAssetTrack *assetTrack = [[item.asset tracksWithMediaType:mediaType] firstObject];
[compositionTrack insertTimeRange:item.timeRange ofTrack:assetTrack atTime:cursorTime error:nil];
// Move cursor to next item time
cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration);
}
}
return compositionTrack;
}
- (AVAudioMix *)buildAudioMix {
NSArray *items = self.timeline.musicItems;
// Only one allowed
if (items.count == 1) {
THAudioItem *item = self.timeline.musicItems[0];
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
AVMutableAudioMixInputParameters *parameters =
[AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:self.musicTrack];
for (THVolumeAutomation *automation in item.volumeAutomation) {
[parameters setVolumeRampFromStartVolume:automation.startVolume
toEndVolume:automation.endVolume
timeRange:automation.timeRange];
}
audioMix.inputParameters = @[parameters];
return audioMix;
}
return nil;
}
@end