AVMutableComposition - 如何将多个音频记录与 1 个视频记录合并

AVMutableComposition -How to Merge Multiple Audio Recordings with 1 Video Recording

我有几个音频剪辑是我用 AVAudioRecorder 在视频上录制的。使用 AVMutableComposition 我想在录制音频时将音频资产与视频合并。例如,视频有 1 分钟长,我分别录制了 5-10 秒、20-25 秒和 30-35 秒的 3 个音频片段。音频剪辑应在这些特定时间范围内与视频合并。当最终视频播放时,音频将在这些时间帧内播放。

型号:

class AudioModel {

    var audioUrl: URL?
    var startTime: Double?
    var endTime: Double?
}

组合:

let mixComposition = AVMutableComposition()

guard let videoCompositionTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
guard let audioFromVideoCompositionTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
guard let audioModelCompositionTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }

let videoAsset = AVURLAsset(url: videoURL)
guard let videoTrack = videoAsset.tracks(withMediaType: .video).first else { return }

for audioModel in audioModels {

    let audioAsset = AVURLAsset(url: audioModel.url!)
    let startTime = CMTime(seconds: audioModel.startTime!, preferredTimescale: 1000)

    do {

        try videoCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration), of: videoTrack, at: .zero)
            
        if let audioTrackFromAudioModel = audioAsset.tracks(withMediaType: .audio).first {
                
            try audioModelCompositionTrack.insertTimeRange(CMTimeRangeMake(start: startTime, duration: audioAsset.duration),
                                                               of: audioTrackFromAudioModel, at: .zero)
        }
            
        if let audioFromVideoTrack = videoAsset.tracks(withMediaType: .audio).first {
            try audioFromVideoCompositionTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration),
                                                                   of: audioFromVideoTrack, at: .zero)
        }

    } catch {
    }
}

let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
// ... I know what to do from here

您的方法是正确的,但是您混淆了用于 insertTimeRange 的两个参数,并且多次从您的视频轨道添加视频和音频。

insertTimeRange中的第一个参数是指原始音频资产中的timeRange,而不是合成;因此假设对于您要添加整个剪辑的每个音频剪辑,时间范围应始终从 .zero 开始,而不是从 startTime 开始。 at: 参数不应为 .zero,而应为“startTime” - 合成中要添加音频的时间。

关于您的视频轨道和 audioFromVideoTrack,我不会将它们添加为循环的一部分,而只是在循环之前添加它们。否则,您将多次添加它们(每个音频项目一次),而不是一次,这可能会导致不必要的行为或导出会话完全失败。

我编辑了您的代码,但无法对其进行实际测试,所以对它持保留态度。

guard let videoCompositionTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
guard let audioFromVideoCompositionTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
guard let audioModelCompositionTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }

let videoAsset = AVURLAsset(url: videoURL)
guard let videoTrack = videoAsset.tracks(withMediaType: .video).first else { return }

do {
    try videoCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration), of: videoTrack, at: .zero)
    if let audioFromVideoTrack = videoAsset.tracks(withMediaType: .audio).first {
        try audioFromVideoCompositionTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: audioFromVideoTrack, at: .zero)
    }
} catch {
}

for audioModel in audioModels {
    let audioAsset = AVURLAsset(url: audioModel.url!)
    let startTime = CMTime(seconds: audioModel.startTime!, preferredTimescale: 1000)
    do {
        if let audioTrackFromAudioModel = audioAsset.tracks(withMediaType: .audio).first {
            try audioModelCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: audioAsset.duration), of: audioTrackFromAudioModel, at: startTime)
        }
    } catch {
    }
}

let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
// ... I know what to do from here