Recover a guessed audio stream's rate by correlating hook vs loopback (step a)
When the host attaches to an already-running game it never saw the stream's Initialize, so the render-hook assumes the device mix format and measures only the sample rate from the render cadence -- which a jittery game can make wrong (intermittent pitch shift). But during the measurement window the game is still audible, so we have the same audio twice: the hook (pre-mix, unknown format) and a process-loopback (post-mix, the known device format). Cross-correlating them pins the true rate from ground truth. - common/include/coop/audio_correlate.hpp: the pure correlator. Resample the hook by each candidate standard rate up to the device rate and score how well it aligns with the loopback across the window (drift-detecting). audio_correlation_test recovers every rate (score ~1.0 vs ~0.01 for wrong ones), incl. 44100-vs-48000, and rejects unrelated signals. - Hook measurement tap: a host-set verify_capture ring flag makes the hook push a still-being-measured (guessed) stream's raw pre-mix bytes WITHOUT silencing, so the host can co-capture both signals (a silenced game's loopback is silent). Inert by default -- the shipping no-echo path is untouched. - host/src/audio/audio_format_verifier: co-captures hook + loopback and correlates, feeding a correction into the existing override channel. Wired into AudioMirror's measurement window (hidden in the gap loopback already covers, so exact streams pay nothing). audio_verify_test drives it end-to-end against coop_mock_game. Rate vs layout are coupled (correlating the waveform needs the right channel de-interleaving), so this step assumes the hook layout matches the device (the common stereo-on-stereo case); recovering a different channel count / bit depth is step b. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -87,7 +87,16 @@ struct AudioRingHeader
|
||||
std::uint32_t op_bits; // override: bits per sample
|
||||
std::uint32_t op_format_tag; // override: WAVE_FORMAT_PCM / _IEEE_FLOAT
|
||||
|
||||
std::uint8_t reserved[40];
|
||||
// Host -> hook: format-verification co-capture. While 1, the hook pushes a still-being-measured
|
||||
// (guessed) stream's raw pre-mix bytes into the ring WITHOUT silencing the game, so the host can
|
||||
// capture both the hook (pre-mix) and a parallel process-loopback (post-mix) of the same audio
|
||||
// and cross-correlate them to recover the true sample rate (and, in step b, channels/bit-depth)
|
||||
// from ground truth instead of guessing. Inert (0) by default -- normal capture is unaffected,
|
||||
// so it never changes the shipping no-echo path. Repurposed from `reserved`, so the layout and
|
||||
// size are unchanged (old builds saw it as a zero reserved byte).
|
||||
std::atomic<std::uint32_t> verify_capture;
|
||||
|
||||
std::uint8_t reserved[36];
|
||||
|
||||
// std::uint8_t data[capacity] follows immediately in the mapping.
|
||||
};
|
||||
@@ -131,6 +140,7 @@ inline void audio_ring_init(AudioRingHeader& h, std::uint32_t capacity)
|
||||
h.op_channels = 0;
|
||||
h.op_bits = 0;
|
||||
h.op_format_tag = 0;
|
||||
h.verify_capture.store(0, std::memory_order_relaxed);
|
||||
std::memset(h.reserved, 0, sizeof(h.reserved));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user