本文介绍如何通过WebSocket连接访问CosyVoice语音合成服务。
DashScope SDK目前仅支持Java和Python。若想使用其他编程语言开发CosyVoice语音合成应用程序,可以通过WebSocket连接与服务进行通信。
用户指南:关于模型介绍和选型建议请参见实时语音合成-CosyVoice。
WebSocket是一种支持全双工通信的网络协议。客户端和服务器通过一次握手建立持久连接,双方可以互相主动推送数据,因此在实时性和效率方面具有显著优势。
对于常用编程语言,有许多现成的WebSocket库和示例可供参考,例如:
Go:
gorilla/websocketPHP:
RatchetNode.js:
ws
建议您先了解WebSocket的基本原理和技术细节,再参照本文进行开发。
CosyVoice 系列模型仅支持通过 WebSocket 连接调用,不支持 HTTP REST API。如果使用 HTTP 请求(如 POST 方式)调用,将返回 InvalidParameter 或 url error 错误。
前提条件
模型与价格
语音合成文本限制与格式规范
文本长度限制
单次通过continue-task指令发送的待合成文本长度不得超过 20000 字符,多次调用continue-task指令累计发送的文本总长度不得超过 20 万字符。
字符计算规则
汉字(包括简/繁体汉字、日文汉字和韩文汉字)按2个字符计算,其他所有字符(如标点符号、字母、数字、日韩文假名/谚文等)均按 1个字符计算
计算文本长度时,不包含SSML 标签内容
示例:
"你好"→ 2(你)+2(好)=4字符"中A文123"→ 2(中)+1(A)+2(文)+1(1)+1(2)+1(3)=8字符"中文。"→ 2(中)+2(文)+1(。)=5字符"中 文。"→ 2(中)+1(空格)+2(文)+1(。)=6字符"<speak>你好</speak>"→ 2(你)+2(好)=4字符
编码格式
需采用UTF-8编码。
数学表达式支持说明
当前数学表达式解析功能仅适用于cosyvoice-v2、cosyvoice-v3-flash和cosyvoice-v3-plus模型,支持识别中小学常见的数学表达式,包括但不限于基础运算、代数、几何等内容。
详情请参见LaTeX 公式转语音。
SSML标记语言支持说明
使用 SSML 功能需要同时满足以下条件:
模型支持:仅cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型支持SSML功能
音色支持: 必须使用支持 SSML 的音色。支持 SSML 的音色包括
所有复刻音色(通过声音复刻 API 创建的自定义音色)
音色列表中标记为支持SSML的系统音色
说明如果使用不支持 SSML 的系统音色(如部分基础音色),即使开启
enable_ssml参数,也会报错“SSML text is not supported at the moment!”。参数配置: 在run-task指令中将
enable_ssml参数设为true
满足上述条件后,通过continue-task指令发送包含SSML的文本即可使用 SSML 功能。完整示例请参见快速开始。
交互流程
客户端发送给服务端的消息称作指令;服务端返回给客户端的消息有两种:JSON格式的事件和二进制音频流。
按时间顺序,客户端与服务端的交互流程如下:
建立连接:客户端与服务端建立WebSocket连接。
开启任务:客户端发送run-task指令以开启任务。
等待确认:客户端收到服务端返回的task-started事件,标志着任务已成功开启,可以进行后续步骤。
发送待合成文本:
客户端按顺序向服务端发送一个或多个包含待合成文本的continue-task指令,服务端接收到完整语句后返回result-generated事件和音频流(文本长度有约束, 详情参见continue-task指令中
text字段描述)。说明您可以多次发送continue-task指令,按顺序提交文本片段。服务端接收文本片段后自动进行分句:
完整语句立即合成,此时客户端能够接收到服务端返回的音频
不完整语句缓存至完整后合成,语句不完整时服务端不返回音频
当发送finish-task指令时,服务端会强制合成所有缓存内容。
接收音频:通过
binary通道接收音频流通知服务端结束任务:
待文本发送完毕后,客户端发送finish-task指令通知服务端结束任务,并继续接收服务端返回的音频流(注意不要遗漏该步骤,否则可能收不到语音或收不到结尾部分的语音)。
任务结束:
客户端收到服务端返回的task-finished事件,标志着任务结束。
关闭连接:客户端关闭WebSocket连接。
为提高资源利用率,建议复用 WebSocket 连接处理多个任务,而非为每个任务建立新连接。参见关于建连开销和连接复用。
task_id 必须全程一致:同一次合成任务中,run-task、所有 continue-task、finish-task 必须使用相同的 task_id。
错误后果:如使用不同的 task_id,会导致:
服务端无法关联请求,音频流返回顺序混乱
文本内容被错误分配到不同任务,语音内容错位
任务状态异常,可能收不到 task-finished 事件
无法正确计费,usage 统计不准确
正确做法:
在 run-task 时生成唯一的 task_id(如使用UUID)
将该 task_id 存储在变量中
后续所有 continue-task 和 finish-task 都使用该 task_id
任务结束后(收到 task-finished),如需发起新任务,生成新的 task_id
客户端实现注意事项
在实现 WebSocket 客户端时,特别是使用 Flutter、Web 或移动端平台时,需要明确服务端与客户端的职责划分,以确保语音合成任务的完整性和稳定性。
服务端与客户端职责
服务端职责
服务端保证按顺序返回完整的音频流。您无需担心音频数据的顺序性或完整性,服务端会按照文本顺序依次生成并推送所有音频分片。
客户端职责
客户端需要负责以下关键任务:
读取并拼接所有音频分片
服务端返回的音频是以多个二进制分片(Binary Frame)的形式推送的。客户端必须完整接收所有分片,并按接收顺序拼接成最终的音频文件。示例代码如下:
# Python 示例:拼接音频分片 with open("output.mp3", "ab") as f: # 追加模式写入 f.write(audio_chunk) # audio_chunk 为每次接收到的二进制音频数据// JavaScript 示例:拼接音频分片 const audioChunks = []; ws.onmessage = (event) => { if (event.data instanceof Blob) { audioChunks.push(event.data); // 收集所有音频分片 } }; // 任务完成后合并音频 const audioBlob = new Blob(audioChunks, { type: 'audio/mp3' });保证 WebSocket 生命周期完整
在整个语音合成任务过程中(从发送 run-task指令 到接收 task-finished事件),不要提前断开 WebSocket 连接。常见错误包括:
在所有音频分片返回前就关闭连接,导致音频不完整;
忘记发送 finish-task指令,导致服务端缓存的文本未能合成;
页面跳转、应用切换到后台等场景下,未妥善处理 WebSocket 的保活机制。
重要移动端应用(如 Flutter、iOS、Android)需要特别注意应用进入后台时的网络连接管理。建议在后台任务或服务中维护 WebSocket 连接,或在恢复前台时检查任务状态并重新建立连接。
ASR→LLM→TTS 联动场景的文本完整性
在语音识别(ASR)→大语言模型(LLM)→语音合成(TTS)的联动流程中,确保传递给 TTS 的文本是完整的,不被中途截断。例如:
等待 LLM 生成完整句子或段落后,再发送 continue-task指令,而非逐字推送;
如果需要流式合成(边生成边播放),可以按自然语句边界(如句号、问号)分批发送文本;
在 LLM 输出完成后,务必发送 finish-task指令,避免遗漏尾部内容。
平台特定提示
Flutter:使用
web_socket_channel包时,注意在dispose方法中正确关闭连接,避免内存泄漏。同时,处理应用生命周期事件(如AppLifecycleState.paused)以应对后台切换场景。Web(浏览器):部分浏览器对 WebSocket 连接数有限制,建议复用同一连接处理多个任务。另外,使用
beforeunload事件在页面关闭前主动断开连接,避免残留连接。移动端(iOS/Android 原生):在应用进入后台时,操作系统可能会暂停或终止网络连接。建议使用后台任务(Background Task)或前台服务(Foreground Service)保持 WebSocket 活跃,或在恢复前台时重新初始化任务。
URL
WebSocket URL固定如下:
国际
在国际部署模式下,接入点与数据存储均位于新加坡地域,模型推理计算资源在全球范围内动态调度(不含中国内地)。
WebSocket URL:wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
中国内地
在中国内地部署模式下,接入点与数据存储均位于北京地域,模型推理计算资源仅限于中国内地。
WebSocket URL:wss://dashscope.aliyuncs.com/api-ws/v1/inference
常见 URL 配置错误:
错误:使用 http:// 或 https:// 开头的 URL → 正确:必须使用 wss:// 协议
错误:将 Authorization 参数放在 URL 查询字符串中(如
?Authorization=bearer <your_api_key>)→ 正确:Authorization 必须在 HTTP 握手的 Headers 中设置(参见Headers)错误:URL 末尾添加模型名称或其他路径参数 → 正确:URL 固定不变,模型通过run-task指令的
payload.model参数指定
Headers
请求头中需添加如下信息:
参数 | 类型 | 是否必选 | 说明 |
Authorization | string | 是 | 鉴权令牌,格式为 |
user-agent | string | 否 | 客户端标识,便于服务端追踪来源。 |
X-DashScope-WorkSpace | string | 否 | 阿里云百炼业务空间ID。 |
X-DashScope-DataInspection | string | 否 | 是否启用数据合规检测功能。默认不传或设为 |
鉴权验证时机与常见错误
Authorization 鉴权在 WebSocket 握手阶段进行验证,而非后续发送run-task指令时。如果 Authorization 头缺失或 API Key 无效,服务端将拒绝握手并返回 HTTP 401/403 错误,客户端库通常解析为 WebSocketBadStatus 异常。
鉴权失败排查步骤
若 WebSocket 连接失败,请按以下步骤排查:
检查 API Key 格式:确认 Authorization 头格式为
bearer <your_api_key>,注意 bearer 和 API Key 之间有一个空格。验证 API Key 有效性:在百炼控制台确认 API Key 未被删除或禁用,且具有调用 CosyVoice 模型的权限。
检查 Headers 设置:确认 Authorization 头在 WebSocket 握手时正确设置。不同编程语言的 WebSocket 库设置方式不同:
Python(websockets 库):
extra_headers={"Authorization": f"bearer {api_key}"}JavaScript:WebSocket 标准 API 不支持自定义 Headers,需使用服务端中转或其他库(如 ws)
Go(gorilla/websocket):
header.Add("Authorization", fmt.Sprintf("bearer %s", apiKey))
网络连通性测试:使用 curl 或 Postman 测试 API Key 是否有效(通过其他支持 HTTP 的 DashScope API)。
浏览器环境 WebSocket 使用说明
在浏览器环境(如 Vue3、React 等前端框架)中使用 WebSocket 时,存在以下限制:浏览器 WebSocket API 不支持自定义 Headers。浏览器原生的 new WebSocket(url) API 不支持在握手时设置自定义请求头(如 Authorization),这是浏览器安全策略的限制。因此,无法直接在前端代码中使用 API Key 进行鉴权。
解决方案:使用后端代理
在后端服务(Node.js、Java、Python 等)中建立 WebSocket 连接到 CosyVoice 服务,后端可以正确设置 Authorization 头。
前端通过 WebSocket 连接到自己的后端服务,后端作为代理转发消息到 CosyVoice。
优点:API Key 不暴露在前端,更安全;可以在后端添加额外的业务逻辑(如鉴权、日志、限流等)。
不要将 API Key 硬编码在前端代码中或通过浏览器直接发送。API Key 泄露会导致账号被盗用、产生高额费用或数据泄露风险。
示例代码:
如需其他编程语言实现,您可以参考示例中的逻辑,使用对应语言实现。或者使用AI工具将示例转换为目标语言。
前端(原生 Web)+ 后端(Node.js Express):cosyvoiceNodeJs_en.zip
前端(原生 Web)+ 后端(Python Flask):cosyvoiceFlask_en.zip
指令(客户端→服务端)
指令是客户端发送给服务端的消息,为JSON格式,以Text Frame方式发送,用于控制任务的起止和标识任务边界。
发送指令需严格遵循以下时序,否则可能导致任务失败:
发送 run-task指令
用于启动语音合成任务。
返回的
task_id需在后续发送continue-task指令和finish-task指令时使用,必须保持一致。
用于发送待合成文本。
必须在接收到服务端返回的task-started事件后,才能发送此指令。
用于结束语音合成任务。
在所有continue-task指令发送完毕后发送此指令。
1. run-task指令:开启任务
该指令用于开启语音合成任务。可在该指令中对音色、采样率等请求参数进行设置。
发送时机:WebSocket连接建立后。
不要发送待合成文本:在 run-task 指令中发送文本不利于问题排查,应避免在此发送文本。待合成文本应通过continue-task指令发送。
input 字段必须存在:payload 中必须包含 input 字段(格式为
{}),不可省略,否则会报错“task can not be null”。
示例:
{
"header": {
"action": "run-task",
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx", // 随机uuid
"streaming": "duplex"
},
"payload": {
"task_group": "audio",
"task": "tts",
"function": "SpeechSynthesizer",
"model": "cosyvoice-v3-flash",
"parameters": {
"text_type": "PlainText",
"voice": "longanyang", // 音色
"format": "mp3", // 音频格式
"sample_rate": 22050, // 采样率
"volume": 50, // 音量
"rate": 1, // 语速
"pitch": 1 // 音调
},
"input": {// input不能省去,不然会报错
}
}
}header参数说明:
参数 | 类型 | 是否必选 | 说明 |
header.action | string | 是 | 指令类型。 当前指令中,固定为"run-task"。 |
header.task_id | string | 是 | 当次任务ID。 为32位通用唯一识别码(UUID),由32个随机生成的字母和数字组成。可以带横线(如 在后续发送continue-task指令和finish-task指令时,用到的task_id需要和发送run-task指令时使用的task_id保持一致。 |
header.streaming | string | 是 | 固定字符串:"duplex" |
payload参数说明:
参数 | 类型 | 是否必选 | 说明 |
payload.task_group | string | 是 | 固定字符串:"audio"。 |
payload.task | string | 是 | 固定字符串:"tts"。 |
payload.function | string | 是 | 固定字符串:"SpeechSynthesizer"。 |
payload.model | string | 是 | 语音合成模型。 不同模型版本需要使用对应版本的音色:
|
payload.input | object | 是 | run-task 指令中必须包含 input 字段(不可省略),但不应在此发送待合成文本(因此应使用空对象
重要 常见错误:省略 input 字段或在 input 中包含非预期字段(如 mode、content 等)会导致服务端拒绝请求并返回“InvalidParameter: task can not be null”或连接关闭(WebSocket code 1007)错误。 |
payload.parameters | |||
text_type | string | 是 | 固定字符串:“PlainText”。 |
voice | string | 是 | 语音合成所使用的音色。 支持系统音色和复刻音色:
|
format | string | 否 | 音频编码格式。 支持pcm、wav、mp3(默认)和opus。 音频格式为opus时,支持通过 |
sample_rate | integer | 否 | 音频采样率(单位:Hz)。 默认值:22050。 取值范围:8000, 16000, 22050, 24000, 44100, 48000。 说明 默认采样率代表当前音色的最佳采样率,缺省条件下默认按照该采样率输出,同时支持降采样或升采样。 |
volume | integer | 否 | 音量。 默认值:50。 取值范围:[0, 100]。50代表标准音量。音量大小与该值呈线性关系,0为静音,100为最大音量。 |
rate | float | 否 | 语速。 默认值:1.0。 取值范围:[0.5, 2.0]。1.0为标准语速,小于1.0则减慢,大于1.0则加快。 |
pitch | float | 否 | 音高。该值作为音高调节的乘数,但其与听感上的音高变化并非严格的线性或对数关系,建议通过测试选择合适的值。 默认值:1.0。 取值范围:[0.5, 2.0]。1.0为音色自然音高。大于1.0则音高变高,小于1.0则音高变低。 |
enable_ssml | boolean | 否 | 是否开启SSML功能。 该参数设为 |
bit_rate | int | 否 | 音频码率(单位kbps)。音频格式为opus时,支持通过 默认值:32。 取值范围:[6, 510]。 |
word_timestamp_enabled | boolean | 否 | 是否开启字级别时间戳。 默认值:false。
该功能仅适用于cosyvoice-v3-flash、cosyvoice-v3-plus和cosyvoice-v2模型的复刻音色,以及音色列表中标记为支持的系统音色。 更多说明请参见时间戳数据提取最佳实践。 |
seed | int | 否 | 生成时使用的随机数种子,使合成的效果产生变化。在模型版本、文本、音色及其他参数均相同的前提下,使用相同的seed可复现相同的合成结果。 默认值0。 取值范围:[0, 65535]。 |
language_hints | array[string] | 否 | 指定语音合成的目标语言,提升合成效果。 当数字、缩写、符号等朗读方式或者小语种合成效果不符合预期时使用,例如:
取值范围:
注意:此参数为数组,但当前版本仅处理第一个元素,因此建议只传入一个值。 重要 此参数用于指定语音合成的目标语言,该设置与声音复刻时的样本音频的语种无关。如果您需要设置复刻任务的源语言,请参见CosyVoice声音复刻API。 |
instruction | string | 否 | 设置指令,用于控制方言、情感或角色等合成效果。该功能仅适用于cosyvoice-v3-flash模型的复刻音色,以及音色列表中标记为支持Instruct的系统音色。 使用要求:
支持的功能: |
enable_aigc_tag | boolean | 否 | 是否在生成的音频中添加AIGC隐性标识。设置为true时,会将隐性标识嵌入到支持格式(wav/mp3/opus)的音频中。 默认值:false。 仅cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2支持该功能。 |
aigc_propagator | string | 否 | 设置AIGC隐性标识中的 默认值:阿里云UID。 仅cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2支持该功能。 |
aigc_propagate_id | string | 否 | 设置AIGC隐性标识中的 默认值:本次语音合成请求Request ID。 仅cosyvoice-v3-flash、cosyvoice-v3-plus、cosyvoice-v2支持该功能。 |
2. continue-task指令
该指令专门用来发送待合成文本。
可以在一个continue-task指令中一次性发送待合成文本,也可以将文本分段并按顺序在多个continue-task指令中发送。
发送时机:在收到task-started事件后发送。
发送文本片段的间隔不得超过23秒,否则触发“request timeout after 23 seconds”异常。
若无待发送文本,需及时发送finish-task指令结束任务。
服务端强制设定23秒超时机制,客户端无法修改该配置。
示例:
{
"header": {
"action": "continue-task",
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx", // 随机uuid
"streaming": "duplex"
},
"payload": {
"input": {
"text": "床前明月光,疑是地上霜"
}
}
}header参数说明:
参数 | 类型 | 是否必选 | 说明 |
header.action | string | 是 | 指令类型。 当前指令中,固定为"continue-task"。 |
header.task_id | string | 是 | 当次任务ID。 需要和发送run-task指令时使用的task_id保持一致。 |
header.streaming | string | 是 | 固定字符串:"duplex" |
payload参数说明:
参数 | 类型 | 是否必选 | 说明 |
input.text | string | 是 | 待合成文本。 |
3. finish-task指令:结束任务
该指令用于结束语音合成任务。
请务必确保发送该指令,否则会出现以下问题:
音频不完整:服务端缓存的不完整语句不会被强制合成,导致音频缺失尾部内容。
连接超时:如果在最后一次continue-task指令后超过 23 秒未发送 finish-task,连接会因超时而断开。
计费异常:未正常结束的任务可能无法返回准确的 usage 信息。
发送时机:finish-task 应在所有continue-task指令发送完毕后立即发送,不要等待音频返回完毕或延迟发送,否则可能触发超时。
示例:
{
"header": {
"action": "finish-task",
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"streaming": "duplex"
},
"payload": {
"input": {}//input不能省去,否则会报错
}
}header参数说明:
参数 | 类型 | 是否必选 | 说明 |
header.action | string | 是 | 指令类型。 当前指令中,固定为"finish-task"。 |
header.task_id | string | 是 | 当次任务ID。 需要和发送run-task指令时使用的task_id保持一致。 |
header.streaming | string | 是 | 固定字符串:"duplex" |
payload参数说明:
参数 | 类型 | 是否必选 | 说明 |
payload.input | object | 是 | 固定格式:{}。 |
事件(服务端→客户端)
事件是服务端返回给客户端的消息,为JSON格式,代表不同的处理阶段。
服务端返回给客户端的二进制音频不包含在任何事件中,需单独接收。
1. task-started事件:任务已开启
当监听到服务端返回的task-started事件时,标志着任务已成功开启。只有在接收到该事件后,才能向服务端发送continue-task指令或finish-task指令;否则,任务将执行失败。
task-started事件的payload没有内容。
示例:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-started",
"attributes": {}
},
"payload": {}
}header参数说明:
参数 | 类型 | 说明 |
header.event | string | 事件类型。 当前事件中,固定为"task-started"。 |
header.task_id | string | 客户端生成的task_id |
2. result-generated事件
客户端发送continue-task指令和finish-task指令的同时,服务端持续返回result-generated事件。
为了让用户能够将音频数据与对应的文本内容关联,服务端在返回音频数据的同时,通过result-generated事件返回句子的元信息。服务端会对用户输入的文本进行自动分句,每个句子的合成过程包含以下3个子事件:
sentence-begin:标识句子开始,返回待合成的句子文本内容sentence-synthesis:标识音频数据块,每个此事件后立即通过WebSocket binary通道传输一个音频数据帧一个句子的合成过程中会产生多个
sentence-synthesis事件,每个对应一个音频数据块客户端需要按顺序接收这些音频数据块并以追加模式写入同一文件
sentence-synthesis事件与其后的音频数据帧是一一对应的关系,不会出现错位
sentence-end:标识句子结束,返回句子文本内容和累计的计费字符数
通过payload.output.type字段区分子事件类型。
示例:
sentence-begin
{
"header": {
"task_id": "3f2d5c86-0550-45c0-801f-xxxxxxxxxx",
"event": "result-generated",
"attributes": {}
},
"payload": {
"output": {
"sentence": {
"index": 0,
"words": []
},
"type": "sentence-begin",
"original_text": "床前明月光,"
}
}
}sentence-synthesis
{
"header": {
"task_id": "3f2d5c86-0550-45c0-801f-xxxxxxxxxx",
"event": "result-generated",
"attributes": {}
},
"payload": {
"output": {
"sentence": {
"index": 0,
"words": []
},
"type": "sentence-synthesis"
}
}
}sentence-end
{
"header": {
"task_id": "3f2d5c86-0550-45c0-801f-xxxxxxxxxx",
"event": "result-generated",
"attributes": {}
},
"payload": {
"output": {
"sentence": {
"index": 0,
"words": []
},
"type": "sentence-end",
"original_text": "床前明月光,"
},
"usage": {
"characters": 11
}
}
}header参数说明:
参数 | 类型 | 说明 |
header.event | string | 事件类型。 当前事件中,固定为"result-generated"。 |
header.task_id | string | 客户端生成的task_id。 |
header.attributes | object | 附加属性,通常为空对象。 |
payload参数说明:
参数 | 类型 | 说明 |
payload.output.type | string | 子事件类型。 取值范围:
完整的事件流程 对于每个待合成的句子,服务端按以下顺序返回事件:
|
payload.output.sentence.index | integer | 句子的编号,从0开始。 |
payload.output.sentence.words | array | 字级别信息数组,通常为空数组。 |
payload.output.original_text | string | 对用户输入文本进行分句后的句内容。最后一个句子可能没有此字段。 |
payload.usage.characters | integer | 截止当前,本次请求中计费的有效字符数。
在一次任务中, |
3. task-finished事件:任务已结束
当监听到服务端返回的task-finished事件时,说明任务已结束。
结束任务后可以关闭WebSocket连接结束程序,也可以复用WebSocket连接,重新发送run-task指令开启下一个任务(参见关于建连开销和连接复用)。
示例:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-finished",
"attributes": {
"request_uuid": "0a9dba9e-d3a6-45a4-be6d-xxxxxxxxxxxx"
}
},
"payload": {
"output": {
"sentence": {
"words": []
}
},
"usage": {
"characters": 13
}
}
}header参数说明:
参数 | 类型 | 说明 |
header.event | string | 事件类型。 当前事件中,固定为"task-finished"。 |
header.task_id | string | 客户端生成的task_id。 |
header.attributes.request_uuid | string | Request ID,可提供给CosyVoice开发人员定位问题。 |
payload参数说明:
参数 | 类型 | 说明 |
payload.usage.characters | integer | 截止当前,本次请求中计费的有效字符数。
在一次任务中, |
payload.output.sentence.index | integer | 句子的编号,从0开始。 本字段和以下字段需要通过word_timestamp_enabled开启字级别时间戳 |
payload.output.sentence.words[k] | ||
text | string | 字的文本。 |
begin_index | integer | 字在句子中的开始位置索引,从 0 开始。 |
end_index | integer | 字在句子中的结束位置索引,从 1 开始。 |
begin_time | integer | 字对应音频的开始时间戳,单位为毫秒。 |
end_time | integer | 字对应音频的结束时间戳,单位为毫秒。 |
时间戳数据提取最佳实践
开启 word_timestamp_enabled 后,时间戳信息会在 task-finished 事件中返回。示例如下:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-finished",
"attributes": {"request_uuid": "0a9dba9e-d3a6-45a4-be6d-xxxxxxxxxxxx"}
},
"payload": {
"output": {
"sentence": {
"index": 0,
"words": [
{
"text": "今",
"begin_index": 0,
"end_index": 1,
"begin_time": 80,
"end_time": 200
},
{
"text": "天",
"begin_index": 1,
"end_index": 2,
"begin_time": 240,
"end_time": 360
},
{
"text": "天",
"begin_index": 2,
"end_index": 3,
"begin_time": 360,
"end_time": 480
},
{
"text": "气",
"begin_index": 3,
"end_index": 4,
"begin_time": 480,
"end_time": 680
},
{
"text": "怎",
"begin_index": 4,
"end_index": 5,
"begin_time": 680,
"end_time": 800
},
{
"text": "么",
"begin_index": 5,
"end_index": 6,
"begin_time": 800,
"end_time": 920
},
{
"text": "样",
"begin_index": 6,
"end_index": 7,
"begin_time": 920,
"end_time": 1000
},
{
"text": "?",
"begin_index": 7,
"end_index": 8,
"begin_time": 1000,
"end_time": 1320
}
]
}
},
"usage": {"characters": 15}
}
}
正确的提取方式:
仅在 task-finished 事件中提取完整时间戳:完整的句子时间戳数据仅在任务结束时(task-finished 事件)返回,包含 payload.output.sentence.words 数组。
result-generated 事件不包含时间戳:result-generated 事件主要用于标识音频流的生成进度,不包含字级别时间戳信息。
事件过滤示例(Python):
def on_event(message): event_type = message["header"]["event"] # 仅在 task-finished 事件中提取时间戳 if event_type == "task-finished": words = message["payload"]["output"]["sentence"]["words"] for word in words: print(f"文字: {word['text']}, 开始: {word['begin_time']}ms, 结束: {word['end_time']}ms") # result-generated 事件用于音频流处理 elif event_type == "result-generated": # 处理音频流,不提取时间戳 pass
如果在多个事件中提取时间戳数据,会导致重复。请确保仅在 task-finished 事件中提取。
4. task-failed事件:任务失败
如果接收到task-failed事件,表示任务失败。此时需要关闭WebSocket连接并处理错误。通过分析报错信息,如果是由于编程问题导致的任务失败,您可以调整代码进行修正。
示例:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-failed",
"error_code": "InvalidParameter",
"error_message": "[tts:]Engine return error code: 418",
"attributes": {}
},
"payload": {}
}header参数说明:
参数 | 类型 | 说明 |
header.event | string | 事件类型。 当前事件中,固定为task-failed。 |
header.task_id | string | 客户端生成的task_id。 |
header.error_code | string | 报错类型描述。 |
header.error_message | string | 具体报错原因。 |
任务中断方式
在流式合成过程中,如需提前终止当前任务(如用户取消播放、实时对话中打断等),可通过以下方式实现:
中断方式 | 服务端行为 | 适用场景 |
直接关闭连接 |
| 立即中断:用户取消播放、切换内容、应用退出等 |
发送 finish-task |
| 优雅结束:停止发送新文本,但需接收已缓存内容的音频 |
发起新 run-task |
| 任务切换:实时对话中用户打断,立即切换到新内容 |
关于建连开销和连接复用
WebSocket服务支持连接复用以提升资源的利用效率,避免建立连接开销。
服务端收到客户端发送的 run-task指令后,将启动一个新的任务,客户端发送finish-task指令后,服务端在任务完成时返回task-finished事件以结束该任务。结束任务后WebSocket连接可以被复用,客户端重新发送run-task指令即可开启下一个任务。
在复用连接中的不同任务需要使用不同 task_id。
如果在任务执行过程中发生失败,服务将依然返回task-failed事件,并关闭该连接。此时这个连接无法继续复用。
如果在任务结束后60秒没有新的任务,连接会超时自动断开。
性能指标与并发限制
并发限制
具体限制请参见限流。
如需提升并发配额(如支持更多并发连接数),请联系客服申请。配额调整可能需要审核,一般在 1~3 个工作日内完成。
最佳实践:为提高资源利用率,建议复用 WebSocket 连接处理多个任务,而非为每个任务建立新连接。参见关于建连开销和连接复用。
连接性能与延迟
正常连接耗时:
中国内地客户端:WebSocket 连接建立(从 newWebSocket 到 onOpen)通常耗时 200~1000 毫秒。
跨境连接(如香港、海外地域):可能出现 1~3 秒的连接延迟,偶发情况下可能达到 10~30 秒。
连接耗时过长排查:
如果 WebSocket 连接建立耗时超过 30 秒,可能的原因包括:
网络问题:客户端与服务端之间的网络延迟较高(如跨境连接、运营商网络质量问题)。
DNS 解析慢:dashscope.aliyuncs.com 的 DNS 解析耗时较长。可尝试使用公共 DNS(如 8.8.8.8)或配置本地 hosts 文件。
TLS 握手慢:客户端 TLS 版本过低或证书校验耗时。建议使用 TLS 1.2 或更高版本。
代理或防火墙:企业网络可能限制 WebSocket 连接或需要通过代理。
排查工具:
使用 Wireshark 或 tcpdump 抓包分析 TCP 握手、TLS 握手、WebSocket Upgrade 各阶段耗时。
使用 curl 测试 HTTP 连接延迟:
curl -w "@curl-format.txt" -o /dev/null -s https://dashscope.aliyuncs.com
CosyVoice WebSocket API 部署在中国内地(北京)地域。如果客户端位于其他地域(如香港、海外),建议使用就近的中转服务器或 CDN 加速连接。
音频生成性能
合成速度:
实时率(RTF): CosyVoice 各模型的合成速度通常为 0.1~0.5 倍实时率(即 1 秒音频约需 0.1~0.5 秒生成),具体速度取决于模型版本、文本长度和服务端负载。
首包延迟:从发送 continue-task 指令到接收到第一个音频分片,通常在 200~800 毫秒之间。
示例代码
示例代码仅提供最基础的服务调通实现,实际业务场景的相关代码需您自行开发。
在编写WebSocket客户端代码时,为了同时发送和接收消息,通常采用异步编程。您可以按照以下步骤来编写程序:
错误码
如遇报错问题,请参见错误信息进行排查。
常见问题
功能特性/计量计费/限流
Q:当遇到发音不准的情况时,有什么解决方案可以尝试?
通过SSML可以对语音合成效果进行个性化定制。
Q:为什么使用WebSocket协议而非HTTP/HTTPS协议?为什么不提供RESTful API?
语音服务选择 WebSocket 而非 HTTP/HTTPS/RESTful,根本在于其依赖全双工通信能力——WebSocket 允许服务端与客户端主动双向传输数据(如实时推送语音合成/识别进度),而基于 HTTP 的 RESTful 仅支持客户端发起的单向请求-响应模式,无法满足实时交互需求。
Q:语音合成是按文本字符数计费的,要如何查看或获取每次合成的文本长度?
通过服务端返回的result-generated事件中payload.usage.characters参数获取字符数,请以收到的最后一个result-generated事件为准。
故障排查
Q:如何获取Request ID?
通过以下两种方式可以获取:
解析result-generated事件中服务端返回的信息。
解析task-finished事件中服务端返回的信息。
Q:使用SSML功能失败是什么原因?
请按以下步骤排查:
确保限制与约束正确
确保用正确的方式进行调用,详情请参见SSML标记语言支持说明
确保待合成文本为纯文本格式且符合格式要求,详情请参见SSML标记语言介绍
Q:为什么音频无法播放?
请根据以下场景逐一排查:
音频保存为完整文件(如xx.mp3)的情况
音频格式一致性:确保请求参数中设置的音频格式与文件后缀一致。例如,如果请求参数设置的音频格式为wav,但文件后缀为mp3,可能会导致播放失败。
播放器兼容性:确认使用的播放器是否支持该音频文件的格式和采样率。例如,某些播放器可能不支持高采样率或特定编码的音频文件。
流式播放音频的情况
将音频流保存为完整文件,尝试使用播放器播放。如果文件无法播放,请参考场景 1 的排查方法。
如果文件可以正常播放,则问题可能出在流式播放的实现上。请确认使用的播放器是否支持流式播放。
常见的支持流式播放的工具和库包括:ffmpeg、pyaudio (Python)、AudioFormat (Java)、MediaSource (Javascript)等。
Q:为什么音频播放卡顿?
请根据以下场景逐一排查:
检查文本发送速度: 确保发送文本的间隔合理,避免前一句音频播放完毕后,下一句文本未能及时发送。
检查回调函数性能:
检查回调函数中是否存在过多业务逻辑,导致阻塞。
回调函数运行在 WebSocket 线程中,若被阻塞,可能会影响 WebSocket 接收网络数据包,进而导致音频接收卡顿。
建议将音频数据写入一个独立的音频缓冲区(audio buffer),然后在其他线程中读取并处理,避免阻塞 WebSocket 线程。
检查网络稳定性: 确保网络连接稳定,避免因网络波动导致音频传输中断或延迟。
Q:语音合成慢(合成时间长)是什么原因?
请按以下步骤排查:
检查输入间隔
如果是流式语音合成,请确认文字发送间隔是否过长(如上段发出后延迟数秒才发送下段),过久间隔会导致合成总时长增加。
分析性能指标
首包延迟:正常500ms左右。
RTF(RTF = 合成总耗时/音频时长):正常小于1.0。
Q:合成的语音发音错误如何处理?
请使用SSML的<phoneme>标签指定正确的发音。
Q:为什么没有返回语音?为什么结尾部分的文本没能成功转换成语音?(合成语音缺失)
请确认是否忘记发送finish-task指令。在语音合成过程中,服务端会在缓存足够文本后才开始合成。如果忘记发送finish-task指令,可能会导致缓存中的结尾部分文本未能被合成为语音。
Q:为什么返回的音频流顺序错乱?导致播放内容混乱
请从以下两个方面排查:
确保同一个合成任务的 run-task指令、continue-task指令和finish-task指令使用相同的
task_id。检查异步操作是否导致音频文件写入顺序与接收二进制数据的顺序不一致。
Q:WebSocket 连接错误如何处理?
WebSocket 连接关闭(code 1007)如何处理?
WebSocket 连接在发送 run-task 指令后立即关闭,且关闭码为 1007。
错误原因:服务端检测到协议或数据格式错误,主动断开连接。常见原因包括:
run-task 指令的 payload 中包含非法字段(如在 payload 中误加了
"input": {}以外的其他字段)。JSON 格式错误(如缺少逗号、括号不匹配等)。
必填字段缺失(如 task_id、action 等)。
解决方案:
检查 JSON 格式:验证请求体格式是否正确。
检查必填字段:确认 header.action、header.task_id、header.streaming、payload.task_group、payload.task、payload.function、payload.model、payload.input 均已正确设置。
移除无效字段:run-task 的 payload.input 中仅允许空对象
{}或包含 text 字段,不要添加其他字段。
WebSocket 连接报错 WebSocketBadStatus 或 401/403 如何处理?
在建立 WebSocket 连接时报错 WebSocketBadStatus、401 Unauthorized 或 403 Forbidden。
错误原因: 鉴权失败。服务端在 WebSocket 握手阶段验证 Authorization 头,如果 API Key 无效或缺失,将拒绝连接。
解决方案:参见鉴权失败排查步骤。
权限与认证
Q:我希望我的 API Key 仅用于 CosyVoice 语音合成服务,而不被百炼其他模型使用(权限隔离),我该如何做?
可以通过新建业务空间并只授权特定模型来限制API Key的使用范围。详情请参见业务空间管理。
更多问题
请参见GitHub QA。