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 <noreply@anthropic.com>
pull/1112/head
Steve Pinkham 2026-06-30 23:22:40 -04:00
parent 16052308b5
commit 3a56ebdb35
1 changed files with 10 additions and 1 deletions

View File

@ -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()