logo
平台介绍
快速接入
密钥管理
文本转语音
文本转语音介绍
POST
接口能力介绍(非流式)
SSE
接口能力介绍(流式)
WSS
接口能力介绍(WSS)
音色克隆
音色列表
智能体
视频生成
语音识别(ASR)
计费规则
常见问题
工作台
立即登录

WebSocket

该 API 提供基于 WebSocket 的实时文本到语音(Text-to-Speech, TTS)合成功能,支持增量式文本输入和流式音频输出。单次请求支持的最大文本长度为 10000 字符,适用于实时对话、在线客服、语音交互等场景。

功能特性

  • 实时流式合成:支持边输入文本边合成语音,首包延迟低
  • 增量式文本输入:可分段发送文本内容,无需等待全部文本就绪
  • 语音参数调节:可灵活调整音量、音调、语速等语音表现参数
  • 长连接保持:单个 WebSocket 连接支持多次文本合成任务

接口信息

接口地址

  • WebSocket: wss://api.senseaudio.cn/ws/v1/t2a_v2
  • Content-Type: application/json
  • 鉴权方式: Bearer Token

重要提示

  • WebSocket 接口只支持返回 hex 格式的音频数据
  • 当最后一次收到服务端返回结果后超过 120 秒没有发送新事件时,WebSocket 连接自动断开
  • 音色模型名称:SenseAudio-TTS-1.0

通信流程

WebSocket TTS 通信遵循以下流程:

plaintext
复制
1. 客户端建立 WebSocket 连接 ↓ 2. 服务端返回 connected_success 事件 ↓ 3. 客户端发送 task_start 事件(包含音色、音频格式等配置) ↓ 4. 服务端返回 task_started 事件 ↓ 5. 客户端发送 task_continue 事件(发送待合成文本) ↓ 6. 服务端返回音频数据(可多次发送 task_continue) ↓ 7. 客户端发送 task_finish 事件 ↓ 8. 服务端返回 task_finished 事件并关闭连接

客户端事件

1. task_start - 开始任务

发送此事件正式开始合成任务。当服务端返回 task_started 事件时,标志着任务已成功开始。只有在接收到该事件后,才能向服务器发送 task_continue 或 task_finish 事件。


请求配置

请求头 (Request Headers)

参数名必填说明示例
Authorization是鉴权 Token。格式:Bearer API_KEYBearer sk-123456…
Content-Type是内容类型。固定为 application/jsonapplication/json

请求参数

参数名类型必填默认值说明
eventstring是无固定值:task_start
modelstring是无模型名称,固定为 SenseAudio-TTS-1.0
voice_settingobject是无声音设置
voice_setting.voice_idstring是无主音色名称
voice_setting.speedfloat否1.0语速,取值范围 [0.5, 2.0]
voice_setting.volfloat否1.0音量,取值范围 (0, 10]
voice_setting.pitchint否0音调,取值范围 [-12, 12],0 表示保持原始音调
audio_settingobject否无音频格式设置
audio_setting.sample_rateint否32000音频采样率,取值范围 [8000, 16000, 22050, 24000, 32000, 44100]
audio_setting.bitrateint否128000音频码率,取值范围 [32000, 64000, 128000, 256000]
audio_setting.formatstring否mp3输出格式:mp3、wav、pcm、flac
audio_setting.channelint否2音频声道,1:单声道,2:双声道

请求示例

json
复制
{ "event": "task_start", "model": "SenseAudio-TTS-1.0", "voice_setting": { "voice_id": "girl_banxia", "speed": 1, "vol": 1, "pitch": 0 }, "audio_setting": { "sample_rate": 32000, "bitrate": 128000, "format": "mp3", "channel": 1 } }

响应参数

参数名类型说明
session_idstring会话 ID
eventstring事件类型
trace_idstring链路追踪 ID
base_respobject请求状态信息
base_resp.status_codeint64状态码
base_resp.status_msgstring状态详情

响应示例

json
复制
{ "session_id": "69c20e38c8761996a85d57881fe4d817", "event": "task_started", "trace_id": "69c20e38c8761996a85d57881fe4d817", "base_resp": { "status_code": 0, "status_msg": "success" } }

2. task_continue - 任务继续

当收到服务端返回的 task_started 事件后,任务正式开始,可通过发送 task_continue 事件发送要合成的文本。支持顺序发送多个 task_continue 事件,实现分段文本合成。

请求参数

参数名类型必填说明示例
eventstring是固定值:task_continue
textstring是合成文本内容(支持中英文)<break time=500>详解见下方停顿符说明
dictionaryarray否多音字配置列表。详见下表(仅克隆音色使用、模型必须为SenseAudio-TTS-1.5)[{“original”: “好干净”,“replacement”: “[hao4]干净”}]

<break> 停顿符说明

<break> 用于在语音合成中插入停顿。

xml
复制
<break time=500>
  • time 单位为毫秒(ms)
  • 500 表示停顿 500 毫秒
  • 最小值为 100 毫秒,最大值无限制

示例:

text
复制
你好<break time=500>欢迎使用我们的服务

dictionary (多音字纠正)

参数名类型必填描述默认值示例
originalstring是原始文本。无铺床铺地,量米量酒杯
replacementint是多音字配置。无铺床铺[di4],[liang2]米[liang4]酒杯

请求示例

json
复制
{ "event": "task_continue", "text": "人生就像一场马拉松,重要的不是跑得多快" }

响应参数

参数名类型说明
session_idstring会话 ID
eventstring事件类型
trace_idstring链路追踪 ID
is_finalbool该请求返回是否完结
base_respobject请求状态信息
base_resp.status_codeint64状态码
base_resp.status_msgstring状态详情
dataobject返回的合成数据对象(可能为 null)
data.audiostring合成后的音频数据(hex 编码)
data.statusint64音频流状态:1 表示合成中,2 表示合成结束
extra_infoobject音频附加信息(流式返回时只有最后一个 chunk 会返回)
extra_info.audio_lengthint64音频时长(毫秒)
extra_info.audio_sample_rateint64音频采样率
extra_info.audio_sizeint64音频文件大小(字节)
extra_info.bitrateint64音频比特率
extra_info.audio_formatstring音频格式,取值范围 [mp3, pcm, flac, wav]
extra_info.audio_channelint音频声道数,1:单声道,2:双声道
extra_info.word_countint64字数统计(按 grapheme cluster 统计,排除纯空白/标点/控制符)
extra_info.character_countint64字符数统计(按 Unicode 码点统计)

响应示例

json
复制
{ "session_id": "69c20e38c8761996a85d57881fe4d817", "event": "task_continue", "trace_id": "69c20e38c8761996a85d57881fe4d817", "is_final": false, "data": { "audio": "hex编码的音频数据...", "status": 1 }, "base_resp": { "status_code": 0, "status_msg": "success" } }

3. task_finish - 结束任务

服务端收到此事件后,会等待当前队列中所有合成任务完成后,关闭 WebSocket 连接并结束任务。

请求参数

参数名类型必填说明
eventstring是固定值:task_finish

请求示例

json
复制
{ "event": "task_finish" }

响应参数

参数名类型说明
session_idstring会话 ID
eventstring事件类型
trace_idstring链路追踪 ID
base_respobject请求状态信息
base_resp.status_codeint64状态码
base_resp.status_msgstring状态详情

响应示例

json
复制
{ "session_id": "69c20e38c8761996a85d57881fe4d817", "event": "task_finished", "trace_id": "69c20e38c8761996a85d57881fe4d817", "base_resp": { "status_code": 0, "status_msg": "success" } }

服务端事件

connected_success - 连接建立成功

初次请求接口时,表示 WebSocket 连接建立成功。

json
复制
{ "session_id": "xxxx", "event": "connected_success", "trace_id": "xxx", "base_resp": { "status_code": 0, "status_msg": "success" } }

task_started - 任务已开始

标志任务已成功开始,客户端可以开始发送 task_continue 事件。

json
复制
{ "session_id": "xxxx", "event": "task_started", "trace_id": "xxxxx", "base_resp": { "status_code": 0, "status_msg": "success" } }

task_finished - 任务已结束

标志任务已结束,WebSocket 连接即将关闭。

json
复制
{ "session_id": "xxxx", "event": "task_finished", "trace_id": "xxxx", "base_resp": { "status_code": 0, "status_msg": "success" } }

task_failed - 任务失败

标志任务失败,包含错误信息。

json
复制
{ "session_id": "xxxx", "event": "task_failed", "trace_id": "xxxxx", "base_resp": { "status_code": 1004, "status_msg": "具体错误信息" } }

完整使用示例

单音色合成示例

JavaScript

javascript
复制
const fs = require('fs') const WebSocket = require('ws') const WS_URL = 'wss://api.senseaudio.cn/ws/v1/t2a_v2' const API_KEY = process.env.SENSEAUDIO_API_KEY if (!API_KEY) { throw new Error('Missing env: SENSEAUDIO_API_KEY') } // 这里以“把所有分片写入文件”为例。若要边播边放,可将 audioBuffer 推入播放器/解码器。 const output = fs.createWriteStream('output.mp3') const ws = new WebSocket(WS_URL, { headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json', }, }) ws.on('open', () => { console.log('WebSocket 连接已建立') }) ws.on('message', (data) => { const response = JSON.parse(data.toString()) console.log('收到服务端事件:', response.event) if (response.event === 'connected_success') { ws.send( JSON.stringify({ event: 'task_start', model: 'SenseAudio-TTS-1.0', voice_setting: { voice_id: 'female_jiaomei', speed: 1.0, vol: 1.0, pitch: 0, }, audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3', channel: 1, }, }) ) return } if (response.event === 'task_started') { ws.send( JSON.stringify({ event: 'task_continue', text: '道可道,非常道。名可名,非常名。无名天地之始,有名万物之母。', }) ) ws.send( JSON.stringify({ event: 'task_continue', text: '故常无欲,以观其妙;常有欲,以观其徼。', }) ) ws.send(JSON.stringify({ event: 'task_finish' })) return } if (response?.data?.audio) { const audioBuffer = Buffer.from(response.data.audio, 'hex') output.write(audioBuffer) } if (response.event === 'task_finished') { output.end() ws.close() } if (response.event === 'task_failed') { console.error('任务失败:', response?.base_resp?.status_msg) output.end() ws.close() } }) ws.on('error', (err) => { console.error('WebSocket 错误:', err) }) ws.on('close', () => { console.log('WebSocket 连接已关闭') })

Python

python
复制
import json import os import websocket WS_URL = "wss://api.senseaudio.cn/ws/v1/t2a_v2" API_KEY = os.getenv("SENSEAUDIO_API_KEY") if not API_KEY: raise RuntimeError("Missing env: SENSEAUDIO_API_KEY") output_path = "output.mp3" output_file = open(output_path, "wb") def on_open(ws): print("WebSocket 连接已建立") def on_message(ws, message: str): resp = json.loads(message) event = resp.get("event") print("收到服务端事件:", event) if event == "connected_success": ws.send(json.dumps({ "event": "task_start", "model": "SenseAudio-TTS-1.0", "voice_setting": { "voice_id": "female_jiaomei", "speed": 1.0, "vol": 1.0, "pitch": 0 }, "audio_setting": { "sample_rate": 32000, "bitrate": 128000, "format": "mp3", "channel": 1 } })) return if event == "task_started": ws.send(json.dumps({"event": "task_continue", "text": "道可道,非常道。名可名,非常名。无名天地之始,有名万物之母。"})) ws.send(json.dumps({"event": "task_continue", "text": "故常无欲,以观其妙;常有欲,以观其徼。"})) ws.send(json.dumps({"event": "task_finish"})) return data = resp.get("data") or {} audio_hex = data.get("audio") if audio_hex: output_file.write(bytes.fromhex(audio_hex)) if event in ("task_finished", "task_failed"): if event == "task_failed": base_resp = resp.get("base_resp") or {} print("任务失败:", base_resp.get("status_msg")) output_file.close() ws.close() def on_error(ws, err): print("WebSocket 错误:", err) def on_close(ws, status_code, msg): print("WebSocket 连接已关闭:", status_code, msg) ws = websocket.WebSocketApp( WS_URL, header=[ f"Authorization: Bearer {API_KEY}", "Content-Type: application/json", ], on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close, ) ws.run_forever()

Go

go
复制
package main import ( "encoding/hex" "encoding/json" "log" "net/http" "os" "github.com/gorilla/websocket" ) const wsURL = "wss://api.senseaudio.cn/ws/v1/t2a_v2" func mustMarshal(v any) []byte { b, err := json.Marshal(v) if err != nil { panic(err) } return b } func main() { apiKey := os.Getenv("SENSEAUDIO_API_KEY") if apiKey == "" { log.Fatal("Missing env: SENSEAUDIO_API_KEY") } header := http.Header{} header.Set("Authorization", "Bearer "+apiKey) header.Set("Content-Type", "application/json") c, _, err := websocket.DefaultDialer.Dial(wsURL, header) if err != nil { log.Fatal("dial:", err) } defer c.Close() out, err := os.Create("output.mp3") if err != nil { log.Fatal(err) } defer out.Close() for { _, msg, err := c.ReadMessage() if err != nil { log.Fatal("read:", err) } var resp map[string]any if err := json.Unmarshal(msg, &resp); err != nil { log.Fatal(err) } event, _ := resp["event"].(string) log.Println("收到服务端事件:", event) switch event { case "connected_success": _ = c.WriteMessage(websocket.TextMessage, mustMarshal(map[string]any{ "event": "task_start", "model": "SenseAudio-TTS-1.0", "voice_setting": map[string]any{ "voice_id": "female_jiaomei", "speed": 1.0, "vol": 1.0, "pitch": 0, }, "audio_setting": map[string]any{ "sample_rate": 32000, "bitrate": 128000, "format": "mp3", "channel": 1, }, })) case "task_started": _ = c.WriteMessage(websocket.TextMessage, mustMarshal(map[string]any{ "event": "task_continue", "text": "道可道,非常道。名可名,非常名。无名天地之始,有名万物之母。", })) _ = c.WriteMessage(websocket.TextMessage, mustMarshal(map[string]any{ "event": "task_continue", "text": "故常无欲,以观其妙;常有欲,以观其徼。", })) _ = c.WriteMessage(websocket.TextMessage, mustMarshal(map[string]any{"event": "task_finish"})) case "task_failed", "task_finished": if event == "task_failed" { if baseResp, ok := resp["base_resp"].(map[string]any); ok { if msg, ok := baseResp["status_msg"].(string); ok { log.Println("任务失败:", msg) } } } return } if data, ok := resp["data"].(map[string]any); ok { if audioHex, ok := data["audio"].(string); ok && audioHex != "" { b, err := hex.DecodeString(audioHex) if err != nil { log.Fatal(err) } if _, err := out.Write(b); err != nil { log.Fatal(err) } } } } }

Java

java
复制
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import okhttp3.*; import okio.ByteString; import java.io.FileOutputStream; import java.util.HashMap; import java.util.Map; public class SenseAudioTtsWsExample { private static final String WS_URL = "wss://api.senseaudio.cn/ws/v1/t2a_v2"; public static void main(String[] args) throws Exception { String apiKey = System.getenv("SENSEAUDIO_API_KEY"); if (apiKey == null || apiKey.isEmpty()) { throw new RuntimeException("Missing env: SENSEAUDIO_API_KEY"); } OkHttpClient client = new OkHttpClient(); ObjectMapper mapper = new ObjectMapper(); Request request = new Request.Builder() .url(WS_URL) .addHeader("Authorization", "Bearer " + apiKey) .addHeader("Content-Type", "application/json") .build(); FileOutputStream out = new FileOutputStream("output.mp3"); client.newWebSocket(request, new WebSocketListener() { @Override public void onOpen(WebSocket webSocket, Response response) { System.out.println("WebSocket 连接已建立"); } @Override public void onMessage(WebSocket webSocket, String text) { try { JsonNode resp = mapper.readTree(text); String event = resp.path("event").asText(); System.out.println("收到服务端事件: " + event); if ("connected_success".equals(event)) { Map<String, Object> payload = new HashMap<>(); payload.put("event", "task_start"); payload.put("model", "SenseAudio-TTS-1.0"); Map<String, Object> voice = new HashMap<>(); voice.put("voice_id", "female_jiaomei"); voice.put("speed", 1.0); voice.put("vol", 1.0); voice.put("pitch", 0); payload.put("voice_setting", voice); Map<String, Object> audio = new HashMap<>(); audio.put("sample_rate", 32000); audio.put("bitrate", 128000); audio.put("format", "mp3"); audio.put("channel", 1); payload.put("audio_setting", audio); webSocket.send(mapper.writeValueAsString(payload)); return; } if ("task_started".equals(event)) { webSocket.send(mapper.writeValueAsString(Map.of("event", "task_continue", "text", "道可道,非常道。名可名,非常名。无名天地之始,有名万物之母。"))); webSocket.send(mapper.writeValueAsString(Map.of("event", "task_continue", "text", "故常无欲,以观其妙;常有欲,以观其徼。"))); webSocket.send(mapper.writeValueAsString(Map.of("event", "task_finish"))); return; } JsonNode audioHex = resp.path("data").path("audio"); if (!audioHex.isMissingNode() && !audioHex.isNull()) { byte[] bytes = ByteString.decodeHex(audioHex.asText()).toByteArray(); out.write(bytes); } if ("task_finished".equals(event) || "task_failed".equals(event)) { if ("task_failed".equals(event)) { System.err.println("任务失败: " + resp.path("base_resp").path("status_msg").asText()); } out.close(); webSocket.close(1000, "done"); } } catch (Exception e) { e.printStackTrace(); webSocket.close(1001, "error"); } } @Override public void onMessage(WebSocket webSocket, ByteString bytes) { // 服务端按文档应返回 JSON 文本消息;这里留作兼容处理 } @Override public void onFailure(WebSocket webSocket, Throwable t, Response response) { t.printStackTrace(); try { out.close(); } catch (Exception ignored) {} } }); Thread.sleep(60_000); client.dispatcher().executorService().shutdown(); } }

技术规格

模型信息

  • 模型名称:SenseAudio-TTS-1.0
  • 最大文本长度:10000 字符
  • 连接超时:最后一次收到服务端返回后 120 秒无新事件时自动断开

音频参数范围

参数取值范围默认值说明
语速[0.5, 2.0]1.0数值越大语速越快
音量[0, 10]1.0数值越大音量越大
音调[-12, 12]0正值提高,负值降低
采样率8000, 16000, 22050, 24000, 32000, 44100 (Hz)32000推荐 32000
码率32000, 64000, 128000, 256000 (bps)128000仅 MP3 格式
声道1 (单声道), 2 (双声道)2-

支持的音频格式

  • MP3:推荐,压缩率高,兼容性好
  • WAV:无损音质,文件较大
  • PCM:原始音频数据
  • FLAC:无损压缩

最佳实践

1. 连接管理

  • 建立连接后,等待 connected_success 事件再发送 task_start
  • 收到 task_started 事件后再发送 task_continue
  • 合理设置心跳机制,避免连接超时
  • 处理好连接断开和重连逻辑

2. 文本分段策略

  • 对于长文本,建议按句子或段落分段发送
  • 每段文本长度控制在 500-1000 字符为宜
  • 确保分段后的文本语义完整,避免在词语中间断开
  • 按顺序发送文本段,保持语义连贯性

3. 音频数据处理

  • 音频数据以 hex 编码返回,需要转换为二进制数据
  • 可以边接收边播放,实现流式播放效果
  • 注意保存 extra_info 中的音频元信息
  • 最后一个 chunk 包含完整的音频统计信息

4. 错误处理

  • 监听 task_failed 事件,根据错误码进行处理
  • 网络异常时实现重连机制
  • 超时情况下主动关闭连接并重试
  • 记录 trace_id 用于问题排查

5. 性能优化

  • 复用 WebSocket 连接处理多个任务
  • 根据网络状况调整文本分段大小
  • 使用合适的采样率和码率平衡音质与带宽
  • 单声道比双声道数据量更小,延迟更低

错误码说明

状态码说明解决方案
0成功-
1001参数错误检查请求参数格式和取值范围
1002模型不存在确认模型名称是否正确
1003音色不存在确认音色 ID 是否正确
1004文本内容违规检查文本内容是否符合规范
1005文本长度超限控制单次文本长度在限制内
2001服务内部错误稍后重试或联系技术支持
2002合成队列已满稍后重试
3001连接已超时重新建立连接

注意事项

  1. 音频数据格式:

    • WebSocket 接口只支持返回 hex 编码的音频数据
    • 需要在客户端将 hex 字符串转换为二进制数据
    • 音频格式由 audio_setting.format 参数决定
  2. 连接超时机制:

    • 最后一次收到服务端返回后,120 秒内无新事件发送时连接自动断开
    • 建议在任务完成后主动发送 task_finish 事件
    • 长时间无操作时可以发送心跳保持连接
  3. 事件发送顺序:

    • 必须按照 task_start → task_continue → task_finish 的顺序发送事件
    • 只有在收到 task_started 后才能发送 task_continue
    • 可以发送多个 task_continue 事件
  4. 内容检测:

    • 默认开启文本风控检测(disable_detect: false)
    • 默认朗读括号文学内容(disable_literature: false)
    • 可根据实际需求调整这些参数

联系支持

如需技术支持或有任何问题,请联系:

  • 邮箱:senseaudio.support@sensetime.com