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:
co-authored by
Claude Opus 5
parent
3496b4a2df
commit
d9554c590c
@@ -75,6 +75,41 @@ stateDiagram-v2
|
||||
- **GUI-Anzeige „⏱ Analysezeit“:** `analysis_started_at`/`analysis_ms` in der DB; beim Polling auf
|
||||
`analysis_status` achten, NICHT auf `status` (sonst wirkt die Summary „leer“).
|
||||
|
||||
## Wiedergabe-MP3: konstante Bitrate, keine Xing-Tabelle
|
||||
|
||||
Die WAV ist die Arbeitskopie (Whisper, Diarization), die MP3 ist nur fürs Abspielen im Browser —
|
||||
`highpass=f=80` gegen Rumpeln, `loudnorm` für gleichmäßige Lautstärke. Beim Codec sind aber **zwei
|
||||
Einstellungen nicht verhandelbar**, sonst läuft die Wortmarkierung im Transkript aus dem Takt:
|
||||
|
||||
```
|
||||
-codec:a libmp3lame -b:a 64k -ar 48000 -ac 1 -write_xing 0
|
||||
```
|
||||
|
||||
**Kein VBR** (früher `-qscale:a 3`) und **keine Xing-Tabelle**. Der Grund liegt im Sprung, nicht in der
|
||||
Wiedergabe:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Klick auf 20:40<br/>audio.currentTime = 1240"] --> B{Wie findet der<br/>Browser das Byte?}
|
||||
B -->|"VBR: Xing-Tabelle<br/>100 Stützstellen ≈ 38 s Raster"| C["landet bei 20:11,5<br/>meldet aber 20:40"]
|
||||
B -->|"CBR ohne Tabelle:<br/>Byte = Zeit × Bitrate"| D["landet bei 20:40,0<br/>Fehler 0,02 s"]
|
||||
C --> E["Ton hinkt 28 s nach,<br/>Markierung läuft voraus"]
|
||||
D --> F["synchron"]
|
||||
```
|
||||
|
||||
Nachgemessen an #85 (63 min): ein Sprung auf 20:40 spielte in Wahrheit ab **20:11,5 (−28,5 s)** — genau
|
||||
das Bild, das der Nutzer meldete („markiert ist ein Wort vier Zeilen unter dem, was ich höre"). Die
|
||||
Wortzeitstempel selbst waren dabei korrekt; per Gegenprobe transkribierte Ausschnitte an t = 600/1240/3400 s
|
||||
enthielten exakt die Wörter, die die Datenbank dort führt.
|
||||
|
||||
Die Tabelle muss auch bei CBR weg: sie speichert Byte-Positionen in 1/256 der Dateigröße, was bei einer
|
||||
Stunde Audio rund 15 s Rundung sind (gemessen: bis −6,4 s Restfehler). Ohne sie rechnet der Decoder
|
||||
linear — bei konstanter Bitrate exakt.
|
||||
|
||||
64 kbit CBR entspricht praktisch der bisherigen Durchschnittsrate (68 kbit); #85 schrumpfte von 32,3 auf
|
||||
30,3 MB. Der Altbestand wurde einmalig mit `lab/recode-cbr.sh` umgeschrieben (Quelle: die WAV, wo noch
|
||||
vorhanden — sonst die MP3 selbst, dann ohne erneute Filter).
|
||||
|
||||
## API-Endpunkte (Auszug)
|
||||
|
||||
| Endpunkt | Zweck |
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user