You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVMutableComposition偶尔无声:Swift音频循环insertTimeRange异常求助

Swift音频循环偶尔出现insertTimeRange异常的排查与修复

问题重现

开发音频循环功能时,多数场景下insertTimeRange运行正常,但约每10次会出现一次异常:播放器显示的合成时长正确,但每次循环仅截取极短的可听片段,其余时段为静音,且无任何错误抛出。原实现代码如下:

private func createComposition(audioURL: URL, minutesToLoop: UInt) -> AVMutableComposition? {
    // Initiate new composition
    let composition = AVMutableComposition()

    // Add one audio track (channel) to the composition
    let compositionAudioTrack: AVMutableCompositionTrack? = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: 0)

    // Create AVAsset from audio file (mp3 -> React Native bundle string -> URL -> AVAsset)
    let asset = AVURLAsset(url: audioURL)

    // Extract (now compatible) audio track from AVAsset
    let track = asset.tracks(withMediaType: AVMediaType.audio)[0]

    // Create a time range from 0 - 60 seconds that we can use to cut that out from the track
    let timeRange = CMTimeRangeMake(start: .zero, duration: CMTimeMakeWithSeconds(60, preferredTimescale: 600))

    if (compositionAudioTrack != nil) {
        // Repeat for as many minutes as user specified in the app
        for _ in 0...(minutesToLoop - 1) {
            do {
                // Take first 60 seconds from the audio file (timeRange, of: track) and paste over and over again exactly at the end of the track (at: composition.duration)
                try compositionAudioTrack!.insertTimeRange(timeRange, of: track, at: composition.duration)
            } catch {
                print("FAILED TO MERGE AUDIO")
                return nil
            }
        }
    }
    return composition
}

核心原因分析

  1. AVAsset未完成异步加载:直接初始化AVURLAsset后就获取轨道,此时asset可能还未完成元数据加载,导致轨道的时间信息不准确,插入时出现隐性异常。
  2. 时间刻度不匹配:手动指定preferredTimescale: 600创建时间范围,与原音频轨道的naturalTimeScale不一致,导致时间范围解析出现偏差。
  3. 线程安全问题:如果该函数在非主线程执行,AVFoundation的部分操作可能因线程冲突出现无报错的异常行为。

修复方案与代码优化

关键修改点

  • 异步等待AVAsset加载完成,确保轨道元数据可用
  • 使用原轨道的时间刻度创建时间范围,避免刻度不兼容
  • 采用异步回调形式,适配AVAsset的异步加载特性

优化后代码

private func createComposition(audioURL: URL, minutesToLoop: UInt, completion: @escaping (AVMutableComposition?) -> Void) {
    let composition = AVMutableComposition()
    
    guard let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: 0) else {
        completion(nil)
        return
    }
    
    let asset = AVURLAsset(url: audioURL)
    // 异步等待asset的轨道数据加载完成
    asset.loadValuesAsynchronously(forKeys: ["tracks"]) {
        var loadError: NSError?
        let loadStatus = asset.statusOfValue(forKey: "tracks", error: &loadError)
        
        guard loadStatus == .loaded else {
            print("Asset加载失败: \(loadError?.localizedDescription ?? "未知错误")")
            completion(nil)
            return
        }
        
        guard let sourceTrack = asset.tracks(withMediaType: .audio).first else {
            print("未找到音频轨道")
            completion(nil)
            return
        }
        
        // 使用原轨道的时间刻度创建60秒时间范围,保证时间计算准确
        let sixtySeconds = CMTimeMakeWithSeconds(60, preferredTimescale: sourceTrack.naturalTimeScale)
        let loopTimeRange = CMTimeRange(start: .zero, duration: sixtySeconds)
        
        do {
            // 循环插入音频片段,改用半开区间避免边界错误
            for _ in 0..<minutesToLoop {
                try compositionAudioTrack.insertTimeRange(loopTimeRange, of: sourceTrack, at: composition.duration)
            }
            completion(composition)
        } catch {
            print("合并音频失败: \(error.localizedDescription)")
            completion(nil)
        }
    }
}

说明

  • 异步加载:通过loadValuesAsynchronously等待asset的tracks属性加载完成,确保获取的轨道数据是完整的。
  • 刻度匹配:使用sourceTrack.naturalTimeScale创建时间范围,保证与原音频的时间单位一致,避免时间转换误差。
  • 回调设计:将函数改为异步回调形式,因为AVAsset加载是异步操作,同步返回可能导致数据未就绪。

内容的提问来源于stack exchange,提问作者ejanson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 16:24:45