Custom Voice Trigger Training
Speaker Verification · On-DeviceTFLite d-vector · Spectral Subtraction · Wiener Filter · Cosine Similarity ≥ 85%
End-to-End Pipeline · 8 Stufen
5× Trigger-Wort sprechen → d-vector Centroid → AES-256 lokal speichern
500ms Umgebungsrauschen → Noise-Profil für Spectral Subtraction
1.5s Sliding Window → Noise Filter → MFCC → TFLite Embedding
Live-Embedding vs. Voiceprint → Score berechnen (0.0 – 1.0)
Score ≥ 0.85 → weiter | Score < 0.85 → verwerfen (Fremdstimme)
3 kurze Vibrationen → Nutzerin weiß: Trigger erkannt
Anti-False-Positive Gate — Abbruch jederzeit möglich
GPS + Encrypted Payload → SEC.SI Leitstelle + Session-Eskalation
5×
Aufnahmen für Baseline
128
d-vector Dimensionen
AES
256-bit lokal gespeichert
// ─── MODUL 1: SPEAKER ENROLLMENT (Einlern-Phase) ────────────────
// Framework: TensorFlow Lite (Android) / CoreML (iOS)
// Modell: d-vector Speaker Embedding (128-dim, GE2E Loss)
// Ziel: Voiceprint der Besitzerin lokal speichern
MODULE SpeakerEnrollment:
CONST SAMPLE_RATE = 16000 // 16 kHz Mono
CONST EMBEDDING_DIM = 128 // d-vector Dimension
CONST ENROLLMENT_COUNT = 5 // 5 Aufnahmen für Baseline
CONST MIN_DURATION_MS = 800 // Mind. 0.8s pro Sample
CONST MAX_DURATION_MS = 3000 // Max. 3s pro Sample
STATE voiceprintEmbeddings = [] // Gesammelte Embeddings
FUNCTION collectEnrollmentSamples():
FOR i IN range(ENROLLMENT_COUNT):
UI.prompt("Sprechen Sie das Triggerwort: Aufnahme " + (i+1))
raw_pcm = AudioCapture.record(MAX_DURATION_MS) // RAM only
filtered = NoiseFilter.apply(raw_pcm) // Modul 2
mfcc = MFCC.extract(filtered, n_mels=40, n_fft=512)
IF mfcc.duration_ms < MIN_DURATION_MS:
UI.warn("Aufnahme zu kurz – bitte wiederholen")
CONTINUE
embedding = TFLite.infer("speaker_encoder.tflite", mfcc)
// embedding.shape = [128] → kompakter Stimmabdruck
voiceprintEmbeddings.append(embedding)
raw_pcm.destroy() // Privacy: sofort löschen
END FOR
IF voiceprintEmbeddings.length < 3:
THROW EnrollmentError("Zu wenige gültige Samples")
FUNCTION buildVoiceprint():
// Centroid der Embeddings = robusterer Voiceprint
centroid = mean(voiceprintEmbeddings, axis=0)
centroid = L2Normalize(centroid)
// Sicherheitscheck: Inter-Sample Cosine-Similarity > 0.75
FOR EACH pair IN combinations(voiceprintEmbeddings, 2):
sim = cosineSimilarity(pair[0], pair[1])
IF sim < 0.75:
UI.warn("Inkonsistente Stimmproben – bitte neu aufnehmen")
// Verschlüsselt lokal speichern (AES-256, Key aus Biometrie-Gate)
encrypted = AES256.encrypt(centroid, key: BiometricKeystore.get())
LocalStorage.store("secsi_voiceprint", encrypted)
voiceprintEmbeddings.clear() // RAM bereinigen
RETURN centroidSpectral Subtraction: Noise-Power-Spektrum vom Signal abziehen — eliminiert stationäres Rauschen (Lüfter, Verkehr)
Wiener-Filter: SNR-basierter Gain für nicht-stationäre Geräusche (Stimmengewirr, Musik)
Phase-Preservation: Originalphase bleibt erhalten — natürlicher Klang nach Filterung
// ─── MODUL 2: FREQUENZ-FILTER · NOISE CANCELLATION ─────────────
// Algorithmus: Spectral Subtraction + Wiener Filter (kombiniert)
// Ziel: Umgebungsgeräusche herausrechnen vor Embedding
MODULE NoiseFilter:
CONST FFT_SIZE = 512
CONST HOP_LENGTH = 128
CONST NOISE_FLOOR_DB = -60.0
CONST ALPHA_OVER = 2.0 // Over-subtraction Faktor
CONST BETA_FLOOR = 0.001 // Spektraler Boden (kein Null-Signal)
STATE noiseProfile = null // Kalibriert bei Session-Start
FUNCTION calibrateNoise(duration_ms = 500):
// 500ms Hintergrundgeräusch aufnehmen BEVOR User spricht
noise_pcm = AudioCapture.record(duration_ms, silent=True)
noise_frames = STFT(noise_pcm, FFT_SIZE, HOP_LENGTH)
noiseProfile = mean(|noise_frames|², axis=time) // Mittleres Noise-Power-Spektrum
noise_pcm.destroy()
FUNCTION apply(raw_pcm):
IF noiseProfile == null:
calibrateNoise()
// 1. Short-Time Fourier Transform
frames = STFT(raw_pcm, FFT_SIZE, HOP_LENGTH)
mag = |frames| // Magnitude
phase = angle(frames) // Phase beibehalten
// 2. Spectral Subtraction
// Ŝ(ω) = max(|X(ω)| - α·N(ω), β·|X(ω)|)
noise_mag = sqrt(noiseProfile)
subtracted = max(mag - ALPHA_OVER * noise_mag, BETA_FLOOR * mag)
// 3. Wiener-Filter Glättung (Spectral Gain)
// G(ω) = SNR(ω) / (SNR(ω) + 1) — optimaler Wiener-Gain
signal_power = subtracted²
noise_power = noiseProfile
snr = signal_power / max(noise_power, 1e-10)
wiener_gain = snr / (snr + 1.0)
filtered_mag = subtracted * wiener_gain
// 4. Rücktransformation
filtered_frames = filtered_mag * exp(j * phase) // Phase rekonstruieren
filtered_pcm = iSTFT(filtered_frames, FFT_SIZE, HOP_LENGTH)
RETURN filtered_pcm // Bereinigtes SignalCosine Similarity Formel:
< 0.85
Fremdstimme → Ignore
0.85–0.92
Validiert → Haptic
> 0.92
Hohe Konfidenz
// ─── MODUL 3: VALIDIERUNGS-FUNKTION · > 85% THRESHOLD ──────────
// Speaker Verification: Cosine-Similarity des Live-Embeddings
// gegen gespeicherten Voiceprint (d-vector Centroid)
MODULE SpeakerValidator:
CONST MATCH_THRESHOLD = 0.85 // Mindest-Übereinstimmung
CONST SLIDING_WINDOW = 1500 // ms — kontinuierliches Fenster
CONST CONFIRM_WINDOW = 3000 // ms — Haptic + Abbruch-Chance
CONST MAX_RETRIES = 3 // Fehlversuche vor Lockout
STATE failCount = 0
STATE voiceprint = null // Geladener Voiceprint
FUNCTION loadVoiceprint():
encrypted = LocalStorage.get("secsi_voiceprint")
IF encrypted == null: RETURN null
centroid = AES256.decrypt(encrypted, key: BiometricKeystore.get())
voiceprint = L2Normalize(centroid)
FUNCTION validate(live_pcm) → ValidationResult:
IF voiceprint == null: loadVoiceprint()
// 1. Noise-Cancellation
filtered = NoiseFilter.apply(live_pcm)
// 2. MFCC Feature Extraction
mfcc = MFCC.extract(filtered, n_mels=40, n_fft=512)
// 3. Speaker Embedding via TFLite
live_emb = TFLite.infer("speaker_encoder.tflite", mfcc)
live_emb = L2Normalize(live_emb)
// 4. Cosine Similarity
// sim = (live_emb · voiceprint) / (‖live_emb‖ · ‖voiceprint‖)
similarity = cosineSimilarity(live_emb, voiceprint)
filtered.destroy() // Privacy
IF similarity >= MATCH_THRESHOLD:
failCount = 0
RETURN { match: true, score: similarity, action: "TRIGGER" }
ELSE:
failCount += 1
IF failCount >= MAX_RETRIES:
UI.warn("Zu viele Fehlversuche – Listener pausiert 60s")
SLEEP(60000)
failCount = 0
RETURN { match: false, score: similarity, action: "IGNORE" }
FUNCTION onValidationSuccess(result):
// DUAL GATE: Stimme erkannt UND Überschwellenwert
LOG("voice_match", score: result.score, ts: now())
// Haptic Feedback starten (Modul 4)
HapticLoop.trigger()
// 3 Sekunden Abbruch-Fenster
cancelToken = UI.showCancelBanner(3000)
WAIT 3000
IF cancelToken.wasCancelled():
LOG("trigger_cancelled_by_user", ts: now())
RETURN
// Schwelle bestätigt + kein Abbruch → Notfall auslösen
EmergencyDispatch.fire()3× 150ms · Pause 120ms
iOS: CHHapticEngine · Android: VibrationEffect.createWaveform · Web: navigator.vibrate
Bedeutung: "Hilfe ist unterwegs" — codiertes SOS-Pattern
// ─── MODUL 4: HAPTIC FEEDBACK LOOP ──────────────────────────────
// Plattform: iOS (CHHapticEngine) / Android (VibrationEffect)
// Bedeutung: 3× kurz = "Hilfe ist unterwegs" — codiertes SOS-Pattern
MODULE HapticLoop:
// iOS: CHHapticPattern via AudioServicesPlaySystemSoundWithCompletion
// Android: VibrationEffect.createWaveform()
CONST PATTERN_IOS = [
{ sharpness: 0.8, intensity: 1.0, duration: 0.15 }, // Puls 1
{ pause: 0.12 },
{ sharpness: 0.8, intensity: 1.0, duration: 0.15 }, // Puls 2
{ pause: 0.12 },
{ sharpness: 0.8, intensity: 1.0, duration: 0.15 }, // Puls 3
]
CONST PATTERN_ANDROID = {
timings: [0, 150, 120, 150, 120, 150], // ms: delay, on, off, on, off, on
amplitudes: [0, 255, 0, 255, 0, 255], // 255 = max Intensität
repeatIdx: -1 // Kein Loop — einmalig
}
FUNCTION trigger():
platform = Device.getPlatform()
IF platform == "iOS":
engine = CHHapticEngine.init()
engine.playPattern(PATTERN_IOS)
ELSE IF platform == "Android":
vibrator = Vibrator.get()
vibrator.vibrate(VibrationEffect.createWaveform(
PATTERN_ANDROID.timings,
PATTERN_ANDROID.amplitudes,
PATTERN_ANDROID.repeatIdx
))
ELSE: // Web / Fallback
// Web Vibration API (Android Chrome)
navigator.vibrate([150, 120, 150, 120, 150])
// Visuelle Bestätigung parallel
UI.flash(color: "#22c55e", duration_ms: 600) // Grünes Aufleuchten
UI.showToast("✅ Erkannt — Hilfe ist unterwegs", duration: 3000)
LOG("haptic_triggered", pattern: "3x_confirm", ts: now())
// ─── EMERGENCY DISPATCH (nach Haptic + Bestätigung) ─────────────
MODULE EmergencyDispatch:
FUNCTION fire():
location = GPS.getHighAccuracy(timeout_ms: 500)
payload = {
event: "sos_voice_trigger",
trigger_type: "speaker_verified",
voice_match_score: SpeakerValidator.lastScore,
haptic_confirmed: true,
location: {
lat: location.lat,
lng: location.lng,
accuracy_m: location.accuracy,
timestamp: ISO8601.now()
},
session_id: Session.currentId,
device_ts: UnixMillis.now()
}
// AES-256-GCM verschlüsseln
encrypted = AES256GCM.encrypt(payload, key: ECDH.sessionKey())
// An SEC.SI Leitstelle senden
HTTP.post("/api/v2/sos/ping", encrypted, headers: {
"Authorization": "Bearer " + Session.jwt,
"X-Trigger-Type": "voice_verified",
"X-Speaker-Score": SpeakerValidator.lastScore
})
// Lokalen Alarm-Status setzen
Session.escalate(level: "intervention")
UI.activateSOSMode()Security Summary · Schutzebenen
Speaker Verification
Nur die Stimme der Besitzerin löst aus — d-vector Cosine Similarity
Noise Cancellation
Spectral Subtraction + Wiener Filter eliminiert Umgebungsgeräusche
85% Threshold
Doppeltes Gate: Keyword-Erkennung UND Sprecher-Verifikation
3× Lockout
Nach 3 Fehlversuchen: 60s Listener-Pause gegen Brute-Force
AES-256 Voiceprint
Stimmabdruck lokal verschlüsselt im Biometric Keystore
Privacy by Design
Audio-Buffer niemals gespeichert — nur Embedding-Vektor im RAM