美文网首页
AVFoundation编辑音视频

AVFoundation编辑音视频

作者: 浪淘沙008 | 来源:发表于2023-04-23 09:51 被阅读0次

在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 {
                    
                }
            }
        }
        
    }

代码地址

相关文章

网友评论

      本文标题:AVFoundation编辑音视频

      本文链接:https://www.haomeiwen.com/subject/yhgejdtx.html