iOS合并多個視頻(Obj-c&Swift)

0x00 寫在前面

  1. 需求是把兩個或多個小視頻合并成一個視頻
  2. 合并效果為首尾相接畜隶,不是視頻重疊
  3. 當(dāng)前代碼為示例代碼壁肋,只展示了合并兩個視頻文件,要實現(xiàn)多個視頻文件合并為一個籽慢,需要改動代碼

0x01 代碼展示(Obj-c&Swift)

//Obj-c
- (void)combVideos {
    NSBundle *mainBundle = [NSBundle mainBundle];
    NSString *firstVideo = [mainBundle pathForResource:@"1" ofType:@"mp4"];
    NSString *secondVideo = [mainBundle pathForResource:@"2" ofType:@"mp4"];
    
    NSDictionary *optDict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
    AVAsset *firstAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:firstVideo] options:optDict];
    AVAsset *secondAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:secondVideo] options:optDict];
    
    AVMutableComposition *composition = [AVMutableComposition composition];
    //為視頻類型的的Track
    AVMutableCompositionTrack *compositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    //由于沒有計算當(dāng)前CMTime的起始位置浸遗,現(xiàn)在插入0的位置,所以合并出來的視頻是后添加在前面,可以計算一下時間嗡综,插入到指定位置
    //CMTimeRangeMake 指定起去始位置
    CMTimeRange firstTimeRange = CMTimeRangeMake(kCMTimeZero, firstAsset.duration);
    CMTimeRange secondTimeRange = CMTimeRangeMake(kCMTimeZero, secondAsset.duration);
    [compositionTrack insertTimeRange:secondTimeRange ofTrack:[secondAsset tracksWithMediaType:AVMediaTypeVideo][0] atTime:kCMTimeZero error:nil];
    [compositionTrack insertTimeRange:firstTimeRange ofTrack:[firstAsset tracksWithMediaType:AVMediaTypeVideo][0] atTime:kCMTimeZero error:nil];
    
    //只合并視頻乙帮,導(dǎo)出后聲音會消失,所以需要把聲音插入到混淆器中
    //添加音頻,添加本地其他音樂也可以,與視頻一致
    AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    [audioTrack insertTimeRange:secondTimeRange ofTrack:[firstAsset tracksWithMediaType:AVMediaTypeAudio][0] atTime:kCMTimeZero error:nil];
    [audioTrack insertTimeRange:firstTimeRange ofTrack:[firstAsset tracksWithMediaType:AVMediaTypeAudio][0] atTime:kCMTimeZero error:nil];
    
    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [cachePath stringByAppendingPathComponent:@"comp.mp4"];
    AVAssetExportSession *exporterSession = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
    exporterSession.outputFileType = AVFileTypeMPEG4;
    exporterSession.outputURL = [NSURL fileURLWithPath:filePath]; //如果文件已存在极景,將造成導(dǎo)出失敗
    exporterSession.shouldOptimizeForNetworkUse = YES; //用于互聯(lián)網(wǎng)傳輸
    [exporterSession exportAsynchronouslyWithCompletionHandler:^{
        switch (exporterSession.status) {
            case AVAssetExportSessionStatusUnknown:
                NSLog(@"exporter Unknow");
                break;
            case AVAssetExportSessionStatusCancelled:
                NSLog(@"exporter Canceled");
                break;
            case AVAssetExportSessionStatusFailed:
                NSLog(@"exporter Failed");
                break;
            case AVAssetExportSessionStatusWaiting:
                NSLog(@"exporter Waiting");
                break;
            case AVAssetExportSessionStatusExporting:
                NSLog(@"exporter Exporting");
                break;
            case AVAssetExportSessionStatusCompleted:
                NSLog(@"exporter Completed");
                break;
        }
    }];
}

Swift代碼:需要先導(dǎo)入 import AVFoundation
*Swift版本注釋可參考Obj-c版本*

func combVideos() {
        let firstVideo = NSBundle.mainBundle().pathForResource("1", ofType: "mp4")
        let secondVideo = NSBundle.mainBundle().pathForResource("2", ofType: "mp4")

        let optDict = [AVURLAssetPreferPreciseDurationAndTimingKey : NSNumber(bool: false)]
        let firstAsset = AVURLAsset(URL: NSURL(fileURLWithPath: firstVideo!), options: optDict)
        let secondAsset = AVURLAsset(URL: NSURL(fileURLWithPath: secondVideo!), options: optDict)
        
        let composition = AVMutableComposition()
        do {
            let compositionTrack = composition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)
            let firstTimeRange = CMTimeRange(start: kCMTimeZero, duration: firstAsset.duration)
            let secondTimeRange = CMTimeRange(start: kCMTimeZero, duration: secondAsset.duration)
            
            // 添加視頻
            try compositionTrack.insertTimeRange(secondTimeRange, ofTrack: secondAsset.tracksWithMediaType(AVMediaTypeVideo).first!, atTime: kCMTimeZero)
            try compositionTrack.insertTimeRange(firstTimeRange, ofTrack: firstAsset.tracksWithMediaType(AVMediaTypeVideo).first!, atTime: kCMTimeZero)
            
            // 添加聲音
            let audioTrack = composition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)
            try audioTrack.insertTimeRange(secondTimeRange, ofTrack: secondAsset.tracksWithMediaType(AVMediaTypeAudio).first!, atTime: kCMTimeZero)
            try audioTrack.insertTimeRange(firstTimeRange, ofTrack: firstAsset.tracksWithMediaType(AVMediaTypeAudio).first!, atTime: kCMTimeZero)
            
            let cache = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).last
            let filePath = cache! + "/comp_sw.mp4"
            
            let exporterSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
            exporterSession?.outputFileType = AVFileTypeMPEG4
            exporterSession?.outputURL = NSURL(fileURLWithPath: filePath)
            exporterSession?.shouldOptimizeForNetworkUse = true
            exporterSession?.exportAsynchronouslyWithCompletionHandler({ () -> Void in
                switch exporterSession!.status {
                case .Unknown:
                    print("unknow")
                case .Cancelled:
                    print("cancelled")
                case .Failed:
                    print("failed")
                case .Waiting:
                    print("waiting")
                case .Exporting:
                    print("exporting")
                case .Completed:
                    print("completed")
                }
            })
        } catch {
            print("\(error)")
        }
    }

0x10 效果 (時間變化)

1.png
2.png
合并后.png

歡迎大家交流指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末察净,一起剝皮案震驚了整個濱河市驾茴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌氢卡,老刑警劉巖锈至,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異译秦,居然都是意外死亡峡捡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門筑悴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來们拙,“玉大人,你說我怎么就攤上這事阁吝⊙馄牛” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵突勇,是天一觀的道長装盯。 經(jīng)常有香客問我,道長甲馋,這世上最難降的妖魔是什么埂奈? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮定躏,結(jié)果婚禮上账磺,老公的妹妹穿的比我還像新娘。我一直安慰自己共屈,他們只是感情好绑谣,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拗引,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幌衣。 梳的紋絲不亂的頭發(fā)上矾削,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音豁护,去河邊找鬼哼凯。 笑死,一個胖子當(dāng)著我的面吹牛楚里,可吹牛的內(nèi)容都是我干的断部。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼班缎,長吁一口氣:“原來是場噩夢啊……” “哼蝴光!你這毒婦竟也來了她渴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蔑祟,失蹤者是張志新(化名)和其女友劉穎趁耗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疆虚,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡苛败,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了径簿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罢屈。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖篇亭,靈堂內(nèi)的尸體忽然破棺而出儡遮,到底是詐尸還是另有隱情,我是刑警寧澤暗赶,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布鄙币,位于F島的核電站,受9級特大地震影響蹂随,放射性物質(zhì)發(fā)生泄漏十嘿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一岳锁、第九天 我趴在偏房一處隱蔽的房頂上張望绩衷。 院中可真熱鬧,春花似錦激率、人聲如沸咳燕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽招盲。三九已至,卻和暖如春嘉冒,著一層夾襖步出監(jiān)牢的瞬間曹货,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工讳推, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留顶籽,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓银觅,卻偏偏與公主長得像礼饱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫镊绪、插件匀伏、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,022評論 4 62
  • 手陽明大腸經(jīng)左右共40個穴位,昨天就全部說完了镰吆。 手陽明大腸經(jīng)的主治 一帘撰、經(jīng)絡(luò)癥:齒痛頸腫,口干喉痹万皿,鼻塞流涕摧找,鼻...
    錦似半夏閱讀 3,356評論 3 20
  • by楓木溪水 留些黑夜給白天, 白天說他沒這么陰險牢硅。 留...
    達(dá)瓦楓溪閱讀 196評論 0 1
  • 告別的時刻蹬耘,總是有點心酸。一段經(jīng)歷减余,一個故事综苔,一個人,沒有區(qū)別位岔。 我將告別兩年的周末時光如筛,這兩年來我花了500個小...
    茉莉大大閱讀 187評論 0 0
  • 題目鏈接 Search a 2D Matrix Write an efficient algorithm that...
    哈比豬閱讀 120評論 0 0