Low Pass filter + sample rate conversion using Avaudioengine iOS
Asked Answered
D

2

10

We are working on a project which allows us to record some sounds from a microphone with a 5k Hz sample rate with some Low-Pass filter & HighPass filter.

What we are using

We are using AvaudioEngine for this purpose.

We are using AVAudioConverter for downgrading the sample rate.

We are using AVAudioUnitEQ for the LowPass & HighPass filter.

Code

let bus = 0
let inputNode = engine.inputNode

let equalizer = AVAudioUnitEQ(numberOfBands: 2)

equalizer.bands[0].filterType = .lowPass
equalizer.bands[0].frequency = 3000
equalizer.bands[0].bypass = false

equalizer.bands[1].filterType = .highPass
equalizer.bands[1].frequency = 1000
equalizer.bands[1].bypass = false
engine.attach(equalizer) //Attach equalizer

// Connect nodes
engine.connect(inputNode, to: equalizer, format: inputNode.inputFormat(forBus: 0))
engine.connect(equalizer, to: engine.mainMixerNode, format: inputNode.inputFormat(forBus: 0))
engine.connect(engine.mainMixerNode, to: engine.outputNode, format: inputNode.inputFormat(forBus: 0))

let outputFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
                                            sampleRate: 5000,
                                            channels: 1,
                                            interleaved: false)!

// Converter to downgrade sample rate
guard let converter: AVAudioConverter = AVAudioConverter(from: inputNode.inputFormat(forBus: 0), to: outputFormat) else {
           print("Can't convert in to this format")
           return
       }

engine.mainMixerNode.installTap(onBus: bus, bufferSize: 2688, format: engine.mainMixerNode.outputFormat(forBus: 0)) { (buffer, time) in
           
     var newBufferAvailable = true
           
     let inputCallback: AVAudioConverterInputBlock = { inNumPackets, outStatus in
           if newBufferAvailable {
                outStatus.pointee = .haveData
                newBufferAvailable = false
                return buffer
           } else {
                outStatus.pointee = .noDataNow
                return nil
           }
     }
           
           
     let convertedBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(outputFormat.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate))!

           var error: NSError?
           let status = converter.convert(to: convertedBuffer, error: &error, withInputFrom: inputCallback)
           assert(status != .error)

           
           if status == .haveData {
             // Process with converted buffer
           }
            
       }
       
       engine.prepare()
       
       do {
           try engine.start()
       } catch {
           print("Can't start the engine: \(error)")
       }

Issue

low-pass and high-pass filters are not working.

Alternate Approach

To check code is working or not, we have added a reverb effect instead of lowpass filter. Reverb effect(Using AVAudioUnitReverb) works with same code.

Can anyone help me where are we doing wrong in applying lowpass filter?

Dashiell answered 16/12, 2021 at 16:19 Comment(1)
@sbooth I have tried with that but the result is the same. No effects.Dashiell
C
5

I think the main problem with this code was that the AVAudioConverter was being created before calling engine.prepare() which can and will change the mainMixerNode output format. Aside from that, there was a redundant connection of mainMixerNode to outputNode, along with a probably incorrect format - mainMixerNode is documented to be automatically created and connected to the output node "on demand". The tap also did not need a format.

let bus = 0
let inputNode = engine.inputNode

let equalizer = AVAudioUnitEQ(numberOfBands: 2)

equalizer.bands[0].filterType = .lowPass
equalizer.bands[0].frequency = 3000
equalizer.bands[0].bypass = false

equalizer.bands[1].filterType = .highPass
equalizer.bands[1].frequency = 1000
equalizer.bands[1].bypass = false
engine.attach(equalizer) //Attach equalizer

// Connect nodes
engine.connect(inputNode, to: equalizer, format: inputNode.inputFormat(forBus: 0))
engine.connect(equalizer, to: engine.mainMixerNode, format: inputNode.inputFormat(forBus: 0))

// call before creating converter because this changes the mainMixer's output format
engine.prepare()

let outputFormat = AVAudioFormat(commonFormat: .pcmFormatInt16,
                                 sampleRate: 5000,
                                 channels: 1,
                                 interleaved: false)!

// Downsampling converter
guard let converter: AVAudioConverter = AVAudioConverter(from: engine.mainMixerNode.outputFormat(forBus: 0), to: outputFormat) else {
    print("Can't convert in to this format")
    return
}

engine.mainMixerNode.installTap(onBus: bus, bufferSize: 2688, format: nil) { (buffer, time) in
    var newBufferAvailable = true
    
    let inputCallback: AVAudioConverterInputBlock = { inNumPackets, outStatus in
        if newBufferAvailable {
            outStatus.pointee = .haveData
            newBufferAvailable = false
            return buffer
        } else {
            outStatus.pointee = .noDataNow
            return nil
        }
    }
    
    
    let convertedBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(outputFormat.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate))!
    
    var error: NSError?
    let status = converter.convert(to: convertedBuffer, error: &error, withInputFrom: inputCallback)
    assert(status != .error)
    
    
    if status == .haveData {
        // Process with converted buffer
    }
}

do {
    try engine.start()
} catch {
    print("Can't start the engine: \(error)")
}
Chilon answered 17/12, 2021 at 10:50 Comment(5)
Thank you it works, I never thought allocating objects will cause the issue. It saves my day. :) Also I have one question what will be the best minimum value for the low pass filter? When I check the frequency parameter, there is something written like (Samplerate / 2). Do you have any idea about this?Dashiell
I'm afraid I have no idea - sorry.Chilon
Okay thank you the help.Dashiell
When you downsample a signal, any frequencies in the signal that are above samplerate/2 Hz (where samplerate is the new, lower samplerate) will show up in your downsampled signal as low frequencies (this is called aliasing). This is why low-pass filtering is a necessary pre-processing step for downsampling. The highest value for the low-pass cutoff-frequency that avoids aliasing is therefore samplerate/2 (the Nyquist frequency). But given that filters are never perfect, your best bet is to choose it a bit lower than samplerate/2.Lonely
Hello @BhavinVaghela I am not able to apply low pass and high pass filter with avaudio engine. here is a question #76762943Floating
E
0
override func viewDidLoad() {
    super.viewDidLoad()
    lableItem.text = "Select Frequency"
    setUpDropdown()
    navigationItem.title = "High Pass Filter"
    
    do{
        try audioSession.setCategory(.playAndRecord, mode: .default, options: [.mixWithOthers, .defaultToSpeaker,.allowBluetoothA2DP,.allowAirPlay,.allowBluetooth])
        try audioSession.setActive(true)
    } catch{
        print(error.localizedDescription)
    }
    
    let bus = 0
    let inputNode = engine.inputNode
    
    let equalizer = AVAudioUnitEQ(numberOfBands: 2)
    
    equalizer.bands[0].filterType = .highPass
    equalizer.bands[0].frequency = 20000.0
    equalizer.bands[0].bypass = false
    
    equalizer.bands[1].filterType = .lowPass
    equalizer.bands[1].frequency = 1000.0
    equalizer.bands[1].bypass = false
    engine.attach(equalizer) //Attach equalizer
    
    
    // Connect nodes
    engine.connect(inputNode, to: equalizer, format: inputNode.inputFormat(forBus: 0))
    engine.connect(equalizer, to: engine.mainMixerNode, format: inputNode.inputFormat(forBus: 0))
    
    // call before creating converter because this changes the mainMixer's output format
    engine.prepare()
    
    let outputFormat = AVAudioFormat(commonFormat: .pcmFormatInt32,
                                     sampleRate: 44100,
                                     channels: 1,
                                     interleaved: false)!
    // Downsampling converter
    guard let converter: AVAudioConverter = AVAudioConverter(from: engine.mainMixerNode.outputFormat(forBus: 0), to: outputFormat) else {
        print("Can't convert in to this format")
        return
    }
    
    engine.mainMixerNode.installTap(onBus: bus, bufferSize: 2688, format: nil) { (buffer, time) in
        var newBufferAvailable = true
        
        let inputCallback: AVAudioConverterInputBlock = { inNumPackets, outStatus in
            if newBufferAvailable {
                outStatus.pointee = .haveData
                newBufferAvailable = false
                
                return buffer
            } else {
                outStatus.pointee = .noDataNow
                return nil
            }
        }
        
        
        let convertedBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: AVAudioFrameCount(outputFormat.sampleRate) * buffer.frameLength / AVAudioFrameCount(buffer.format.sampleRate))!
        
        var error: NSError?
        let status = converter.convert(to: convertedBuffer, error: &error, withInputFrom: inputCallback)
        assert(status != .error)
        
        
        if status == .haveData {
            // Process with converted buffer
        }
    }
    
    do {
        try engine.start()
    } catch {
        print("Can't start the engine: \(error)")
    }
    
}
Eleaseeleatic answered 20/5, 2022 at 6:51 Comment(2)
1) I have added you code right now there is no effect when we take the input from the microphone. 2)My goal is I want the frequency about 20k suppress output and after that I want to apply high pass filter with 1000 lowPass filter 3 )When I run this code I am getting the error that when we put 25k there is no effectEleaseeleatic
I think you misinterpreted HighPass and low pass, you should interchange the value and it should work.Dashiell

© 2022 - 2024 — McMap. All rights reserved.