feat(wake): Voxtral-Bestätigungsstufe gegen Musik-Fehltrigger
openWakeWord triggert oft auf Musik (Pet Shop Boys "West End Girls" & Co.). Neu:
nach dem Trigger prüft Voxtral den Vor-Trigger-Audio ("war das wirklich das
Wake-Wort oder nur Musik?") — erst bei Bestätigung Gong + Mikro, sonst still
verworfen + re-arm. Kostet ~0,5-1s vor dem Gong.
- Native (OpenWakeWordModule.kt): 2s Roh-PCM-Ringpuffer; bei Erkennung wird der
1,5s-Vor-Trigger-Schnipsel base64 (s16le 16kHz) ans WakeWordDetected-Event
gehängt (preTriggerPcm).
- Bridge (voxtral): neuer One-Shot-Handler stt_transcribe_blob → Silero + Voxtral
→ stt_transcribe_result. Musik/Rauschen → leerer Text.
- App: audio.transcribeBlob() schickt den Schnipsel, wakeword.confirmWake() prüft
ob das distinktive Keyword (>=4 Zeichen) im Transkript steht. Gate sitzt in
onWakeDetected VOR Gong/Dialog. Setting "Wake-Wort per Voxtral bestätigen"
(default aus, opt-in).
FAIL-OPEN durchgängig: kein preTriggerPcm (altes APK) / Timeout / Fehler / VAD
weg → Wake läuft normal durch. Nie schlechter als heute. NATIVER TEIL UNGETESTET
(kein Gerät hier) — braucht Build + Test auf dem Handy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -700,6 +700,30 @@ async def run_loop(sessions: SessionManager) -> None:
|
||||
sessions.feed_chunk(payload)
|
||||
elif mtype == "stt_stream_end":
|
||||
sessions.end_session(payload.get("requestId", ""))
|
||||
elif mtype == "stt_transcribe_blob":
|
||||
# One-Shot-Transkription eines PCM-Schnipsels (kein Live-
|
||||
# Stream) — fuer die Wake-Wort-Bestaetigung: die App schickt
|
||||
# den Vor-Trigger-Audio, wir sagen was gesagt wurde, die App
|
||||
# prueft ob "Computer" drin ist. Silero vorgeschaltet:
|
||||
# Musik/Rauschen → leerer Text (nicht bestaetigt).
|
||||
req_id = payload.get("requestId", "")
|
||||
try:
|
||||
pcm = base64.b64decode(payload.get("pcm", ""))
|
||||
audio = pcm_s16le_to_float32(pcm)
|
||||
segs = _speech_segments(audio)
|
||||
if segs is not None and len(segs) == 0:
|
||||
text = ""
|
||||
else:
|
||||
text = (await sessions.runner.transcribe(
|
||||
audio, payload.get("language", "de"))).strip()
|
||||
logger.info("stt_transcribe_blob (%.1fs) → %r",
|
||||
audio.size / 16000.0, text[:60])
|
||||
await _send(ws, "stt_transcribe_result",
|
||||
{"requestId": req_id, "text": text})
|
||||
except Exception as exc:
|
||||
logger.warning("stt_transcribe_blob fehlgeschlagen: %s", exc)
|
||||
await _send(ws, "stt_transcribe_result",
|
||||
{"requestId": req_id, "text": "", "error": str(exc)[:200]})
|
||||
elif mtype == "voice_id_status_request":
|
||||
req_id = payload.get("requestId", "")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user