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:
@@ -1,9 +1,13 @@
|
||||
// Mirrors a target process's audio so Steam Remote Play Together (which streams
|
||||
// THIS host's audio) carries the real game's sound. Captures the game via WASAPI
|
||||
// process loopback and re-renders it on the default output endpoint.
|
||||
// THIS host's audio) carries the real game's sound, re-rendering it on the
|
||||
// default output endpoint.
|
||||
//
|
||||
// The real game keeps playing locally too, so the host hears it twice ("double
|
||||
// audio"); that's an accepted trade-off for now (see plan, Phase 2).
|
||||
// Two source paths (see docs/audio-render-hook-plan.md):
|
||||
// - Hooked: the injected render-hook copies the game's frames into a shared
|
||||
// audio ring AND silences the game locally, so there is no echo. Preferred.
|
||||
// - Loopback: WASAPI process-loopback capture of the game (the game still
|
||||
// plays locally, so the operator hears it twice). Automatic fallback when
|
||||
// the hook isn't present / doesn't publish a format in time.
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
@@ -13,6 +17,9 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "coop/audio_ring.hpp"
|
||||
#include "coop/shared_memory.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
|
||||
@@ -54,17 +61,50 @@ public:
|
||||
return channels_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Which capture path is active, for the UI's source indicator.
|
||||
enum class Source
|
||||
{
|
||||
None,
|
||||
Hooked, // shared audio ring from the render-hook (no echo)
|
||||
Loopback, // WASAPI process loopback (echo)
|
||||
};
|
||||
[[nodiscard]] Source source() const
|
||||
{
|
||||
return source_.load(std::memory_order_relaxed);
|
||||
}
|
||||
[[nodiscard]] const char* source_name() const
|
||||
{
|
||||
switch (source())
|
||||
{
|
||||
case Source::Hooked:
|
||||
return "Hooked (no echo)";
|
||||
case Source::Loopback:
|
||||
return "Loopback (echo)";
|
||||
default:
|
||||
return "—";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string status() const;
|
||||
|
||||
private:
|
||||
void thread_main(DWORD pid);
|
||||
// Returns true if it owned the session to a clean stop; false if setup failed
|
||||
// and the caller should fall back to the loopback path.
|
||||
bool run_hooked(AudioRingHeader* ring);
|
||||
void run_loopback(DWORD pid);
|
||||
bool wait_for_format(AudioRingHeader* ring, DWORD timeout_ms);
|
||||
bool stop_requested() const;
|
||||
void set_status(std::string s);
|
||||
|
||||
std::thread thread_;
|
||||
HANDLE stop_event_ = nullptr;
|
||||
DWORD pid_ = 0;
|
||||
|
||||
SharedMemory audio_ring_shm_; // host-created shared ring (named coop_audio_<pid>)
|
||||
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<Source> source_{Source::None};
|
||||
std::atomic<unsigned> sample_rate_{0};
|
||||
std::atomic<unsigned> channels_{0};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user