AVFoundation-視頻過渡效果

兩個獨立的視頻拼接起來以后很有可能會出現(xiàn)銜接處過于生硬的問題,此時就需要給視頻添加過渡效果鳞仙,這一效果需要用到 AVVideoComposition 及其子類 AVMutableVideoComposition君躺。

AVMutableVideoComposition 是過渡效果實現(xiàn)的核心峭判,它能夠表示多個視頻軌道的合并,同時表達(dá)合并的方式棕叫,也就是過渡效果林螃,同時提供了配置視頻組合的渲染尺寸、縮放俺泣、幀時長等疗认。AVMutableVideoComposition 由一組 AVMutableVideoCompositionInstruction 組成,AVMutableVideoCompositionInstruction 定義了時間范圍信息伏钠,以及每一幀的層級横漏,也就是 AVMutableVideoCompositionLayerInstruction,AVMutableVideoCompositionLayerInstruction 用于真正實現(xiàn)各類模糊熟掂、變形和裁剪效果缎浇。

總結(jié)一下就是:

  • AVMutableVideoCompositionLayerInstruction 負(fù)責(zé)執(zhí)行具體的過渡動畫
  • AVMutableVideoCompositionInstruction 負(fù)責(zé)管理過渡動畫在何時執(zhí)行
  • AVMutableVideoComposition 代表最終修改過的視頻組合對象

而 AVMutableVideoComposition 就可以被提供給 AVPlayerItem、AVAssetExportSession赴肚、AVAssetReaderVideoCompositionOutput 和 AVAssetImageGenerator 使用了素跺,但是要注意的是二蓝,與 AVAudioMix 類似,AVMutableVideoComposition 并不能與 AVComposition 關(guān)聯(lián)指厌,這一點導(dǎo)致在編輯和傳遞 AVMutableVideoComposition 過程中刊愚,需要時時考慮附帶 AVMutableVideoComposition 參數(shù)。

AVVideoComposition 與 AVComposition 沒有關(guān)系仑乌。

實現(xiàn)過渡效果的基本步驟可以分為

  • 合并視頻和音頻,生成多視頻軌道的 AVMutableComposition
  • 對視頻過渡區(qū)域琴锭,生成過渡動畫
  • 組裝 AVMutableVideoComposition晰甚,提供給 AVPlayerItem 或 AVAssetExportSession 使用

1. 合并媒體

由于 AVMutableVideoCompositionLayerInstruction 是與視頻軌道綁定的,因此在處理過渡效果時决帖,需要在不同軌道之間處理厕九,常見的方式是交錯放置多個視頻,形成如下形式的視頻布局

段1 段2 段3
視頻A 視頻C
視頻B

可以用一個視頻軌道數(shù)組來表達(dá)多個軌道地回。同時扁远,為了實現(xiàn)過渡效果,兩個相鄰的視頻刻像,如視頻 A 和 B 之間畅买,應(yīng)當(dāng)在時間軸上有重疊區(qū)域,因此需要對時間軸進(jìn)行如下區(qū)分

視頻 A AB 過渡區(qū) 視頻 B BC 過渡區(qū) 視頻 C

這樣的劃分也需要記錄下來细睡,所以最終合并媒體的步驟如下

1.1 初始化相關(guān)對象

                    AVMutableComposition *composition = [AVMutableComposition composition];
                    __block CMTime cursor = kCMTimeZero;
                    CMTime transitionTime = CMTimeMake(2, 1); // 過渡時間
                    NSMutableArray *passRanges = [NSMutableArray array];// 視頻獨立區(qū)時間數(shù)組
                    NSMutableArray *transitionRanges = [NSMutableArray array]; // 過渡區(qū)時間數(shù)組
                    
                    AVMutableCompositionTrack *videoCompositionTrackA = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
                    AVMutableCompositionTrack *videoCompositionTrackB = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
                    NSArray *videoTracks = @[videoCompositionTrackA, videoCompositionTrackB]; // 生成 AB 軌道

1.2 遍歷資源

遍歷資源過程中谷羞,首先需要將視頻軌道和音頻軌道加入到 AVMutableComposition 中,其次需要更獨立區(qū)時間數(shù)組和過渡區(qū)時間數(shù)組

                        // 視頻軌道
                        AVMutableCompositionTrack *videoCompositionTrack = videoTracks[idx % 2];
                        AVAssetTrack *videoTrack = [[targetAsset tracksWithMediaType:AVMediaTypeVideo] firstObject];
                        [videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, targetAsset.duration) ofTrack:videoTrack atTime:cursor error:nil];
                        
                        // 音頻軌道
                        AVMutableCompositionTrack *audioCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
                        AVAssetTrack *audioTracck = [[targetAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
                        [audioCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, targetAsset.duration) ofTrack:audioTracck atTime:cursor error:nil];
                        
                        CMTimeRange timeRange = CMTimeRangeMake(cursor, targetAsset.duration);
                        // 去除每一個視頻的頭部過渡區(qū)
                        if (idx > 0) { // 第一個視頻只需要裁剪尾部過渡區(qū)溜徙,不需要裁剪頭部過渡區(qū)
                            timeRange.start = CMTimeAdd(timeRange.start, transitionTime);
                            timeRange.duration = CMTimeSubtract(timeRange.duration, transitionTime);
                        }
                        // 去除每一個視頻的尾部過渡區(qū)
                        if (idx + 1 < mediaAssets.count) { // 末尾視頻沒有尾部過渡區(qū)湃缎,其他視頻還需要去除尾部過渡區(qū)
                            timeRange.duration = CMTimeSubtract(timeRange.duration, transitionTime);
                        }
                        [passRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
                        
                        cursor = CMTimeAdd(cursor, targetAsset.duration);
                        cursor = CMTimeSubtract(cursor, transitionTime);
                        
                        if (idx + 1 < mediaAssets.count) { // 末尾一個視頻沒有尾部過渡區(qū)
                            timeRange = CMTimeRangeMake(cursor, transitionTime);
                            [transitionRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
                        }

這里我們將視頻錯開放入了兩個視頻軌道里,要注意由于視頻軌道內(nèi)具有 z 索引行為蠢壹,因此目前是不能播放多個視頻軌道的嗓违。

2. 過渡動畫

現(xiàn)在我們有了兩個視頻軌道,一個表示獨立區(qū)的時間數(shù)組图贸,一個表示過渡區(qū)的時間數(shù)組蹂季,接下來需要在每一個過渡區(qū)里定義具體的過渡動畫,并將所有

                    NSMutableArray *compositionInstructions = [NSMutableArray array];
                    NSArray *tracks = [composition tracksWithMediaType:AVMediaTypeVideo];
                    [passRanges enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                        NSUInteger trackIndex = idx % 2;
                        
                        AVMutableCompositionTrack *currentTrack = tracks[trackIndex]; // 取出對應(yīng)軌道
                        AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
                        instruction.timeRange = [obj CMTimeRangeValue];// 取出獨立分區(qū)的 duration
                        AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:currentTrack];// 取出當(dāng)前軌道的 layerInstruction
                        instruction.layerInstructions = @[layerInstruction];
                        [compositionInstructions addObject:instruction];// 將 AVMutableVideoCompositionInstruction 加入到數(shù)組里
                        
                        if (idx < transitionRanges.count) { // 過渡區(qū)處理
                            AVCompositionTrack *foregroundTrack = tracks[trackIndex];//當(dāng)前的track
                            AVCompositionTrack *backgroundTrack = tracks[1 - trackIndex];// 下一個 track
                            AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
                            instruction.timeRange = [transitionRanges[idx] CMTimeRangeValue];
                            AVMutableVideoCompositionLayerInstruction *frontLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:foregroundTrack];// 取出當(dāng)前軌道的 layerInstruction
                            AVMutableVideoCompositionLayerInstruction *backLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:backgroundTrack];// 取出下一個軌道的 layerInstruction
                            
                            // 實際過渡動畫的定義
                            
                            instruction.layerInstructions = @[frontLayerInstruction, backLayerInstruction];
                            [compositionInstructions addObject:instruction];
                        }
                    }];

要注意疏日,compositionInstructions 數(shù)組必須按順序組裝 AVMutableVideoCompositionInstruction 對象乏盐。

AVMutableVideoCompositionLayerInstruction 本身支持三種過渡動畫效果

  • opacity 透明度變化、溶解效果
                            [frontLayerInstruction setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:[transitionRanges[idx] CMTimeRangeValue]];
                            [backLayerInstruction setOpacityRampFromStartOpacity:0.0 toEndOpacity:1.0 timeRange:[transitionRanges[idx] CMTimeRangeValue]];
  • Transform 矩陣變換制恍、推入效果
                            CGAffineTransform identityTransform = CGAffineTransformIdentity;
                            CGFloat videoWidth = 1280.f;
                            CGAffineTransform from = CGAffineTransformMakeTranslation(-videoWidth, 0);
                            CGAffineTransform to = CGAffineTransformMakeTranslation(videoWidth, 0.0);
                            [frontLayerInstruction setTransformRampFromStartTransform:identityTransform toEndTransform:from timeRange:[transitionRanges[idx] CMTimeRangeValue]];
                            [backLayerInstruction setTransformRampFromStartTransform:to toEndTransform:identityTransform timeRange:[transitionRanges[idx] CMTimeRangeValue]];
  • CropRectangle 裁剪區(qū)域父能、擦除效果
                            CGFloat videoWidth = 1280.f;
                            CGFloat videoHeight = 720.f;

                            CGRect startRect = CGRectMake(0.0f, 0.0f, videoWidth, videoHeight);
                            CGRect endRect = CGRectMake(0.0f, 0.0f, videoWidth, 0.0f);

                            [frontLayerInstruction setCropRectangleRampFromStartCropRectangle:startRect toEndCropRectangle:endRect timeRange:[transitionRanges[idx] CMTimeRangeValue]];

組裝媒體

獲得了裝有 AVMutableVideoCompositionInstruction 的 compositionInstructions 數(shù)組后,就可以組裝 AVMutableVideoComposition 了

                    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
                    videoComposition.instructions = [compositionInstructions copy];
                    videoComposition.renderSize = CGSizeMake(1280.f, 720.f);
                    videoComposition.frameDuration = CMTimeMake(1, 30);
                    videoComposition.renderScale = 1.0;

這里定義了四個主要屬性

  • instructions 屬性用于設(shè)置所有組合指令净神,也就是 AVMutableVideoCompositionInstruction
  • renderSize 定義渲染尺寸
  • frameDuration 定義有效幀率何吝,30 FPS 的幀率對應(yīng) frameDuration 為 1/30
  • renderScale 定義視頻組合的縮放值

當(dāng)然還可以用快捷方式來獲取一個 AVMutableVideoComposition

AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoCompositionWithPropertiesOfAsset:composition];

這個方法所配置的屬性如下所示

  • instructions 屬性包含一組完整的基于組合視頻軌道的組合和層指令
  • renderSize 被設(shè)置為 AVComposition 的 naturalSize溉委,如果沒有,則設(shè)置為能夠滿足最大視頻維度的尺寸值
  • frameDuration 設(shè)置為組合視頻軌道的最大 nominalFrameRate 的值爱榕,如果都為 0 則設(shè)置為 1/30
  • renderScale 設(shè)置為 1.0

生成了 AVMutableVideoComposition 以后就可以直接用于播放或?qū)С隽恕?/p>

AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:[composition copy]];
item.videoComposition = videoComposition;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓣喊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子黔酥,更是在濱河造成了極大的恐慌藻三,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跪者,死亡現(xiàn)場離奇詭異棵帽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)渣玲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門逗概,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人忘衍,你說我怎么就攤上這事逾苫。” “怎么了枚钓?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵铅搓,是天一觀的道長。 經(jīng)常有香客問我搀捷,道長狸吞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任指煎,我火速辦了婚禮蹋偏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘至壤。我一直安慰自己威始,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布像街。 她就那樣靜靜地躺著黎棠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪镰绎。 梳的紋絲不亂的頭發(fā)上脓斩,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機(jī)與錄音畴栖,去河邊找鬼随静。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的燎猛。 我是一名探鬼主播恋捆,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼重绷!你這毒婦竟也來了沸停?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤昭卓,失蹤者是張志新(化名)和其女友劉穎愤钾,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體候醒,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡能颁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了火焰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劲装。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡胧沫,死狀恐怖昌简,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绒怨,我是刑警寧澤纯赎,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站南蹂,受9級特大地震影響犬金,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜六剥,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一晚顷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疗疟,春花似錦该默、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至店诗,卻和暖如春裹刮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背庞瘸。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工捧弃, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人擦囊。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓塔橡,卻偏偏與公主長得像梅割,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子葛家,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 用到的組件 1户辞、通過CocoaPods安裝 2、第三方類庫安裝 3癞谒、第三方服務(wù) 友盟社會化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 14,613評論 1 180
  • 9月1號底燎,中國傳統(tǒng)的開學(xué)日。與以往不大一樣弹砚,今年倆孩子都要上學(xué)了双仍,姐姐升初一,弟弟要上幼兒園桌吃,各自開始新...
    愛華王閱讀 378評論 3 1
  • 曾構(gòu)思過無數(shù)次的未來朱沃,隨時間流逝,近茅诱,也不近逗物。我期許的未來,能夠每天任性瑟俭,做一個精神上的富人翎卓,和喜歡的一切,呆在一...
    urnotseven閱讀 220評論 0 0
  • 兩天后有個朋友過生日逗扒,因為很早就看到了消息提示,所以就記住了欠橘。想著都是好朋友矩肩,況且他又是一個來自遠(yuǎn)方的人,怕他生日...
    午后檸檬樹下的陽光_4e24閱讀 238評論 0 0
  • 在ES6標(biāo)準(zhǔn)中出現(xiàn)了 Promise(承諾) 類似于回調(diào)函數(shù) 1:創(chuàng)建Promise的實例 2:Promise結(jié)合...
    煎包小混沌閱讀 1,455評論 0 1