AVFoundation 視頻剪切合成

本篇將要用到的是AVMutableComposition ,這是一個(gè)將現(xiàn)有的媒體資源進(jìn)行合并剪切成一個(gè)新的視頻、音頻的類。它提供了以下功能:

  • 添加/刪除音視頻軌道
  • 選擇音視頻軌道的時(shí)間段

AVMutableComposition是繼承自AVComposition。簡(jiǎn)單的來說,AVComposition就是將來自多個(gè)基于源文件的媒體數(shù)據(jù)組合在一起顯示,或處理來自多個(gè)源媒體數(shù)據(jù)的東西。它是繼承自AVAsset也就是說這都是一家子的東西。如果想更多的了解AVMutableCompositionAVComposition的區(qū)別的話,建議看一下這篇文章:《OC之合成資源AVComposition》
話不多說,我們先來看看,這個(gè)東西應(yīng)該怎么應(yīng)用起來吧。
這里大家可以先理解它其實(shí)就是一個(gè)容器,需要我們放入各種各樣的音視頻媒體數(shù)據(jù),去訂制出我們所希望得到的東西,當(dāng)然正式導(dǎo)出生成視頻的是另一個(gè)東西:AVAssetExportSession
首先我們導(dǎo)入了需要進(jìn)行合并的媒體資源文件:

       path = Bundle.main.path(forResource: "IMG_2735", ofType: "mp4")
       let url = URL.init(fileURLWithPath: path)
       let options = [AVURLAssetAllowsCellularAccessKey:false,AVURLAssetPreferPreciseDurationAndTimingKey:true]
       firstAsset = AVURLAsset.init(url: url, options: options)
       
       let voicePath = Bundle.main.path(forResource: "SuperMarioBros", ofType: "mp3")
       let url2 = URL.init(fileURLWithPath: voicePath!)
       secAsset = AVURLAsset.init(url: url2)
       
       let voicePath2 = Bundle.main.path(forResource: "MaiDou", ofType: "mp3")
       let url3 = URL.init(fileURLWithPath: voicePath2!)
       let thireAsset = AVURLAsset.init(url: url3)
       
       let videoAssetTrack = firstAsset.tracks(withMediaType: .video).first
       let audioAssetTrack = secAsset.tracks(withMediaType: .audio).first
       let audioAssetTrack2 = thireAsset.tracks(withMediaType: .audio).first

然后我們?cè)賮沓跏蓟幌氯萜?code>AVMutableComposition,并創(chuàng)建對(duì)應(yīng)要放內(nèi)容的軌道

//合成器
composotion = AVMutableComposition.init()
  //視頻軌道
let videoCompostiontrack = composotion.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
        //音頻軌道1
let audioCompostiontrack = composotion.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
        //音軌2
let audioCompositiontrack2 = composotion.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)

這里導(dǎo)入了一個(gè)視頻文件內(nèi)容還有,兩個(gè)音頻軌道的內(nèi)容
這里需要說明一下kCMPersistentTrackID_Invalid這個(gè)ID是用來指定當(dāng)前所有的資源信息,放到一個(gè)Composition里,是準(zhǔn)備合成一個(gè)的標(biāo)記。

再把這上面的三個(gè)資源都放入里面,在放進(jìn)去的時(shí)候,需要選擇每一個(gè)資源的時(shí)間起止,通過這樣來進(jìn)行準(zhǔn)確的混合:

//為視頻軌道放入視頻
let v = 0.8 * Float(firstAsset.duration.seconds) * videoAssetTrack!.nominalFrameRate
let e = 0.1 * Float(firstAsset.duration.seconds) * videoAssetTrack!.nominalFrameRate
let v_start = CMTime.init(value: CMTimeValue(v), timescale: CMTimeScale(videoAssetTrack!.nominalFrameRate))
let v_endTime = CMTime.init(value: CMTimeValue(e), timescale: CMTimeScale(videoAssetTrack!.nominalFrameRate))
print(v_start.seconds)
print(v_endTime.seconds)
let theTime = CMTimeRangeMake(start: v_start, duration: v_endTime)
        
try?videoCompostiontrack?.insertTimeRange(theTime, of: videoAssetTrack!, at: CMTime.zero)
//為音頻軌道放入音頻
let theTimeAudio = CMTimeRangeMake(start: CMTime.zero, duration: CMTime.init(value: CMTimeValue(e*0.65), timescale: CMTimeScale(videoAssetTrack!.nominalFrameRate)))
try?audioCompostiontrack?.insertTimeRange(theTimeAudio, of: audioAssetTrack!, at: CMTime.zero)
//為音頻軌道放入音頻2
let theTimeAudio2 = CMTimeRangeMake(start: CMTime.zero, duration: (audioAssetTrack2?.timeRange.duration)!)
        
let st2 = CMTime.init(value: CMTimeValue(e*0.65), timescale: CMTimeScale(videoAssetTrack!.nominalFrameRate))
print(st2.seconds)
try?audioCompositiontrack2?.insertTimeRange(theTimeAudio2, of: audioAssetTrack2!, at: st2)

這里需要提醒一下的是,由于每個(gè)資源文件的時(shí)間幀間隔都有可能存在差異性,所以當(dāng)為視頻軌道配上音頻軌道的時(shí)候,最好以視頻軌道的時(shí)間幀間隔作為基準(zhǔn)來設(shè)置截取的時(shí)間段,不然就有可能出現(xiàn)音視頻的同步和想象中的不一樣的問題。
同樣的音視頻的裁剪截取、視頻幀獲取等等,都離不開關(guān)鍵的CMTime,還不了解這個(gè)的,需要在進(jìn)一步的學(xué)習(xí)高浩浩浩浩浩浩

最后,需要把這個(gè)容器內(nèi)的音視頻軌道資源,導(dǎo)出成一個(gè)新的文件,這時(shí)候就需要用到導(dǎo)出類AVAssetExportSession


        let exporter = AVAssetExportSession.init(asset: composotion, presetName: AVAssetExportPresetHighestQuality)
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
        let filePath = path!+"/test.mp4"
        try?FileManager.default.removeItem(atPath: filePath)
        
        exporter?.outputURL = URL.init(fileURLWithPath: filePath)
        
        exporter?.outputFileType = .mp4
        exporter?.shouldOptimizeForNetworkUse = true
        exporter?.exportAsynchronously(completionHandler: {
            if exporter?.error != nil{
                print(exporter?.error)
            }else{
            
            }
            DispatchQueue.main.async {
                self.playIt(url: URL.init(fileURLWithPath: filePath))
            }
        })

為了驗(yàn)證我們最后是不是成功導(dǎo)出了該視頻,我決定在導(dǎo)出成功之后,使用AVPlayer來播放一下:

    func playIt(url:URL) {
        let item = AVPlayerItem.init(url: url)
        let player = AVPlayer.init(playerItem: item)
        let layer = AVPlayerLayer.init(player: player)
        layer.frame = playerImgv.frame
        layer.videoGravity = .resizeAspect
        playerImgv.layer.addSublayer(layer)
        player.play()
    }

后面或有更多AVFoundation的相關(guān)知識(shí)記錄在此,歡迎關(guān)注。

?著作權(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ù)。

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