Stop throwing away the audio Whisper has to read
Build edge-node / build (push) Successful in 35s
CI / lint (push) Successful in 7s
CI / test (push) Successful in 42s

The single encode at save time was %mp3(bitrate=16), and the comment said why:
it matched what Liquidsoap pushes to Icecast. That was the wrong thing to
match. Icecast is the LISTENING path and 16 kbps is a bandwidth budget for a
live stream; this file is the ACCURACY path -- it is what Whisper transcribes,
and CLAUDE.md is explicit that everything downstream is hostage to it. P25 has
already been through a vocoder, so 16 kbps MP3 stacked a second lossy stage on
the one copy that had to stay faithful.

FLAC instead. Lossless, so the bytes Whisper receives are the bytes PulseAudio
captured. ~1.3 MB/min against 120 KB/min, which keeps a 600 s call (the time
cap) around 13 MB -- inside Whisper's 25 MB request cap and well inside
upload_max_bytes. Icecast's own 16 kbps stream is untouched; nothing about
live listening changes.

Capture, buffering, silence detection and the byte-offset trim are all
unchanged: they operate on raw PCM and never saw the encode. The sample rate
stays pinned to pcm.SAMPLE_RATE so the encode remains a straight pass -- the
trim arithmetic depends on that, and Whisper resamples to 16 kHz itself.

encode_mp3 is now encode_recording, the upload sends audio/flac, and the test
that pinned the old contract now pins losslessness instead, including an
assertion that no bitrate constant comes back.

This is the before/after boundary for STT quality. Last night's window is the
16 kbps baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-08-23 12:48:32 -04:00
co-authored by Claude Opus 5
parent fb13bb8ae3
commit 0c08275482
2 changed files with 64 additions and 31 deletions
+18 -8
View File
@@ -59,7 +59,7 @@ def encodes(monkeypatch):
path.write_bytes(audio)
return True
monkeypatch.setattr(recorder_mod, "encode_mp3", _encode)
monkeypatch.setattr(recorder_mod, "encode_recording", _encode)
return calls
@@ -412,21 +412,31 @@ async def test_a_failed_encode_leaves_no_file_and_no_recording(recorder, monkeyp
async def _fail(audio, path):
return False
monkeypatch.setattr(recorder_mod, "encode_mp3", _fail)
monkeypatch.setattr(recorder_mod, "encode_recording", _fail)
ingest(recorder, T0, T0 + 5.0)
await recorder.start_recording("call-1", start_epoch=T0 + 1.0)
assert await recorder.stop_recording(end_epoch=T0 + 3.0) is None
assert list(recorder._recordings_dir.glob("*.mp3")) == []
assert list(recorder._recordings_dir.glob("*.flac")) == []
def test_encoder_command_contract_matches_what_c2_expects():
"""
/upload has always received mono MP3 at 22050 Hz / 16 kbps, and Whisper
consumes it downstream. The single encode must not quietly change that.
The saved file is what Whisper transcribes, so the encode must stay
LOSSLESS and must not resample. It was 16 kbps MP3 — a bitrate copied from
Icecast's live stream, i.e. the listening path's budget applied to the
accuracy path — which put a second lossy stage on top of the P25 vocoder.
The sample rate must equal pcm.SAMPLE_RATE or the encode stops being a
straight pass and the byte-offset trim arithmetic no longer lines up.
"""
assert recorder_mod.MP3_SAMPLE_RATE == str(pcm.SAMPLE_RATE) == "22050"
assert recorder_mod.MP3_BITRATE == "16k"
assert recorder_mod.AUDIO_SAMPLE_RATE == str(pcm.SAMPLE_RATE) == "22050"
assert recorder_mod.AUDIO_FORMAT == "flac"
assert recorder_mod.AUDIO_SUFFIX == ".flac"
assert recorder_mod.AUDIO_MIME == "audio/flac"
# No bitrate constant should exist: a bitrate on a lossless codec would mean
# someone reintroduced lossy encoding.
assert not hasattr(recorder_mod, "MP3_BITRATE")
# ---------------------------------------------------------------------------
@@ -523,7 +533,7 @@ async def test_discard_drops_the_audio_without_writing_anything(recorder, encode
assert not recorder.is_recording
assert encodes == []
assert list(recorder._recordings_dir.glob("*.mp3")) == []
assert list(recorder._recordings_dir.glob("*.flac")) == []
# ...and the recorder is immediately reusable.
assert await recorder.start_recording("call-next", start_epoch=T0 + 2.0) is True