Qualité de transcription : audio propre pour le moteur (corrige les erreurs)

L'AGC sur-amplifiait (jusqu'à x60) l'audio envoyé au moteur -> signal
déformé/bruité -> mauvaise reconnaissance. Désormais :
- Le VAD détecte sur l'audio amplifié, mais le segment stocke l'audio BRUT
- Le segment est normalisé proprement (mise à l'échelle linéaire unique vers
  crête ~22000, sans saturation) juste avant le moteur
- Segment max ramené de 25 s à 15 s pour un retour plus rapide
Validé bout en bout : micro faible -> détecté -> audio propre -> transcrit.

https://claude.ai/code/session_01YHMp3EKzr4s6o8w1ygxuUe
This commit is contained in:
Claude committed 2026-06-13 15:45:33 +00:00
1 parent 55ccef1340
commit 1e72e82965
2 files changed
+26 -15

No files matched your search

+10 -8
View File
@@ -22,7 +22,7 @@ PREROLL_FRAMES = 10 # 300 ms conservées avant le déclenchement
TRIGGER_RATIO = 0.6 # part de trames "parole" du pré-roll pour démarrer
SILENCE_END_MS = 700 # silence qui clôt un segment
MIN_SPEECH_MS = 300 # en dessous, le segment est ignoré (bruit)
MAX_SEGMENT_S = 25 # coupe forcée pour garder une latence raisonnable
MAX_SEGMENT_S = 15 # coupe forcée pour garder une latence raisonnable
# Contrôle de gain automatique appliqué AVANT la détection de parole : sans lui,
# un micro faible (casque...) reste sous le seuil du VAD et aucune phrase n'est
@@ -83,10 +83,12 @@ class SpeechSegmenter:
self._pending.extend(data)
segments = []
while len(self._pending) >= FRAME_BYTES:
frame = bytes(self._pending[:FRAME_BYTES])
raw = bytes(self._pending[:FRAME_BYTES])
del self._pending[:FRAME_BYTES]
frame = self._agc.process(frame) # amplifie avant le VAD
seg = self._process_frame(frame)
# Le VAD travaille sur l'audio amplifié (détection fiable même micro
# faible) ; le segment stocke l'audio BRUT (transcription propre).
amplified = self._agc.process(raw)
seg = self._process_frame(raw, amplified)
if seg is not None:
segments.append(seg)
return segments
@@ -98,12 +100,12 @@ class SpeechSegmenter:
self._pending.clear()
return seg
def _process_frame(self, frame: bytes) -> Segment | None:
is_speech = self._vad.is_speech(frame, SAMPLE_RATE)
def _process_frame(self, raw: bytes, amplified: bytes) -> Segment | None:
is_speech = self._vad.is_speech(amplified, SAMPLE_RATE)
self._frame_index += 1
if not self._triggered:
self._ring.append((frame, is_speech))
self._ring.append((raw, is_speech))
voiced = sum(1 for _, s in self._ring if s)
if len(self._ring) == self._ring.maxlen and voiced >= TRIGGER_RATIO * self._ring.maxlen:
self._triggered = True
@@ -114,7 +116,7 @@ class SpeechSegmenter:
self._ring.clear()
return None
self._segment.extend(frame)
self._segment.extend(raw)
if is_speech:
self._speech_frames += 1
self._silence_frames = 0