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