Fix audio device problems #1

Merged
NeilClack merged 7 commits from dev into main 2026-08-28 21:30:13 +00:00
3 changed files with 53 additions and 23 deletions
Showing only changes of commit 42b915bd25 - Show all commits
+47 -20
View File
@@ -512,9 +512,19 @@ namespace radiantwave::audio
return false; return false;
} }
// Must be called with mutex_ held. Destroys the current mixer and creates a // Must be called with mutex_ held. Creates a fresh mixer on the given
// fresh one on the given device, recreating the two track slots and // device, recreating the two track slots and resuming the active track if
// resuming the active track if one was playing. // one was playing.
//
// The new mixer is created BEFORE the current one is destroyed. Reversing
// the order (destroy-then-reopen) quits the whole SDL audio subsystem the
// moment the last device closes: MIX_DestroyMixer() follows up with
// SDL_QuitSubSystem(SDL_INIT_AUDIO), which tears down SDL's device registry
// and renumbers every enumerated device id on the way back up. The id this
// switch was asked to open is then stale and the reopen fails with
// "Invalid audio device instance ID". Keeping the current device open while
// the replacement opens also means a failed switch leaves the current
// output playing instead of going permanently mute.
bool AudioManager::recreateMixerOnDevice(SDL_AudioDeviceID targetId) bool AudioManager::recreateMixerOnDevice(SDL_AudioDeviceID targetId)
{ {
// Capture the playback state we want to preserve across the switch. // Capture the playback state we want to preserve across the switch.
@@ -523,7 +533,37 @@ namespace radiantwave::audio
? slots_[activeSlot_].fadeInDuration ? slots_[activeSlot_].fadeInDuration
: 0.0f; : 0.0f;
// Tear down the existing mixer (its tracks die with it). // Open the replacement mixer on the target device first, while the
// current device (and SDL's device registry) is still alive.
MIX_Mixer *newMixer = MIX_CreateMixerDevice(targetId, nullptr);
if (!newMixer)
{
std::string err = SDL_GetError();
logger::Logger::instance().error(
"Audio",
"Could not open audio output device "
+ mapToFriendlyDeviceName(resolvePlaybackDeviceName(targetId))
+ "; keeping the current device (MIX_CreateMixerDevice error: "
+ err + ")");
return false;
}
MIX_Track *newTracks[2];
for (int i = 0; i < 2; ++i)
{
newTracks[i] = MIX_CreateTrack(newMixer);
}
if (!newTracks[0] || !newTracks[1])
{
MIX_DestroyMixer(newMixer);
logger::Logger::instance().error(
"Audio", "Could not create mixer tracks; keeping the current device (SDL error: "
+ std::string(SDL_GetError()) + ")");
return false;
}
// Tear down the existing mixer (its tracks die with it) and adopt the
// new one.
for (int i = 0; i < 2; ++i) for (int i = 0; i < 2; ++i)
{ {
slots_[i].track = nullptr; slots_[i].track = nullptr;
@@ -539,22 +579,9 @@ namespace radiantwave::audio
mixer_ = nullptr; mixer_ = nullptr;
} }
mixer_ = MIX_CreateMixerDevice(targetId, nullptr); mixer_ = newMixer;
if (!mixer_) slots_[0].track = newTracks[0];
{ slots_[1].track = newTracks[1];
initialized_ = false;
return false;
}
slots_[0].track = MIX_CreateTrack(mixer_);
slots_[1].track = MIX_CreateTrack(mixer_);
if (!slots_[0].track || !slots_[1].track)
{
MIX_DestroyMixer(mixer_);
mixer_ = nullptr;
initialized_ = false;
return false;
}
float gain = enabled_ ? (volume_ / 100.0f) : 0.0f; float gain = enabled_ ? (volume_ / 100.0f) : 0.0f;
MIX_SetMixerGain(mixer_, gain); MIX_SetMixerGain(mixer_, gain);
+5 -2
View File
@@ -111,8 +111,11 @@ private:
// Recreates the mixer on the given playback device id (0 = system default). // Recreates the mixer on the given playback device id (0 = system default).
// Called with mutex_ held. Preserves playback state: whichever track was // Called with mutex_ held. Preserves playback state: whichever track was
// playing on the active slot is reloaded and resumed at full gain, an // playing on the active slot is reloaded and resumed at full gain, an
// in-progress fade is restarted. Returns false on failure (previous mixer is // in-progress fade is restarted. The replacement mixer is opened before the
// left destroyed but the app remains safe to keep running muted). // current one is destroyed so SDL's device ids stay valid (destroy-first
// quits the whole audio subsystem and orphans the id being opened).
// Returns false on failure without touching the current mixer, so the
// existing output keeps playing.
bool recreateMixerOnDevice(SDL_AudioDeviceID targetId); bool recreateMixerOnDevice(SDL_AudioDeviceID targetId);
config::ConfigManager* configManager_ = nullptr; config::ConfigManager* configManager_ = nullptr;
+1 -1
View File
@@ -288,7 +288,7 @@ void AudioSettingsPage::applyDeviceSelection() {
if (ok) { if (ok) {
statusMessage_ = "Switched to " + devices_[currentDeviceIndex_].friendly_name; statusMessage_ = "Switched to " + devices_[currentDeviceIndex_].friendly_name;
} else { } else {
statusMessage_ = "Device unavailable - using system default"; statusMessage_ = "Device unavailable - kept the current output";
} }
refreshDeviceAndTrackLists(); refreshDeviceAndTrackLists();
} }