Appearance
实时语音合成(WebSocket)
wss://limapi.com/api/v1/realtime/audio/speech流式实时语音合成接口:文本边输入、音频边输出,支持连接复用(同一连接串行合成多个任务)。 协议兼容百炼 Qwen-Audio-TTS / CosyVoice WebSocket 协议,透传全部能力:流式合成、SSML、指令控制、hot_fix、声音复刻音色、任务取消。
与 HTTP 版 POST /api/v1/audio/speech 的区别:
| 维度 | HTTP 版 | WebSocket 版 |
|---|---|---|
| 输入方式 | 一次性提交完整文本 | 流式分段输入(continue-task) |
| 输出方式 | 完整音频一次返回 | 音频帧实时流式下发 |
| 连接复用 | 每请求一连接 | 同连接可串行多任务 |
| 适用场景 | 离线合成、短文本 | 对话播报、长文本、低延迟流式场景 |
认证
三种方式任选其一:
| 方式 | 说明 |
|---|---|
Authorization: Bearer sk-xxx 请求头 | 推荐(服务端 SDK) |
x-api-key: sk-xxx 请求头 | 备选 |
?api_key=sk-xxx 查询参数 | 浏览器 WebSocket 无法设置自定义请求头时使用 |
认证失败时网关会下发错误 JSON 帧并以 1008 状态关闭连接:
json
{"error": {"code": "401", "type": "unauthorized", "message": "无效的 API 密钥"}}支持模型
| 模型 | 计费 |
|---|---|
qwen/qwen-audio-3.0-tts-flash | 按合成字符数,1 元/万字符 |
cosyvoice-*、sambert-* 系列 | 以模型列表(GET /api/v1/models)价格为准 |
计费以上游返回的
usage.characters(实际合成字符数)为准;连接异常中断时对已发送文本兑底计费。
客户端事件
run-task:创建合成任务(model 支持带 qwen/ 前缀,网关自动去除)
json
{
"header": {"action": "run-task", "task_id": "task-001", "streaming": "duplex"},
"payload": {
"task_group": "audio", "task": "tts", "function": "SpeechSynthesizer",
"model": "qwen/qwen-audio-3.0-tts-flash",
"parameters": {
"text_type": "PlainText",
"voice": "longanhuan_v3.6",
"format": "mp3"
},
"input": {}
}
}parameters 常用字段:
| 字段 | 说明 |
|---|---|
| voice | 音色:预置音色名,或声音复刻生成的 voice_id |
| text_type | PlainText(默认)/ SSML |
| format | 音频格式:mp3(默认)/ wav / pcm 等 |
| sample_rate | 采样率,如 24000 |
| volume / rate / pitch | 音量 / 语速 / 语调 |
| instruct | 指令控制(仅支持的模型),如语气、情绪描述 |
continue-task:流式追加待合成文本
json
{
"header": {"action": "continue-task", "task_id": "task-001", "streaming": "duplex"},
"payload": {"input": {"text": "要合成的文本片段"}}
}限制:单条 ≤ 20000 字符;单任务累计 ≤ 200000 字符;相邻两次发送间隔 ≤ 23 秒(超时连接会被上游关闭)。
finish-task:结束任务(正常合成全部剩余文本)
json
{
"header": {"action": "finish-task", "task_id": "task-001", "streaming": "duplex"},
"payload": {"input": {}}
}取消任务(丢弃未合成部分,已合成部分正常计费):
json
{"payload": {"input": {"directive": "cancel"}}}服务端事件
| 事件 | 说明 |
|---|---|
task-started | 任务创建成功 |
result-generated | 合成进度:sentence-begin / synthesis / sentence-end(sentence-end 携带累计 usage.characters) |
task-finished | 任务完成,payload.usage.characters 为计费字符数 |
task-failed | 任务失败,header.error_code / header.error_message 携带原因 |
音频数据以二进制帧下发(与文本事件帧交替到达),拼接即为完整音频。
连接复用
收到 task-finished(或 task-failed)后,可在同一连接上直接再发 run-task 开启下一个任务,无需重新握手,每个任务独立计费。
JavaScript 示例(浏览器)
javascript
const ws = new WebSocket(
"wss://limapi.com/api/v1/realtime/audio/speech?api_key=sk-xxx"
);
const audioChunks = [];
ws.onopen = () => {
ws.send(JSON.stringify({
header: { action: "run-task", task_id: "task-1", streaming: "duplex" },
payload: {
task_group: "audio", task: "tts", function: "SpeechSynthesizer",
model: "qwen/qwen-audio-3.0-tts-flash",
parameters: { voice: "longanhuan_v3.6", format: "mp3" },
input: {}
}
}));
};
ws.onmessage = (e) => {
if (typeof e.data === "string") {
const msg = JSON.parse(e.data);
if (msg.header.event === "task-started") {
ws.send(JSON.stringify({
header: { action: "continue-task", task_id: "task-1", streaming: "duplex" },
payload: { input: { text: "你好,欢迎使用实时语音合成" } }
}));
ws.send(JSON.stringify({
header: { action: "finish-task", task_id: "task-1", streaming: "duplex" },
payload: { input: {} }
}));
} else if (msg.header.event === "task-finished") {
console.log("合成完成,计费字符:", msg.payload.usage.characters);
ws.close();
}
} else {
audioChunks.push(e.data); // 二进制音频帧
}
};Python 示例
python
import json
import websocket # pip install websocket-client
ws = websocket.create_connection(
"wss://limapi.com/api/v1/realtime/audio/speech",
header=["Authorization: Bearer sk-xxx"],
)
ws.send(json.dumps({
"header": {"action": "run-task", "task_id": "task-1", "streaming": "duplex"},
"payload": {
"task_group": "audio", "task": "tts", "function": "SpeechSynthesizer",
"model": "qwen/qwen-audio-3.0-tts-flash",
"parameters": {"voice": "longanhuan_v3.6", "format": "mp3"},
"input": {}
}
}))
audio = bytearray()
while True:
opcode, data = ws.recv_data()
if opcode == websocket.ABNF.OPCODE_BINARY:
audio += data
continue
msg = json.loads(data)
event = msg["header"]["event"]
if event == "task-started":
ws.send(json.dumps({
"header": {"action": "continue-task", "task_id": "task-1", "streaming": "duplex"},
"payload": {"input": {"text": "你好,欢迎使用实时语音合成"}}
}))
ws.send(json.dumps({
"header": {"action": "finish-task", "task_id": "task-1", "streaming": "duplex"},
"payload": {"input": {}}
}))
elif event == "task-finished":
break
ws.close()
open("speech.mp3", "wb").write(audio)错误处理
| 场景 | 行为 |
|---|---|
| 无/无效 API Key | 错误帧 401 unauthorized + 1008 关闭 |
| 额度/余额不足 | 错误帧 402 + 1008 关闭 |
| 模型被禁用/不在 Key 白名单/非 TTS 模型 | 合成 task-failed(error_code=ModelNotSupported),连接保持 |
| 上游连接失败 | 错误帧 502 upstream_unavailable + 1008 关闭 |
| 合成过程中连接断开 | 已发送文本按字符数兑底计费 |