本文介绍如何通过WebSocket连接访问Sambert语音合成服务。
DashScope SDK目前仅支持Java和Python。若想使用其他编程语言开发Sambert语音合成应用程序,可以通过WebSocket连接与服务进行通信。
WebSocket是一种支持全双工通信的网络协议。客户端和服务器通过一次握手建立持久连接,双方可以互相主动推送数据,因此在实时性和效率方面具有显著优势。
对于常用编程语言,有许多现成的WebSocket库和示例可供参考,例如:
Go:
gorilla/websocket
PHP:
Ratchet
建议您先了解WebSocket的基本原理和技术细节,再参照本文进行开发。
前提条件
流程简述
客户端和服务端交互流程如下:
建立连接。
发送并接收消息。
客户端发送run-task指令,该指令中包含待合成语音的文本。
服务端收到run-task指令后返回task-started事件。
服务端持续返回合成的音频流和result-generated事件。
服务端返回task-finished事件,标志着语音合成任务结束。
关闭连接。
流程详情
对于不同的编程语言,尽管实现细节可能有所不同,但基本流程是一致的。
一、建立连接
通过调用工具库,将请求头和URL传入以建立WebSocket连接。
请求头中需添加如下鉴权信息:
{
"Authorization": "bearer <your-dashscope-api-key>", // 将<your-dashscope-api-key>替换成您自己的API-KEY
"user-agent": "your-platform-info", //可选
"X-DashScope-WorkSpace": workspace, // 可选
"X-DashScope-DataInspection": "enable"
}
WebSocket URL固定如下:
wss://dashscope.aliyuncs.com/api-ws/v1/inference
二、发送并接收消息
什么是指令和事件?
客户端发送给服务端的消息叫做指令,指令以Text Frame方式发送,用于控制语音合成任务的起止和标识任务边界。
服务端返回给客户端的消息叫做事件,事件代表不同的处理阶段。
指令和事件都是JSON格式,由header
和payload
这两部分组成。
header
包含基础信息,格式较为统一。指令中的header
所有指令的header格式统一。
header示例:
{ "header": { "action": "run-task", "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx", // 随机uuid "streaming": "out" } }
header参数:
参数
类型
是否必选
说明
header
请求头
-
-
header.action
String
是
指令类型,固定为run-task。用法参见下文。
header.task_id
String
是
当次任务ID,随机生成32位唯一ID。
为32位通用唯一识别码(UUID),由32个随机生成的字母和数字组成。可以带横线(如
"2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx"
)或不带横线(如"2bf83b9abaeb4fda8d9axxxxxxxxxxxx"
)。大多数编程语言都内置了生成UUID的API,例如Python:import uuid def generateTaskId(self): # 生成随机UUID return uuid.uuid4().hex
header.streaming
String
是
固定字符串:"out"
事件中的header
除task-failed外,所有事件的header格式统一。
header示例:
{ "header": { "task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx", "event": "task-started", "attributes": {} } }
header参数:
参数
类型
说明
header
请求头
-
header.event
String
事件类型
task-started
result-generated
task-finished
task-failed
详细说明参见下文。
header.task_id
String
客户端生成的task_id
payload
包含基础信息外的其他信息。不同指令或者事件的payload格式可能不同。
消息发送与接收流程
1、发送run-task指令:开启语音合成任务
该指令用于开启语合成任务。
示例:
{
"header": {
"action": "run-task",
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"streaming": "out"
},
"payload": {
"model": "sambert-zhichu-v1",
"task_group": "audio",
"task": "tts",
"function": "SpeechSynthesizer",
"input": {
"text": "床前明月光," //待合成文本
},
"parameters": {
"text_type": "PlainText",
"format": "mp3", //音频格式
"sample_rate": 16000, //采样率
"volume": 50, //音量
"rate": 1, //语塞
"pitch": 1, //音调
"word_timestamp_enabled": true, //是否开启词时间戳
"phoneme_timestamp_enabled": true //是否开启音素时间戳
}
}
}
payload参数说明:
参数 | 类型 | 是否必选 | 说明 |
payload.task_group | String | 是 | 固定字符串:"audio"。 |
payload.task | String | 是 | 固定字符串:"tts"。 |
payload.function | String | 是 | 固定字符串:"SpeechSynthesizer"。 |
payload.model | String | 是 | 模型名称。支持的模型请参考模型列表。 |
payload.input.text | String | 是 | 待合成文本,要求采用UTF-8编码且不能为空,一次性合成最高一万字符,其中每个汉字、英文、标点符号均按照1个字计算,支持SSML格式。SSML标记语言使用,请点击SSML标记语言介绍。 |
payload.parameters | |||
text_type | String | 是 | 固定字符串:“PlainText” |
format | String | 是 | 音频编码格式,支持pcm/wav/mp3格式。 |
sample_rate | Integer | 是 | 合成音频的采样率。建议使用模型默认采样率(参考模型列表),如果不匹配,服务会进行必要的升降采样处理。 |
volume | Integer | 否 | 音量,取值范围:0~100。默认值:50。 |
rate | Float | 否 | 合成音频的语速,取值范围:0.5~2。
默认值:1.0 |
pitch | Float | 否 | 合成音频的语调,取值范围:0.5~2。 默认值:1.0。 |
2、接收task-started事件:语音合成任务已开启
客户端成功发送run-task指令后,服务端返回task-started事件。
task-started事件的payload
没有内容。
示例:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-started",
"attributes": {}
},
"payload": {}
}
3、接收音频流
服务端在返回task-started事件后,会持续返回音频流。
在Sambert语音合成中,音频通过二进制通道以数据流方式分帧下发。下发的所有音频可以合成为一个完整的音频文件,也可以通过支持流式播放的播放器实时播放。
若要将所有音频合成为一个完整的音频文件,需使用追加模式写入同一个文件。
若要流式播放音频,需使用支持流式播放的音频播放器,否则无法播放。
支持流式播放的播放器包括:FFmpeg、PyAudio(Python)、AudioFormat(Java)、MediaSource(JavaScript)等。
在使用 WAV/MP3 格式合成音频时,由于文件按流式合成,因此仅在第一帧中包含当前任务的文件头信息。
4、接收result-generated事件:获取时间戳信息
服务器在返回音频流的同时,也会返回result-generated事件。
如果模型支持时间戳功能并且选择开启该功能,result-generated事件中会附带时间戳信息。
开启词时间戳:将run-task指令中的word_timestamp_enabled设置为true。
开启音素时间戳:将run-task指令中的phoneme_timestamp_enabled设置为true。
示例:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "result-generated",
"attributes": {}
},
"payload": {
"output": {
"sentence": {
"begin_time": 0,
"end_time": 1162,
"words": [
{
"text": "床",
"begin_time": 0,
"end_time": 263,
"phonemes": [
{
"begin_time": 0,
"end_time": 119,
"text": "ch_c",
"tone": 2
},
{
"begin_time": 119,
"end_time": 263,
"text": "uang_c",
"tone": 2
}
]
},
{
"text": "前",
"begin_time": 263,
"end_time": 463,
"phonemes": [
{
"begin_time": 263,
"end_time": 375,
"text": "q_c",
"tone": 2
},
{
"begin_time": 375,
"end_time": 463,
"text": "ian_c",
"tone": 2
}
]
},
{
"text": "明",
"begin_time": 463,
"end_time": 688,
"phonemes": [
{
"begin_time": 463,
"end_time": 575,
"text": "m_c",
"tone": 2
},
{
"begin_time": 575,
"end_time": 688,
"text": "ing_c",
"tone": 2
}
]
},
{
"text": "月",
"begin_time": 688,
"end_time": 863,
"phonemes": [
{
"begin_time": 688,
"end_time": 738,
"text": "y_c",
"tone": 4
},
{
"begin_time": 738,
"end_time": 863,
"text": "ve_c",
"tone": 4
}
]
},
{
"text": "光",
"begin_time": 863,
"end_time": 1150,
"phonemes": [
{
"begin_time": 863,
"end_time": 975,
"text": "g_c",
"tone": 1
},
{
"begin_time": 975,
"end_time": 1150,
"text": "uang_c",
"tone": 1
}
]
}
]
}
},
"usage": null
}
}
payload参数说明:
参数 | 类型 | 说明 |
output.sentence | ||
begin_time | Integer | 句子开始时间,单位为ms。 |
end_time | Integer | 句子结束时间,单位为ms。 |
words | List[] | 包含的词时间戳信息,需要将run-task指令中的word_timestamp_enabled设置为true。 |
sentence.words为字时间戳列表,其中每一个word格式如下:
参数 | 类型 | 说明 |
text | String | 文本。 |
begin_time | Integer | 字开始时间,单位为ms。 |
end_time | Integer | 字结束时间,单位为ms。 |
phonemes | List[] | 包含音素时间戳信息,需要将run-task指令中的phoneme_timestamp_enabled设置为true。 |
words.phonemes为音素时间戳列表,其中每一个phoneme格式如下:
参数 | 类型 | 说明 |
begin_time | Long | 音素开始时间 |
end_time | Long | 音素结束时间 |
text | String | 音素 |
tone | String | 音调。英文中0/1/2分别代表轻音/重音/次重音。拼音中1/2/3/4/5分别代表一声/二声/三声/四声/轻声。 |
5、接收task-finished事件:语音合成任务已结束
服务端在所有语音合成结果返回完毕后返回task-finished事件,标志着合成任务结束。
示例:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-finished",
"attributes": {}
},
"payload": {
"output": null,
"usage": {
"characters": 6
}
}
}
任务失败
若接收到task-failed事件,表示任务失败。
task-failed事件:任务失败
客户端收到该事件后结束连接并处理报错。
示例:
{
"header": {
"task_id": "2bf83b9a-baeb-4fda-8d9a-xxxxxxxxxxxx",
"event": "task-failed",
"error_code": "CLIENT_ERROR",
"error_message": "request timeout after 23 seconds.",
"attributes": {}
},
"payload": {}
}
header参数说明:
参数 | 类型 | 说明 |
header.error_code | String | 报错类型描述。 |
header.error_message | String | 具体报错原因。 |
三、关闭连接
接收到task-finished事件后,可以关闭WebSocket连接。通常通过调用工具库中的close
函数实现。