macOS + AVAudioEngine + AVAudioUnitEQ + Bluetooth = Distorted Audio

Good day to everyone.
For the past 3 weeks, I've been trying to resolve issues I'm having with Equalizing a simple Audio Stream on macOS through Bluetooth.

The setup is basically this:
Player -> EQ Unit -> Mixer -> Bluetooth Audio Device
Whenever I use a wired audio device (HDMI, Internal Speaker, 3.5mm Jack etc.) everything works brilliantly.
If I try and use a Bluetooth device (tried many, cheap and super expensive headphones) at first glance everything works fine, but when you start to increase the gain on the Bands, the resulting Audio becomes very distorted.
On a wired connection with the same gain values, everything works great and the gain increases properly and sounds great.
At first, I thought it's something to do with Sampling Rate, but after playing around with it for a while I can it's definitely not the problem.

I have assembled a quick example in a Swift Playground that anyone can try out (you will need to add a track.mp3 file to the Playground resources):

import Cocoa
import AVFoundation

func getCurrentOutputDeviceId () -> AudioDeviceID {
    var addr = AudioObjectPropertyAddress(
        mSelector: kAudioHardwarePropertyDefaultOutputDevice,
        mScope: kAudioObjectPropertyScopeGlobal,
        mElement: kAudioObjectPropertyElementMaster)
    
    var id: AudioObjectID = kAudioDeviceUnknown
    var size = UInt32(MemoryLayout.size(ofValue: id))
    AudioObjectGetPropertyData(
        AudioObjectID(kAudioObjectSystemObject),
        &addr,
        0,
        nil,
        &size,
        &id
    )
    return id
}

let engine = AVAudioEngine()

// File
let path = Bundle.main.path(forResource: "track", ofType: "mp3")!
let url = URL(fileURLWithPath: path)
let file = try! AVAudioFile(forReading: url)
let fileFormat = file.processingFormat
let frameCount = UInt32(file.length)
let buffer = AVAudioPCMBuffer(pcmFormat: fileFormat, frameCapacity: frameCount)!
try! file.read(into: buffer, frameCount: frameCount)

let player = AVAudioPlayerNode()

let eq = AVAudioUnitEQ(numberOfBands: 3)
eq.globalGain = 0
eq.bypass = false

let gain = Float32(23.0)
let band1 = eq.bands[0]
band1.filterType = .parametric
band1.bandwidth = 0.5
band1.gain = gain
band1.frequency = 32.0
band1.bypass = false

let band2 = eq.bands[1]
band2.filterType = .parametric
band2.bandwidth = 0.5
band2.gain = gain
band2.frequency = 64.0
band2.bypass = false

let band3 = eq.bands[2]
band3.filterType = .parametric
band3.bandwidth = 0.5
band3.gain = gain
band3.frequency = 125.0
band3.bypass = false


var converter = AVAudioMixerNode()

// Why do I have to do this to force playback through an already selected device?
// I don't need to do this on Non Bluetooth Devices.
// Also sometimes works if you switch to internal Microphone rather than the Bluetooth Device Mic in System Preferences
try! engine.outputNode.auAudioUnit.setDeviceID(getCurrentOutputDeviceId())
let deviceFormat = engine.outputNode.outputFormat(forBus: 0)

engine.attach(player)
engine.attach(eq)
engine.attach(converter)
engine.connect(player, to: eq, format: fileFormat)
engine.connect(eq, to:converter, format: fileFormat)
engine.connect(converter, to: engine.mainMixerNode, format: deviceFormat)

engine.prepare()
try! engine.start()
print(engine)

player.play()
player.scheduleBuffer(buffer, at: AVAudioTime(hostTime: 0), options:.loops, completionHandler: nil)


Please try it on a Wired vs. Wireless device and you will instantly see the difference.
A couple of things I've noticed along my painful debugging process:

1. Look at lines 66-69, I only have to do this when using a Bluetooth device, otherwise, there is a microphone feedback loop for half a second and no Sound after that.

2. If I go to System Preferences -> Sound and switch to the Input Tab, whenever I have my Bluetooth headphones selected I can head a Microphone feedback loop (only when the same Bluetooth device Microphone Input is selected)
My setup:

macOS 10.14 (18A391)
Xcode 10.1 (10B61)

macOS SDK 10.14
I'm trying to rewrite my https://github.com/nodeful/eqMac2 app to Swift and AVAudioEngine, as AUGraph is being deprecated (I heard?).
This bug is holding me back big time, would love to get this resolved.
Kind Regards,
Roman

Replies

Filed a Bug report, here's the ID: 47812460

This is also affecting iTunes Equalizer, there's even no need to run the code.
Just try these settings over a wire and then over Bluetooth:

Hi, I'm running into the same problem. However, after doing a bit of research, I discovered it is not "distortion", it is simply the fact that macOS is choosing a low-quality audio codec for use when your Bluetooth device is connected to your AVAudioEngine.

A low quality codec is, for example, "SCO" while a high quality codec is "AptX".

There is a page with easy instructions on how to troubleshoot the issue and how to set codec options for Bluetooth. See if this helps. Please let me know if you're able to get it working, as I am also struggling with the same problem.

Just Google "How to Enable the Optimal Audio Codec for Your Bluetooth Headphones in macOS" and look for a page on the MacRumors site that explains it. This forum won't let me post a direct link.