diff --git a/src/audio/audio_manager.cpp b/src/audio/audio_manager.cpp index b5e59a9..d7cc723 100644 --- a/src/audio/audio_manager.cpp +++ b/src/audio/audio_manager.cpp @@ -32,6 +32,7 @@ namespace radiantwave::audio if (lower.find("analog") != std::string::npos || lower.find("built-in") != std::string::npos || lower.find("builtin") != std::string::npos || + lower.find("virtual") != std::string::npos || lower.find("realtek") != std::string::npos || lower.find("hda") != std::string::npos || lower.find("speaker") != std::string::npos || @@ -85,6 +86,34 @@ namespace radiantwave::audio return false; } + // Startup diagnostics: which audio backend SDL picked (PipeWire, ALSA, + // Pulse, dummy, ...) and what playback devices it actually sees. This is + // the first place a "only the built-in speakers show up" report points; + // a single device here means SDL itself never enumerated the HDMI sink. + const char *driver = SDL_GetCurrentAudioDriver(); + std::string driverName = driver ? driver : "(none)"; + int deviceCount = 0; + SDL_AudioDeviceID *devices = SDL_GetAudioPlaybackDevices(&deviceCount); + std::string deviceNames; + if (devices) + { + for (int i = 0; i < deviceCount; ++i) + { + if (i) + { + deviceNames += ", "; + } + const char *n = SDL_GetAudioDeviceName(devices[i]); + deviceNames += n ? n : "(unnamed)"; + } + SDL_free(devices); + } + logger::Logger::instance().info( + "Audio", + "SDL audio driver: " + driverName + "; " + + std::to_string(deviceCount) + " playback device(s) [" + + (deviceNames.empty() ? "(none)" : deviceNames) + "]"); + // Try to open the mixer on the device previously persisted in config; // fall back to the system default when the configured device is gone. SDL_AudioDeviceID targetId = SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK; @@ -247,7 +276,21 @@ namespace radiantwave::audio } else { - list.push_back({"", "Built-In Speakers", 0}); + if (devices) + { + SDL_free(devices); + } + const char *driver = SDL_GetCurrentAudioDriver(); + // Zero enumerated devices is a symptom, not a real output. Surface + // the truth (a single "System Default" entry that still routes to + // whatever the OS default is) instead of pretending a fabricated + // device, and say which backend was active so kiosk logs show why. + logger::Logger::instance().debug( + "Audio", + "No playback devices enumerated (audio driver: " + + std::string(driver ? driver : "none") + + "); falling back to the system default"); + list.push_back({"", "System Default", 0}); } return list; diff --git a/src/audio_settings_page.cpp b/src/audio_settings_page.cpp index 7bad55a..f28aa3a 100644 --- a/src/audio_settings_page.cpp +++ b/src/audio_settings_page.cpp @@ -531,7 +531,7 @@ void AudioSettingsPage::render(SDL_Renderer* renderer) { } std::string devName = (currentDeviceIndex_ >= 0 && currentDeviceIndex_ < static_cast(devices_.size())) - ? devices_[currentDeviceIndex_].friendly_name : "Built-In Speakers"; + ? devices_[currentDeviceIndex_].friendly_name : "System Default"; deviceText_.ensure(renderer, fontHeader_, "Device: " + devName, white); if (deviceText_.texture) { SDL_FRect dst{500.0f, currentY, deviceText_.width, deviceText_.height}; diff --git a/tests/test_audio.cpp b/tests/test_audio.cpp index 6e319e5..e35c4d6 100644 --- a/tests/test_audio.cpp +++ b/tests/test_audio.cpp @@ -8,6 +8,7 @@ TEST(AudioTest, FriendlyDeviceNameMapping) { EXPECT_EQ(mapToFriendlyDeviceName("AMD Radeon HD Audio Controller HDMI"), "TV / HDMI Monitor"); EXPECT_EQ(mapToFriendlyDeviceName("USB Audio Headset Dongle"), "USB Audio Device"); EXPECT_EQ(mapToFriendlyDeviceName("bluez_sink.00_11_22_33_44_55"), "Bluetooth Audio"); + EXPECT_EQ(mapToFriendlyDeviceName("Virtual Speaker"), "Built-In Speakers"); EXPECT_EQ(mapToFriendlyDeviceName(""), "Default Audio Output"); } @@ -30,6 +31,7 @@ TEST(AudioTest, GetPlaybackDevicesFallsBackWithIdZeroWhenNone) { for (const auto& d : devices) { if (d.raw_name.empty()) { EXPECT_EQ(d.id, 0u); + EXPECT_EQ(d.friendly_name, "System Default"); } } }