feat: redesign audio networking for real-network resilience
Replaces the fire-and-forget datagram path with sequenced packets, a per-peer jitter buffer, and persistent per-peer send tasks. Together these fix three intertwined weaknesses that only showed up off localhost. Packet format: every audio frame now carries a 4-byte little-endian sequence number header ([seq][opus payload]), the basis for reordering and loss detection. Jitter buffer (core/jitter.rs): incoming packets are reordered by sequence behind a fixed ~60ms playout delay. Missing sequences with later packets already buffered are concealed via Opus PLC (decode(None)) -- a path the decoder supported but nothing ever invoked. Underruns go idle and re-buffer rather than concealing indefinitely. Covered by unit tests using real encoded frames (reorder, gap-conceal, prime, late-drop). Transport (network/iroh_impl.rs): each peer gets one long-lived send task fed by a shallow bounded channel (drop-oldest on backpressure), instead of spawning a throwaway task per peer per 20ms frame. Connections are now established reactively on peer-join and torn down on peer-leave; the lexicographically-lower EndpointId dials so a full-mesh pair forms exactly one shared bidirectional connection instead of two racing ones. This also removes the previous lock-held-across-connect().await serialization. Opus decoder: PLC output is now sized to one 20ms frame, so concealment synthesizes 20ms instead of a 120ms burst from the oversized max buffer. Known follow-up (Tier 2): no reconnect on transient connection loss; a send error currently retires the peer until they rejoin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-2
@@ -69,8 +69,17 @@ impl FromStr for PeerSpeakTicket {
|
||||
|
||||
#[async_trait]
|
||||
pub trait NetworkTransport: Send + Sync {
|
||||
/// Send a low-latency unreliable datagram to a specific peer (for audio).
|
||||
async fn send_datagram(&self, peer_id: EndpointId, data: Bytes) -> Result<(), NetError>;
|
||||
/// Establish (or ensure) a connection to a peer and set up its send path.
|
||||
/// Idempotent; safe to call again for an already-connected peer.
|
||||
async fn connect_peer(&self, peer_id: EndpointId);
|
||||
|
||||
/// Tear down the connection and send path for a peer that has left.
|
||||
async fn disconnect_peer(&self, peer_id: EndpointId);
|
||||
|
||||
/// Fan a single audio datagram out to every connected peer. Non-blocking:
|
||||
/// per-peer queues drop the oldest-pending frame when full, so a slow link
|
||||
/// can never stall the capture/encode thread. Callable from any thread.
|
||||
fn broadcast(&self, data: Bytes);
|
||||
|
||||
/// Subscribes to incoming datagrams from any peer.
|
||||
async fn receive_datagrams(&self) -> Result<Receiver<(EndpointId, Bytes)>, NetError>;
|
||||
|
||||
Reference in New Issue
Block a user