fix(audio): Wiedergabe-MP3 als CBR ohne Xing-Tabelle -- Wortmarkierung war 28 s voraus

Die Markierung im Transkript stand auf einem Wort, gesprochen wurde eins vier
Zeilen weiter oben (gemessen an #85: 24 s Vorlauf).

Nicht die Ursache -- jeweils per Gegenprobe ausgeschlossen: die Wortzeiten
(Ausschnitte bei 600/1240/3400 s einzeln transkribiert, enthalten exakt die
Wörter der DB), MP3- vs WAV-Zeitachse (identisch), ffmpeg-Seek (trifft),
ontimeupdate-Logik (korrekt).

Ursache war der Sprung im Browser: in einer VBR-Datei gibt es keine feste
Byte-pro-Sekunde-Regel, also nutzt er die Xing-Tabelle -- 100 Stützstellen,
bei 63 Minuten ein 38-s-Raster. Er landet auf der Stützstelle davor, meldet
über currentTime aber die angefragte Zeit. Nachgerechnet: Ziel 20:40 spielt
ab 20:11,5 (-28,5 s).

Fix: -b:a 64k -ar 48000 -ac 1 -write_xing 0 statt -qscale:a 3. Byte-Position
proportional zur Zeit. Die Tabelle muss auch bei CBR weg, sie rastert auf
1/256 der Dateigröße (~15 s pro Stunde, Restfehler bis -6,4 s gemessen). Ohne
sie rechnet der Decoder linear: Restfehler 0,02 s an neun Stichpunkten.

lab/recode-cbr.sh schreibt den Altbestand einmalig um (Quelle WAV, sonst die
MP3 selbst), lab/tocerr.py ist das Messwerkzeug dahinter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-08-02 23:40:29 +02:00
co-authored by Claude Opus 5
parent 3496b4a2df
commit d9554c590c
5 changed files with 239 additions and 3 deletions
+40
View File
@@ -3,6 +3,46 @@
Chronik der Entwicklungsschritte. **Regel: jede zukünftige Änderung bekommt hier einen Eintrag** (Datum,
Was, Warum, betroffene Dateien/Backups).
## 2026-08-02 — Wortmarkierung lief dem Ton voraus: VBR-MP3 war schuld
**Symptom:** im Transkript war ein Wort markiert, gesprochen wurde aber eins rund vier Zeilen weiter oben.
Gemessen an #85: markiert „auf" (20:40,3), zu hören „OneDrive" (20:16,3) — **24 s Vorlauf**.
**Was es NICHT war** (der Reihe nach ausgeschlossen, jeweils mit Gegenprobe):
| Verdacht | Prüfung | Ergebnis |
|---|---|---|
| Wortzeiten driften (VAD-Trimming im Batch-Modus) | Ausschnitte bei t = 600/1240/3400 s aus der WAV geschnitten und einzeln transkribiert | dort stehen exakt die Wörter, die die DB führt — **Zeitstempel korrekt** |
| MP3 und WAV haben verschiedene Zeitachsen | MP3 komplett dekodiert, dieselben Stellen geprüft | identisch |
| Datei kaputt / ffmpeg-Seek daneben | `ffmpeg -ss` an denselben Stellen | trifft genau |
| Anzeige-Logik (`ontimeupdate`) | Code gelesen: letztes Wort mit `t_start <= t` | korrekt |
**Was es war:** der Sprung im Browser. Die MP3 war VBR, dort gibt es keine feste Byte-pro-Sekunde-Regel,
also nutzt der Browser die **Xing-Tabelle — 100 Stützstellen, bei 63 Minuten ein Raster von 38 s**. Er
landet auf der Stützstelle davor, meldet über `currentTime` aber die angefragte Zeit zurück. Nachgerechnet
(`lab/tocerr.py`, Frames gezählt + Tabelle geparst): Ziel 20:40 → tatsächliche Abspielposition **20:11,5,
also −28,5 s**. Genau die beobachtete Größenordnung, und der Versatz bleibt bis zum nächsten Sprung stehen.
**Fix:** MP3 künftig **CBR mit 64 kbit und ohne Xing-Tabelle**
(`-b:a 64k -ar 48000 -ac 1 -write_xing 0` statt `-qscale:a 3`). Dann ist die Byte-Position proportional
zur Zeit und jeder Sprung sitzt. Die Tabelle muss auch bei CBR weg — sie rastert Byte-Positionen auf
1/256 der Dateigröße, was bei einer Stunde ~15 s sind (Restfehler gemessen: bis −6,4 s). Ohne sie rechnet
der Decoder linear: **Restfehler 0,02 s = ein halber Frame**, an neun Stichpunkten über die ganze Datei
geprüft.
Dateigröße bleibt praktisch gleich (#85: 32,3 → 30,3 MB), 64 kbit CBR ≈ die bisherige Durchschnittsrate
von 68 kbit.
**Altbestand:** `lab/recode-cbr.sh` schreibt alle vorhandenen Aufnahmen einmalig um — Quelle ist die WAV
mit derselben Filterkette wie beim Aufnehmen, für die alten Aufnahmen ohne WAV die MP3 selbst (dann ohne
erneutes `loudnorm`, das steckt dort schon drin). Schreibt erst in eine `.tmp`, vergleicht die Dauer
(±2 s) und ersetzt erst dann; pausiert oberhalb von 88 °C, weil das Board keinen Lüfter hat.
Zwei Stolpersteine dabei: `-f mp3` ist Pflicht (aus der Endung `.mp3.tmp` leitet ffmpeg kein Format ab),
und eine 0-Byte-Altdatei (`rec_1784247236781.mp3`) bleibt als Fehler liegen statt den Lauf abzubrechen.
Dateien: `app/recorder.js` (Board-Backup `.bak-cbr`), `app/lab/recode-cbr.sh` (neu),
`app/lab/tocerr.py` (neu, Messwerkzeug), Kapitel [03 – Aufnahme-Pipeline](03-aufnahme-pipeline.md).
## 2026-08-02 (nachgereicht) — Grüne Nadel, Logo führt zurück zu den Aufnahmen
**Nadel grün statt rot:** der Anheften-Knopf in der Aufnahmenliste zeigte bei einer angehefteten Aufnahme