mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-08-27 21:19:56 +00:00
test(lxst): bidirectional audio content-fidelity validation
Extends the LXST harness from frame-flow only (#62) and decoder QoS only (#63) to a full content-fidelity test: a known 1kHz sine wave flows in BOTH directions through the Codec2-3200 round-trip, and the harness asserts the decoded RMS at each end matches expected energy within tolerance (after Codec2's lossy speech-codec behavior). Firmware additions (under PYXIS_TEST_HOOKS): I2SCapture setInjectSine(enabled, freq=1000, amp=0.5) Replaces mic input with a phase-continuous synthesized sine. Bypasses ES7210 capture and the voice filter chain so the encoder sees pure samples — peer's decoded RMS validates that pyxis's TX path delivers content. I2SPlayback pcmSampleCount(), pcmSumSquares() Decoded-PCM energy accumulators, fed from each successful Codec2 decode. uint64 sumsq holds ~2³⁴ frames before overflow, far longer than any test call. LXSTAudio + UIManager (test-only) captureSetInjectSine, playbackPcmSampleCount, playbackPcmSumSquares test_call_set_inject_sine, test_call_pcm_sample_count, test_call_pcm_sum_squares Serial T: hooks (main.cpp) T:CALL_INJECT <on|off> [freq] [amp_pct] Drive the capture-side injection from the harness. T:CALL_QOS now also returns pcm_n + pcm_ss Harness divides + sqrts to RMS for content validation. Validated with /tmp/lxst_call_harness.py + /tmp/lxst_call_bot.py (scripts vault-local per the no-PII rule): pyxis_rms = 6363, bot_rms = 1930, decode_fail = 0 PASS: bidirectional audio + content-fidelity validated The empirical RMS floor is 800 (pycodec2 self-test on 1kHz amp 0.5 yields ~1400; pyxis decoder hits ~6300; bot decoder hits ~1900 — all far above the ~5-50 silence floor). Codec2 is a speech codec so pure-tone round-trip is naturally lossy; the test gates on "audio bytes carry actual content energy", not lossless round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
8b608ec258
commit
12fd28b67c
+40
-4
@@ -1845,18 +1845,54 @@ static void handle_test_hook_command(const String& line) {
|
||||
// T:CALL_QOS — wire-level audio fidelity counters from the
|
||||
// playback decode path. decode_ok = frames Codec2 successfully
|
||||
// decoded into PCM; decode_fail = frames it rejected (bad mode
|
||||
// header, corrupt subframe, internal codec error). With a
|
||||
// well-behaved peer all received frames decode_ok and
|
||||
// decode_fail stays 0 — a non-zero fail count points at
|
||||
// the peer's encoder OR network corruption.
|
||||
// header, corrupt subframe, internal codec error). pcm_n /
|
||||
// pcm_ss = sample count + cumulative sum-of-squares the harness
|
||||
// divides into RMS for content-level validation. With a peer
|
||||
// injecting a 1kHz sine at peak P the expected RMS = P/√2.
|
||||
if (!ui_manager) { Serial.println("T:ERR no ui_manager"); return; }
|
||||
Serial.print("T:OK decode_ok=");
|
||||
Serial.print((unsigned long)ui_manager->test_call_decode_ok());
|
||||
Serial.print(" decode_fail=");
|
||||
Serial.print((unsigned long)ui_manager->test_call_decode_fail());
|
||||
Serial.print(" pcm_n=");
|
||||
Serial.print((unsigned long)ui_manager->test_call_pcm_sample_count());
|
||||
Serial.print(" pcm_ss=");
|
||||
Serial.print((unsigned long long)ui_manager->test_call_pcm_sum_squares());
|
||||
Serial.print(" state=");
|
||||
Serial.println(ui_manager->test_call_state_name());
|
||||
}
|
||||
else if (cmd == "T:CALL_INJECT") {
|
||||
// T:CALL_INJECT <on|off> [freq_hz] [amp_pct]
|
||||
// Replace mic capture with a synthesized sine wave for the
|
||||
// active call (bypasses ES7210 + voice filters). The bot
|
||||
// decodes pyxis's audio packets and computes RMS over the
|
||||
// decoded PCM — should match the expected sine energy.
|
||||
if (!ui_manager) { Serial.println("T:ERR no ui_manager"); return; }
|
||||
int sp = args.indexOf(' ');
|
||||
String on_off = (sp < 0) ? args : args.substring(0, sp);
|
||||
bool enabled = (on_off == "on" || on_off == "1" || on_off == "true");
|
||||
int freq = 1000;
|
||||
float amp = 0.5f;
|
||||
if (sp >= 0) {
|
||||
String rest = args.substring(sp + 1);
|
||||
int sp2 = rest.indexOf(' ');
|
||||
if (sp2 < 0) {
|
||||
freq = rest.toInt();
|
||||
} else {
|
||||
freq = rest.substring(0, sp2).toInt();
|
||||
amp = rest.substring(sp2 + 1).toFloat();
|
||||
}
|
||||
if (freq <= 0) freq = 1000;
|
||||
if (amp <= 0.f || amp > 1.f) amp = 0.5f;
|
||||
}
|
||||
ui_manager->test_call_set_inject_sine(enabled, freq, amp);
|
||||
Serial.print("T:OK inject=");
|
||||
Serial.print(enabled ? "on" : "off");
|
||||
Serial.print(" freq=");
|
||||
Serial.print(freq);
|
||||
Serial.print(" amp=");
|
||||
Serial.println(amp, 3);
|
||||
}
|
||||
else {
|
||||
Serial.print("T:ERR unknown cmd ");
|
||||
Serial.println(cmd);
|
||||
|
||||
Reference in New Issue
Block a user