本篇將要用到的是AVMutableComposition ,這是一個(gè)將現(xiàn)有的媒體資源進(jìn)行合并剪切成一個(gè)新的視頻、音頻的類。它提供了以下功能:
- 添加/刪除音視頻軌道
- 選擇音視頻軌道的時(shí)間段
AVMutableComposition是繼承自AVComposition。簡(jiǎn)單的來說,AVComposition就是將來自多個(gè)基于源文件的媒體數(shù)據(jù)組合在一起顯示,或處理來自多個(gè)源媒體數(shù)據(jù)的東西。它是繼承自AVAsset也就是說這都是一家子的東西。如果想更多的了解AVMutableComposition和AVComposition的區(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)注。