150ms cap was too tight for Iran relay (high jitter), causing constant
audio drops. Raised to 1s — packet bursts are absorbed smoothly,
drift reset only fires on real accumulation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pull-based ScriptProcessor approach broke audio completely.
Back to createBufferSource scheduling which worked, but with
tighter 200ms max drift (was 300ms). Snaps back when exceeded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous version output 960 samples into 1024-sample callback frames,
causing 64 samples of silence per frame (choppy/robotic sound).
Now accumulates float samples in a continuous buffer, output callback
pulls exactly 1024 at a time regardless of input frame size.
Buffer capped at 200ms to prevent drift.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web playback rewritten from push-scheduling to pull-based ring buffer:
- ScriptProcessorNode pulls frames from buffer every ~21ms
- Buffer capped at 10 frames (~200ms) — drops oldest on overflow
- Latency permanently bounded, no drift over time
Also: install ring crypto provider for rustls TLS on Linux,
build on debian-12 to match mequ glibc.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generates a self-signed certificate at startup for HTTPS.
Required for mic access on Android/remote browsers (getUserMedia
needs a secure context).
Usage: wzp-web --port 9090 --relay 127.0.0.1:4433 --tls
Browser: accept the self-signed cert warning, then mic works.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When playback buffer drifts beyond 300ms ahead of real-time, reset
to 40ms. This prevents the unbounded latency growth over long sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Schedule each playback buffer to start exactly where the last ended
(was causing gaps/overlaps with fixed 60ms offset)
- Log AudioContext sample rate to console for debugging
- Reset playback timeline when falling behind
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use power-of-2 buffer (1024) for ScriptProcessorNode
- Accumulate samples and send exact 960-sample frames
- Remove unused watch import from relay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New wzp-web crate serves a web page with:
- Browser mic capture via Web Audio API (48kHz mono)
- WebSocket transport for raw PCM audio
- Server-side Opus encode/decode + FEC through wzp relay
- Real-time audio playback in browser
- Level meter and connection stats
Usage:
wzp-relay --listen 0.0.0.0:4433 # start relay
wzp-web --port 8080 --relay 127.0.0.1:4433 # start web bridge
Open http://localhost:8080 in browser
Two browsers connected to the same relay get bridged for a call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The --record recv loop was using while-drain which exhausted the jitter
buffer and stopped decoding after the first burst. Now decodes once per
source packet, matching the live mode fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- --send-file <file.raw> sends a raw PCM file (48kHz mono s16le) through relay
- Combine with --record: --send-file talk.raw --record echo.raw <relay>
- Fixed all unused import warnings in echo_test.rs
Convert any audio to test format:
ffmpeg -i input.mp3 -ar 48000 -ac 1 -f s16le input.raw
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New --echo-test <secs> flag sends a 440Hz tone through relay echo,
records the return, and analyzes quality in 5-second windows:
- Per-window: frames sent/received, loss %, SNR (dB), correlation
- Detects quality degradation over time (compares first vs second half)
- Reports jitter buffer stats (depth, lost, late packets)
- Diagnoses jitter buffer drift and packet loss accumulation
Also exposes jitter_stats() on CallDecoder for diagnostics.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no --remote is configured, the relay now operates in bridge mode:
- First client connects → echoes while waiting for a peer
- Second client connects → both clients are bridged bidirectionally
- A's packets go to B, B's packets go to A
- Stats logged every 5 seconds (a_to_b / b_to_a packet counts)
- Falls back to echo if only one client connects
This enables the core use case: two clients on different networks
calling each other through a single relay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Reduced jitter buffer min_depth from 25 (500ms) to 3 (60ms) for fast start
- Fixed live recv loop: decode once per source packet instead of draining
the jitter buffer dry (which advanced seq past future packets)
- Fixed Ok(None) handling: connection closed, not "no packet yet"
Live echo test confirmed working with continuous audio.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- --record now handles Ctrl+C: saves PCM file before exiting
- Relay without --remote runs in echo mode (loops packets back to sender)
instead of sink mode, enabling single-relay audio testing
- recv task returns collected PCM via channel for clean file write
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- cpal is now behind an 'audio' feature flag (off by default)
- --live mode requires --features audio at build time
- --send-tone and --record work on headless servers without audio libs
- Linux build script no longer installs libasound2-dev
Build for headless: cargo build --release
Build with mic/speakers: cargo build --release --features audio
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CLI modes:
- --send-tone <secs>: send 440Hz test tone (no mic needed)
- --record <file.raw>: save received audio to raw PCM file
- --help: usage info
- Combine: --send-tone 10 --record out.raw
Raw PCM format: 48kHz mono s16le
Play with: ffplay -f s16le -ar 48000 -ac 1 out.raw
Build scripts:
- scripts/build-linux.sh: Hetzner VPS build with auto-cleanup
- scripts/cleanup-builder.sh: kill stale builders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bench tool now auto-calculates the FEC ratio needed to survive
the requested loss percentage, matching how the adaptive quality
controller would behave in production.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>