实时语音合成 (TTS)
流式传输文本,并即时获取原始 PCM 或 MP3 音频数据。
WSS
/api/v1/tts/ws/
建立双向 WebSocket 连接,实现实时流式文本转语音。支持分段发送文本(非常适合 LLM 输出)并持续接收二进制音频流。额度计算方式与 HTTP 接口相同。
⚡ 延迟优化 (TTFB)
若要实现超低延迟(首字节时间约为 200 毫秒),请确保文本片段以硬标点符号(句号 .、感叹号 ! 或问号 ?)结尾。AI 会在检测到句子边界之前自动缓冲文本,以确保呈现出完美自然的语调。
身份验证
在 WebSocket 握手期间使用 Authorization 标头进行身份验证:
Authorization: Bearer YOUR_API_KEY
连接设置
连接后,发送一条包含所需配置的初始化消息:
{
"type": "init",
"voice_id": 1710,
"prerecording": 1,
"output_format": "pcm_24000",
"voice_settings": {
"temperature": 25,
"similarity": 100,
"expressiveness": 0,
"speed": 1.0,
"fast_mode": 2
}
}
初始化参数
| 参数 | 必填 | 类型 | 描述 |
|---|---|---|---|
| type | 必需的 | 字符串 | 初始化消息必须为 "init"。 |
| voice_id | 必需的 | 整数 | 来自……的语音识别 语音列表. |
| output_format | 可选的 | 字符串 | 所需的音频格式。默认为 pcm_24000。选项包括:
|
| prerecording | 可选的 | 整数 | 1(普通,默认)或 2(AI 增强)。会影响所需点数。 |
| voice_settings | 可选的 | 物体 | 语音自定义选项。请参阅下方的“语音设置”部分。 |
语音设置对象
| 参数 | 类型 | 范围 | 默认 | 描述 |
|---|---|---|---|---|
| temperature | 整数 | 0 – 100 | 25 | 较低 = 稳定且可预测;较高 = 富有创意且多样化。 |
| similarity | 整数 | 0 – 100 | 100 | 语音与原始克隆声音的匹配程度。 |
| speed | 漂浮 | 0.5 – 2.0 | 1.0 | 播放速度倍率 |
| fast_mode | 整数 | 0, 1, or 2 | 0 |
显著降低延迟。
|
流式处理流水线
1. 发送文本片段
在 LLM 生成文本时逐步发送:
{"type": "text", "data": "مرحبا بكم."}
服务器将立即处理该数据片段,扣除对应时长的点数,并开始流式传输音频。
2. 信号完成(停止)
当文本生成完全结束后,发送停止信号:
{"type": "stop"}
服务器将完成缓冲区内剩余文本的合成,将最终的音频数据发送给您,并发送流结束信号。
接收消息
服务器通过 WebSocket 推送两种类型的帧:
1. 二进制音频数据块 (PCM / MP3)
原始音频字节在生成的同时即时流式传输。具体格式取决于您请求的 `output_format`(默认为 24kHz 16-bit PCM)。如果您请求的是 MP3 格式,则可以直接将这些字节写入文件。
2. JSON 控制消息
初始化就绪:
{"status": "ready", "message": "已初始化,准备接收文本。"}
流结束(安全关闭信号):
{"type": "end_of_stream"}
当您收到此消息时,即从数学上保证 100% 的音频分块已送达。此时您可以安全地在客户端关闭 WebSocket 连接。
流处理中途错误:
{"error": "直播期间积分不足"}
生产就绪示例
# pip install websockets
import asyncio
import websockets
import json
API_KEY = "YOUR_API_KEY"
url = "wss://api.moknah.io/api/v1/tts/ws/"
async def stream_tts():
headers = {"Authorization": f"Bearer {API_KEY}"}
async with websockets.connect(url, additional_headers=headers) as ws:
# 1. Initialize session (Requesting MP3 so we can save to file)
await ws.send(json.dumps({
"type": "init",
"voice_id": 1710,
"output_format": "mp3_44100_128",
"voice_settings": {"fast_mode": 2}
}))
print(f"Server: {await ws.recv()}")
# 2. Receiver Task
async def receive_audio():
with open("audio.mp3", "wb") as f:
async for msg in ws:
if isinstance(msg, bytes):
f.write(msg)
else:
data = json.loads(msg)
if data.get("type") == "end_of_stream":
print("✅ Stream finished safely.")
await ws.close()
break
receiver = asyncio.create_task(receive_audio())
# 3. Send Text
for chunk in ["مرحبا بكم.", "في منصة مُكنة."]:
await ws.send(json.dumps({"type": "text", "data": chunk}))
await asyncio.sleep(0.5)
# 4. Signal Stop
await ws.send(json.dumps({"type": "stop"}))
await receiver
asyncio.run(stream_tts())
// npm install ws
const WebSocket = require('ws');
const fs = require('fs');
const API_KEY = "YOUR_API_KEY";
const ws = new WebSocket('wss://api.moknah.io/api/v1/tts/ws/', {
headers: { "Authorization": `Bearer ${API_KEY}` }
});
const audioStream = fs.createWriteStream('audio.mp3');
ws.on('open', function open() {
// Requesting MP3 to safely write to an MP3 file
ws.send(JSON.stringify({
type: "init",
voice_id: 1710,
output_format: "mp3_44100_128",
voice_settings: { fast_mode: 2 }
}));
});
ws.on('message', function incoming(data, isBinary) {
if (isBinary) {
audioStream.write(data); // Save MP3 chunks
} else {
const msg = JSON.parse(data.toString());
if (msg.status === 'ready') {
console.log("Ready! Sending text...");
ws.send(JSON.stringify({ type: "text", data: "مرحبا بكم." }));
setTimeout(() => ws.send(JSON.stringify({ type: "text", data: "في منصة مُكة." })), 500);
setTimeout(() => ws.send(JSON.stringify({ type: "stop" })), 1000);
} else if (msg.type === 'end_of_stream') {
console.log("✅ All audio received. Closing connection.");
ws.close();
}
}
});
<?php
// composer require textalk/websocket
require 'vendor/autoload.php';
use WebSocket\Client;
$apiKey = "YOUR_API_KEY";
$client = new Client("wss://api.moknah.io/api/v1/tts/ws/", [
'headers' => ['Authorization' => "Bearer $apiKey"],
'timeout' => 60
]);
// 1. Initialize (Requesting MP3 format)
$client->text(json_encode([
"type" => "init",
"voice_id" => 1710,
"output_format" => "mp3_44100_128",
"voice_settings" => ["fast_mode" => 2]
]));
echo "Server: " . $client->receive() . "\n";
// 2. Send Text Chunks
$client->text(json_encode(["type" => "text", "data" => "مرحبا بكم."]));
$client->text(json_encode(["type" => "text", "data" => "في منصة مُكنة."]));
$client->text(json_encode(["type" => "stop"]));
// 3. Receive Audio & Control Messages
$audioFile = fopen("audio.mp3", "wb");
while (true) {
$message = $client->receive();
$decoded = json_decode($message, true);
if ($decoded && isset($decoded['type']) && $decoded['type'] == 'end_of_stream') {
echo "✅ Stream finished successfully.\n";
break;
} elseif (!$decoded) {
// If it can't be decoded as JSON, it's our binary MP3 audio chunk
fwrite($audioFile, $message);
}
}
fclose($audioFile);
$client->close();
?>
# Standard cURL does not support bidirectional WebSockets easily.
# Use 'websocat' to test WebSockets from the command line:
websocat wss://api.moknah.io/api/v1/tts/ws/ \
-H "Authorization: Bearer YOUR_API_KEY"
# Once connected, paste the JSON payloads interactively:
{"type": "init", "voice_id": 1710, "output_format": "mp3_44100_128"}
{"type": "text", "data": "مرحبا بكم."}
{"type": "stop"}
关闭代码与错误
WebSocket 连接可能会以以下标准代码之一关闭:
| 代码 | 类型 | 描述 |
|---|---|---|
1000 |
成功 | 流自然结束。 |
3011 |
生成错误 | 上游 API 音频生成失败。请重试请求。 |
4001 |
缺少 API 密钥 | Authorization 标头中未提供 API 密钥。 |
4002 |
认证失败 | 密钥无效、账户未激活、初始额度不足或受 IP 限制。 |
4290 |
并发限制 | 该用户已有一个活跃的 TTS WebSocket 连接。每个用户仅限建立一个连接。 |
API 支持
如有关于 API 的问题或疑问,请联系我们: api@moknah.io.