Open hollance opened 1 month ago
I have traced it to the following: When Bluetooth is active, iOS for some reason first attempts to set the sample rate to 8000 Hz (and use a mono bus). With JUCE_IOS_AUDIO_LOGGING enabled, it prints the following:
I'd be interested to see the stack trace that leads to setTargetSampleRateAndBufferSize
being called with a target sample rate of 8 kHz.
I've tested this a bit, both on the develop branch, and on the 8.0.3 release. I'm using a 9th gen iPad running iOS 17.7. So far, I haven't been able reproduce the behaviour you describe when connecting and disconnecting a bluetooth headset while audio is playing via the AudioPlaybackDemo in the DemoRunner.
Another issue in this area was reported after the 8.0.3 release here. My potential fix for that issue is to add the following at line 523 in juce_Audio_ios.cpp, in updateAvailableSampleRates()
before the call to AudioUnitAddPropertyListener:
sampleRate = trySampleRate (sampleRate);
bufferSize = getBufferSize (sampleRate);
The idea is to set the stored sampleRate and bufferSize back to whatever values they held before querying the available sample rates. This looks similar to the solution you're suggesting, but in a different location.
Please could you try making the change I outlined above and see whether it solves the problem for you?
Sure, I can try your solution tomorrow and set a breakpoint to capture the stack trace.
When I connect to Bluetooth (cheap AirPods imitation) while the app is running, the JUCE_IOS_AUDIO_LOGGING stuff prints the following:
handleRouteChange: New device available
Updating hardware info
Lowest supported sample rate: 44100
Highest supported sample rate: 44100
Available sample rates: 44100
Sample rate after detecting available sample rates: 44100
Available buffer sizes: 1024
Buffer size after detecting available buffer sizes: 1024
Setting target sample rate: 44100
Actual sample rate: 44100
Setting target buffer size: 256
Actual buffer size: 1024
Input channel configuration: {Number of hardware channels: 2, Hardware channel names: "Left" "Right", Are channels available: yes, Active channel indices:, Inactive channel indices: 0 1}
Output channel configuration: {Number of hardware channels: 2, Hardware channel names: "SL-001 Left" "SL-001 Right", Are channels available: yes, Active channel indices: 0 1, Inactive channel indices:}
Creating the audio unit
Internal buffer size: 4096
Note that here the sample rate is fine but there is a mismatch between the buffer sizes. This causes the sound to break up.
The stack trace for setTargetSampleRateAndBufferSize
in this scenario is as follows:
By the way, if I turn off the Bluetooth headset while the app is still running, the logging shows:
handleRouteChange: Old device unavailable
Updating hardware info
Lowest supported sample rate: 22050
Highest supported sample rate: 48000
Trying a sample rate of 23050, got 24000
Trying a sample rate of 25000, got 32000
Trying a sample rate of 33000, got 44100
Trying a sample rate of 45100, got 48000
Available sample rates: 22050 24000 32000 44100 48000
Sample rate after detecting available sample rates: 44100
Available buffer sizes: 59 118 236 472 944 1888 3763
Buffer size after detecting available buffer sizes: 235
Setting target sample rate: 8000
Actual sample rate: 22050
Setting target buffer size: 256
Actual buffer size: 256
Input channel configuration: {Number of hardware channels: 2, Hardware channel names: "Left" "Right", Are channels available: yes, Active channel indices:, Inactive channel indices: 0 1}
Output channel configuration: {Number of hardware channels: 2, Hardware channel names: "Headphones Left" "Headphones Right", Are channels available: yes, Active channel indices: 0 1, Inactive channel indices:}
Creating the audio unit
Internal buffer size: 4096
Note how it suddenly attempts to set the target sample rate to 8000 and chooses the closest, which is 22050, even though this device handles 44100 just fine.
The stacktrace for that is the same as above.
If I now connect the Bluetooth headset again, it might ask for 8000 Hz or for 44100 Hz. It all seems a bit arbitrary. I'm sure that's due to these headphones being a bit janky. ;-)
If I connect to Bluetooth before starting the app, the logging shows:
Creating iOS audio device
Updating hardware info
Lowest supported sample rate: 8000
Highest supported sample rate: 8000
Available sample rates: 8000
Sample rate after detecting available sample rates: 44100
Available buffer sizes: 44 88 176 352 704 1408 2816 2822
Buffer size after detecting available buffer sizes: 176
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "SL-001", Are channels available: yes, Active channel indices:, Inactive channel indices: 0}
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "SL-001", Are channels available: yes, Active channel indices:, Inactive channel indices: 0}
Opening audio device: inputChannelsWanted: 0, outputChannelsWanted: 11, targetSampleRate: 8000, targetBufferSize: 256
Input channel configuration: {Number of hardware channels: 2, Hardware channel names: "Left" "Right", Are channels available: yes, Active channel indices:, Inactive channel indices: 0 1}
Output channel configuration: {Number of hardware channels: 2, Hardware channel names: "SL-001 Left" "SL-001 Right", Are channels available: yes, Active channel indices: 0 1, Inactive channel indices:}
Updating hardware info
Lowest supported sample rate: 44100
Highest supported sample rate: 44100
Available sample rates: 44100
Sample rate after detecting available sample rates: 44100
Available buffer sizes: 64 128 256 512 1024
Buffer size after detecting available buffer sizes: 128
Setting target sample rate: 8000
Actual sample rate: 44100
Setting target buffer size: 256
Actual buffer size: 256
Creating the audio unit
Internal buffer size: 4096
Here the sample rate is the issue while the buffer size is OK. Note that it's weird that now it suddenly does support a 256 buffer while previously it only supported 1024.
The stack trace is:
The App/Window/PluginHolder are essentially copies of the JUCE standalone stuff and it goes wrong with the default standalone as well.
My potential fix for that issue is to add the following at line 523 in juce_Audio_ios.cpp, in updateAvailableSampleRates() before the call to AudioUnitAddPropertyListener:
Unfortunately your suggestion does not work in this case.
I've spent a bit more time investigating this issue, but haven't made much progress so far.
When I connect to Bluetooth while the app is running, the JUCE_IOS_AUDIO_LOGGING stuff prints the following... Note that here the sample rate is fine but there is a mismatch between the buffer sizes. This causes the sound to break up.
I'm not convinced that a buffer-size mismatch here would cause garbled audio. The targetBufferSize
is the buffer size that we would like to apply, while the bufferSize
is the size that was actually applied after last attempting to change the configuration. When the iOS audio device is restarted, it calls AudioIODeviceCallback::audioDeviceAboutToStart
, and interested listeners can call device->getCurrentSampleRate()
and device->getCurrentBufferSizeSamples()
to determine the current device configuration. These member functions return the sampleRate
and bufferSize
members respectively, which should reflect the real state of the audio device. That is, even if the target and actual values don't match, the audio processor will be initialised with the actual values picked by the device, so playback should work as expected.
The "Internal buffer size" value appears to be an upper limit on the size of any given buffer. If I stick a DBG in the actual process callback, the numFrames
argument seems to match the bufferSize
member.
Note how it suddenly attempts to set the target sample rate to 8000 and chooses the closest, which is 22050, even though this device handles 44100 just fine.
The target sample rate is only set when opening the device. It looks like the AudioDeviceManager will query the device for available sample rates, and then pick the best one of those rates before opening the device. From the output you posted, it looks like your device initially only reports a supported rate of 8kHz, so this is the value that ends up being set as the target sample rate.
The fix I mentioned earlier is now on the develop branch. I know you said that it didn't help for you, but maybe it's worth checking out the version on the develop branch just in case it differs in any way from the version you tested.
https://github.com/juce-framework/JUCE/commit/6f20de54349470aff16453052a82aa7e8e0aea26
Other than that, I'm starting to run out of ideas. The Apple docs suggest that it's best to call setPreferredIOBufferDuration
while the device is inactive, so maybe you could try commenting out the if (@available (ios 18, *))
checks on line 429 and 434 of juce_Audio_ios.cpp, so that we unconditionally call setAudioSessionActive(false)
and setAudioSessionActive(true)
before/after the setPreferredIOBufferDuration
call. We recently added the activate/deactivate because it seems to be necessary for iOS 18, but maybe it's needed in some situations on older iOS versions too.
Hi, just wanted to check whether you'd had a chance to test the latest changes on the develop branch.
Sorry, I have not had time to test this yet.
Detailed steps on how to reproduce the bug
I have an AUv3 that's wrapped inside a standalone app running on iPad. When the app is active and I switch to a Bluetooth speaker, the sound becomes all garbled. This also happens if Bluetooth is active before starting the app. Without Bluetooth, it sounds fine. Other audio apps (not written with JUCE) work fine over Bluetooth. The audio processing code in my AUv3 can handle different sample rates and buffer sizes without problems.
I have traced it to the following: When Bluetooth is active, iOS for some reason first attempts to set the sample rate to 8000 Hz (and use a mono bus). With
JUCE_IOS_AUDIO_LOGGING
enabled, it prints the following:It's possible to reproduce this behavior even without Bluetooth. In
juce_Audio_ios.cpp
, inopen()
, change the linetargetSampleRate = sampleRateWanted;
totargetSampleRate = 8000.0;
.After some debugging, I was able to fix the issue by doing the following in
juce_Audio_ios.cpp
insetTargetSampleRateAndBufferSize()
:trySampleRate
sets thepreferredSampleRate
on theAVAudioSession
, even if the audio hardware cannot handle that sample rate. My hypothesis is that at some point iOS may still attempt to use this preferred rate, while JUCE is using a different sample rate. My fix/workaround changes thepreferredSampleRate
to something that is actually supported. Same thing for the buffer size.What is the expected behaviour?
The audio plays over the Bluetooth speaker.
Operating systems
iOS
What versions of the operating systems?
Tried on iPadOS 17.6.1 on iPad Pro (10.5-inch). Several beta testers have mentioned this Bluetooth problem on their devices as well. JUCE 8.0.3.
Architectures
ARM, 64-bit
Stacktrace
Plug-in formats (if applicable)
AUv3
Plug-in host applications (DAWs) (if applicable)
Standalone app
Testing on the
develop
branchI have not tested against the
develop
branchCode of Conduct