Qwen3-TTS — Streaming-API (Token-Stream → Audio)
WebSocket-Endpoint, um Text inkrementell (z. B. Token für Token aus einem LLM)
zu senden und Audio satzweise und chunked zurückzubekommen. Der Server puffert
den einströmenden Text, segmentiert ihn an Satz-/Teilsatzgrenzen und synthetisiert
jeden Abschnitt, sobald er vollständig ist.
Endpunkte & Auth
|
Lokal |
Extern |
| WebSocket |
ws://localhost:8091/v1/audio/speech/stream |
wss://qwen3-tts.aquantico.de/v1/audio/speech/stream |
| REST (Vollausgabe) |
http://localhost:8091/v1/audio/speech |
https://qwen3-tts.aquantico.de/v1/audio/speech |
| Health / Modelle |
…/health, …/v1/models |
dito |
- Auth (nur extern): Header
Authorization: Bearer <TOKEN>. Lokal ist kein Token nötig.
- TLS (extern): Das Zertifikat ist self-signed → Client-Zertifikatsprüfung ggf. deaktivieren
(
curl -k, bzw. ssl.CERT_NONE in Python). Intern kein TLS.
- Audioformat: 24 000 Hz, mono, 16-bit PCM (little-endian). Bei
response_format: "wav"
kommt pro Satz eine komplette WAV-Datei; bei "pcm" rohe PCM-Chunks (ohne Header).
Protokoll
Eine WebSocket-Verbindung = eine Session. Ablauf:
Client → Server (JSON-Textframes)
session.config — genau einmal, als allererste Nachricht (Timeout 10 s):
input.text — beliebig viele; je Nachricht ein Token/Fragment/Satz (max. 128 KB je Frame):
Der Server hängt alles an einen Puffer an und löst Synthese aus, sobald eine
Satz-/Teilsatzgrenze erkannt wird. Unvollständige Reste bleiben gepuffert.
Max. 30 s Pause zwischen zwei Nachrichten (idle timeout).
input.done — Eingabe beendet; verbleibender Pufferinhalt wird noch synthetisiert,
dann folgt session.done.
Server → Client
| Nachricht |
Inhalt |
audio.start (JSON) |
{"type":"audio.start","sentence_index":0,"sentence_text":"…","format":"pcm","sample_rate":24000} — Beginn eines Satzes. sample_rate nur bei PCM-Streaming. |
| (Binärframe) |
Audio-Bytes. Bei stream_audio:true + pcm mehrere Chunks, die schon während der Generierung kommen. Bei wav ein vollständiger WAV-Block. |
audio.done (JSON) |
{"type":"audio.done","sentence_index":0} — Satz fertig. |
session.done (JSON) |
{"type":"session.done","total_sentences":N} — Session fertig. |
error (JSON) |
{"type":"error","message":"…"} |
session.config — Felder
| Feld |
Default |
Beschreibung |
model |
— |
Modell-ID (wird validiert). |
voice |
(modellabh.) |
Eingebauter Sprecher, z. B. Ryan, Aiden, Vivian (CustomVoice). |
language |
Auto |
z. B. German, English. |
task_type |
CustomVoice |
CustomVoice | VoiceDesign | Base. |
instructions |
— |
Natürlichsprachliche Stil-/Emotionssteuerung (z. B. „sachlich, ruhig"). |
response_format |
wav |
pcm (für echtes Low-Latency-Streaming empfohlen) oder wav. |
stream_audio |
false |
true = Audio-Chunks während der Generierung; false = ganzer Satz auf einmal. |
split_granularity |
sentence |
sentence oder clause (an Kommas etc. → noch kleinere Stücke, niedrigere Latenz). |
speed |
1.0 |
Sprechtempo. |
max_new_tokens |
(modellabh.) |
Obergrenze Token. |
initial_codec_chunk_frames |
(deploy-cfg) |
Größe des ersten Audio-Chunks (Latenz-Tuning). |
ref_audio / ref_text |
— |
Referenz für Voice-Clone (Base-Task; nur mit klon-fähigem Modell). |
speaker_embedding |
— |
Fester 2048-dim-Stimmvektor (1.7B) für konstantes Timbre. |
x_vector_only_mode |
— |
Klon nur über x-Vector (ohne ICL-Transkript). |
Segmentierung & Stimm-Konsistenz
- Synthese erfolgt pro Segment (Satz bzw. Teilsatz).
split_granularity: "clause"
senkt die Latenz, erzeugt aber mehr Segmentgrenzen.
- ⚠️ Drift: Jedes Segment wird unabhängig generiert; Stimmlage/Stimmung können zwischen
Segmenten leicht schwanken (gemessen ~11 % F0-Streuung). Temperatur senken hilft nicht.
Gegenmittel: (a) mehr Text pro Segment bündeln, (b) Timbre über einen festen
speaker_embedding verankern (Base-Modell nötig).
Gemessene Latenz
| Pfad |
Time-to-first-audio |
Hinweis |
| Lokal, 1 Stream |
~0,16 s |
ab Satzende |
| Lokal, 8 parallele Streams |
~0,23 s |
TTFT lastunabhängig stabil |
| Extern (WSS, Token-Stream à 0,12 s) |
~1,6 s gesamt |
inkl. Token-Eintrudeln + Netz; ~0,3 s ab Satzende |
Python-Beispiel (Token-Stream → PCM)
Wichtige Limits
session.config muss innerhalb 10 s kommen, sonst Abbruch.
- Max. 30 s Pause zwischen Nachrichten (idle timeout).
session.config ≤ 4 MB (für große ref_audio-Payloads), input.text ≤ 128 KB pro Frame.