Files
spice2x.github.io/src/spice2x/hooks/audio/audio.cpp
T
bicarus 6bcadccf09 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
2026-06-10 00:20:59 -07:00

135 lines
4.1 KiB
C++

#include "audio.h"
#include <vector>
#include <windows.h>
#include <initguid.h>
#include <audioclient.h>
#include <mmdeviceapi.h>
#include "hooks/audio/backends/mmdevice/device_enumerator.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/memutils.h"
#include "audio_private.h"
#include "acm.h"
#include "asio_proxy.h"
#ifdef _MSC_VER
DEFINE_GUID(CLSID_MMDeviceEnumerator,
0xBCDE0395, 0xE52F, 0x467C,
0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
#endif
// {ef985e71-d5c7-42d4-ba4d-2d073e2e96f4}
DEFINE_GUID(CLSID_DirectSoundI3DL2ReverbDMO,
0xef985e71, 0xd5c7, 0x42d4,
0xba, 0x4d, 0x2d, 0x07, 0x3e, 0x2e, 0x96, 0xf4);
// {87fc0268-9a55-4360-95aa-004a1d9de26c}
DEFINE_GUID(CLSID_DirectSoundWavesReverbDMO,
0x87fc0268, 0x9a55, 0x4360,
0x95, 0xaa, 0x00, 0x4a, 0x1d, 0x9d, 0xe2, 0x6c);
// function pointers
static decltype(CoCreateInstance) *CoCreateInstance_orig = nullptr;
namespace hooks::audio {
// public globals
bool ENABLED = true;
bool VOLUME_HOOK_ENABLED = true;
std::optional<DownmixAlgorithm> DOWNMIX_ALGORITHM = std::nullopt;
float VOLUME_BOOST = 1.0f;
std::optional<uint32_t> RESAMPLE_RATE = std::nullopt;
std::optional<uint32_t> EXCLUSIVE_BUFFER_MS = std::nullopt;
bool WASAPI_COMPATIBILITY_MODE = false;
bool USE_DUMMY = false;
WAVEFORMATEXTENSIBLE FORMAT {};
std::optional<Backend> BACKEND = std::nullopt;
bool INJECT_FAKE_REALTEK_AUDIO = false;
std::optional<size_t> ASIO_DRIVER_ID = std::nullopt;
std::string ASIO_DRIVER_NAME = "";
bool ASIO_FORCE_UNLOAD_ON_STOP = false;
// private globals
IAudioClient *CLIENT = nullptr;
std::mutex INITIALIZE_LOCK; // for asio
}
static HRESULT STDAPICALLTYPE CoCreateInstance_hook(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID *ppv)
{
HRESULT ret;
// call original
ret = CoCreateInstance_orig(rclsid, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(ret)) {
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
log_warning("audio", "CoCreateInstance failed for CLSID_MMDeviceEnumerator, hr={}", FMT_HRESULT(ret));
} else if (IsEqualCLSID(rclsid, CLSID_DirectSoundI3DL2ReverbDMO)) {
// deal with reverb DMO missing from dsdmo.dll
// it usually fails with 0x80040154 (REGDB_E_CLASSNOTREG), but we have also seen 0x8007007e (ERROR_MOD_NOT_FOUND)
static std::once_flag printed;
std::call_once(printed, [ret]() {
log_warning(
"audio",
"CoCreateInstance for CLSID_DirectSoundI3DL2ReverbDMO failed with {}, swap with CLSID_DirectSoundWavesReverbDMO...",
FMT_HRESULT(ret));
});
ret = CoCreateInstance_orig(CLSID_DirectSoundWavesReverbDMO, pUnkOuter, dwClsContext, riid, ppv);
if (FAILED(ret)) {
log_warning("audio", "CoCreateInstance(CLSID_DirectSoundWavesReverbDMO...) failed, hr={}", FMT_HRESULT(ret));
}
}
return ret;
}
// check if this is the audio device enumerator
if (IsEqualCLSID(rclsid, CLSID_MMDeviceEnumerator)) {
// wrap object
auto mmde = reinterpret_cast<IMMDeviceEnumerator **>(ppv);
*mmde = new WrappedIMMDeviceEnumerator(*mmde);
} else if (ppv != nullptr && *ppv != nullptr && hooks::audio::asio::is_asio_creation(rclsid, riid)) {
// wrap every ASIO driver so calls pass through to the real driver
*ppv = hooks::audio::asio::wrap(rclsid, *ppv);
}
// return original result
return ret;
}
namespace hooks::audio {
void init() {
if (!ENABLED) {
return;
}
log_info("audio", "initializing");
init_low_latency();
hooks::audio::acm::init();
// general hooks
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
}
void stop() {
log_info("audio", "stopping");
if (CLIENT) {
CLIENT->Stop();
CLIENT->Release();
CLIENT = nullptr;
}
stop_low_latency();
}
}