如何基于AVAudioEngine实现可动态开关的带阻滤波器滤除录音蜂声
实现方案
首先修正参数笔误:日常提示蜂声不可能为2350kHz(千赫兹,2.35MHz属于超声波范畴),结合阻带范围描述,实际应为23002400Hz(2.32.4kHz),以下实现均按Hz参数处理。
核心原理
- 使用AVAudioEngine内置的
AVAudioUnitEQ实现带阻(陷波)滤波,无需自行编写DSP代码 - 滤波仅作用于麦克风输入链路,不影响蜂声播放输出
- 可实时调整滤波参数,完全无需中断录制即可开关滤波
节点链改造
原节点链路:
inputNode(麦克风) → 自定义mixerNode → mainMixerNode → 扬声器 audioPlayer(蜂声) → mainMixerNode → 扬声器
改造后链路(仅麦输入增加滤波环节):
inputNode(麦克风) → EQ滤波单元 → 自定义mixerNode → mainMixerNode → 扬声器 audioPlayer(蜂声) → mainMixerNode → 扬声器
参数配置说明
AVAudioUnitEQ仅需1个滤波频段即可实现需求,参数配置如下:
- 滤波类型:
.bandRes(带阻/陷波) - 中心频率:2350Hz
- 带宽:约0.06倍频程(对应2300~2400Hz的-3dB衰减范围,可根据实际效果微调)
- 衰减增益:-96dB(完全衰减阻带信号)
- 初始状态:启用滤波
实时开关滤波
直接修改EQ频段的bypass属性即可,无需断开连接、停止引擎,实时生效无中断:
- 启用滤波:
eqNode.bands.first?.bypass = false - 关闭滤波:
eqNode.bands.first?.bypass = true
改造后代码
class Recorder { enum RecordingState { case recording, paused, stopped } private var engine: AVAudioEngine! private var mixerNode: AVAudioMixerNode! private var eqNode: AVAudioUnitEQ! // 新增:滤波单元 private var state: RecordingState = .stopped private var currentRecordingFile: AVAudioFile? // 新增:持有录制文件引用 private var audioPlayer = AVAudioPlayerNode() private var beepFile: AVAudioFile? // 新增:预加载蜂声文件 init() { setupSession() setupEngine() preloadResources() } fileprivate func setupSession() { let session = AVAudioSession.sharedInstance() try? session.setCategory(.playAndRecord, options: [.mixWithOthers, .defaultToSpeaker]) try? session.setActive(true, options: .notifyOthersOnDeactivation) } fileprivate func setupEngine() { engine = AVAudioEngine() mixerNode = AVAudioMixerNode() mixerNode.volume = 0 // 避免麦输入回声输出 // 初始化带阻滤波单元 eqNode = AVAudioUnitEQ(numberOfBands: 1) guard let band = eqNode.bands.first else { return } band.filterType = .bandRes band.frequency = 2350 // 蜂声中心频率 band.bandwidth = 0.06 // 对应2300-2400Hz阻带,可微调 band.gain = -96 // 最大衰减 band.bypass = false // 初始启用滤波 // 挂载所有节点 engine.attach(mixerNode) engine.attach(eqNode) engine.attach(audioPlayer) makeConnections() engine.prepare() } fileprivate func preloadResources() { // 预加载蜂声文件,避免播放延迟 guard let path = Bundle.main.path(forResource: "beep.mp3", ofType: nil) else { return } let url = URL(fileURLWithPath: path) beepFile = try? AVAudioFile(forReading: url) } fileprivate func makeConnections() { let inputNode = engine.inputNode let inputFormat = inputNode.outputFormat(forBus: 0) // 改造麦输入链路:input → EQ → 自定义mixer engine.connect(inputNode, to: eqNode, format: inputFormat) engine.connect(eqNode, to: mixerNode, format: inputFormat) let mainMixerNode = engine.mainMixerNode let mixerFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: inputFormat.sampleRate, channels: 1, interleaved: false) engine.connect(mixerNode, to: mainMixerNode, format: mixerFormat) // 蜂声链路:audioPlayer → mainMixer,不受滤波影响 engine.connect(audioPlayer, to: mainMixerNode, format: nil) } //MARK: Start Recording Function func startRecording() throws { print("Start Recording!") let tapNode: AVAudioNode = mixerNode let format = tapNode.outputFormat(forBus: 0) let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] currentRecordingFile = try AVAudioFile(forWriting: documentURL.appendingPathComponent("recording.caf"), settings: format.settings) tapNode.installTap(onBus: 0, bufferSize: 4096, format: format, block: { [weak self] (buffer, time) in guard let file = self?.currentRecordingFile else { return } try? file.write(from: buffer) }) try engine.start() // 每次录制重新调度蜂声,确保正常播放 if let beepFile = beepFile { audioPlayer.scheduleFile(beepFile, at: nil) } audioPlayer.play() // 1秒后自动关闭滤波,无需中断录制 DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in self?.eqNode.bands.first?.bypass = true } state = .recording } //MARK: Other recording functions func resumeRecording() throws { try engine.start() state = .recording } func pauseRecording() { engine.pause() state = .paused } func stopRecording() { mixerNode.removeTap(onBus: 0) audioPlayer.stop() engine.stop() // 重置滤波状态,便于下次录制 eqNode.bands.first?.bypass = false currentRecordingFile = nil state = .stopped } }
注意事项
- 若滤波阻带范围不符合预期,可微调
bandwidth参数(单位为倍频程),越大阻带范围越宽 - 若存在蜂声残留,可适当降低
gain值(最低为-96dB)或微调中心频率至实际蜂声频率 - 代码中已修正原实现的几个问题:蜂声文件预加载、每次录制重新调度蜂声、持有录制文件引用避免写入失败
内容的提问来源于stack exchange,提问作者coder
相关产品推荐
相关产品推荐

