AVAudioPlayer循环播放FLAC音频出现点击噪音问题求助
问题
使用AVAudioPlayer循环播放FLAC格式音频时,每次循环启动都会产生点击噪音。该音频在Audacity中播放流畅,但在设备或模拟器中循环时,每次噪音强度不同。选择FLAC格式是因为它不会像MP3或M4A那样在文件首尾添加静音,且体积不如WAV庞大。
音频会话配置代码
// Audio let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(.playback, mode: .default, options: [.mixWithOthers]) try audioSession.setActive(true) } catch { print("Setting category session for AVAudioSession Failed") }
播放音频方法代码
// PlaySound func playSound(soundId: Int) { let sound = ModelData.shared.sounds[soundId] if let bundle = Bundle.main.path(forResource: sound.filename, ofType: "flac") { let backgroundMusic = NSURL(fileURLWithPath: bundle) do { audioPlayer = try AVAudioPlayer(contentsOf: backgroundMusic as URL) audioPlayer?.prepareToPlay() audioPlayer?.numberOfLoops = -1 // 无限循环 audioPlayer?.play() isPlayingSounds = true } catch { print(error) } } }
已尝试的方案:
- 使用AVQueuePlayer重复播放时噪音消失,但无法实现无限循环且内存占用过高;
- 使用AVLooper则循环间隔出现静音。
求可行的解决思路。
解决思路
1. 修复音频文件的衔接平滑度
点击噪音本质是音频首尾电平突变导致的,哪怕Audacity中听着流畅,AVAudioPlayer的循环衔接逻辑可能没做平滑处理。可以直接在Audacity中给音频首尾添加10ms以内的线性淡入淡出,或者手动将首尾采样点拉至0电平,从源头上消除电平突变的可能。
2. 改用AVAudioEngine实现精准无缝循环
AVAudioEngine是更底层的音频播放框架,能精准控制音频缓冲的循环播放,避免AVAudioPlayer的衔接缺陷。实现代码如下:
var audioEngine: AVAudioEngine? var playerNode: AVAudioPlayerNode? var audioBuffer: AVAudioPCMBuffer? func setupLoopedSound(soundId: Int) { let sound = ModelData.shared.sounds[soundId] guard let audioUrl = Bundle.main.url(forResource: sound.filename, withExtension: "flac") else { return } audioEngine = AVAudioEngine() playerNode = AVAudioPlayerNode() guard let engine = audioEngine, let node = playerNode else { return } engine.attach(node) do { let audioFile = try AVAudioFile(forReading: audioUrl) audioBuffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: AVAudioFrameCount(audioFile.length)) try audioFile.read(into: audioBuffer!) // 连接播放器节点到主混音器 let mainMixer = engine.mainMixerNode engine.connect(node, to: mainMixer, format: audioBuffer?.format) // 循环播放缓冲 node.scheduleBuffer(audioBuffer!, at: nil, options: .loops, completionHandler: nil) try engine.start() node.play() isPlayingSounds = true } catch { print("Audio engine setup failed: \(error)") } }
这种方式只会加载一次音频缓冲到内存,既实现无限循环,又能精准控制衔接点,消除点击噪音。
3. 调整AVAudioPlayer的循环衔接逻辑
如果不想换框架,可以监听AVAudioPlayerDelegate的audioPlayerDidFinishPlaying(_:successfully:)方法,用双播放器无缝切换的方式避免原生循环的缺陷:
// 声明两个播放器实例 var audioPlayer1: AVAudioPlayer? var audioPlayer2: AVAudioPlayer? var currentPlayerIndex = 0 func setupDualPlayers(soundId: Int) { let sound = ModelData.shared.sounds[soundId] guard let url = Bundle.main.url(forResource: sound.filename, withExtension: "flac") else { return } do { audioPlayer1 = try AVAudioPlayer(contentsOf: url) audioPlayer2 = try AVAudioPlayer(contentsOf: url) audioPlayer1?.delegate = self audioPlayer2?.delegate = self audioPlayer1?.prepareToPlay() audioPlayer2?.prepareToPlay() audioPlayer1?.play() isPlayingSounds = true } catch { print(error) } } // 实现代理方法切换播放器 func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { if flag { if currentPlayerIndex == 0 { audioPlayer2?.play() currentPlayerIndex = 1 } else { audioPlayer1?.play() currentPlayerIndex = 0 } } }
这种方式需要维护两个播放器实例,内存占用略高,但能避免原生循环的点击噪音。
4. 优化音频会话配置
尝试移除.mixWithOthers选项,或者将模式改为.moviePlayback,部分场景下音频会话的配置会影响播放的精度:
try audioSession.setCategory(.playback, mode: .moviePlayback, options: [])
内容的提问来源于stack exchange,提问作者Fran
相关产品推荐
相关产品推荐

