From 3a56ebdb358a0e2dd4663df669e05dcdbb7ff986 Mon Sep 17 00:00:00 2001 From: Steve Pinkham Date: Tue, 30 Jun 2026 23:22:40 -0400 Subject: [PATCH] perf(audio): lower RX latency with blocksize=0 and set the input ring depth explicitly blocksize=4800 makes PortAudio hand the RX callback fixed 100 ms blocks, so received audio sits in the input buffer for up to 100 ms before the demodulator can see it, and that delay is paid again on every ARQ turnaround. With blocksize=0 PortAudio delivers whatever is available (small blocks in the 10 to 50 ms range in our measurements), cutting the RX buffering delay to a fraction of the old fixed block. On its own, blocksize=0 also shrinks the negotiated input ring. We measured 40 ms total where blocksize=4800 had negotiated 200 ms on a CM108 USB codec, which makes short processing stalls more likely to drop audio. The explicit latency=0.2 closes that gap: it requests a 200 ms ring built from small periods, so the stream keeps the old depth while gaining the low latency. The explicit ring depth also makes the negotiation deterministic on virtual devices. On snd-aloop (the ALSA loopback used for hardware-free testing) the default "high" latency maps to only two periods. At 100 ms periods that double buffer misses its service deadline on a fixed cycle and the capture stream drops audio continuously from the moment it opens, leaving the modem deaf on that device class. With an explicit depth the same stream runs clean; we measured buffer 12000 frames with 2400 frame periods and zero overflows, identically on two machines. TX stays at blocksize=2400; only the RX side changes. Co-Authored-By: Claude Fable 5 --- freedata_server/modem.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/freedata_server/modem.py b/freedata_server/modem.py index 68661d4e..873a88c0 100644 --- a/freedata_server/modem.py +++ b/freedata_server/modem.py @@ -179,13 +179,22 @@ class RF: self.resampler = codec2.resampler() # SoundDevice audio input stream + # blocksize=0 lets PortAudio deliver small blocks, keeping RX + # buffering delay low. latency=0.2 sets the ring depth explicitly: + # the default ("high") can negotiate as little as two periods on + # some devices (measured on snd-aloop, where a two period ring at + # 100 ms periods drops audio continuously), and with blocksize=0 + # alone the ring can come out as shallow as 40 ms. An explicit + # 200 ms request gives a deep ring of small periods on every + # device we measured (CM108 hardware and snd-aloop alike). self.sd_input_stream = sd.InputStream( channels=1, dtype="int16", callback=self.sd_input_audio_callback, device=in_dev_index, samplerate=self.AUDIO_SAMPLE_RATE, - blocksize=4800, + blocksize=0, + latency=0.2, ) self.sd_input_stream.start()