Speech Server
speech-server est un serveur HTTP + WebSocket local qui expose chaque modèle Soniqo via une API REST simple, ainsi qu'un WebSocket compatible avec l'OpenAI Realtime API sur /v1/realtime. Il est livré dans le même paquet Homebrew (bottle) que la CLI speech — brew install speech place les deux dans votre PATH.
Installation et lancement
brew install speech
speech-server --port 8080
# Starting server on http://127.0.0.1:8080
# Endpoints:
# POST /transcribe - Speech-to-text (WAV body or JSON with audio_base64)
# POST /speak - Text-to-speech (JSON: {text, engine?, language?})
# POST /respond - Speech-to-speech (WAV body, voice/max_steps via query)
# POST /enhance - Speech enhancement (WAV body)
# GET /health - Health check
# WS /v1/realtime - OpenAI Realtime API (JSON events, base64 PCM16 audio)
Options de ligne de commande
| Option | Par défaut | Description |
|---|---|---|
--host | 127.0.0.1 | Adresse d'écoute. Passez à 0.0.0.0 pour exposer le serveur sur le réseau local. |
--port | 8080 | Port TCP. |
--preload | désactivé | Charge tous les modèles dès le démarrage. Démarrage plus lent (~30–60 s) mais aucune latence à la première requête. |
Les modèles sont téléchargés à la première utilisation et mis en cache dans ~/Library/Caches/qwen3-speech/. La première requête pour un modèle donné paie le coût du téléchargement + chargement (30 s à 2 min selon la taille du modèle) ; les requêtes suivantes sont servies à chaud.
Endpoints REST
POST /transcribe — Parole vers texte
Accepte soit un corps WAV brut, soit une enveloppe JSON avec l'audio encodé en base64.
# WAV body (preferred — lower overhead)
curl -X POST http://127.0.0.1:8080/transcribe \
-H "Content-Type: audio/wav" \
--data-binary @recording.wav
# JSON with base64
curl -X POST http://127.0.0.1:8080/transcribe \
-H "Content-Type: application/json" \
-d '{"audio_base64":"'"$(base64 -i recording.wav)"'","language":"en"}'
Réponse : {"text": "…", "language": "en", "confidence": 0.93}.
POST /speak — Texte vers parole
curl -X POST http://127.0.0.1:8080/speak \
-H "Content-Type: application/json" \
-d '{"text":"Hello, world!","engine":"kokoro","language":"en"}' \
--output hello.wav
Le corps de la réponse est un blob WAV. Valeurs prises en charge pour engine : qwen3 (par défaut), cosyvoice, kokoro.
POST /respond — Parole-à-parole
curl -X POST "http://127.0.0.1:8080/respond?voice=en_female_calm&max_steps=256" \
-H "Content-Type: audio/wav" \
--data-binary @question.wav \
--output answer.wav
Exécute PersonaPlex 7B — parole en entrée, parole en sortie. La transcription est renvoyée dans un en-tête X-Response-Text. Consultez le guide PersonaPlex pour les noms des préréglages de voix.
POST /enhance — Amélioration de la parole
curl -X POST http://127.0.0.1:8080/enhance \
-H "Content-Type: audio/wav" \
--data-binary @noisy.wav \
--output clean.wav
DeepFilterNet3 à 48 kHz. L'entrée est rééchantillonnée si nécessaire.
GET /health — Sonde de vivacité
curl http://127.0.0.1:8080/health
# {"status":"ok"}
WebSocket : /v1/realtime
Directement compatible avec l'OpenAI Realtime API — le même schéma d'événements JSON (session.update, input_audio_buffer.append, response.create, response.audio.delta, …) avec de l'audio PCM16 encodé en base64 à 24 kHz. Les clients écrits pour le SDK Realtime d'OpenAI fonctionnent avec speech-server sans modifier le code (il suffit de changer l'URL du WebSocket).
Exemple JavaScript
const ws = new WebSocket("ws://127.0.0.1:8080/v1/realtime");
ws.onopen = () => {
ws.send(JSON.stringify({
type: "session.update",
session: { modalities: ["audio", "text"] }
}));
// Stream PCM16 mono 24kHz audio from the mic:
const audioBase64 = await capturePCM16Chunk();
ws.send(JSON.stringify({
type: "input_audio_buffer.append",
audio: audioBase64
}));
ws.send(JSON.stringify({ type: "response.create" }));
};
ws.onmessage = ev => {
const msg = JSON.parse(ev.data);
if (msg.type === "response.audio.delta") {
playPCM16Base64(msg.delta);
}
};
Exemple Python
import asyncio, base64, json, wave, websockets
async def main():
async with websockets.connect("ws://127.0.0.1:8080/v1/realtime") as ws:
await ws.send(json.dumps({"type": "session.update",
"session": {"modalities": ["audio", "text"]}}))
with wave.open("question.wav", "rb") as wav:
pcm16 = wav.readframes(wav.getnframes())
await ws.send(json.dumps({"type": "input_audio_buffer.append",
"audio": base64.b64encode(pcm16).decode()}))
await ws.send(json.dumps({"type": "response.create"}))
async for raw in ws:
msg = json.loads(raw)
if msg["type"] == "response.audio.delta":
open("answer.pcm", "ab").write(base64.b64decode(msg["delta"]))
elif msg["type"] == "response.done":
break
asyncio.run(main())
Notes de déploiement
- Pas d'authentification par défaut.
speech-serverécoute sur127.0.0.1et fait confiance à chaque appelant. Placez-le derrière un proxy inverse (Caddy, nginx, tailscale) si vous l'exposez au-delà de localhost. - Les modèles sont chargés à la demande. La première requête vers
/transcribedéclenche le téléchargement + chargement de l'ASR (~700 Mo, 30–60 s). Utilisez--preloadpour supprimer le démarrage à froid, au prix d'un lancement plus lent. - Pas encore de transcription en streaming.
POST /transcribeattend le fichier WAV complet d'emblée. Utilisez/v1/realtimepour le streaming. - Systemd / launchd. Aucune unité de service n'est fournie à ce jour. Un simple
nohup speech-server &ou votre superviseur de processus habituel fait l'affaire.
Source
- Sources/AudioServer — routeur HTTP basé sur Hummingbird, registre de modèles à chargement différé, gestionnaire WebSocket
/v1/realtime. - Sources/AudioServerCLI — point d'entrée
@main, analyseur d'arguments. - En amont : référence de l'OpenAI Realtime API.