Monophonic and bad quality playback with Bluetooth headphones on iOS

When I’m attaching my bluetooth headphones (Sony MDR-1000x, but tested with others, too), to either my iPhone 8+ running iOS 12.0.1 or my iPad (2018, 9.7in) iOS 11.3.1, I only get a monophonic connection if I run my JUCE app.
To make it reproducable, I tested this with JUCE’s AudioPlaybackDemo.

Here’s my log for:
a) iPad 2018 9.7in, iOS 11.3.1

JUCE v5.4.1
Creating iOS audio device
Updating hardware info
Lowest supported sample rate: 8000
Highest supported sample rate: 16000
Trying a sample rate of 9000, got 16000
Available sample rates: 8000 16000
Sample rate after detecting available sample rates: 16000
Available buffer sizes: 64 128 256 512 1024 2048 4096
Buffer size after detecting available buffer sizes: 256
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices:}
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices:}
Opening audio device: inputChannelsWanted: 11, outputChannelsWanted: 11, targetSampleRate: 16000, targetBufferSize: 256
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices: 0}
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices: 0}
handleRouteChange: Category change
Setting target sample rate: 16000
Actual sample rate: 16000
Setting target buffer size: 256
Actual buffer size: 256
Creating the audio unit
handleRouteChange: Category change
Internal buffer size: 4096
handleRouteChange: Category change
handleRouteChange: Category change
handleRouteChange: Category change

Here I can see, that JUCE thinks, that only one hardware channel seems to be available and no higher Sample rate is found than 16000 Hz.

b) iPhone 8+, iOS 12.0.1

iOS 12.0.1 iPhone 8+
JUCE v5.4.1
2018-12-06 17:49:34.016793+0100 AudioPlaybackDemo[611:110775] +[CATransaction synchronize] called within transaction
Creating iOS audio device
Updating hardware info
Lowest supported sample rate: 8000
Highest supported sample rate: 16000
Trying a sample rate of 9000, got 16000
2018-12-06 17:49:34.091988+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothA2DPOutput)
2018-12-06 17:49:34.092264+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:49:34.092355+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
Available sample rates: 8000 16000
Sample rate after detecting available sample rates: 16000
Available buffer sizes: 64 128 256 512 1024 2048 4096
Buffer size after detecting available buffer sizes: 256
2018-12-06 17:49:34.100768+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:49:34.100811+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices:}
2018-12-06 17:49:34.102859+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:49:34.102889+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices:}
Opening audio device: inputChannelsWanted: 11, outputChannelsWanted: 11, targetSampleRate: 16000, targetBufferSize: 256
2018-12-06 17:49:51.556093+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:49:51.556503+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
handleRouteChange: Category change
2018-12-06 17:49:51.557572+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothA2DPOutput)
2018-12-06 17:49:51.557763+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:49:51.557987+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices: 0}
2018-12-06 17:49:51.561552+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:49:51.561732+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices: 0}
2018-12-06 17:50:04.703434+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothA2DPOutput)
handleRouteChange: Category change
2018-12-06 17:50:04.704149+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:50:04.704430+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:50:04.835810+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:50:04.836097+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
handleRouteChange: Category change
2018-12-06 17:50:04.836429+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port Empfänger (type: Receiver)
2018-12-06 17:50:04.836836+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port Empfänger (type: Receiver)
handleRouteChange: Category change
2018-12-06 17:50:04.836948+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:50:04.837010+0100 AudioPlaybackDemo[611:110827] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
Setting target sample rate: 16000
Actual sample rate: 16000
Setting target buffer size: 256
Actual buffer size: 256
2018-12-06 17:50:04.850994+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
2018-12-06 17:50:04.851039+0100 AudioPlaybackDemo[611:110775] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port MDR-1000X (type: BluetoothHFP)
Creating the audio unit
Internal buffer size: 4096
2018-12-06 17:50:31.627967+0100 AudioPlaybackDemo[611:110775] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2018-12-06 17:50:31.628153+0100 AudioPlaybackDemo[611:110775] [MC] Reading from public effective user settings.
2018-12-06 17:50:32.066394+0100 AudioPlaybackDemo[611:110775] Unbalanced calls to begin/end appearance transitions for <JuceUIViewController: 0x108b025f0>.

In iOS 12 there are these errors in AVAudioSessionPortImpl.mm.
The right type: BluetoothA2DPOutput is recognised, but with only one channel and a poor sample rate of 16000 Hz, too.

ROLI’s Noise app doesn’t seem to have the problem.
@ed95 Maybe you have a hint for me for getting JUCE AudioPlaybackDemo working like Noise. Thank you for any help!

Can you try changing line 280 of juce_ios_Audio.cpp to:

if (category == AVAudioSessionCategoryPlayAndRecord)
    options |= (AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionAllowBluetoothA2DP);

and see if you get a higher sample rate.

How are you testing this? I can’t see any options in the app to display or change the current sample rate.

Hi Ed, sorry for the delay. I could test it today with the change:
iPad 2018 9.7in, iOS 11.3.1 (i couldn’t test it with 12 because I accidentally updated to 12.1.1 and can’t compile at the moment)

JUCE v5.4.1
Creating iOS audio device
Updating hardware info
Lowest supported sample rate: 8000
Highest supported sample rate: 16000
Trying a sample rate of 9000, got 16000
Available sample rates: 8000 16000
Sample rate after detecting available sample rates: 16000
Available buffer sizes: 64 128 256 512 1024 2048 4096
Buffer size after detecting available buffer sizes: 256
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices:}
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices:}
Opening audio device: inputChannelsWanted: 11, outputChannelsWanted: 11, targetSampleRate: 16000, targetBufferSize: 256
handleRouteChange: Category change
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices: 0}
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "MDR-1000X", Are channels available: yes, Active channel indices: 0}
handleRouteChange: Category change
Setting target sample rate: 16000
Actual sample rate: 16000
Setting target buffer size: 256
Actual buffer size: 256
handleRouteChange: Category change
Creating the audio unit
handleRouteChange: Category change
handleRouteChange: Category change
Internal buffer size: 4096
2018-12-13 09:29:16.335302+0100 AudioPlaybackDemo[1899:6221019] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2018-12-13 09:29:16.335950+0100 AudioPlaybackDemo[1899:6221019] [MC] Reading from public effective user settings.
2018-12-13 09:29:16.343059+0100 AudioPlaybackDemo[1899:6221019] Unbalanced calls to begin/end appearance transitions for <_UIWaitingForRemoteViewContainerViewController: 0x109e27a00>.

In this case nothing is changing (as you see), I only get monophonic playback with 16kHz.

Getting back to ROLI’s Noise app:
Of course I can’t see the internal samplerate / buffersize used, but my ears tell me, that I get a high fidelity sound with my headphones, which is definitively not mono and >16kHz.

OK, I’ve made a couple of changes to the iOS audio device here -

This will ensure that the best sample rate will be chosen for the bluetooth headphones, but it depends on what input is selected as the OS ties you to a lower sample rate when you are using the BT headphones input (e.g. if they have a mic attached). You’ll need to either deselect the headphones input in the AudioDeviceSelectorComponent or change how the device manager is initialised in the AudioPlaybackDemo:

audioDeviceManager.initialise (0, 2, nullptr, true, {}, nullptr);

As this demo only outputs audio, you don’t need to request any input channels and then you will get the best sample rate available for your headphones and stereo output.

@ed95 Great, thank you. I’ll try it on Monday.

@ed95 It works much better now, thank you!

I’m running into the same issue. With bluetooth heaphones in iOS, AUv3 is stereo, but standalone is mono. I’ve pulled the recent juce changes from the dev branch, and am initializing the deviceManager with 0 inputs. Still mono. I get the following errors:

2019-02-04 17:04:53.324873-0500 Primer[486:34679] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port E7 (type: BluetoothHFP)

2019-02-04 17:04:53.326546-0500 Primer[486:34679] [avas] AVAudioSessionPortImpl.mm:56:ValidateRequiredFields: Unknown selected data source for Port E7 (type: BluetoothA2DPOutput)

Any suggestions?

I enabled ios audio logging (see below), and there are some things I’m not understanding.

  1. In projucer I have the mic disabled, yet here it shows it’s active

  2. In projucer the only channel configuration I have is {0,2}, and when initializing the deviceManager it request 0 inputs. Yet, here it shows two channel configurations: {1,1} and {1,2}

  3. The hardware info updates twice. The first time it shows a highest supported sample rate of 8000 and sets it there. The second time it shows a highest supported rate of 44100, yet still sets the target to 8000.

Here’s the log:

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: 8000
Available buffer sizes: 64 128 256 512 1024 2048 4096
Buffer size after detecting available buffer sizes: 256
Input channel configuration: {Number of hardware channels: 1, Hardware channel names: "E7", Are channels available: yes, Active channel indices:}
Output channel configuration: {Number of hardware channels: 1, Hardware channel names: "E7", Are channels available: yes, Active channel indices:}
Opening audio device: inputChannelsWanted: 0, outputChannelsWanted: 11, targetSampleRate: 8000, targetBufferSize: 256
Input channel configuration: {Number of hardware channels: 1, Hardware channel names:  "iPad Microphone", Are channels available: yes, Active channel indices:}
Output channel configuration: {Number of hardware channels: 2, Hardware channel names: "E7 Left" "E7 Right", Are channels available: yes, Active channel indices: 0 1}
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: 256
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
handleRouteChange: Category change
handleRouteChange: Category change
handleRouteChange: Category change

Any ideas?

Hi Hanely, I didn’t have further problems after Ed’s change. But I didn’t test it too much, yet.
In my setup I don’t use any channel setting in Projucer (I just left the field empty) and override instead AudioProcessor::isBusesLayoutSupported () Here I allow mono or stereo support:

bool MyAudioProcessor::isBusesLayoutSupported (const BusesLayout& layout) const {
    int numChannelsWanted = layout.getMainOutputChannels ();
    if (numChannelsWanted == 1
        || numChannelsWanted == 2)
        return true;
    
    return false;
}

Could you try that?

Are you sure the sample rate is actually 8000? What do you see if you put a breakpoint in the prepareToPlay() of your plug-in? I’ve just tried this with a blank audio plug-in template and I get the same output as you in the logs but, as you can see on the second update, the higher sample rate is chosen and I get 44.1kHz as expected:

Setting target sample rate: 8000
Actual sample rate: 44100
Setting target buffer size: 256
Actual buffer size: 256
Creating the audio unit

@ed95 You’re right. It’s 44.1K. The synth sounded strange but I think that’s due to the differences in the LR stereo signal being overlapped into mono.

@instrumentMakers Thanks for the suggestion. If I set Projucer channel configs blank, I get no sound and the isBusesLayoutSupported doesn’t get called.

In prepareToPlay I’ve confirmed that regardless of output device, there are 0 input channels, and 2 output channels. And I’ve confirmed that in the processBlock the buffer has two channels.

Weirder still, in the most recent version of my app, wired headphones are picking up the correct stereo signal, while BT heaphones, as I mentioned, are mono. However, after updating JUCE, even wired headphones are mono now. Which makes this all the more confusing.

Overall, this doesn’t appear to be a bus issue. But if it isn’t a bus issue, what else would force the stereo buffer into a single centered mono signal?

@Hanley @ed95 I can confirm, that after updating to JUCE 5.4.2 I always get a mono-signal in my standalone app, if I attach headphones - like Hanley.
@ed95 After going back to 5.4.1 with only swapping juce_ios_audio.cpp (your fix 81f162a43dd66f6759efa6951ab8a22538283874) it works again! Please look, which changes in 5.4.2 caused the damage.

Thank you both with your persistence with this!

This is serious enough to justify pushing 5.4.3 out over the next few days…

1 Like

Fixed! Latest pull gives me stereo for both wired and bluetooth headphones.

@t0m @ed95 Thanks for the quick fix on this.

1 Like

On iOS standalone I’m getting terrible audio quality and mono using WF-1000XM3 headphones. Is there anything I need to do to fix this? I’m on JUCE master branch as of 31st Jan 2021.

1 Like

Yeah, having same issue with WF-1000XM3 headphones in standalone app.