Translation pending

Die Übersetzung dieser Seite ist noch nicht verfügbar. Der folgende Inhalt ist auf Englisch — Referenz: die englische Version.

Speech Server

Native Laufzeit für Apple · PocketTTS

PocketTTS ergänzt speech-swift um native CoreML-Sprachausgabe. Alle Stufen verwenden standardmäßig nur die CPU. Die englische Stimme Alba liefert jeweils 1.920 Samples mit 24 kHz, also 80 ms pro Audioblock.

Verwendung mit Swift und CLI

Verwenden Sie pocket-tts mit der Stimme alba. HTTP-Streaming erfordert PCM; WebSocket sendet Audioblöcke. Die bisherigen Standardmodelle bleiben bestehen.

curl http://localhost:8080/v1/audio/speech \
  -H 'Content-Type: application/json' \
  -d '{"model":"pocket-tts","voice":"alba","input":"Hello world.","response_format":"pcm","stream":true}' \
  -o hello.pcm

speech-server is a local HTTP + WebSocket server that exposes every Soniqo model through a simple REST API, plus an OpenAI Realtime API-compatible WebSocket at /v1/realtime. It ships in the same Homebrew bottle as the speech CLI — brew install speech drops both on your PATH.

Install and run

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)

Command-line flags

FlagDefaultDescription
--host127.0.0.1Bind address. Change to 0.0.0.0 to expose on LAN.
--port8080TCP port.
--preloadoffLoad all models eagerly at startup. Slower boot (~30–60 s) but zero first-request latency.

Models are downloaded on first use and cached in ~/Library/Caches/qwen3-speech/. The first request for a given model pays the download + load cost (30 s – 2 min depending on model size); subsequent requests are warm.

REST endpoints

POST /transcribe — Speech-to-text

Accepts either a raw WAV body or a JSON envelope with base64-encoded audio.

# 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"}'

Response: {"text": "…", "language": "en", "confidence": 0.93}.

POST /speak — Text-to-speech

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

Response body is a WAV blob. Supported engine values: qwen3 (default), cosyvoice, kokoro.

POST /respond — Speech-to-speech

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

Runs PersonaPlex 7B — speech in, speech out. Transcript is returned as an X-Response-Text header. See PersonaPlex guide for voice preset names.

POST /enhance — Speech enhancement

curl -X POST http://127.0.0.1:8080/enhance \
  -H "Content-Type: audio/wav" \
  --data-binary @noisy.wav \
  --output clean.wav

DeepFilterNet3 at 48 kHz. Input is resampled if needed.

GET /health — Liveness probe

curl http://127.0.0.1:8080/health
# {"status":"ok"}

Die HTTP- und Echtzeit-WebSocket-Tests warten bis zu 10 Sekunden, bis /health HTTP 200 mit {"status":"ok"} zurückgibt. Startfehler lassen die Testvorbereitung fehlschlagen. Die Bereinigung wartet auf das Ende des Servers, bevor der Port erneut verwendet wird.

WebSocket: /v1/realtime

Drop-in compatible with the OpenAI Realtime API — the same JSON event schema (session.update, input_audio_buffer.append, response.create, response.audio.delta, …) with base64-encoded PCM16 audio at 24 kHz. Clients written against OpenAI's Realtime SDK work against speech-server without code changes (just switch the WebSocket URL).

Automatische Turns mit Server-VAD

Manuelles input_audio_buffer.commit bleibt die Voreinstellung. Setze session.turn_detection auf server_vad, damit Silero VAD auf dem Server jeden abgeschlossenen Sprach-Turn automatisch transkribiert. Präfix-Audio bleibt erhalten, der Leerlaufpuffer ist begrenzt, die Ereignisse speech_started, speech_stopped und committed werden der Reihe nach gesendet und ein dauerhaft aktiver Turn wird am konfigurierten Maximum beendet.

ws.send(JSON.stringify({
  type: "session.update",
  session: {
    turn_detection: {
      type: "server_vad",
      threshold: 0.5,
      prefix_padding_ms: 300,
      silence_duration_ms: 500,
      max_turn_duration_ms: 120000
    }
  }
}));

JavaScript example

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);
  }
};

Python example

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())

Deployment notes

Source

Optionale Modellverwaltung

SpeechRuntime bietet gemeinsames Laden, abbrechbare FIFO-Zulassung und serielle Ausführung. Qwen3ASRRuntime wählt MLX oder vollständiges CoreML über explizite Deskriptoren; native APIs und CLI-Optionen bleiben verfügbar.

Der Server hält eine Ausführungsfreigabe während der gesamten Anfrage einschließlich Streaming-Ausgabe und lässt bis zu 32 Anfragen warten. ASR-Fehler ergeben HTTP-Fehler oder WebSocket-Fehlerereignisse; gültige leere Transkripte bleiben erfolgreich. Die Wiedergabe behält überzählige Samples. Modelle im Cache und wartende Audiodaten belegen weiterhin Speicher.

Voxtral TTS

Voxtral 4B TTS 2603 läuft über das Modul VoxtralTTS lokal auf Apple Silicon: 20 voreingestellte Stimmen, neun Sprachen und Mono-Sprache mit 24 kHz.

curl http://localhost:8080/v1/audio/speech \
  -H 'Content-Type: application/json' \
  -d '{"model":"voxtral-tts","input":"Hello, welcome to Voxtral.","voice":"casual_male","response_format":"wav"}' \
  -o hello.wav

Nur vorgegebene Stimmen; das Klonen anhand von Referenzaudio ist nicht verfügbar. SDK, CLI, WebSocket und HTTP PCM geben Audio während der Erzeugung aus. Der Eingabetext muss zu Beginn vollständig vorliegen. Die Modellgewichte stehen unter CC BY-NC 4.0.

Voxtral-TTS-Anleitung lesen