From f10484834c252b19cdb15ec8dcb0f5b87f9bafb8 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 12 Jul 2026 16:49:25 +0200 Subject: [PATCH] [APU] Fix mission-audio silence: ALSA underrun keepalive + guest_audio_flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Linux mission-audio fixes for Project Sylpheed (audio played in intro/menu but died when a mission finished loading, never returning): - alsa_audio_driver: when the guest stalls (mission "Preparing for Sortie" load) and the ring buffer empties, feed silence to keep the PCM alive instead of sleeping. Previously the small buffer drained, XRUN'd, and playback never recovered (matches the known "audio muted permanently"). - xconfig: make the guest speaker config a cvar (guest_audio_flags, default Digital Stereo) instead of hardcoded Dolby Digital surround. (The practical fix on this box also needed log_mask=13 to stop kernel log spam starving the audio pipeline during missions — config, not code.) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/xenia/apu/alsa/alsa_audio_driver.cc | 26 +++++++++++++++++++++++-- src/xenia/kernel/xconfig.cc | 14 ++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/xenia/apu/alsa/alsa_audio_driver.cc b/src/xenia/apu/alsa/alsa_audio_driver.cc index 700318fdf..a38477b9c 100644 --- a/src/xenia/apu/alsa/alsa_audio_driver.cc +++ b/src/xenia/apu/alsa/alsa_audio_driver.cc @@ -336,8 +336,30 @@ void ALSAAudioDriver::WorkerThread() { size_t current_write = write_index_.load(std::memory_order_acquire); if (current_read == current_write) { - // No data available, sleep and try again - std::this_thread::sleep_for(std::chrono::milliseconds(5)); + // No guest audio available. During long guest stalls (e.g. a mission + // "Preparing for Sortie" load) the game stops submitting frames; if we + // merely sleep, ALSA drains its small buffer, XRUNs, and playback never + // recovers (the game's audio is silent for the rest of the session). + // Keep the PCM alive by topping it up with silence whenever the queued + // audio runs low, so real audio resumes seamlessly once the stall ends. + snd_pcm_sframes_t avail = snd_pcm_avail_update(pcm_handle_); + if (avail < 0) { + if (!RecoverFromUnderrun(avail)) { + running_ = false; + } + continue; + } + snd_pcm_uframes_t queued = + (buffer_size_ > (snd_pcm_uframes_t)avail) ? buffer_size_ - avail : 0; + if (queued < period_size_ * 2) { + snd_pcm_sframes_t w = + snd_pcm_writei(pcm_handle_, silence.get(), period_size_); + if (w < 0) { + RecoverFromUnderrun(w); + } + } else { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } continue; } diff --git a/src/xenia/kernel/xconfig.cc b/src/xenia/kernel/xconfig.cc index 57cc50f95..344feba0f 100644 --- a/src/xenia/kernel/xconfig.cc +++ b/src/xenia/kernel/xconfig.cc @@ -9,6 +9,7 @@ #include "xenia/kernel/xconfig.h" +#include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/base/filesystem.h" @@ -19,6 +20,17 @@ #include +// Speaker configuration reported to the guest (XCONFIG_USER_AUDIO_FLAGS). +// Default = 0 (Digital Stereo): the Linux/ALSA APU driver's 5.1 (6-channel) +// path yields silence in some titles (e.g. Project Sylpheed missions), while +// stereo output works, so stereo is the safe default. Set to 0x00010001 for +// Dolby Digital + Pro Logic surround. +DEFINE_uint32(guest_audio_flags, 0, + "Speaker config reported to the guest (XCONFIG_USER_AUDIO_FLAGS): " + "0 = Digital Stereo (safe on Linux/ALSA), 0x00010001 = Dolby " + "Digital + Pro Logic surround.", + "APU"); + namespace xe { namespace kernel { @@ -94,7 +106,7 @@ void XConfig::SetDefaults() { xconfig_data_.user.language = static_cast(XLanguage::kEnglish); xconfig_data_.user.country = static_cast(XOnlineCountry::kUnitedStates); - xconfig_data_.user.audio_flags = DolbyDigital | DolbyProLogic; + xconfig_data_.user.audio_flags = cvars::guest_audio_flags; xconfig_data_.user.av_pack_hdmi_sz = XHDTVResolution.at(1).to_host(); xconfig_data_.user.av_pack_component_sz = XHDTVResolution.at(1).to_host(); xconfig_data_.user.av_pack_vga_sz = XVGAResolution.at(3).to_host();