Capture every audio stream into its own ring and mix them on the host

Games with several concurrent WASAPI render streams (e.g. Spider-Man: Miles
Morales) only had their first ("primary") stream mirrored; the rest kept playing
locally and never reached the guest. Now the render-hook captures + silences EVERY
tracked stream into its own ring (coop_audio_<pid>[_<index>]), each published with
that stream's own detected format (Initialize when caught, else GetMixFormat -- the
per-stream format detection, now actually used per ring rather than only for the
primary). The host creates a ring per stream and mixes the same-format streams with
a soft clip (host/src/audio/audio_mix.hpp); streams whose format differs from the
primary are still silenced (no echo) but skipped from the mix (would need
resampling).

The single-stream case is byte-for-byte unchanged: when only one stream is active
the host passes it through without the mixer, so the common path has no overhead or
fidelity change.

Verified: new audio_mix_test covers the decode/sum/soft-clip/encode math (float32 +
int16); audio_hook_test (x64 + x86) still passes, guarding the primary
capture+silence path against regression; full build x64 + x86 clean; ctest x64
11/11, x86 3/3. Multi-stream mixing against a real multi-stream game needs a live
session to fully confirm.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 05:45:47 +02:00
parent dfd2be8c17
commit 784c31a9b5
10 changed files with 412 additions and 147 deletions

View File

@@ -185,10 +185,18 @@ inline std::uint32_t audio_ring_pop(AudioRingHeader& h, void* dst, std::uint32_t
return bytes;
}
// Build the per-pid audio ring name both sides agree on.
inline std::wstring audio_ring_name(unsigned long target_pid)
// Build the per-pid audio ring name both sides agree on. Stream 0 keeps the bare
// coop_audio_<pid> name (backward compatible / the single-stream case); additional
// streams append _<index> (coop_audio_<pid>_1, _2, ...). The host captures every
// render stream into its own ring and mixes them.
inline std::wstring audio_ring_name(unsigned long target_pid, unsigned index = 0)
{
return std::wstring(kAudioRingPrefix) + std::to_wstring(target_pid);
std::wstring name = std::wstring(kAudioRingPrefix) + std::to_wstring(target_pid);
if (index != 0)
{
name += L"_" + std::to_wstring(index);
}
return name;
}
} // namespace coop