Commit 15e75239 by PLN (Algolia)

gig_record: flush every packet so quiet audio is on disk within a second

Measured: on a quiet sink the segment muxer held the Opus pages in a buffer; a
segment was still 0 bytes after 25 s. A kill or a power cut would lose the quiet
stretch, and 'is the file growing' could not tell a healthy recorder from a dead
one (the health check cried 'rig is down' on a working recorder). With
segment_format_options flush_packets=1 the file grows every second, in silence
too.
parent 2efc7575
......@@ -82,6 +82,13 @@ default_monitor() {
echo "${sink}.monitor"
}
# WHY flush_packets=1 (measured 2026-10-11): without it, ffmpeg's segment muxer holds the
# Opus pages in a buffer until it fills. On a QUIET sink that is minutes, not seconds: a
# segment was still 0 bytes after 25 s. Two consequences, both fatal for a black box: audio
# from a quiet stretch is not on disk (a kill or a power cut loses it), and "is the file
# growing?" cannot tell a healthy recorder from a dead one. With it, the file grows every
# second even in silence (~230 B/s), so mtime means something and at most ~1 s is at risk.
cmd_start() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "gig_record: already running (pid $(cat "$PID_FILE"))"
......@@ -94,6 +101,7 @@ cmd_start() {
-f pulse -i "$monitor" \
-c:a libopus -b:a "$BITRATE" -vbr on \
-f segment -segment_time "$SEGMENT_SECS" -strftime 1 \
-segment_format_options flush_packets=1 \
"$OUT_DIR/%Y-%m-%d_%H%M%S.opus" \
>>"$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
......
......@@ -145,3 +145,24 @@ def test_a_large_graph_does_not_kill_the_script_with_a_sigpipe(tmp_path):
env=env, capture_output=True, text=True, timeout=60)
assert r.returncode == 0, r.returncode
assert r.stdout.strip() == LAPTOP
def test_the_recorder_flushes_to_disk_every_packet_so_quiet_audio_is_not_buffered(tmp_path):
"""Measured 2026-10-11: on a quiet sink the segment muxer held the audio in a buffer
(0 bytes after 25 s). That loses a quiet stretch to a kill or a power cut and makes
'is the file growing' meaningless. The option that fixes it is part of the contract."""
stubs = tmp_path / "bin"
stubs.mkdir()
argv_file = tmp_path / "ffmpeg.argv"
ff = stubs / "ffmpeg"
ff.write_text(f"#!/bin/sh\necho \"$@\" > '{argv_file}'\nsleep 5\n")
ff.chmod(0o755)
env = dict(os.environ, PATH=f"{stubs}:{os.environ['PATH']}", GIG_RECORD_DIR=str(tmp_path / "rec"),
GIG_RECORD_TARGET="fake.monitor")
r = subprocess.run(["bash", str(SCRIPT), "start"], env=env, capture_output=True, text=True, timeout=30)
assert r.returncode == 0, r.stderr
import time; time.sleep(0.5)
argv = argv_file.read_text()
assert "-segment_format_options flush_packets=1" in argv, argv
assert "fake.monitor" in argv
subprocess.run(["bash", str(SCRIPT), "stop"], env=env, capture_output=True, timeout=10)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment