Update to spice2x-26-02-17 (pre-apply)
> broken commit
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <msacm.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/time.h"
|
||||
#include "util/unique_plain_ptr.h"
|
||||
|
||||
#define ACM_CACHE_ENABLED 1
|
||||
#define ACM_DEBUG_PERF 0
|
||||
#define ACM_DEBUG_VERBOSE 0
|
||||
|
||||
#if ACM_DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
struct WAVE_FORMAT {
|
||||
uint32_t suggest;
|
||||
// for WAVEFORMATEX
|
||||
std::vector<uint8_t> wave_format_ex;
|
||||
|
||||
bool operator==(const WAVE_FORMAT& other) const {
|
||||
return (this->suggest == other.suggest &&
|
||||
this->wave_format_ex == other.wave_format_ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace robin_hood {
|
||||
template <>
|
||||
struct hash<hooks::audio::acm::WAVE_FORMAT> {
|
||||
std::size_t operator()(const hooks::audio::acm::WAVE_FORMAT& k) const noexcept {
|
||||
std::size_t h = robin_hood::hash_bytes(
|
||||
k.wave_format_ex.data(), k.wave_format_ex.size());
|
||||
|
||||
h = h ^ robin_hood::hash<uint32_t>{}(k.suggest) << 1;
|
||||
return h;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
|
||||
static decltype(acmFormatSuggest) *acmFormatSuggest_orig = nullptr;
|
||||
static std::mutex acm_formats_mutex;
|
||||
static robin_hood::unordered_map<WAVE_FORMAT, std::vector<uint8_t>> acm_formats;
|
||||
|
||||
// hooks calls to acmFormatSuggest and returns cached results from previous calls
|
||||
//
|
||||
// iidx calls this many times during song load and reload (function of # of keysounds)
|
||||
// - with near-identical arguments over and over again
|
||||
// (when hovering over song in song select, selecting a song, and exiting result screen)
|
||||
//
|
||||
// on Linux this results in ACM module load/unload which is very expensive,
|
||||
// resulting in significant perf improvement (seconds per song load);
|
||||
// on Windows this saves milliseconds per song, at best
|
||||
//
|
||||
// https://codeberg.org/nixac/spicetools/issues/2
|
||||
// https://codeberg.org/nixac/spicetools/issues/4
|
||||
MMRESULT
|
||||
acmFormatSuggest_cached (
|
||||
HACMDRIVER had,
|
||||
LPWAVEFORMATEX pwfxSrc,
|
||||
LPWAVEFORMATEX pwfxDst,
|
||||
DWORD cbwfxDst,
|
||||
DWORD fdwSuggest) {
|
||||
|
||||
if (had != 0 || cbwfxDst == 0) {
|
||||
return acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
}
|
||||
|
||||
WAVE_FORMAT key;
|
||||
key.suggest = fdwSuggest;
|
||||
const size_t src_size = sizeof(*pwfxSrc) + pwfxSrc->cbSize;
|
||||
key.wave_format_ex.insert(
|
||||
key.wave_format_ex.end(),
|
||||
reinterpret_cast<uint8_t *>(pwfxSrc),
|
||||
reinterpret_cast<uint8_t *>(pwfxSrc) + src_size);
|
||||
|
||||
std::lock_guard<std::mutex> lock(acm_formats_mutex);
|
||||
|
||||
MMRESULT mmresult = 0;
|
||||
|
||||
if (acm_formats.contains(key)) {
|
||||
// found in cache
|
||||
const auto &result = acm_formats.at(key);
|
||||
|
||||
if (cbwfxDst >= result.size()) {
|
||||
// dest buffer big enough, return cached result
|
||||
log_debug("audio::acm", "acmFormatSuggest cache hit, copying {} bytes", result.size());
|
||||
std::memcpy(reinterpret_cast<uint8_t *>(pwfxDst), result.data(), result.size());
|
||||
mmresult = 0;
|
||||
} else {
|
||||
// dest buffer not big enough, call original
|
||||
log_debug("audio::acm", "acmFormatSuggest cache fail; cbwfxDst too small ({})", cbwfxDst);
|
||||
mmresult = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
}
|
||||
|
||||
} else {
|
||||
log_debug("audio::acm", "acmFormatSuggest cache miss; calling original");
|
||||
mmresult = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
|
||||
// cache the result, but don't allow unconstrained growth
|
||||
if (mmresult == 0 && acm_formats.size() < 128) {
|
||||
const size_t dest_size = sizeof(*pwfxDst) + pwfxDst->cbSize;
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest cache add; current cache size {}, new data {} bytes",
|
||||
acm_formats.size(),
|
||||
dest_size);
|
||||
|
||||
acm_formats[key] = std::vector<uint8_t>();
|
||||
acm_formats[key].reserve(dest_size);
|
||||
acm_formats[key].insert(
|
||||
acm_formats[key].begin(),
|
||||
reinterpret_cast<uint8_t *>(pwfxDst),
|
||||
reinterpret_cast<uint8_t *>(pwfxDst) + dest_size);
|
||||
}
|
||||
}
|
||||
|
||||
return mmresult;
|
||||
}
|
||||
|
||||
MMRESULT
|
||||
ACMAPI
|
||||
acmFormatSuggest_hook (
|
||||
HACMDRIVER had,
|
||||
LPWAVEFORMATEX pwfxSrc,
|
||||
LPWAVEFORMATEX pwfxDst,
|
||||
DWORD cbwfxDst,
|
||||
DWORD fdwSuggest) {
|
||||
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest called: had={}, "
|
||||
"formattag={}, ch={}, samplespersec={}, bytespersec={}, blockalign={}, bits={}, extrasize={}",
|
||||
fmt::ptr(had),
|
||||
pwfxSrc->wFormatTag,
|
||||
pwfxSrc->nChannels,
|
||||
pwfxSrc->nSamplesPerSec,
|
||||
pwfxSrc->nAvgBytesPerSec,
|
||||
pwfxSrc->nBlockAlign,
|
||||
pwfxSrc->wBitsPerSample,
|
||||
pwfxSrc->cbSize);
|
||||
|
||||
#if ACM_DEBUG_PERF
|
||||
const auto start = get_performance_milliseconds();
|
||||
#endif
|
||||
|
||||
// make a call to acmFormatSuggest
|
||||
|
||||
#if ACM_CACHE_ENABLED
|
||||
const auto result = acmFormatSuggest_cached(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
#else
|
||||
const auto result = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
#endif
|
||||
|
||||
// log result
|
||||
|
||||
#if ACM_DEBUG_PERF
|
||||
const auto delta = get_performance_milliseconds() - start;
|
||||
static double delta_total = 0;
|
||||
delta_total += delta;
|
||||
|
||||
log_info(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest_hook returned {}, took {} us (running total {} ms)",
|
||||
result,
|
||||
delta * 1000,
|
||||
delta_total);
|
||||
#else
|
||||
log_debug("audio::acm", "acmFormatSuggest_hook returned {}", result);
|
||||
#endif
|
||||
|
||||
if (result == 0 && cbwfxDst > sizeof(WAVEFORMATEX)) {
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"....formattag={}, ch={}, samplespersec={}, bytespersec={}, blockalign={}, bits={}, extrasize={}",
|
||||
pwfxDst->wFormatTag,
|
||||
pwfxDst->nChannels,
|
||||
pwfxDst->nSamplesPerSec,
|
||||
pwfxDst->nAvgBytesPerSec,
|
||||
pwfxDst->nBlockAlign,
|
||||
pwfxDst->wBitsPerSample,
|
||||
pwfxDst->cbSize);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void init() {
|
||||
|
||||
// only enabled on Linux as the performance gains are negligible on Windows
|
||||
#if SPICE_LINUX
|
||||
|
||||
HMODULE msacm = libutils::try_library("msacm32.dll");
|
||||
if (msacm == nullptr) {
|
||||
log_info("audio::acm", "msacm32.dll failed to hook");
|
||||
return;
|
||||
}
|
||||
|
||||
acmFormatSuggest_orig = detour::iat_try(
|
||||
"acmFormatSuggest", acmFormatSuggest_hook, avs::game::DLL_INSTANCE);
|
||||
if (acmFormatSuggest_orig != nullptr) {
|
||||
log_misc("audio::acm", "acmFormatSuggest hooked");
|
||||
|
||||
#if !ACM_CACHE_ENABLED
|
||||
log_warning("audio::acm", "acmFormatSuggest cache DISABLED");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
void init();
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "util/memutils.h"
|
||||
|
||||
#include "audio_private.h"
|
||||
#include "acm.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||
@@ -101,6 +102,7 @@ namespace hooks::audio {
|
||||
|
||||
log_info("audio", "initializing");
|
||||
init_low_latency();
|
||||
hooks::audio::acm::init();
|
||||
|
||||
// general hooks
|
||||
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#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 { \
|
||||
@@ -56,6 +57,7 @@ ULONG STDMETHODCALLTYPE WrappedIMMDevice::Release() {
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
|
||||
if (refs == 0) {
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Release");
|
||||
delete this;
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
PROPVARIANT *pActivationParams,
|
||||
void **ppInterface)
|
||||
{
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate");
|
||||
log_misc("audio::mmdevice", "WrappedIMMDevice::Activate {}", guid2s(iid));
|
||||
|
||||
// call original
|
||||
HRESULT ret = pReal->Activate(iid, dwClsCtx, pActivationParams, ppInterface);
|
||||
@@ -80,7 +82,10 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (iid == IID_IAudioClient) {
|
||||
// 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
|
||||
@@ -90,23 +95,18 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDevice::Activate(
|
||||
}
|
||||
std::lock_guard initialize_guard(hooks::audio::INITIALIZE_LOCK, std::adopt_lock);
|
||||
|
||||
auto client = reinterpret_cast<IAudioClient *>(*ppInterface);
|
||||
|
||||
// release old audio client if initialized
|
||||
if (hooks::audio::CLIENT) {
|
||||
hooks::audio::CLIENT->Release();
|
||||
}
|
||||
|
||||
/*
|
||||
ret = wrap_audio_client(pReal, dwClsCtx, pActivationParams, &client);
|
||||
if (FAILED(ret)) {
|
||||
return ret;
|
||||
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));
|
||||
}
|
||||
*/
|
||||
client = wrap_audio_client(client);
|
||||
|
||||
*ppInterface = client;
|
||||
|
||||
// persist the audio client
|
||||
hooks::audio::CLIENT = client;
|
||||
hooks::audio::CLIENT->AddRef();
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "device_collection.h"
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
if (ppvObj == nullptr) {
|
||||
return E_POINTER;
|
||||
}
|
||||
if (riid == __uuidof(IMMDeviceCollection)) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
return S_OK;
|
||||
}
|
||||
return pReal->QueryInterface(riid, ppvObj);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::AddRef() {
|
||||
return pReal->AddRef();
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE WrappedIMMDeviceCollection::Release() {
|
||||
// get reference count of underlying interface
|
||||
ULONG refs = pReal != nullptr ? pReal->Release() : 0;
|
||||
if (refs == 0) {
|
||||
delete this;
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::GetCount(UINT *pcDevices) {
|
||||
return pReal->GetCount(pcDevices);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceCollection::Item(UINT nDevice, IMMDevice **ppDevice) {
|
||||
log_info("audio", "WrappedIMMDeviceCollection::Item[{}]", nDevice);
|
||||
|
||||
// call original
|
||||
const auto hr = pReal->Item(nDevice, ppDevice);
|
||||
|
||||
// wrap interface
|
||||
*ppDevice = new WrappedIMMDevice(*ppDevice);
|
||||
return hr;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <initguid.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
struct WrappedIMMDeviceCollection : IMMDeviceCollection {
|
||||
explicit WrappedIMMDeviceCollection(IMMDeviceCollection *orig) : pReal(orig) {
|
||||
}
|
||||
|
||||
WrappedIMMDeviceCollection(const WrappedIMMDeviceCollection &) = delete;
|
||||
WrappedIMMDeviceCollection &operator=(const WrappedIMMDeviceCollection &) = delete;
|
||||
|
||||
virtual ~WrappedIMMDeviceCollection() = default;
|
||||
|
||||
#pragma region IUnknown
|
||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObj) override;
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() override;
|
||||
virtual ULONG STDMETHODCALLTYPE Release() override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IMMDeviceCollection
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *pcDevices) override;
|
||||
virtual HRESULT STDMETHODCALLTYPE Item(UINT nDevice, IMMDevice **ppDevice) override;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
IMMDeviceCollection *const pReal;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "device_enumerator.h"
|
||||
#include "device_collection.h"
|
||||
#include "device.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
@@ -42,7 +43,11 @@ HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::EnumAudioEndpoints(
|
||||
DWORD dwStateMask,
|
||||
IMMDeviceCollection **ppDevices)
|
||||
{
|
||||
return pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
|
||||
const auto hr = pReal->EnumAudioEndpoints(dataFlow, dwStateMask, ppDevices);
|
||||
if (SUCCEEDED(hr) && (ppDevices != nullptr) && (*ppDevices != nullptr)) {
|
||||
*ppDevices = new WrappedIMMDeviceCollection(*ppDevices);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIMMDeviceEnumerator::GetDefaultAudioEndpoint(
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "hooks/audio/implementations/asio.h"
|
||||
#include "hooks/audio/implementations/wave_out.h"
|
||||
//#include "util/co_task_mem_ptr.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "defs.h"
|
||||
#include "dummy_audio_client.h"
|
||||
@@ -40,114 +41,9 @@ static void fix_rec_format(WAVEFORMATEX *pFormat) {
|
||||
pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nBlockAlign;
|
||||
}
|
||||
|
||||
// TODO(felix): is it appropriate to automatically switch to shared mode? should we do a
|
||||
// `MessageBox` to notify the user?
|
||||
/*
|
||||
static bool check_for_exclusive_access(IAudioClient *client) {
|
||||
static bool checked_once = false;
|
||||
static bool previous_check_result = false;
|
||||
|
||||
CoTaskMemPtr<WAVEFORMATEX> mix_format;
|
||||
REFERENCE_TIME requested_duration = 0;
|
||||
|
||||
if (checked_once) {
|
||||
return previous_check_result;
|
||||
}
|
||||
if (audio::BACKEND.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// scope function so it has access to the local static variables
|
||||
auto set_result = [](bool result) {
|
||||
checked_once = true;
|
||||
previous_check_result = result;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
HRESULT ret = client->GetMixFormat(mix_format.ppv());
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetMixFormat", ret);
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "Mix Format:");
|
||||
print_format(mix_format.data());
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (ret == AUDCLNT_E_UNSUPPORTED_FORMAT) {
|
||||
auto mix_format_ex = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.data());
|
||||
|
||||
log_warning("audio::wasapi", "device does not natively support the mix format, converting to PCM");
|
||||
|
||||
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE &&
|
||||
IsEqualGUID(GUID_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, mix_format_ex->SubFormat))
|
||||
{
|
||||
mix_format_ex->Format.wBitsPerSample = 16;
|
||||
mix_format_ex->Format.nBlockAlign = mix_format_ex->Format.nChannels * (mix_format_ex->Format.wBitsPerSample / 8);
|
||||
mix_format_ex->Format.nAvgBytesPerSec = mix_format_ex->Format.nSamplesPerSec * mix_format_ex->Format.nBlockAlign;
|
||||
mix_format_ex->Samples.wValidBitsPerSample = 16;
|
||||
mix_format_ex->SubFormat = GUID_KSDATAFORMAT_SUBTYPE_PCM;
|
||||
} else if (mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
|
||||
mix_format->wBitsPerSample = 16;
|
||||
mix_format->nBlockAlign = mix_format->nChannels * (mix_format->wBitsPerSample / 8);
|
||||
mix_format->nAvgBytesPerSec = mix_format->nSamplesPerSec * mix_format->nBlockAlign;
|
||||
mix_format->wFormatTag = WAVE_FORMAT_PCM;
|
||||
} else {
|
||||
log_warning("audio::wasapi", "mix format is not a floating point format");
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
ret = client->IsFormatSupported(AUDCLNT_SHAREMODE_EXCLUSIVE, mix_format.data(), nullptr);
|
||||
if (FAILED(ret)) {
|
||||
log_warning("audio::wasapi", "mix format is not supported");
|
||||
return set_result(false);
|
||||
}
|
||||
}
|
||||
|
||||
ret = client->GetDevicePeriod(nullptr, &requested_duration);
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient::GetDevicePeriod", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = client->Initialize(
|
||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||
requested_duration,
|
||||
requested_duration,
|
||||
mix_format.data(),
|
||||
nullptr);
|
||||
|
||||
if (ret == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED || SUCCEEDED(ret)) {
|
||||
log_info("audio::wasapi", "exclusive mode is available, disabling backend");
|
||||
return set_result(true);
|
||||
} else {
|
||||
log_warning("audio::wasapi", "exclusive mode is not available, enabling backend, hr={}", FMT_HRESULT(ret));
|
||||
}
|
||||
|
||||
return set_result(false);
|
||||
}
|
||||
|
||||
HRESULT wrap_audio_client(
|
||||
IMMDevice *device,
|
||||
DWORD cls_ctx,
|
||||
PROPVARIANT *activation_params,
|
||||
IAudioClient **audio_client)
|
||||
{
|
||||
auto exclusive_available = check_for_exclusive_access(*audio_client);
|
||||
(*audio_client)->Stop();
|
||||
(*audio_client)->Reset();
|
||||
(*audio_client)->Release();
|
||||
*audio_client = nullptr;
|
||||
|
||||
SAFE_CALL("IMMDevice", "Activate", device->Activate(
|
||||
IID_IAudioClient,
|
||||
cls_ctx,
|
||||
activation_params,
|
||||
reinterpret_cast<void **>(audio_client)));
|
||||
*/
|
||||
IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient");
|
||||
|
||||
AudioBackend *backend = nullptr;
|
||||
bool requires_dummy = false;
|
||||
|
||||
@@ -164,9 +60,6 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
//} else if (!exclusive_available) {
|
||||
// backend = new WaveOutBackend();
|
||||
//}
|
||||
|
||||
IAudioClient *new_client;
|
||||
|
||||
@@ -182,6 +75,23 @@ IAudioClient *wrap_audio_client(IAudioClient *audio_client) {
|
||||
|
||||
return new_client;
|
||||
}
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *audio_client) {
|
||||
// TODO: ASIO backend for IAudioClient3, if there is a game that needs it
|
||||
log_misc("audio::wasapi", "wrapping IAudioClient3");
|
||||
|
||||
if (hooks::audio::BACKEND.has_value()) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support backends! clear -audiobackend and try again");
|
||||
}
|
||||
if (hooks::audio::USE_DUMMY) {
|
||||
log_fatal(
|
||||
"audio::wasapi",
|
||||
"IAudioClient3 does not currently support dummy context, clear -audiodummy and try again");
|
||||
}
|
||||
|
||||
return new WrappedIAudioClient(audio_client, nullptr);
|
||||
}
|
||||
|
||||
// IUnknown
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void **ppvObj) {
|
||||
@@ -190,8 +100,8 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::QueryInterface(REFIID riid, void
|
||||
}
|
||||
|
||||
if (riid == IID_WrappedIAudioClient ||
|
||||
riid == IID_IAudioClient)
|
||||
{
|
||||
riid == IID_IAudioClient ||
|
||||
riid == IID_IAudioClient3) {
|
||||
this->AddRef();
|
||||
*ppvObj = this;
|
||||
|
||||
@@ -282,20 +192,6 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::Initialize(
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient::Initialize success, hr={}", FMT_HRESULT(ret));
|
||||
|
||||
/*
|
||||
if (ShareMode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
IAudioClockAdjustment *clock = nullptr;
|
||||
|
||||
SAFE_CALL("IAudioClient", "GetService", pReal->GetService(
|
||||
IID_IAudioClockAdjustment,
|
||||
reinterpret_cast<void **>(&clock)));
|
||||
|
||||
SAFE_CALL("IAudioClockAdjustment", "SetSampleRate", clock->SetSampleRate(
|
||||
static_cast<float>(pFormat->nSamplesPerSec)));
|
||||
}
|
||||
*/
|
||||
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
|
||||
return ret;
|
||||
@@ -482,3 +378,93 @@ HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetService(REFIID riid, void **pp
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) {
|
||||
WRAP_VERBOSE;
|
||||
|
||||
CHECK_RESULT(pReal3->IsOffloadCapable(Category, pbOffloadCapable));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::SetClientProperties(
|
||||
const AudioClientProperties *pProperties) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->SetClientProperties(pProperties));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetBufferSizeLimits(
|
||||
pFormat,
|
||||
bEventDriven,
|
||||
phnsMinBufferDuration,
|
||||
phnsMaxBufferDuration));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetSharedModeEnginePeriod(
|
||||
pFormat,
|
||||
pDefaultPeriodInFrames,
|
||||
pFundamentalPeriodInFrames,
|
||||
pMinPeriodInFrames,
|
||||
pMaxPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) {
|
||||
|
||||
WRAP_VERBOSE;
|
||||
CHECK_RESULT(pReal3->GetCurrentSharedModeEnginePeriod(
|
||||
ppFormat,
|
||||
pCurrentPeriodInFrames));
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE WrappedIAudioClient::InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) {
|
||||
|
||||
if (!pFormat) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
// verbose output
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream hook hit");
|
||||
log_info("audio::wasapi", "... ShareMode : {}", share_mode_str(AUDCLNT_SHAREMODE_SHARED));
|
||||
log_info("audio::wasapi", "... StreamFlags : {}", stream_flags_str(StreamFlags));
|
||||
log_info("audio::wasapi", "... PeriodInFrames : {}", PeriodInFrames);
|
||||
print_format(pFormat);
|
||||
|
||||
// call next
|
||||
HRESULT ret = pReal3->InitializeSharedAudioStream(
|
||||
StreamFlags,
|
||||
PeriodInFrames,
|
||||
pFormat,
|
||||
AudioSessionGuid);
|
||||
|
||||
// check for failure
|
||||
if (FAILED(ret)) {
|
||||
PRINT_FAILED_RESULT("IAudioClient3", "InitializeSharedAudioStream", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_info("audio::wasapi", "IAudioClient3::InitializeSharedAudioStream success, hr={}", FMT_HRESULT(ret));
|
||||
copy_wave_format(&hooks::audio::FORMAT, pFormat);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -16,9 +16,16 @@ static const GUID IID_WrappedIAudioClient = {
|
||||
};
|
||||
|
||||
IAudioClient *wrap_audio_client(IAudioClient *client);
|
||||
IAudioClient3 *wrap_audio_client3(IAudioClient3 *client);
|
||||
|
||||
struct WrappedIAudioClient : IAudioClient {
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) : pReal(orig), backend(backend) {
|
||||
struct WrappedIAudioClient : IAudioClient3 {
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient3 *orig3, AudioBackend *backend) :
|
||||
pReal(orig3), pReal3(orig3), backend(backend) {
|
||||
}
|
||||
|
||||
explicit WrappedIAudioClient(IAudioClient *orig, AudioBackend *backend) :
|
||||
pReal(orig), pReal3(nullptr), backend(backend) {
|
||||
}
|
||||
|
||||
WrappedIAudioClient(const WrappedIAudioClient &) = delete;
|
||||
@@ -47,9 +54,43 @@ struct WrappedIAudioClient : IAudioClient {
|
||||
HRESULT STDMETHODCALLTYPE GetService(REFIID riid, void **ppv) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
AudioBackend *const backend;
|
||||
#pragma region IAudioClient2
|
||||
HRESULT STDMETHODCALLTYPE IsOffloadCapable(
|
||||
AUDIO_STREAM_CATEGORY Category,
|
||||
BOOL *pbOffloadCapable) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE SetClientProperties(
|
||||
const AudioClientProperties *pProperties) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
BOOL bEventDriven,
|
||||
REFERENCE_TIME *phnsMinBufferDuration,
|
||||
REFERENCE_TIME *phnsMaxBufferDuration) override;
|
||||
#pragma endregion
|
||||
|
||||
#pragma region IAudioClient3
|
||||
HRESULT STDMETHODCALLTYPE GetSharedModeEnginePeriod(
|
||||
const WAVEFORMATEX *pFormat,
|
||||
UINT32 *pDefaultPeriodInFrames,
|
||||
UINT32 *pFundamentalPeriodInFrames,
|
||||
UINT32 *pMinPeriodInFrames,
|
||||
UINT32 *pMaxPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetCurrentSharedModeEnginePeriod(
|
||||
WAVEFORMATEX **ppFormat,
|
||||
UINT32 *pCurrentPeriodInFrames) override;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE InitializeSharedAudioStream(
|
||||
DWORD StreamFlags,
|
||||
UINT32 PeriodInFrames,
|
||||
const WAVEFORMATEX *pFormat,
|
||||
LPCGUID AudioSessionGuid) override;
|
||||
#pragma endregion
|
||||
|
||||
IAudioClient *const pReal;
|
||||
IAudioClient3 *const pReal3;
|
||||
AudioBackend *const backend;
|
||||
bool exclusive_mode = false;
|
||||
int frame_size = 0;
|
||||
};
|
||||
|
||||
@@ -149,5 +149,12 @@ LowLatencyAudioClient *LowLatencyAudioClient::Create(IMMDevice *device) {
|
||||
log_info("audio::lowlatency", "... max buffer size : {} samples ({} ms)", maxPeriod, 1000.0f * maxPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... default buffer size : {} samples ({} ms)", defaultPeriod, 1000.0f * defaultPeriod / pFormat->nSamplesPerSec);
|
||||
log_info("audio::lowlatency", "... Windows will use minimum buffer size (instead of default) for shared mode audio clients from now on");
|
||||
if (minPeriod < defaultPeriod) {
|
||||
log_info("audio::lowlatency", "minimum period is less than default period, you will see latency improvement");
|
||||
} else {
|
||||
log_warning("audio::lowlatency", "minimum period is not less than default period, you will see NO latency improvement");
|
||||
log_warning("audio::lowlatency", "this an audio driver / hardware limitation");
|
||||
}
|
||||
|
||||
return new LowLatencyAudioClient(audioClient);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "mme.h"
|
||||
#include <mmeapi.h>
|
||||
|
||||
#include "util/detour.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace hooks::audio::mme {
|
||||
|
||||
static decltype(mixerSetControlDetails) *mixerSetControlDetails_orig = nullptr;
|
||||
|
||||
MMRESULT
|
||||
WINAPI
|
||||
mixerSetControlDetails_hook(
|
||||
HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) {
|
||||
log_misc("audio::mme", "mixerSetControlDetails_hook called; ignoring volume change");
|
||||
return MMSYSERR_NOERROR;
|
||||
}
|
||||
|
||||
void init(HINSTANCE module) {
|
||||
mixerSetControlDetails_orig =
|
||||
detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, module);
|
||||
|
||||
if (mixerSetControlDetails_orig != nullptr) {
|
||||
log_misc("audio::mme", "mixerSetControlDetails hooked");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace hooks::audio::mme {
|
||||
void init(HINSTANCE module);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ std::string share_mode_str(AUDCLNT_SHAREMODE share_mode) {
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_SHARED);
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_EXCLUSIVE);
|
||||
default:
|
||||
return fmt::format("ShareMode(0x{:08x})", share_mode);
|
||||
return fmt::format("ShareMode(0x{:08x})", static_cast<uint32_t>(share_mode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user