/// 视频添加背景音乐
/// - Parameters:
/// - videoURL: 视频文件URL
/// - audioURL: 音频文件URL
/// - startTime: 音频开始时间
/// - endTime: 音频结束时间
/// - isOriginal: 是否保留视频原音
/// - oriVolume: 视频原音音量
/// - newVolume: 新增背景音乐音量
/// - outputURL: 视频输出文件地址
/// - completion: 完成回掉
static func addBackgroundMusic(videoURL : URL, audioURL : URL, startTime : Double, endTime : Double, isOriginal : Bool = true, oriVolume : Float, newVolume : Float, outputURL : String, completion : @escaping (_ success : Bool) -> Void) {
if FileManager.default.fileExists(atPath: outputURL) {
do {
try FileManager.default.removeItem(atPath: outputURL)
} catch {
logger(item: "addBackgroundMusic remove exit file error")
}
}
do {
//导出路径
let outputFileURL = URL.init(fileURLWithPath: outputURL)
let nextClipStartTime = CMTime.zero
//创建可变的音频视频组合
let mixComposition = AVMutableComposition()
//视频采集
let videoAsset = AVURLAsset.init(url: videoURL)
let videoTimeRange = CMTimeRange.init(start: .zero, duration: videoAsset.duration)
let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
if compositionVideoTrack == nil {
completion(false)
return
}
try compositionVideoTrack!.insertTimeRange(videoTimeRange, of: videoAsset.tracks(withMediaType: .video).first!, at: nextClipStartTime)
let start = CMTime.init(seconds: startTime, preferredTimescale: videoAsset.duration.timescale)
let duration = CMTime.init(seconds: endTime - startTime, preferredTimescale: videoAsset.duration.timescale)
let audioTimeRange = CMTimeRange.init(start: start, duration: duration)
//创建最终混合的音频实例
let audioMix = AVMutableAudioMix()
//添加新的音频
let audioAsset = AVURLAsset.init(url: audioURL)
let newAudioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
if compositionVideoTrack == nil {
completion(false)
return
}
try newAudioTrack!.insertTimeRange(audioTimeRange, of: audioAsset.tracks(withMediaType: .audio).first!, at: start)
let newAudioInputParams = AVMutableAudioMixInputParameters.init(track: newAudioTrack!)
newAudioInputParams.setVolumeRamp(fromStartVolume: newVolume, toEndVolume: 0.0, timeRange: CMTimeRange.init(start: .zero, duration: videoAsset.duration))
newAudioInputParams.trackID = newAudioTrack!.trackID
//视频文件原始音频通道
if isOriginal {
//视频声音采集(也可不执行这段代码不采集视频音轨,合并后的视频文件将没有视频)
if videoAsset.tracks(withMediaType: .audio).first != nil {
if let originVoiceTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid) {
try originVoiceTrack.insertTimeRange(videoTimeRange, of:videoAsset.tracks(withMediaType: .audio).first!, at: .zero)
let originAudioInputParams = AVMutableAudioMixInputParameters.init(track: originVoiceTrack)
originAudioInputParams.setVolumeRamp(fromStartVolume: newVolume, toEndVolume: 0.0, timeRange: CMTimeRange.init(start: .zero, duration: videoAsset.duration))
originAudioInputParams.trackID = originVoiceTrack.trackID
audioMix.inputParameters = [newAudioInputParams,originAudioInputParams]
} else {
audioMix.inputParameters = [newAudioInputParams]
}
} else {
audioMix.inputParameters = [newAudioInputParams]
}
} else {
audioMix.inputParameters = [newAudioInputParams]
}
//创建一个输出
let assetExport = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
if assetExport == nil {
completion(false)
return
}
assetExport!.outputFileType = .mp4
assetExport!.outputURL = outputFileURL
assetExport!.shouldOptimizeForNetworkUse = true
assetExport!.audioMix = audioMix
assetExport!.exportAsynchronously {
switch assetExport!.status {
case .unknown:
break
case .waiting:
break
case .exporting:
break
case .completed:
completion(true)
break
case .failed:
//合成失败
completion(false)
break
case .cancelled:
completion(false)
break
@unknown default:
break
}
}
} catch {
completion(false)
}
}
网友评论