在做 AI 对话 / 语音播报 / 实时助手 时,低延迟语音合成是一个绕不开的能力。
腾讯云提供了 WebSocket 实时语音合成(TTS) 接口,支持边合成边返回音频流,非常适合实时场景。
本文将基于 腾讯云官方文档,手把手实现一套 Node.js 可直接运行的 WebSocket 实时语音合成示例。
一、效果预览
- 使用 WebSocket 建立连接
- 文本发送后,服务端 实时返回音频二进制流
- 最终生成一个
pcm音频文件 - 延迟极低,适合实时播放
二、准备工作
1️⃣ 开通服务
在腾讯云控制台开通:
- 语音合成(TTS)
- 确保支持 实时语音合成(WebSocket)
2️⃣ 获取密钥
需要以下信息:
SecretIdSecretKeyAppId(数字)
建议通过 环境变量 注入:
export TENCENT_SECRET_ID=你的SecretId
export TENCENT_SECRET_KEY=你的SecretKey
export TENCENT_APPID=你的AppId
三、技术原理简述
腾讯云实时 TTS 的核心流程:
-
按规则拼接 URL 参数
-
使用 HMAC-SHA1 对请求字符串进行签名
-
建立 WebSocket 连接
-
服务端返回:
- 二进制帧:音频数据
- 文本帧:JSON 状态信息
-
final = 1表示合成结束
四、安装依赖
npm install ws uuid
Node.js 自带 crypto,无需额外安装。
五、完整 Node.js 示例代码
该代码可以 直接运行,并生成
tts_output.pcm文件
const WebSocket = require("ws");
const crypto = require("crypto");
const { v4: uuidv4 } = require("uuid");
const fs = require("fs");
// 从环境变量读取密钥和 AppID
const SECRET_ID = process.env.TENCENT_SECRET_ID;
const SECRET_KEY = process.env.TENCENT_SECRET_KEY;
const APP_ID = process.env.TENCENT_APPID;
// 当前时间戳和过期时间
const timestamp = Math.floor(Date.now() / 1000);
const expired = timestamp + 300; // 签名 5 分钟有效
// 生成唯一会话 ID
const sessionId = uuidv4();
// 请求参数(必须严格按文档)
const params = {
Action: "TextToStreamAudioWS",
AppId: APP_ID,
SecretId: SECRET_ID,
Timestamp: timestamp,
Expired: expired,
SessionId: sessionId,
Text: "你好,实时语音合成测试。",
VoiceType: 101001,
SampleRate: 16000,
Codec: "pcm",
Speed: 0,
Volume: 0,
};
// 参数按字典序排序
const sortedKeys = Object.keys(params).sort();
const queryStr = sortedKeys
.map((k) => `${k}=${encodeURIComponent(params[k])}`)
.join("&");
// 构建签名源字符串
const method = "GET";
const host = "tts.cloud.tencent.com";
const path = "/stream_ws";
const originStr = `${method}${host}${path}?${queryStr}`;
// HMAC-SHA1 签名
const signature = crypto
.createHmac("sha1", SECRET_KEY)
.update(originStr)
.digest("base64");
// WebSocket URL
const url = `wss://${host}${path}?${queryStr}&Signature=${encodeURIComponent(
signature
)}`;
console.log("WebSocket URL:", url);
// 保存音频数据
const audioBuffers = [];
const ws = new WebSocket(url);
ws.on("open", () => {
console.log("WebSocket 已连接");
});
ws.on("message", (data) => {
// 二进制帧:音频数据
if (data instanceof Buffer) {
audioBuffers.push(data);
console.log("收到音频分片:", data.length);
return;
}
// 文本帧:状态信息
try {
const msg = JSON.parse(data.toString());
console.log("文本消息:", msg);
if (msg.final === 1) {
console.log("语音合成完成");
ws.close();
}
} catch (err) {
console.error("JSON 解析失败:", err);
}
});
ws.on("close", () => {
const audio = Buffer.concat(audioBuffers);
fs.writeFileSync("tts_output.pcm", audio);
console.log("音频已保存为 tts_output.pcm");
});
ws.on("error", (err) => {
console.error("WebSocket 错误:", err);
});
六、关键参数说明
VoiceType(音色)
| 音色 | 值 |
|---|---|
| 女声标准 | 101001 |
| 男声标准 | 101002 |
| 情感音色 | 需看套餐 |
Codec(音频格式)
| 格式 | 说明 |
|---|---|
| pcm | 延迟最低,适合实时播放 |
| mp3 | 压缩格式,体积小 |
实时场景强烈推荐
pcm
七、常见坑总结
1️⃣ 参数顺序错误
- 签名必须基于“排序后的参数”
- 少一个参数都会鉴权失败
2️⃣ timestamp / expired 不合法
Expired必须大于Timestamp- 过期会直接 403
3️⃣ 音频不是完整 wav
- WebSocket 返回的是 纯音频流
- pcm 需要自行加 wav 头或直接播放
4️⃣ 文本不要太长
- 建议 ≤ 500 字
- 长文本应拆句,多次请求
八、如何真正做到“实时播放”
正确做法是:
- 服务端收到音频分片
- 立即推送给前端
- 前端使用
AudioContext播放 PCM
❌ 错误做法:
等全部音频返回后再播放(那就不实时了)
九、适用场景
- 🤖 AI 对话语音播报
- 📢 实时通知系统
- 🎧 语音助手
- 🧠 大模型语音输出