在iOS中可以通过AVFoundation进行音视频混合,如下代码通过AVFoundation实现了视频的剪辑、转场动画、音频的混合及渐升渐降功能:
func videoChangeWithAnimation() {
// 创建AVMutableComposition和tracks
let composition = AVMutableComposition()
// 在composition中添加两个视频轨道和两个音频轨道
composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
// 获取composition下的视频轨道
let videoTracks = composition.tracks(withMediaType: .video)
var cursorTime:CMTime = CMTime.zero
//转场动画时间
let transitionDuration = CMTime(value: 2, timescale: 1)
let videos = [AVURLAsset(url: videoUrl0!), AVURLAsset(url: videoUrl1!), AVURLAsset(url: videoUrl2!), AVURLAsset(url: videoUrl3!)]
// 设置截取每个视频的长度
let duration = CMTime(value: 5, timescale: 1)
for (index, value) in videos.enumerated() {
// 交叉循环A、B轨道
let trackIndex = index % 2
let currentTrack = videoTracks[trackIndex]
// 获取视频轨道
guard let assetTrack = value.tracks(withMediaType: .video).first else {
continue
}
do {
// 插入视频片段
try currentTrack.insertTimeRange(CMTimeRange(start: .zero, duration: duration), of: assetTrack, at: cursorTime)
//光标移动到视频末尾处,以便插入下一段视频
cursorTime = CMTimeAdd(cursorTime, duration)
//光标回退转场动画时长的距离,这一段前后视频重叠部分组合成转场动画
cursorTime = CMTimeSubtract(cursorTime, transitionDuration)
} catch {
fatalError("")
}
}
// 通过AVVideoComposition设置视频转场动画相关
let videoComposition = AVMutableVideoComposition.init(propertiesOf: composition)
// 获取composition下重叠的过度片段
let instructions = videoComposition.instructions as! [AVMutableVideoCompositionInstruction]
var a = 0 // 设置变量参数控制视频转场动画
for (index, instruct) in instructions.enumerated() {
guard instruct.layerInstructions.count > 1 else {
continue
}
//需要判断转场动画是从A轨道到B轨道,还是B-A
var fromLayer: AVMutableVideoCompositionLayerInstruction
var toLayer: AVMutableVideoCompositionLayerInstruction
//获取前一段画面的轨道id
let beforeTrackId = instructions[index - 1].layerInstructions[0].trackID;
//跟前一段画面同一轨道的为转场起点,另一轨道为终点
let tempTrackId = instruct.layerInstructions[0].trackID
// 按照顺序获取前后视频id
if beforeTrackId == tempTrackId {
fromLayer = instruct.layerInstructions[0] as! AVMutableVideoCompositionLayerInstruction
toLayer = instruct.layerInstructions[1] as! AVMutableVideoCompositionLayerInstruction
}else{
fromLayer = instruct.layerInstructions[1] as! AVMutableVideoCompositionLayerInstruction
toLayer = instruct.layerInstructions[0] as! AVMutableVideoCompositionLayerInstruction
}
let identityTransform = CGAffineTransform.identity
let timeRange = instruct.timeRange
let videoWidth = videoComposition.renderSize.width
let videoHeight = videoComposition.renderSize.height
// 推入动画
if a == 0 {
let formEndTransform = CGAffineTransform(translationX: -videoWidth, y: 0)
let toStartTransform = CGAffineTransform(translationX: videoWidth, y: 0)
fromLayer.setTransformRamp(fromStart: identityTransform, toEnd: formEndTransform, timeRange: timeRange)
toLayer.setTransformRamp(fromStart: toStartTransform, toEnd: identityTransform, timeRange: timeRange)
a += 1
} else if a == 1 {
// 溶解动画
fromLayer.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.0, timeRange: timeRange)
toLayer.setOpacityRamp(fromStartOpacity: 0.0, toEndOpacity: 1.0, timeRange: timeRange)
a += 1
} else if a == 2 {
// 擦除动画
let startRect = CGRectMake(0.0, 0.0, videoWidth, videoHeight)
let endRect = CGRectMake(videoWidth, videoHeight, videoWidth, videoHeight)
fromLayer.setCropRectangleRamp(fromStartCropRectangle: startRect, toEndCropRectangle: endRect, timeRange: timeRange)
a += 1
}
instruct.layerInstructions = [fromLayer, toLayer]
}
let audioTracks = composition.tracks(withMediaType: .audio)
let musicAsset = AVURLAsset(url: musicUrl0!)
let audioAsset = AVURLAsset(url: voiceUrl0!)
let musicTrack = audioTracks[0]
let audioTrack = audioTracks[1]
// 根据视频时长初始化整个音频的时长
let timeRange = CMTimeRangeMake(start: .zero, duration: composition.duration)
// 初始混合语音的时长
let audioRange = CMTimeRange(start: CMTime(value: 2, timescale: 1), duration: CMTime(value: 4, timescale: 1))
do {
// 插入背景音乐
try musicTrack.insertTimeRange(timeRange, of: musicAsset.tracks(withMediaType: .audio).first!, at: .zero)
} catch {
fatalError("插入音频失败")
}
do {
// 插入语音
try audioTrack.insertTimeRange(audioRange, of: audioAsset.tracks(withMediaType: .audio).first!, at: CMTime(value: 2, timescale: 1))
} catch {
fatalError("插入音频失败")
}
// 创建AVMutableAudioMix对象用来混合背景音乐和语音
let audioMix = AVMutableAudioMix()
// 分别设置两条音轨的变化,这里设置1~2秒背景音乐逐渐降低,3~5秒音频渐起渐落,5~最后背景音乐
// 背景音乐的设置
let parameters = AVMutableAudioMixInputParameters(track: musicTrack)
// 1~2S降低
parameters.setVolumeRamp(fromStartVolume: 1.0, toEndVolume: 0.0, timeRange: CMTimeRangeMake(start: .zero, duration: CMTimeMake(value: 2, timescale: 1)))
// 5S升高
parameters.setVolumeRamp(fromStartVolume: 0.0, toEndVolume: 1.0, timeRange: CMTimeRangeMake(start: CMTimeMake(value: 5, timescale: 1), duration: CMTimeMake(value: 5, timescale: 1)))
// 语音的设置
let audioParameters = AVMutableAudioMixInputParameters(track: audioTrack)
audioParameters.setVolumeRamp(fromStartVolume: 0.0, toEndVolume: 1.0, timeRange: CMTimeRangeMake(start: CMTime(value: 2, timescale: 1), duration: CMTimeMake(value: 1, timescale: 2)))
audioParameters.setVolumeRamp(fromStartVolume: 1.0, toEndVolume: 0.0, timeRange: CMTimeRangeMake(start: CMTime(value: 4, timescale: 1), duration: CMTimeMake(value: 1, timescale: 1)))
audioMix.inputParameters = [parameters, audioParameters]
// 导出
let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)
exportSession?.audioMix = audioMix
exportSession?.outputURL = self.exportURL()
exportSession?.outputFileType = AVFileType.mp4
exportSession?.videoComposition = videoComposition
exportSession?.exportAsynchronously {
DispatchQueue.main.async {
let status = exportSession?.status
if (status == .completed) {
self.playWithURL(url: exportSession!.outputURL!)
self.writeExportedVideoToAssetsLibrary(exportSession: exportSession!);
} else {
}
}
}
}
网友评论