From 6bcadccf093a53a15131f964150e8f259373d39d Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Wed, 10 Jun 2026 00:20:59 -0700 Subject: [PATCH] audio: WASAPI Force Shared Mode option (#745) ## Link to GitHub Issue or related Pull Request, if one exists n/a ## Description of change This new option, `-wasapishared`, detects when the game opens WASAPI Exclusive stream and forcibly opens a shared mode stream instead on the real device. #### Benefits of this: 1. No need for `force wasapi shared` patches 2. No need to change sample rate of audio devices before starting up games 3. Adds ability to boot in shared mode even for games that do not have shared wasapi changes (old GITADORA, popn HC) As a result, this option is strictly better than `shared wasapi` patches available for many games. #### Downsides: 1. OS resampling will add a small latency, but not big enough to be noticeable. 2. Using shared mode instead of exclusive mode adds latency, of course. (If you cared about latency, you would use the low latency option, or use exclusive or asio) Basically it's the "make things work" button for audio that should work for almost all games that we support. The option has no effect if the game opens in shared mode. ## Testing SDVX7 - ok GITADORA GW - ok popn HC - ok IIDX - ok --- src/spice2x/CMakeLists.txt | 1 + src/spice2x/hooks/audio/audio.cpp | 1 + src/spice2x/hooks/audio/audio.h | 6 ++ .../audio/backends/wasapi/audio_client.cpp | 75 +++++++++++----- .../audio/backends/wasapi/audio_client.h | 5 ++ .../hooks/audio/backends/wasapi/shared.cpp | 78 ++++++++++++++++ .../hooks/audio/backends/wasapi/shared.h | 31 +++++++ src/spice2x/launcher/launcher.cpp | 3 + src/spice2x/launcher/options.cpp | 90 +++++++++++-------- src/spice2x/launcher/options.h | 5 +- src/spice2x/util/deferlog.cpp | 4 +- 11 files changed, 234 insertions(+), 65 deletions(-) create mode 100644 src/spice2x/hooks/audio/backends/wasapi/shared.cpp create mode 100644 src/spice2x/hooks/audio/backends/wasapi/shared.h diff --git a/src/spice2x/CMakeLists.txt b/src/spice2x/CMakeLists.txt index 76b5ed8..cfcfa89 100644 --- a/src/spice2x/CMakeLists.txt +++ b/src/spice2x/CMakeLists.txt @@ -510,6 +510,7 @@ set(SOURCE_FILES ${SOURCE_FILES} hooks/audio/backends/wasapi/audio_render_client.cpp hooks/audio/backends/wasapi/downmix.cpp hooks/audio/backends/wasapi/resample.cpp + hooks/audio/backends/wasapi/shared.cpp hooks/audio/backends/wasapi/dummy_audio_client.cpp hooks/audio/backends/wasapi/dummy_audio_clock.cpp hooks/audio/backends/wasapi/dummy_audio_render_client.cpp diff --git a/src/spice2x/hooks/audio/audio.cpp b/src/spice2x/hooks/audio/audio.cpp index dc5b9f6..59123a2 100644 --- a/src/spice2x/hooks/audio/audio.cpp +++ b/src/spice2x/hooks/audio/audio.cpp @@ -44,6 +44,7 @@ namespace hooks::audio { float VOLUME_BOOST = 1.0f; std::optional RESAMPLE_RATE = std::nullopt; std::optional EXCLUSIVE_BUFFER_MS = std::nullopt; + bool WASAPI_COMPATIBILITY_MODE = false; bool USE_DUMMY = false; WAVEFORMATEXTENSIBLE FORMAT {}; std::optional BACKEND = std::nullopt; diff --git a/src/spice2x/hooks/audio/audio.h b/src/spice2x/hooks/audio/audio.h index 7deade2..aef1374 100644 --- a/src/spice2x/hooks/audio/audio.h +++ b/src/spice2x/hooks/audio/audio.h @@ -37,6 +37,12 @@ namespace hooks::audio { // minimum WASAPI exclusive buffer duration (milliseconds), if set. enlarges the device buffer // to avoid underrun crackle on endpoints that cannot service a tiny buffer in time. extern std::optional EXCLUSIVE_BUFFER_MS; + + // when true, WASAPI compatibility mode is active: exclusive-mode streams are redirected to + // shared mode. the Windows audio engine performs any required sample-rate / channel / bit-depth + // conversion, so the game's format is passed through unchanged and other applications can play + // audio simultaneously. + extern bool WASAPI_COMPATIBILITY_MODE; extern bool USE_DUMMY; extern WAVEFORMATEXTENSIBLE FORMAT; extern std::optional BACKEND; diff --git a/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp b/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp index 2265136..38012fe 100644 --- a/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp +++ b/src/spice2x/hooks/audio/backends/wasapi/audio_client.cpp @@ -167,31 +167,46 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize( fix_rec_format(const_cast(pFormat)); } - // when downmixing, open the real device as stereo while the game keeps writing its native - // multi-channel format into the scratch buffer. - WAVEFORMATEXTENSIBLE stereo_storage = {}; - const WAVEFORMATEX *device_format = pFormat; - - if (auto algorithm = resolve_downmix(pFormat)) { - this->downmix.setup(pFormat, &stereo_storage, *algorithm); - device_format = reinterpret_cast(&stereo_storage); - log_info("audio::wasapi", "downmix enabled: {} channels -> 2 channels ({})", - pFormat->nChannels, hooks::audio::Downmix::algorithm_name(*algorithm)); - } else if (games::gitadora::is_arena_model()) { - games::gitadora::fix_audio_channel_mask(const_cast(pFormat)); + // apply the -wasapishared option: redirect an exclusive request to shared mode. once redirected, + // spice's own downmix/resample paths below are skipped (gated on redirected_from_exclusive) and + // the shared engine handles any format conversion via AUTOCONVERTPCM (PCM / float only). + if (hooks::audio::SharedRedirect::wants(ShareMode, pFormat)) { + this->shared.apply(&ShareMode, &StreamFlags, &hnsPeriodicity); + } else if (hooks::audio::WASAPI_COMPATIBILITY_MODE && ShareMode == AUDCLNT_SHAREMODE_SHARED) { + log_warning( + "audio::wasapi", + "-wasapishared is enabled but the game is already opening a shared-mode stream; " + "the option has no effect"); } - // when resampling, open the real device at the target rate while the game keeps writing its - // native-rate audio into the scratch buffer. this runs on whatever device_format is now: the - // game's native format, or the stereo format produced above when downmix is also active, so - // the two stages chain as multi-channel -> stereo -> resampled stereo. + WAVEFORMATEXTENSIBLE stereo_storage = {}; WAVEFORMATEXTENSIBLE resample_storage = {}; - if (auto target_rate = hooks::audio::Resampler::resolve(device_format)) { - const uint32_t src_rate = device_format->nSamplesPerSec; - this->resample.setup(device_format, &resample_storage, *target_rate); - device_format = reinterpret_cast(&resample_storage); - log_info("audio::wasapi", "resample enabled: {} Hz -> {} Hz{}", - src_rate, *target_rate, this->downmix.enabled ? " (after downmix)" : ""); + const WAVEFORMATEX *device_format = pFormat; + + if (!this->shared.redirected_from_exclusive) { + + // when downmixing, open the real device as stereo while the game keeps writing its native + // multi-channel format into the scratch buffer. + if (auto algorithm = resolve_downmix(pFormat)) { + this->downmix.setup(pFormat, &stereo_storage, *algorithm); + device_format = reinterpret_cast(&stereo_storage); + log_info("audio::wasapi", "downmix enabled: {} channels -> 2 channels ({})", + pFormat->nChannels, hooks::audio::Downmix::algorithm_name(*algorithm)); + } else if (games::gitadora::is_arena_model()) { + games::gitadora::fix_audio_channel_mask(const_cast(pFormat)); + } + + // when resampling, open the real device at the target rate while the game keeps writing its + // native-rate audio into the scratch buffer. this runs on whatever device_format is now: the + // game's native format, or the stereo format produced above when downmix is also active, so + // the two stages chain as multi-channel -> stereo -> resampled stereo. + if (auto target_rate = hooks::audio::Resampler::resolve(device_format)) { + const uint32_t src_rate = device_format->nSamplesPerSec; + this->resample.setup(device_format, &resample_storage, *target_rate); + device_format = reinterpret_cast(&resample_storage); + log_info("audio::wasapi", "resample enabled: {} Hz -> {} Hz{}", + src_rate, *target_rate, this->downmix.enabled ? " (after downmix)" : ""); + } } // verbose output @@ -308,6 +323,12 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSize(UINT32 *pNumBufferF *pNumBufferFrames = this->resample.frames_device_to_game(*pNumBufferFrames); } + // redirected to shared mode: clamp the reported buffer to one device period (see SharedRedirect). + if (SUCCEEDED(ret) && this->shared.redirected_from_exclusive && pNumBufferFrames) { + *pNumBufferFrames = this->shared.clamp_buffer_size( + pReal, this->device_format.Format.nSamplesPerSec, *pNumBufferFrames); + } + CHECK_RESULT(ret); } HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetStreamLatency(REFERENCE_TIME *phnsLatency) { @@ -380,6 +401,16 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsFormatSupported( log_info("audio::wasapi", "IAudioClient::IsFormatSupported hook hit"); print_format(ShareMode, pFormat); + // under the exclusive->shared redirect, report the exclusive format as supported so the game + // doesn't fall back before reaching Initialize. + if (hooks::audio::SharedRedirect::wants(ShareMode, pFormat)) { + log_info("audio::wasapi", "... reporting supported (will redirect to shared mode)"); + if (ppClosestMatch) { + *ppClosestMatch = nullptr; + } + return S_OK; + } + // when downmixing, the real device is opened as stereo, so check whether the equivalent // stereo format is supported instead of the multi-channel one. when resampling is also active // it chains onto that stereo format, so check the resampled stereo format. diff --git a/src/spice2x/hooks/audio/backends/wasapi/audio_client.h b/src/spice2x/hooks/audio/backends/wasapi/audio_client.h index 8ce6181..d2531df 100644 --- a/src/spice2x/hooks/audio/backends/wasapi/audio_client.h +++ b/src/spice2x/hooks/audio/backends/wasapi/audio_client.h @@ -10,6 +10,7 @@ #include "downmix.h" #include "resample.h" +#include "shared.h" #include "audio_render_client.h" // {1FBC8530-AF3E-4128-B418-115DE72F76B6} @@ -94,6 +95,10 @@ struct WrappedIAudioClient : IAudioClient3 { IAudioClient3 *const pReal3; AudioBackend *const backend; bool exclusive_mode = false; + + // -wasapishared redirect state: when an exclusive request was redirected to shared mode, the + // engine converts the native format and the reported buffer size is clamped (see SharedRedirect). + hooks::audio::SharedRedirect shared; int frame_size = 0; // the format the real device was opened with (after any downmix). used to scale the final diff --git a/src/spice2x/hooks/audio/backends/wasapi/shared.cpp b/src/spice2x/hooks/audio/backends/wasapi/shared.cpp new file mode 100644 index 0000000..70968b4 --- /dev/null +++ b/src/spice2x/hooks/audio/backends/wasapi/shared.cpp @@ -0,0 +1,78 @@ +#include "shared.h" + +#include + +#include "hooks/audio/audio.h" +#include "util/logging.h" + +#include "defs.h" + +namespace hooks::audio { + + // whether the engine's PCM converter can handle this format. PCM / float only; non-PCM + // bitstream (AC-3 / DTS passthrough) must be left alone. + static bool is_pcm_or_float(const WAVEFORMATEX *format) { + if (format == nullptr) { + return false; + } + + switch (format->wFormatTag) { + case WAVE_FORMAT_PCM: + case WAVE_FORMAT_IEEE_FLOAT: + return true; + case WAVE_FORMAT_EXTENSIBLE: { + + // SubFormat is only valid when the extra-bytes block is large enough + if (format->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) { + return false; + } + const auto *ext = reinterpret_cast(format); + return ext->SubFormat == GUID_KSDATAFORMAT_SUBTYPE_PCM + || ext->SubFormat == GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } + default: + return false; + } + } + + bool SharedRedirect::wants(AUDCLNT_SHAREMODE share_mode, const WAVEFORMATEX *format) { + + // only redirect PCM / float exclusive streams: the engine converter (AUTOCONVERTPCM) can + // handle those, but non-PCM bitstream (AC-3 / DTS passthrough) would fail in shared mode, + // so leave it in exclusive untouched. + return hooks::audio::WASAPI_COMPATIBILITY_MODE + && share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE + && is_pcm_or_float(format); + } + + void SharedRedirect::apply(AUDCLNT_SHAREMODE *share_mode, DWORD *stream_flags, + REFERENCE_TIME *periodicity) { + + // shared mode requires periodicity == 0; AUTOCONVERTPCM lets the engine accept the game's + // native format (else shared Initialize returns AUDCLNT_E_UNSUPPORTED_FORMAT). + log_info("audio::wasapi", "redirecting exclusive WASAPI to shared mode"); + *share_mode = AUDCLNT_SHAREMODE_SHARED; + *periodicity = 0; + *stream_flags |= AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; + this->redirected_from_exclusive = true; + } + + UINT32 SharedRedirect::clamp_buffer_size(IAudioClient *real, uint32_t sample_rate, + UINT32 device_frames) const { + if (!this->redirected_from_exclusive || real == nullptr || sample_rate == 0 || device_frames == 0) { + return device_frames; + } + + // GetDevicePeriod returns REFERENCE_TIME units (100 ns), 10^7 per second, so + // period_frames = period * sample_rate / 10^7. + REFERENCE_TIME period = 0; + if (SUCCEEDED(real->GetDevicePeriod(&period, nullptr)) && period > 0) { + const UINT32 period_frames = (UINT32) ((period * sample_rate) / 10000000); + if (period_frames > 0 && period_frames < device_frames) { + return period_frames; + } + } + + return device_frames; + } +} diff --git a/src/spice2x/hooks/audio/backends/wasapi/shared.h b/src/spice2x/hooks/audio/backends/wasapi/shared.h new file mode 100644 index 0000000..67a646e --- /dev/null +++ b/src/spice2x/hooks/audio/backends/wasapi/shared.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include +#include + +namespace hooks::audio { + + // The -wasapishared option redirects an exclusive WASAPI stream to shared mode. lets other apps + // play sound and works on devices that can't open the exclusive format, at the cost of some + // latency. Only PCM / float is converted; bitstream (AC-3 / DTS) is left alone. + struct SharedRedirect { + + // true once apply() has redirected an exclusive request. gates the buffer clamp; stays false + // for a natively-shared stream (it paces itself, so must not be clamped). + bool redirected_from_exclusive = false; + + // whether an exclusive-mode request should be redirected, given the -wasapishared option. + // only PCM / float is eligible; bitstream (AC-3 / DTS) is left in exclusive mode. + static bool wants(AUDCLNT_SHAREMODE share_mode, const WAVEFORMATEX *format); + + // redirect an exclusive request to shared mode. caller must have checked wants() first. + void apply(AUDCLNT_SHAREMODE *share_mode, DWORD *stream_flags, REFERENCE_TIME *periodicity); + + // clamp a reported buffer size to one device period. some games write the whole buffer per + // event, which overflows shared-mode buffering (AUDCLNT_E_BUFFER_TOO_LARGE -> stutter); one + // period always fits. a no-op unless an exclusive request was redirected. + UINT32 clamp_buffer_size(IAudioClient *real, uint32_t sample_rate, UINT32 device_frames) const; + }; +} diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index a799f00..f184e33 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -1132,6 +1132,9 @@ int main_implementation(int argc, char *argv[]) { hooks::audio::EXCLUSIVE_BUFFER_MS = ms; } } + if (options[launcher::Options::AudioShared].value_bool()) { + hooks::audio::WASAPI_COMPATIBILITY_MODE = true; + } if (options[launcher::Options::AudioBackend].is_active()) { auto &name = options[launcher::Options::AudioBackend].value_text(); diff --git a/src/spice2x/launcher/options.cpp b/src/spice2x/launcher/options.cpp index 1b34769..fda1c7e 100644 --- a/src/spice2x/launcher/options.cpp +++ b/src/spice2x/launcher/options.cpp @@ -28,6 +28,7 @@ static const std::vector CATEGORY_ORDER_BASIC = { "Graphics (Full Screen)", "Graphics (Windowed)", "Audio", + "Audio (Conversion)", }; static const std::vector CATEGORY_ORDER_ADVANCED = { @@ -1933,6 +1934,30 @@ static const std::vector OPTION_DEFINITIONS = { .type = OptionType::Bool, .category = "Audio (Hacks)", }, + { + // AudioShared + .title = "WASAPI Force Shared Mode", + .name = "wasapishared", + .desc = "This option converts all WASAPI exclusive mode requests to shared mode.\n\n" + "Many games have patches that turn on shared mode, but this option is better! " + "This will automatically perform sample rate conversion - " + "you do not need to manually set the sample rate of your audio device before launching the game.", + .type = OptionType::Bool, + .category = "Audio", + }, + { + // spice2x_LowLatencySharedAudio + .title = "Low Latency Shared Audio", + .name = "sp2x-lowlatencysharedaudio", + .display_name = "lowlatencysharedaudio", + .aliases= "lowlatencysharedaudio", + .desc = "Force the usage of smallest buffer size supported by the device when shared mode audio is used. " + "Works for games using DirectSound or shared WASAPI; no effect for exclusive WASAPI and ASIO. " + "For best results (under 10ms), use the default Windows inbox audio driver instead of manufacturer supplied driver. " + "Requires Windows 10 and above.", + .type = OptionType::Bool, + .category = "Audio", + }, { // AudioBackend .title = "Spice Audio Hook Backend (DEPRECATED - use -asioconvert instead)", @@ -1941,7 +1966,7 @@ static const std::vector OPTION_DEFINITIONS = { "Does nothing for games that do not output to exclusive WASAPI.", .type = OptionType::Enum, .hidden = true, - .category = "Audio", + .category = "Audio (Conversion)", .elements = { {"asio", "ASIO"}, {"waveout", "broken, do not use"} @@ -1954,7 +1979,7 @@ static const std::vector OPTION_DEFINITIONS = { .desc = "Selects the ASIO driver id to use when Spice Audio Backend is set to ASIO.", .type = OptionType::Integer, .hidden = true, - .category = "Audio", + .category = "Audio (Conversion)", }, { // AsioDriverName @@ -1965,7 +1990,7 @@ static const std::vector OPTION_DEFINITIONS = { "This should only be used as last resort if your audio device does not support WASAPI Exclusive.\n\n" "Does nothing for games that do not output to exclusive WASAPI.", .type = OptionType::Text, - .category = "Audio", + .category = "Audio (Conversion)", .picker = OptionPickerType::AsioDriver, }, { @@ -1990,7 +2015,7 @@ static const std::vector OPTION_DEFINITIONS = { "rear: rear channels only.\n\n" "side: side channels only.", .type = OptionType::Enum, - .category = "Audio", + .category = "Audio (Conversion)", .elements = { {"ac4", "AC-4 downmix"}, {"normalize", "All channels"}, @@ -1999,26 +2024,6 @@ static const std::vector OPTION_DEFINITIONS = { {"side", "Side channels only"}, }, }, - { - // AsioDownmixToStereo - .title = "ASIO 7.1 to Stereo Downmix", - .name = "asiodownmix", - .desc = "Extracts a single stereo channel pair from a multi-channel ASIO output, " - "mapping the selected pair to the device's first two channels.\n\n" - "Channel pairs assume a standard 7.1 ASIO layout (0-indexed):\n\n" - "front: channels 0/1 (front left/right).\n\n" - "center: channel 2 (front center, duplicated to both outputs).\n\n" - "rear: channels 4/5 (rear left/right).\n\n" - "side: channels 6/7 (side left/right).", - .type = OptionType::Enum, - .category = "Audio", - .elements = { - {"front", "Front channels (0/1)"}, - {"center", "Center channel (2)"}, - {"rear", "Rear channels (4/5)"}, - {"side", "Side channels (6/7)"}, - }, - }, { // VolumeBoost .title = "WASAPI/ASIO Boost Audio Volume", @@ -2050,7 +2055,7 @@ static const std::vector OPTION_DEFINITIONS = { "Select the TARGET sample rate (one that your audio card supports).\n\n" "Will result in couple milliseconds of latency and increased CPU usage when active.", .type = OptionType::Enum, - .category = "Audio", + .category = "Audio (Conversion)", .elements = { {"44100", "44.1 kHz"}, {"48000", "48 kHz"}, @@ -2069,7 +2074,27 @@ static const std::vector OPTION_DEFINITIONS = { "(at the cost of slightly increased latency).", .type = OptionType::Integer, .setting_name = "16", - .category = "Audio", + .category = "Audio (Conversion)", + }, + { + // AsioDownmixToStereo + .title = "ASIO 7.1 to Stereo Downmix", + .name = "asiodownmix", + .desc = "Extracts a single stereo channel pair from a multi-channel ASIO output, " + "mapping the selected pair to the device's first two channels.\n\n" + "Channel pairs assume a standard 7.1 ASIO layout (0-indexed):\n\n" + "front: channels 0/1 (front left/right).\n\n" + "center: channel 2 (front center, duplicated to both outputs).\n\n" + "rear: channels 4/5 (rear left/right).\n\n" + "side: channels 6/7 (side left/right).", + .type = OptionType::Enum, + .category = "Audio (Conversion)", + .elements = { + {"front", "Front (ch 0/1)"}, + {"center", "Center (ch 2)"}, + {"rear", "Rear (ch 4/5)"}, + {"side", "Side (ch 6/7)"}, + }, }, { // DelayBy5Seconds @@ -2740,19 +2765,6 @@ static const std::vector OPTION_DEFINITIONS = { {"both", ""}, }, }, - { - // spice2x_LowLatencySharedAudio - .title = "Low Latency Shared Audio", - .name = "sp2x-lowlatencysharedaudio", - .display_name = "lowlatencysharedaudio", - .aliases= "lowlatencysharedaudio", - .desc = "Force the usage of smallest buffer size supported by the device when shared mode audio is used. " - "Works for games using DirectSound or shared WASAPI; no effect for exclusive WASAPI and ASIO. " - "For best results (under 10ms), use the default Windows inbox audio driver instead of manufacturer supplied driver. " - "Requires Windows 10 and above.", - .type = OptionType::Bool, - .category = "Audio", - }, { // spice2x_TapeLedAlgorithm .title = "Tape LED Avg Algorithm", diff --git a/src/spice2x/launcher/options.h b/src/spice2x/launcher/options.h index 2d7ec9d..4ae6175 100644 --- a/src/spice2x/launcher/options.h +++ b/src/spice2x/launcher/options.h @@ -198,15 +198,17 @@ namespace launcher { spice2x_NvapiProfile, DisableAudioHooks, spice2x_DisableVolumeHook, + AudioShared, + spice2x_LowLatencySharedAudio, AudioBackend, AsioDriverId, AsioDriverName, AudioDummy, DownmixAudioToStereo, - AsioDownmixToStereo, VolumeBoost, AudioResample, AudioExclusiveBuffer, + AsioDownmixToStereo, DelayBy5Seconds, spice2x_DelayByNSeconds, LoadStubs, @@ -272,7 +274,6 @@ namespace launcher { IIDXSubMonitorOverride, spice2x_IIDXEmulateSubscreenKeypadTouch, spice2x_AutoCard, - spice2x_LowLatencySharedAudio, spice2x_TapeLedAlgorithm, spice2x_NoNVAPI, spice2x_NoD3D9DeviceHook, diff --git a/src/spice2x/util/deferlog.cpp b/src/spice2x/util/deferlog.cpp index 0dc3497..6274373 100644 --- a/src/spice2x/util/deferlog.cpp +++ b/src/spice2x/util/deferlog.cpp @@ -8,10 +8,10 @@ namespace deferredlogs { const std::initializer_list SUPERSTEP_SOUND_ERROR_MESSAGE = { - "audio initialization error was previously detected during boot!", + "audio initialization error was detected during boot!", " this crash is most likely related to audio init failure", + " * try enabling WASAPI Force Shared Mode (-wasapishared)", " * check if the default audio device has changed", - " * fix your audio device settings (e.g., sample rate)", " * double check your spice audio options and patches", " * ensure no other application is using the device in exclusive mode" };