mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 06:40:42 -07:00
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
This commit is contained in:
@@ -167,31 +167,46 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
|
||||
fix_rec_format(const_cast<WAVEFORMATEX *>(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<const WAVEFORMATEX *>(&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<WAVEFORMATEX *>(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<const WAVEFORMATEX *>(&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<const WAVEFORMATEX *>(&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<WAVEFORMATEX *>(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<const WAVEFORMATEX *>(&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.
|
||||
|
||||
Reference in New Issue
Block a user