AVFoundation開發(fā)秘籍筆記-11創(chuàng)建視頻過(guò)渡效果

一、常用的類

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)建AVVideoCompositionInstructionAVVideoCompositionLayerInstruction示例,提供視頻組合方法所執(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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,228評(píng)論 3 119
  • 初入職場(chǎng)的我們,剛從安逸的校園環(huán)境出來(lái),就不得不適應(yīng)這個(gè)快節(jié)奏的社會(huì)。這個(gè)社會(huì)似乎在告訴我們,我們今后的日子或許就...
    愛(ài)思考的安妮閱讀 2,522評(píng)論 3 2
  • 1、面朝大海春暖花開 帶你玩轉(zhuǎn)世界 2、三月桃花劫 只想和你在一起 3、遠(yuǎn)離城市的喧鬧 慢生活時(shí)光 廣告~書 1、...
    芬芳姐377閱讀 187評(píng)論 0 0
  • 我發(fā)現(xiàn)了一個(gè)規(guī)律, 朋友圈經(jīng)常曬美顏?zhàn)耘暮兔朗车墓經(jīng)?,大多是單身的,為啥,因?yàn)檎剳賽?ài)的狀態(tài)下一般很少曬幸福, 而單...
    朱劍潭閱讀 630評(píng)論 0 4
  • Objective 你對(duì)今天學(xué)的記得什么? 人的大腦分三種: 思考腦:準(zhǔn)確,速度慢!專一,像藍(lán)牙 反射腦:自發(fā)無(wú)意...
    學(xué)霸教練李斌閱讀 243評(píng)論 1 2

友情鏈接更多精彩內(nèi)容