Funktioniert
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# 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
|
||||
WebSocket-Endpoint, um Text **inkrementell** zu senden und Audio **satzweise und
|
||||
chunked** zurückzubekommen. LLM-Tokens sollen clientseitig zu vollstaendigen
|
||||
Woertern gepuffert werden; der Client splittet nicht selbst in Saetze. Der Server puffert
|
||||
den einströmenden Text, segmentiert ihn an Satz-/Teilsatzgrenzen und synthetisiert
|
||||
jeden Abschnitt, sobald er vollständig ist.
|
||||
|
||||
@@ -37,14 +38,15 @@ Eine WebSocket-Verbindung = eine Session. Ablauf:
|
||||
"split_granularity": "sentence"
|
||||
}
|
||||
```
|
||||
2. **`input.text`** — beliebig viele; je Nachricht ein Token/Fragment/Satz (max. 128 KB je Frame):
|
||||
2. **`input.text`** — beliebig viele; je Nachricht ein vollstaendiges Wort oder Wort mit Satzzeichen (max. 128 KB je Frame):
|
||||
```json
|
||||
{"type": "input.text", "text": "Hallo"}
|
||||
{"type": "input.text", "text": ", wie geht es "}
|
||||
{"type": "input.text", "text": "Ihnen?"}
|
||||
{"type": "input.text", "text": "Hallo "}
|
||||
{"type": "input.text", "text": "Aquantico, "}
|
||||
{"type": "input.text", "text": "willkommen!"}
|
||||
```
|
||||
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.
|
||||
Der Client soll rohe LLM-Tokens zu Woertern puffern und **keine Saetze selbst
|
||||
splitten**. Der Server haengt alles an einen Puffer an und loest Synthese aus,
|
||||
**sobald eine Satz-/Teilsatzgrenze erkannt wird**. Unvollstaendige Reste bleiben gepuffert.
|
||||
Max. 30 s Pause zwischen zwei Nachrichten (idle timeout).
|
||||
3. **`input.done`** — Eingabe beendet; verbleibender Pufferinhalt wird noch synthetisiert,
|
||||
dann folgt `session.done`.
|
||||
@@ -98,7 +100,7 @@ Eine WebSocket-Verbindung = eine Session. Ablauf:
|
||||
## Python-Beispiel (Token-Stream → PCM)
|
||||
|
||||
```python
|
||||
import asyncio, json, ssl, inspect, websockets
|
||||
import asyncio, json, ssl, inspect, re, websockets
|
||||
|
||||
WSS = "wss://qwen3-tts.aquantico.de/v1/audio/speech/stream"
|
||||
TOKEN = "<BEARER_TOKEN>"
|
||||
@@ -131,11 +133,24 @@ async def speak(token_iter):
|
||||
if msg["type"] == "error": raise RuntimeError(msg["message"])
|
||||
|
||||
rt = asyncio.create_task(reader())
|
||||
for tok in token_iter: # Tokens direkt aus dem LLM
|
||||
await ws.send(json.dumps({"type": "input.text", "text": tok}))
|
||||
async for word in words_from_tokens(token_iter):
|
||||
await ws.send(json.dumps({"type": "input.text", "text": word}))
|
||||
await ws.send(json.dumps({"type": "input.done"}))
|
||||
await rt
|
||||
|
||||
async def words_from_tokens(token_iter):
|
||||
buf = ""
|
||||
for tok in token_iter:
|
||||
buf += tok
|
||||
while True:
|
||||
m = re.match(r"(\S+\s+)(.*)", buf)
|
||||
if not m:
|
||||
break
|
||||
yield m.group(1)
|
||||
buf = m.group(2)
|
||||
if buf:
|
||||
yield buf
|
||||
|
||||
def handle_pcm(chunk: bytes):
|
||||
... # z.B. sounddevice / Datei / Web-Client
|
||||
```
|
||||
@@ -145,3 +160,17 @@ def handle_pcm(chunk: bytes):
|
||||
- `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.
|
||||
|
||||
|
||||
## WebSocket-Logging
|
||||
|
||||
Fuer Diagnose kann der Logging-Proxy verwendet werden:
|
||||
|
||||
```text
|
||||
ws://localhost:8094/v1/audio/speech/stream
|
||||
```
|
||||
|
||||
Er leitet an `ws://localhost:8091/v1/audio/speech/stream` weiter und schreibt
|
||||
`session.config`, `input.text`, JSON-Events und Audio-Chunk-Groessen in die
|
||||
Containerlogs (`docker logs qwen3-tts`). So ist sichtbar, ob `seed`, `voice`,
|
||||
`language` und `instructions` tatsaechlich im WebSocket-Call gesendet werden.
|
||||
|
||||
Reference in New Issue
Block a user