Well, I’m round tripping around this component, but can’t get it to work as I’m expecting.
I’ve added a tab for this component (exactly like the juce’s demo).
I select a audio output (I have 2 possible)
Then, in another tab, I start a AudioSourcePlayer with a resampling source attached.
It plays for, well few sec, then it stops.
I’ve traced this and stop happens because the AudioDeviceSelectorComponent comboChanged callback is called (I haven’t, it’s called asynchronously), and this line is executed:
384 else if (comboBoxThatHasChanged == sampleRateDropDown)
385 {
386 if (sampleRateDropDown->getSelectedId() > 0)
387 {
388 config.sampleRate = sampleRateDropDown->getSelectedId();
389 error = setup.manager->setAudioDeviceSetup (config, true); ////////// HERE, it stops the device and doesn't restart it.
390 }
391 }
I’m a bit lost here.
Everything was working fine until I added this component (but obviously, writing the output device name in the source code wasn’t good)
Since it’s called async’ly I can’t figure out why it’s called at all. Even then, I don’t understand why setAudioDeviceSetup stop the device.
I can modify it with a global “isPlaying” flag, but it’s dirty, and I’m sure you have a better solution.
I expect the state (playing) is kept, or the source to be prepared again if the parameters changed, but it’s not.
I’ve used that component a lot, and never seen anything like that. Are you saying that the comboBoxChanged method is getting called without you using the combo box? It’s a synchronous callback so you should be able to see what’s triggering it. (Or maybe I’m misunderstanding what you mean).
Yes, the combobox is on a tab that’s not even visible by that time!
When I start the player, I can hear, let’s say, 1 sec of audio then it stops.
If I put a breakpoint on comboBoxChanged, and changeListenerCallback, I get the code which is triggered (automatically) this way:
Breakpoint 1, juce::AudioDeviceSettingsPanel::comboBoxChanged (this=0x2514a30, comboBoxThatHasChanged=0x2455ea0) at ../../src/gui/components/special/juce_AudioDeviceSelectorComponent.cpp:389
389 error = setup.manager->setAudioDeviceSetup (config, true);
(gdb) list
384 else if (comboBoxThatHasChanged == sampleRateDropDown)
385 {
386 if (sampleRateDropDown->getSelectedId() > 0)
387 {
388 config.sampleRate = sampleRateDropDown->getSelectedId();
389 error = setup.manager->setAudioDeviceSetup (config, true);
390 }
391 }
392 else if (comboBoxThatHasChanged == bufferSizeDropDown)
393 {
(gdb) c
Continuing.
Breakpoint 2, juce::AudioDeviceSelectorComponent::changeListenerCallback (this=0x25148a0) at ../../src/gui/components/special/juce_AudioDeviceSelectorComponent.cpp:1106
1106 if (deviceTypeDropDown != 0)
(gdb) n
1111 if (audioDeviceSettingsComp == 0
(gdb) n
1142 if (midiInputsList != 0)
(gdb) n
1148 if (midiOutputSelector != 0)
(gdb) n
1168 resized();
(gdb) c
Continuing.
Breakpoint 1, juce::AudioDeviceSettingsPanel::comboBoxChanged (this=0x2514a30, comboBoxThatHasChanged=0x2455ea0) at ../../src/gui/components/special/juce_AudioDeviceSelectorComponent.cpp:389
389 error = setup.manager->setAudioDeviceSetup (config, true);
(gdb) c
and so on. If I add a breakpoint on the ::stop method of the device, it’s called in setAudioDeviceSetup.
I’ve no pointer on the AudioDeviceSelectorComponent except for the one in the tab. The AudioDeviceManager refered is the one used in the player.
If I don’t instantiate the AudioDeviceSelectorComponent, it’s working with no issue.
I’m guessing this must be something to do with the fact that you’re using Linux, or I’d have seen this before.
…but what are you doing that’s different from the demo app? That also uses tabs, but the audio doesn’t stop for me. Can you suggest a hack that I can make to reproduce this?
There isn’t anything special here.
AudioDeviceManager start a AudioSourcePlayer where the source is a ResamplingAudioSource.
I’ve tried to set up the jucedemo to reproduce the bug but I aborted (it would take too much time).
Is there a way to add the source of an async message so I can find out who triggered the combobox changed callback ?
Not easy to catch the culprit that is triggering a change message, but in this case, the only thing that could be doing would be the AudioDeviceSelectorComponent::changeListenerCallback, which would be triggered by a change in the audio device. Maybe the ALSA device is firing a change message unnecessarily?
Ok, more evidences here:
In the message thread, I query the stream’s source sample rate, and open the device with such sample rate.
On success, I start the device and then start the decoding thread.
Then, I exit the loading function, and the message loop resume.
It seems like the open(device) step queue an async message to the DeviceSelectorComponent to tell it that the sampling rate changed.
However, between the time the device is started, and the message is received, the thread have started and start to play content.
When the message is received (later), the DeviceSelectorComponent try to re-change the sampling rate (likely, it doesn’t do anything, but this still stop the device).
Seems to me that the DeviceSelectorComponent should only call setAudioDeviceSetup() if the sampling rate actually changed (so the callback will still trigger, but it won’t cause the device manager to stop the callback).
What do you think ?
audiodevicemanger::setAudioDeviceSetup always checks the new setup, so that if you call it with the same settings that are already playing, it’ll ignore it. So I don’t really see how the component could make it restart unless something actually did change the sampling rate.
I’ve debugged the “setup” state in the comboBoxChanged callback, and it seems that in this callback, the audio device manager still doesn’t use the new sampling rate value by that time.
So I’ve solved it by adding this ugly hack in my code:
if (audioManager.getCurrentAudioDevice()->open(0, channelMap, sampleRate, sampleCount) == String::empty)
{
AudioDeviceManager::AudioDeviceSetup config;
audioManager.getAudioDeviceSetup (config);
config.sampleRate = sampleRate;
config.bufferSize = sampleCount;
config.outputChannels = channelMap;
config.inputChannels = 0;
// This prevent the audio manager to stop the device when
// the callback called by the open() call above will be triggered
audioManager.setAudioDeviceSetup(config, true);
I think the open() call above should have triggered a synchronous message in that case, to avoid my code from doing what the callback does.
Are you setting these properties directly on the AudioIODevice object, or via the AudioDeviceManager? The device manager doesn’t actually listen for changes to its device, because it doesn’t expect you to change the device’s settings directly. So if you just get a pointer to the device and start setting values, the manager won’t pick that up at all. The idea is that you do all the setup through the manager, and avoid communicating with the device directly.
So you mean I’m not supposed to call device->open() at all, but instead use deviceManager.setAudioSetup() and deviceManager.addAudioCallback()
So this code is wrong ?
if (!audioManager.getCurrentAudioDevice())
audioManager.initialise(0, track->audioFormat.getChannelCount(), savedXMLSetup, true);
if (audioManager.getCurrentAudioDevice())
{
if (audioManager.getCurrentAudioDevice()->open(0, channelMap, sampleRate, sampleCount) == String::empty)
{
// Start threads and playback here
audioManager.getCurrentAudioDevice()->start(&sourcePlayer);
}
}
How can I ensure the audio device opened successfully then ?
Yes, that’s completely wrong. The whole point of the manager is that it saves you from having to deal with the device objects directly. You don’t need to open it, or really to care whether it opened correctly or not, you just deal with the manager and let it worry about creating and deleting its device. All you have to do is:
It’s not completely equivalent. I can’t set the samplingRate or the channelMap (or the buffer size) this way.
Or, maybe I have to deal with modifying the XML source, but it seems complex for this task.
Or should I initialize, then query the setup, change it, and set it back ? (in that case, it still tedious, or am I missing something ?)
The manager is designed to make it easy to let the system create and manage the device, and to let the user choose their settings. It’s not designed to give you programmatic control over the device - if you need more control, then maybe it’d be better to create and manage the device directly, without using a manager object.
I guess it’s not a solution, as I would loose the AudioDeviceSelectorComponent functionality.
Anyway, what about adding at least a sampling rate parameter to the AudioDeviceManager::initialise call ?
I don’t think it’s a bad idea to set the sampling rate and check if the audio device accept it (as most audio device support multiple sampling rate). This saves some systematic software based resampling why the sound card probably does this better and faster.
This way I can save the getSetup / update / setSetup state and remove all my audio device hacking code directly.
I guess nothing. It cause me headache to understand this (I understand it as a chicken & egg issue, as how can you get a setup object while the audio device is not initialized ?)
My setup use 2 cards, so if I query a getAudioDeviceSetup on an AudioDeviceManager that’s not initialized, won’t that return a valid setup object only for the first sound card ?
Then, if the latter call to initialise fails, is it because of the unsupported sample rate or because of any -not related- unsupported feature that the first card support but not the second one ?
As I can’t parse the returned string for the call (driver dependent error), there is no way to know, am I wrong ?