Recover a guessed stream's channels + bit depth by correlation too (step b)
Extends the two-path correlation from rate-only to the full layout, removing the "channels/bit-depth assumed = device" limitation. correlate_format tries each candidate de-interleaving (float32 / int16; mono..7.1) of the hook capture, runs the rate correlation per layout, and keeps whichever aligns with the loopback; a wrong de-interleaving is noise and won't. The catch: the hook can't know a guessed stream's real frame size, so its verify tap pads each render buffer to the device block -- which over-reads stale staging bytes for a stream with fewer channels/bits, scrambling the audio. So the tap is now self-describing: it prefixes each buffer with its frame count ([count][count*device_block bytes]), and the host strips the padding per candidate layout (take the real count*real_block of each chunk) before de-interleaving. - audio_correlate.hpp: ChunkedCapture + chunk-aware correlate_format + candidate layouts; absolute-margin confidence gate (the true layout scores ~1.0, a truly ambiguous alternative within ~0.001 -- 2ch@R == 1ch@2R for identical channels -- is correctly left unconfident). - audio_hook.cpp: chunked verify tap (free-space-checked so framing can't tear). - audio_format_verifier: parse chunks; recover_layout path. AudioMirror now corrects the full format. - audio_correlation_test: layout recovery from padded chunks (stereo float, 16-bit PCM, 5.1, mono). audio_verify_test gains scenario (b): 2ch on a multichannel endpoint with distinct per-channel content (new env-gated ToneSource mode) -> recovers ch=2/32-bit float end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
@@ -87,6 +88,13 @@ public:
|
||||
client_->GetBufferSize(&buffer_frames_);
|
||||
|
||||
step_ = kTwoPi * freq_hz / static_cast<double>(fmt_.rate);
|
||||
// Optional: give each channel genuinely different content (a per-channel frequency scale),
|
||||
// so a downstream test can *recover* the channel count by correlation (identical channels
|
||||
// are ambiguous: 2ch@R looks like 1ch@2R). Off by default -> the usual single-tone source.
|
||||
if (const char* d = std::getenv("COOP_TONE_DISTINCT_CH"); d != nullptr && d[0] == '1')
|
||||
{
|
||||
distinct_ = true;
|
||||
}
|
||||
write(buffer_frames_); // pre-roll
|
||||
client_->Start();
|
||||
return true;
|
||||
@@ -220,13 +228,24 @@ private:
|
||||
}
|
||||
for (unsigned c = 0; c < fmt_.channels; ++c)
|
||||
{
|
||||
double sc = s;
|
||||
if (distinct_ && c < 8)
|
||||
{
|
||||
// Each channel at its own frequency scale -> genuinely different content.
|
||||
sc = std::sin(phase_c_[c]) * 0.25;
|
||||
phase_c_[c] += step_ * (1.0 + 0.37 * static_cast<double>(c));
|
||||
if (phase_c_[c] > kTwoPi)
|
||||
{
|
||||
phase_c_[c] -= kTwoPi;
|
||||
}
|
||||
}
|
||||
if (float_)
|
||||
{
|
||||
reinterpret_cast<float*>(data)[i * fmt_.channels + c] = static_cast<float>(s);
|
||||
reinterpret_cast<float*>(data)[i * fmt_.channels + c] = static_cast<float>(sc);
|
||||
}
|
||||
else
|
||||
{
|
||||
reinterpret_cast<INT16*>(data)[i * fmt_.channels + c] = static_cast<INT16>(s * 32767.0);
|
||||
reinterpret_cast<INT16*>(data)[i * fmt_.channels + c] = static_cast<INT16>(sc * 32767.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,6 +262,8 @@ private:
|
||||
bool float_ = false;
|
||||
double phase_ = 0.0;
|
||||
double step_ = 0.0;
|
||||
bool distinct_ = false; // per-channel distinct content (recoverable channel count)
|
||||
double phase_c_[8] = {}; // per-channel phase when distinct_
|
||||
};
|
||||
|
||||
} // namespace coop::tone
|
||||
|
||||
Reference in New Issue
Block a user