logo
平台介绍
快速接入
密钥管理
模型列表
计费规则
音色列表
文本转语音
文本转语音介绍
POST
HTTP API 非流式
SSE
HTTP API 流式
WSS
WebSocket API
音色克隆
文生音色
语音识别
多模态理解模型
音乐生成
图片生成
视频生成
语音Agent
自定义Agent
常见问题
工作台
立即登录

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.5-260319

通信流程

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 SENSEAUDIO_API_KEYBearer sk-123456…
Content-Type是内容类型。固定为 application/jsonapplication/json

请求参数

参数名类型必填默认值说明
eventstring是无固定值:task_start
modelstring是无模型名称,示例值:senseaudio-tts-1.5-260319
voice_settingobject是无声音设置
voice_setting.voice_idstring是无主音色名称
voice_setting.speedfloat否1.0语速,取值范围 [0.5, 2.0]
voice_setting.volfloat否1.0音量,取值范围 [0.01, 10.0]
voice_setting.pitchint否0音调,取值范围 [-12, 12],0 表示保持原始音调
voice_setting.latex_readboolean否false数学公式朗读,支持 LaTeX、MathML、Unicode 数学符号等格式。(会产生额外的性能损耗)
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.5-260319", "voice_setting": { "voice_id": "male_0004_a", "speed": 1, "vol": 1, "pitch": 0, "latex_read": false }, "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-260319)[{"original": "好干净","replacement": "[hao4]干净"}]

<break> 停顿符说明

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

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

示例:

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

dictionary (多音字纠正)

参数名类型必填描述默认值示例
originalstring是原始文本。无好干净
replacementstring是多音字配置。无[hao4]干净

请求示例

json
复制
{ "event": "task_continue", "text": "好干净", "dictionary": [ { "original": "好干净", "replacement": "[hao4]干净" } ] }

响应参数

参数名类型说明
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.usage_charactersint64字符数统计(按 Unicode 码点统计)

响应示例

json
复制
{ "session_id": "69c20e38c8761996a85d57881fe4d817", "event": "task_continued", "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.5-260319', voice_setting: { voice_id: 'male_0004_a', speed: 1.0, vol: 1.0, pitch: 0, latex_read: false, }, 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.5-260319", "voice_setting": { "voice_id": "male_0004_a", "speed": 1.0, "vol": 1.0, "pitch": 0, "latex_read": False }, "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.5-260319", "voice_setting": map[string]any{ "voice_id": "male_0004_a", "speed": 1.0, "vol": 1.0, "pitch": 0, "latex_read": false, }, "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.5-260319"); Map<String, Object> voice = new HashMap<>(); voice.put("voice_id", "male_0004_a"); voice.put("speed", 1.0); voice.put("vol", 1.0); voice.put("pitch", 0); voice.put("latex_read", false); 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.5-260319
  • 最大文本长度:10000 字符
  • 连接超时:最后一次收到服务端返回后 120 秒无新事件时自动断开

音频参数范围

参数取值范围默认值说明
语速[0.5, 2.0]1.0数值越大语速越快
音量[0.01, 10.0]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参数错误检查请求参数格式和取值范围

注意事项

  1. 音频数据格式:

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

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

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

联系支持

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

  • 邮箱:senseaudio.support@sensetime.com