Audio render-hook M3: wire DLL + host hooked mode + loopback fallback
End-to-end plumbing of the render-hook audio path. Hook (coop_hook.dll): - CMake: build audio_hook.cpp, link ole32/mmdevapi, NTDDI_WIN10_CO. - dllmain: CoInitializeEx(MTA) on the worker thread; install the audio hooks even before the ring exists (so streams are counted); open the host's coop_audio_<pid> ring when it appears and attach it (enabling capture+silence); remove_audio_hooks on clean detach. Host (coop_host.exe): - AudioMirror now creates the shared audio ring (owns capture_enabled) and tries the Hooked path first: waits ~1s for the hook to publish a format, then re-renders the game's frames from the ring with AUTOCONVERTPCM (no echo, since the hook silences the game locally). - Automatic fallback: if the ring can't be created, no format arrives in time, or the render client won't initialize, it disables capture (so the game stays audible) and reverts to the existing process-loopback path (echo, no regress). - Exposes Source (Hooked/Loopback/None) for the upcoming panel indicator. Loopback render loop kept intact as run_loopback. Manual end-to-end (M5) and the panel UI (M4) are next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,11 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <objbase.h>
|
||||
|
||||
#include "audio_hook.hpp"
|
||||
#include "coop/audio_ring.hpp"
|
||||
#include "coop/shared_memory.hpp"
|
||||
#include "focus_spoof.hpp"
|
||||
#include "ipc_client.hpp"
|
||||
#include "xinput_hook.hpp"
|
||||
@@ -19,6 +24,7 @@ namespace
|
||||
|
||||
coop::hook::IpcClient g_ipc;
|
||||
std::atomic<bool> g_running{true};
|
||||
coop::SharedMemory g_audio_shm; // the host's audio ring, opened when present
|
||||
|
||||
DWORD WINAPI worker_thread(LPVOID)
|
||||
{
|
||||
@@ -28,8 +34,13 @@ DWORD WINAPI worker_thread(LPVOID)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The audio render-hook instantiates a COM enumerator on this thread.
|
||||
const bool com_ok = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
|
||||
|
||||
bool xinput_installed = false;
|
||||
bool focus_installed = false;
|
||||
bool audio_installed = false;
|
||||
bool audio_ring_open = false;
|
||||
|
||||
// Keep retrying the installs (XInput and the game window may both appear
|
||||
// lazily) and beat a heartbeat so the host can show the hook is alive.
|
||||
@@ -43,10 +54,39 @@ DWORD WINAPI worker_thread(LPVOID)
|
||||
{
|
||||
focus_installed = coop::hook::install_focus_spoof(g_ipc);
|
||||
}
|
||||
// Install the audio render-hook even before the host's ring exists, so
|
||||
// render streams are counted for the debug view regardless; attach the
|
||||
// ring (enabling capture+silence) once the host creates it.
|
||||
if (com_ok && !audio_installed)
|
||||
{
|
||||
audio_installed = coop::hook::install_audio_hooks(g_ipc, nullptr);
|
||||
}
|
||||
if (audio_installed && !audio_ring_open)
|
||||
{
|
||||
const std::wstring name = coop::audio_ring_name(GetCurrentProcessId());
|
||||
if (g_audio_shm.open(name, coop::audio_ring_total_size(coop::kAudioRingCapacity)))
|
||||
{
|
||||
auto* ring = g_audio_shm.as<coop::AudioRingHeader>();
|
||||
if (coop::audio_ring_valid(*ring))
|
||||
{
|
||||
coop::hook::set_audio_ring(ring);
|
||||
audio_ring_open = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
g_audio_shm.reset(); // present but not our contract; retry
|
||||
}
|
||||
}
|
||||
}
|
||||
coop::hook::update_input_diagnostics(g_ipc); // refreshes each tick; registrations can change
|
||||
g_ipc.heartbeat();
|
||||
Sleep(250);
|
||||
}
|
||||
|
||||
if (com_ok)
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -71,6 +111,7 @@ BOOL APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
|
||||
g_running.store(false, std::memory_order_relaxed);
|
||||
coop::hook::remove_focus_spoof();
|
||||
coop::hook::remove_xinput_hooks();
|
||||
coop::hook::remove_audio_hooks();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user