通过 WebSocket 协议向 CosyVoice 大模型分段发送文本,并接收流式合成音频。适用于不使用 SDK、直接开发语音合成客户端的场景。
交互流程
客户端通过文本帧发送 JSON 指令,服务端通过文本帧返回 JSON 事件,通过二进制帧返回音频。
携带 Token 建立 WebSocket 连接。
发送
StartSynthesis,配置音色和音频格式。收到
SynthesisStarted后,通过一次或多次RunSynthesis发送文本,并持续接收事件和音频。文本发送完毕后,发送
StopSynthesis。服务端继续合成缓存中的文本。收到
SynthesisCompleted后,结束本次任务的音频接收。
有关服务交互方式,请参见CosyVoice 大模型语音合成。
鉴权与连接
服务端使用临时 Token 鉴权。获取方法请参见获取Token。
|
访问类型 |
使用条件 |
URL |
|
外网访问 |
通过公网连接北京地域的服务。 |
|
|
ECS 内网访问 |
使用华北 2(北京)地域 VPC 内的 ECS。经典网络不支持此访问方式。 |
|
使用内网访问方式,不产生 ECS 实例的公网流量费用。
在连接 URL 中添加 token 查询参数,例如:
wss://nls-gateway-cn-beijing.aliyuncs.com/ws/v1?token=<token>
请求指令
请求使用 JSON 文本帧。header 标识任务和指令,payload 携带对应指令的参数。以下 JSON 为请求结构示例;实际调用时替换 Appkey,并为每条消息生成唯一 ID。
Header 格式
|
参数 |
类型 |
必选 |
说明 |
|
|
String |
是 |
在智能语音交互控制台创建的项目 Appkey。 |
|
|
String |
是 |
当前消息的唯一 ID,长度为 32 位。 |
|
|
String |
是 |
当前合成任务的唯一 ID,长度为 32 位。同一任务的所有指令使用相同值。 |
|
|
String |
是 |
固定为 |
|
|
String |
是 |
指令名称: |
StartSynthesis 指令
开始合成任务并设置音色和输出格式。
|
参数 |
类型 |
必选 |
说明 |
|
|
String |
否 |
音色,默认值: |
|
|
String |
否 |
音频格式: |
|
|
Integer |
否 |
音频采样率,单位:Hz。默认值:16000。 |
|
|
Integer |
否 |
音量,取值范围:0~100。默认值:50。 |
|
|
Integer |
否 |
语速,取值范围:-500~500。默认值:0。-500、0、500 分别对应 0.5、1、2 倍速。 |
|
|
Integer |
否 |
语调,取值范围:-500~500。默认值:0。 |
|
|
Boolean |
否 |
是否开启 SSML。标签用法请参见SSML 标记语言。 |
|
|
Boolean |
否 |
使用 |
以下参数用于配置 AIGC 隐性标识:
参数 | 类型 | 必选 | 说明 |
payload.enable_aigc_tag | Boolean | 否 | 是否在生成的音频中添加AIGC隐性标识。设置为true时,会将隐性标识嵌入到支持格式(wav/mp3/opus)的音频中。 默认值:false。 |
payload.aigc_propagator | String | 否 | 设置AIGC隐性标识中的 默认值:阿里云UID。 |
payload.aigc_propagate_id | String | 否 | 设置AIGC隐性标识中的 默认值:本次语音合成请求Task ID。 |
{
"header": {
"message_id": "05450bf69c53413f8d88aed1ee600001",
"task_id": "640bc797bb684bd69601856513070001",
"namespace": "FlowingSpeechSynthesizer",
"name": "StartSynthesis",
"appkey": "<appkey>"
},
"payload": {
"voice": "longxiaochun_v2",
"format": "wav",
"sample_rate": 16000,
"volume": 50,
"speech_rate": 0,
"pitch_rate": 0,
"enable_ssml": true
}
}
RunSynthesis 指令
收到 SynthesisStarted 后发送文本。同一任务中可多次调用。
|
参数 |
类型 |
必选 |
说明 |
|
|
String |
是 |
本次发送的待合成文本。 |
{
"header": {
"message_id": "05450bf69c53413f8d88aed1ee600002",
"task_id": "640bc797bb684bd69601856513070001",
"namespace": "FlowingSpeechSynthesizer",
"name": "RunSynthesis",
"appkey": "<appkey>"
},
"payload": {
"text": "今天阳光很好。"
}
}
StopSynthesis 指令
文本发送完毕后,发送此指令结束输入。服务端合成剩余缓存文本,返回剩余音频后发送 SynthesisCompleted。不要把此指令理解为立即丢弃剩余文本或中断音频输出。该指令不需要 payload。
{
"header": {
"message_id": "05450bf69c53413f8d88aed1ee600003",
"task_id": "640bc797bb684bd69601856513070001",
"namespace": "FlowingSpeechSynthesizer",
"name": "StopSynthesis",
"appkey": "<appkey>"
}
}
响应事件
事件使用 JSON 文本帧。header.name 标识事件,header.status 为状态码,header.status_text 为状态描述。成功状态码为 20000000。以下 JSON 为结构示例,仅展示相关字段。
SynthesisStarted 事件
表示合成任务已开始,可以发送 RunSynthesis。
{
"header": {
"namespace": "FlowingSpeechSynthesizer",
"name": "SynthesisStarted",
"status": 20000000,
"status_text": "Gateway:SUCCESS:Success."
}
}
Sentence 系列事件
根据 payload.index 关联同一合成分段的事件。序号从 0 开始。不要将 RunSynthesis 的调用次数或输入文本的标点数量直接作为输出分段数量。
|
事件 |
payload 字段 |
说明 |
|
|
|
分段开始。 |
|
|
|
合成过程中的分段信息。不要假设每个分段固定返回一次此事件。 |
|
|
|
分段结束。 |
subtitles 为时间戳数组。longxiaochun_v2 开启 enable_subtitle 后可返回字级文本及 begin_time、end_time;未开启时可返回空数组。时间戳支持情况取决于音色,longxiaochun 在相同开关下仍返回空数组。
SynthesisCompleted 事件
表示本次任务的音频已经全部下发。文本发送完毕后必须发送 StopSynthesis,再等待此事件;一直不结束输入可能触发空闲超时。
{
"header": {
"namespace": "FlowingSpeechSynthesizer",
"name": "SynthesisCompleted",
"status": 20000000,
"status_text": "Gateway:SUCCESS:Success."
},
"payload": {
"measureType": "TextLengthHD",
"measureLength": 57
}
}
TaskFailed 事件
表示任务失败。记录 header.status、header.status_text 和 header.task_id 排查问题。例如,未结束输入且连接长时间空闲时,可返回 40000004 和 Gateway:IDLE_TIMEOUT 状态描述。
下行数据处理
将文本帧解析为事件,将二进制帧按接收顺序保存为音频。以下代码仅展示 websocket-client 的消息回调,不包含连接和发送指令的流程。创建 WebSocketApp 时,将该函数作为 on_message 传入;每次任务开始前创建新的 audio_data。
import json
audio_data = bytearray()
def on_message(ws, message):
if isinstance(message, str):
# 解析服务端事件。
try:
event = json.loads(message)
except json.JSONDecodeError:
print("Invalid JSON event")
return
header = event.get("header", {})
print(header.get("name"), header.get("status"))
elif isinstance(message, (bytes, bytearray)):
# 将音频数据按接收顺序追加到同一缓冲区。
audio_data.extend(message)
WebSocket 接口定义请参见WebSocket 标准。
音频保存与播放
音频从发送 RunSynthesis 后开始返回,直到 SynthesisCompleted 前完成下发。
按接收顺序将所有二进制帧写入同一个音频文件,或交给支持对应格式和流式输入的播放器。
不要将每个 WebSocket 二进制帧视为独立音频文件分别解码。帧边界不等于音频文件边界。
WAV 等带文件头的音频流包含文件头信息,后续二进制帧可能只包含连续音频数据。保存时不要为每一帧重复添加文件头。
JavaScript 示例代码
参考长文本语音合成JS播放示例实现浏览器中的流式语音合成和播放。
打开 index.html 前,替换 app.js 中的 appkey 和 token,并将 voice 改为 CosyVoice 音色,例如 longxiaochun。在示例目录启动 HTTP 服务后,访问 http://localhost:8000。
python -m http.server 8000
页面提供以下按钮:
StartSynthesis:建立连接并开始任务。
RunSynthesis:发送文本框中的文本片段。同一任务可多次单击。
-
StopSynthesis:结束文本输入,等待剩余音频和完成事件。
流式播放器说明
audio_player.js使用 Web Audio API 实现PCMAudioPlayer,将 16 位 PCM 采样点转换为浮点数后写入AudioBuffer,在上一段音频的onended回调中播放下一段。重要如果使用
MediaSource,先检查目标浏览器和音频格式的支持情况。兼容性信息请参见MediaSource。使用 openai-realtime-console 中的 wavtools 时,注意移动端和 Safari 可能出现的播放噪声,接入前应在目标设备验证播放效果。
测试工具
使用NlsStreamInputTtsMockServer.py在本地模拟语音合成服务。脚本依赖 websockets,并读取运行目录中的 test.wav;运行前准备该音频文件。
pip install websockets
python NlsStreamInputTtsMockServer.py
脚本默认监听 ws://127.0.0.1:12345。模拟服务用于调试消息交互,不能用于验证云端音色、时间戳或其他模型能力。最终应连接云端服务验证。