Files
spice2x.github.io/src/spice2x/hooks/audio/backends/mmdevice/device.cpp
T
bicarus a6f29d6a5a audio: implement wrapped IAudioClient3 (#511)
## Link to GitHub Issue, if one exists
Fixes #509

## Description of change
Implement `WrappedIAudioClient3`.

For now, we have only seen SDVX use it (EG final) and only when the
shared mode patch is enabled. Exclusive still goes through version 1.
This means that for V3 we can forego implementing backend conversion
logic (for now).

Only significant addition is `InitializeSharedAudioStream` and this IS
called by SDVX when shared mode patch is enabled.

## Testing
Tested EG final, tested year 1 data, also IIDX shared/exclusive wasapi.
2026-01-10 17:41:10 -08:00

129 lines
3.9 KiB
C++

#include "device.h"
#include <mutex>
#include <audioclient.h>
#include <endpointvolume.h>
#include "hooks/audio/audio_private.h"
#include "hooks/audio/backends/mmdevice/audio_endpoint_volume.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
#include "util/utils.h"
#define PRINT_FAILED_RESULT(name, ret) \
do { \
if (AUDIO_LOG_HRESULT) { \
log_warning("audio::mmdevice", "{} failed, hr={}", name, FMT_HRESULT(ret)); \
} \
} while (0)
#define CHECK_RESULT(x) \
do { \
HRESULT __ret = (x); \
if (FAILED(__ret)) { \
PRINT_FAILED_RESULT(__FUNCTION__, __ret); \
} \
return __ret; \
} while (0)
#ifdef _MSC_VER
DEFINE_GUID(IID_IMMDevice,
0xd666063f, 0x1587, 0x4e43,
0x81, 0xf1, 0xb9, 0x48, 0xe8, 0x07, 0x36, 0x3f);
#endif
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::QueryInterface(REFIID riid, void **ppvObj) {
if (ppvObj == nullptr) {
return E_POINTER;
}
if (riid == IID_WrappedIMMDevice ||
riid == IID_IMMDevice)
{
this->AddRef();
*ppvObj = this;
return S_OK;
}
return pReal->QueryInterface(riid, ppvObj);
}
ULONG STDMETHODCALLTYPE WrappedIMMDevice::AddRef() {
return pReal->AddRef();
}
ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
// get reference count of underlying interface
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
if (refs == 0) {
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
delete this;
}
return refs;
}
// IMMDevice
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
REFIID iid,
DWORD dwClsCtx,
PROPVARIANT *pActivationParams,
void **ppInterface)
{
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
// call original
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
// check for failure
if (FAILED(ret)) {
PRINT_FAILED_RESULT("IMMDevice::Activate", ret);
return ret;
}
// almost all games request IAudioClient
// so far we have not seen any games request IAudioClient2
// SDVX EG Final uses IID_IAudioClient3, but only when shared mode patch is on
if (iid == IID_IAudioClient || iid == IID_IAudioClient3) {
// prevent initialization recursion when using some ASIO backends that proxy to DirectSound, WASAPI, or WDM
// like ASIO4All or FlexASIO
if (!hooks::audio::INITIALIZE_LOCK.try_lock()) {
log_warning("audio::mmdevice", "ignoring wrap request while backend is initializing, possible recursion");
return ret;
}
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
// release old audio client if initialized
if (hooks::audio::CLIENT) {
hooks::audio::CLIENT->Release();
}
IAudioClient *client = nullptr;
if (iid == IID_IAudioClient) {
client = wrap_audio_client(reinterpret_cast<IAudioClient *>(*ppInterface));
} else { // IID_IAudioClient3
client = wrap_audio_client3(reinterpret_cast<IAudioClient3 *>(*ppInterface));
}
*ppInterface = client;
// persist the audio client
hooks::audio::CLIENT = client;
hooks::audio::CLIENT->AddRef();
} else if (iid == __uuidof(IAudioEndpointVolume) && hooks::audio::VOLUME_HOOK_ENABLED) {
*ppInterface = new WrappedIAudioEndpointVolume(reinterpret_cast<IAudioEndpointVolume *>(*ppInterface));
}
return ret;
}
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::OpenPropertyStore(DWORD stgmAccess, IPropertyStore **ppProperties) {
CHECK_RESULT(pReal->OpenPropertyStore(stgmAccess, ppProperties));
}
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::GetId(LPWSTR *ppstrId) {
CHECK_RESULT(pReal->GetId(ppstrId));
}
HRESULT STDMETHODCALLTYPE WrappedIMMDevice::GetState(DWORD *pdwState) {
CHECK_RESULT(pReal->GetState(pdwState));
}