如何使用 AVAudioEngine

问题描述

我正在构建一个应用程序,需要对其从麦克风接收的音频进行实时分析。在我的应用中,我也需要同时播放哔声并开始录制音频,换句话说,我无法播放哔声然后开始录音。这引入了在我的录音中听到哔声的问题,(这可能是因为我正在通过扬声器播放哔声,但不幸的是我在这方面也不能妥协)。由于哔声只是大约 2350 kHz 的音调,我想知道如何在我的录音中排除该频率范围(例如从 2300 kHz 到 2400 kHz)并防止它影响我的音频样本。在做了一些谷歌搜索之后,我想出了我认为可能的解决方案,带阻滤波器。根据维基百科:“带阻滤波器或带阻滤波器是一种滤波器,它可以原样通过大多数频率,但将特定范围内的频率衰减到非常低的水平”。这似乎是我需要在我的录音中排除 2300 kHz 到 2400 kHz 的频率(或至少在播放哔声时录音的第一秒)。我的问题是:我将如何使用 AVAudioEngine 实现这一点?有没有一种方法可以在录音的第一秒后在哔声播放完毕而不停止录音时关闭过滤器?

由于我是使用 AVAudioEngine 处理音频的新手(我一直只是坚持更高级别的 AVFoundation),因此我按照 this 教程帮助我创建了一个类来处理所有杂乱的东西。这是我的代码的样子:

class Recorder {
  enum RecordingState {
    case recording,paused,stopped
  }
  
  private var engine: AVAudioEngine!
  private var mixerNode: AVAudiomixerNode!
  private var state: RecordingState = .stopped
    
  private var audioPlayer = AVAudioPlayerNode()
  
  init() {
    setupSession()
    setupEngine()
  }
    
    
  fileprivate func setupSession() {
      let session = AVAudioSession.sharedInstance()
      //The original tutorial sets the category to .record
      //try? session.setCategory(.record)
      try? session.setCategory(.playAndRecord,options: [.mixWithOthers,.defaultToSpeaker])
      try? session.setActive(true,options: .notifyOthersOnDeactivation)
   }
    
    fileprivate func setupEngine() {
      engine = AVAudioEngine()
      mixerNode = AVAudiomixerNode()

      // Set volume to 0 to avoid audio Feedback while recording.
      mixerNode.volume = 0

      engine.attach(mixerNode)

    //Attach the audio player node
    engine.attach(audioPlayer)
        
      makeConnections()

      // Prepare the engine in advance,in order for the system to allocate the necessary resources.
      engine.prepare()
    }

    
    fileprivate func makeConnections() {
       
      let inputNode = engine.inputNode
      let inputFormat = inputNode.outputFormat(forBus: 0)
      engine.connect(inputNode,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 Connection
      let path = Bundle.main.path(forResource: "beep.mp3",ofType:nil)!
      let url = URL(fileURLWithPath: path)
      let file = try! AVAudioFile(forReading: url)
      engine.connect(audioPlayer,format: nil)
      audioPlayer.scheduleFile(file,at: 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]
        
      // AVAudioFile uses the Core Audio Format (CAF) to write to disk.
      // So we're using the caf file extension.
        let file = try AVAudioFile(forWriting: documentURL.appendingPathComponent("recording.caf"),settings: format.settings)
       
      tapNode.installTap(onBus: 0,bufferSize: 4096,format: format,block: {
        (buffer,time) in
        
        try? file.write(from: buffer)
        print(buffer.description)
        print(buffer.stride)
        let floatArray = Array(UnsafeBufferPointer(start: buffer.floatChannelData![0],count:Int(buffer.frameLength)))
        
      })

      try engine.start()
      audioPlayer.play()
      state = .recording
    }
    
    
    //MARK: Other recording functions
    func resumeRecording() throws {
      try engine.start()
      state = .recording
    }

    func pauseRecording() {
      engine.pause()
      state = .paused
    }

    func stopRecording() {
      // Remove existing taps on nodes
      mixerNode.removeTap(onBus: 0)
      
      engine.stop()
      state = .stopped
    }
    
    
}

解决方法

AVAudioUnitEQ 支持带阻滤波器。

也许是这样的:

// Create an instance of AVAudioUnitEQ and connect it to the engine's main mixer
let eq = AVAudioUnitEQ(numberOfBands: 1)
engine.attach(eq)
engine.connect(eq,to: engine.mainMixerNode,format: nil)
engine.connect(player,to: eq,format: nil)
eq.bands[0].frequency = 2350
eq.bands[0].filterType = .bandStop
eq.bands[0].bypass = false
,

一个稍微完整的答案,链接到一个 IBAction;在这个例子中,我使用 .parametric 作为过滤器类型,带比所需的更多,以更广泛地了解如何使用它:

@IBAction func PlayWithEQ(_ sender: Any) {
    self.engine.stop()
    self.engine = AVAudioEngine()
    let player = AVAudioPlayerNode()
    let url = Bundle.main.url(forResource:"yoursong",withExtension: "m4a")!
    let f = try! AVAudioFile(forReading: url)
    self.engine.attach(player)

    // adding eq effect node
    let effect = AVAudioUnitEQ(numberOfBands: 4)
    let bands = effect.bands
    let freq = [125,250,2350,8000]
    for i in 0...(bands.count - 1) {
        bands[i].frequency = Float(freq[i])
    }
    bands[0].gain = 0.0
    bands[0].filterType = .parametric
    bands[0].bandwidth = 1
    bands[1].gain = 0.0
    bands[1].filterType = .parametric
    bands[1].bandwidth = 0.5
    // filter of interest,rejecting 2350Hz (adjust bandwith as needed)
    bands[2].gain = -60.0
    bands[2].filterType = .parametric
    bands[2].bandwidth = 1
    bands[3].gain = 0.0
    bands[3].filterType = .parametric
    bands[3].bandwidth = 1

    self.engine.attach(effect)
    self.engine.connect(player,to: effect,format: f.processingFormat)
    let mixer = self.engine.mainMixerNode
    self.engine.connect(effect,to: mixer,format: f.processingFormat)
    player.scheduleFile(f,at: nil) {
        delay(0.05) {
            if self.engine.isRunning {
                self.engine.stop()
            }
        }
    }
    self.engine.prepare()
    try! self.engine.start()
    player.play()        
}