From 5c429ab077e9da133e96028d081884a15163fa89 Mon Sep 17 00:00:00 2001 From: yusufipk Date: Sat, 1 Aug 2026 20:52:43 +0700 Subject: [PATCH] Ask parec for sound as it arrives, not two seconds of it at a time Left alone, parec buffers about two seconds and then hands the lot over at once. Measured against the chunk the level meter reads: parec, as it was longest gap 2005 ms parec --latency-msec=64 longest gap 86 ms pw-record longest gap 129 ms On a machine that has parec, which is every PulseAudio one and every PipeWire one with the compatibility service, the waveform stood still and then jumped, looking like a microphone that was not picking anything up. The recording itself was fine, but the tail of one can be lost that way too: stop() ends the process, and whatever is still held back goes with it. The number asked for is the meter's own chunk, which is the unit the rest of this file is measured in. --- audio.py | 7 +++++++ tests/test_audio.py | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/audio.py b/audio.py index 39bceb2..0ef5038 100644 --- a/audio.py +++ b/audio.py @@ -27,6 +27,7 @@ CHANNELS = 1 SAMPLE_WIDTH = 2 # s16 CHUNK_FRAMES = 1024 CHUNK_BYTES = CHUNK_FRAMES * SAMPLE_WIDTH * CHANNELS +CHUNK_LATENCY_MS = round(CHUNK_FRAMES / RATE * 1000) MIN_FRAMES = int(RATE * 0.25) @@ -184,6 +185,12 @@ def recording_command(target=""): cmd = [ "parec", "--record", "--raw", f"--rate={RATE}", f"--channels={CHANNELS}", "--format=s16le", + # Left alone, parec holds about two seconds before handing anything + # over, and then hands over all of it at once: the level meter sits + # still and jumps, and the tail of a recording can be lost on the + # way out. A chunk of the meter is the unit the rest of this file + # is measured in, so ask for that. + f"--latency-msec={CHUNK_LATENCY_MS}", ] if target: cmd.append(f"--device={target}") diff --git a/tests/test_audio.py b/tests/test_audio.py index 52f2f54..7595d1b 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -243,6 +243,18 @@ class RecordingCommand(DikteTest): self.assertIn(str(audio.CHANNELS), joined) self.assertIn("s16", joined) + def test_parec_is_asked_for_the_level_meter_s_own_chunk(self): + """Left alone it buffers about two seconds, which the waveform shows as + a still bar that jumps once a second, and which can cost the tail of a + recording when the process is asked to stop.""" + with only_these_tools("parec"): + self.assertIn(f"--latency-msec={audio.CHUNK_LATENCY_MS}", + audio.recording_command()) + + def test_the_latency_asked_for_is_the_chunk_the_meter_reads(self): + self.assertEqual(audio.CHUNK_LATENCY_MS, + round(audio.CHUNK_FRAMES / audio.RATE * 1000)) + def test_a_chosen_microphone_reaches_either_one(self): with only_these_tools("parec"): self.assertIn("--device=alsa_input.usb", audio.recording_command(