|
@@ -0,0 +1,373 @@
|
|
|
|
|
+# 腾讯云语音合成(TTS)Windows C++ 对接指南
|
|
|
|
|
+
|
|
|
|
|
+本目录包含:
|
|
|
|
|
+- REST 版轻量客户端(`TextToVoice`,带情感参数)
|
|
|
|
|
+- 流式客户端(`TextToStreamAudioWS`,OpenSSL 自建 WSS)+ waveOut 实时播放器,**全程不落盘**
|
|
|
|
|
+- 给官方语音 SDK 打情感补丁的说明(可选方案)
|
|
|
|
|
+
|
|
|
|
|
+依赖只有两个:**libcurl** + **OpenSSL**。支持 **Windows 7 及以上**(`_WIN32_WINNT=0x0601`),
|
|
|
|
|
+Win7 的适配说明见 [第九节](#九win7--_win32_winnt0x0601-兼容)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 一、三条接入路径,先选一条
|
|
|
|
|
+
|
|
|
|
|
+| 路径 | 协议 | 情感参数 | C++ 成本 | 适合场景 |
|
|
|
|
|
+|---|---|---|---|---|
|
|
|
|
|
+| **A. 基础语音合成 `TextToVoice`**(本目录 `TtsClient`) | HTTPS REST | ✅ 原生支持 `EmotionCategory`/`EmotionIntensity` | 低:libcurl + OpenSSL | 短文本播报、配音、生成文件 |
|
|
|
|
|
+| **B. 实时/流式语音合成 `TextToStreamAudioWS`**(本目录 `StreamTtsPlayer`) | WebSocket (wss) | ✅ 支持 | 低:OpenSSL 自建 WSS,不依赖 Win8+ API | 边合成边播放、LLM 逐字输出、实时客服 |
|
|
|
|
|
+| **C. 长文本语音合成 `CreateTtsTask`** | REST 异步 | ⚠️ 免费额度不含 | 低(同 A,换接口) | 有声书、10 万字以内批量导出 |
|
|
|
|
|
+
|
|
|
|
|
+**建议路径**:先用 A 跑通业务闭环 → 需要"边生成边出声"再上 B。
|
|
|
|
|
+
|
|
|
|
|
+> 官方语音 SDK(`tencentcloud-speech-sdk-cpp`)也能做 B,但它要编 Boost/OpenSSL/websocketpp,
|
|
|
|
|
+> 且未暴露情感 setter。本目录自己实现 WSS,依赖更少、情感参数直接支持,且 Win7 可用
|
|
|
|
|
+> (WinHTTP 的 WebSocket API 是 Win8+ 才有的,Win7 工程编不过)。
|
|
|
|
|
+> 若你已在用官方 SDK,见 `patch/官方语音SDK增加情感参数.md`。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 二、开通与准备(10 分钟)
|
|
|
|
|
+
|
|
|
|
|
+1. 开通服务:https://console.cloud.tencent.com/tts
|
|
|
|
|
+2. 领取免费额度(**必须手动领,不领默认没有**):控制台 → 资源包,三类:
|
|
|
|
|
+ - 基础/精品音色 **800 万字符**
|
|
|
|
|
+ - 大模型音色 **10 万字符**
|
|
|
|
|
+ - 超自然大模型音色 **2 万字符**
|
|
|
|
|
+ - 自领取起 **3 个月内有效,一个账号限领一次**
|
|
|
|
|
+3. 建密钥:https://console.cloud.tencent.com/cam/capi → 新建密钥,拿到 `SecretId` / `SecretKey`
|
|
|
|
|
+4. **取账号 `AppId`**(只有 B 路径的流式接口需要,REST 接口不用):
|
|
|
|
|
+ 官网右上角**头像** → **账号信息** → 基本信息栏里的 **APPID** 一行,纯数字(如 `1300123456`)。
|
|
|
|
|
+ 直达链接:https://console.cloud.tencent.com/developer
|
|
|
|
|
+
|
|
|
|
|
+ > ⚠️ 同一个页面还有个「**账号 ID(UIN)**」,也是一串数字,但**不是 AppId**,别拿错。
|
|
|
|
|
+ > 填错的典型现象是流式接口握手返回 **403**。
|
|
|
|
|
+ > 另外别和 `SecretId`(`AKID` 开头的字符串)混为一谈。
|
|
|
|
|
+5. **试听选音色(最关键的一步)**:https://cloud.tencent.com/document/product/1073/92668
|
|
|
|
|
+ - 筛选"情感"场景 → 只有**多情感音色**才支持 `EmotionCategory`,普通音色传了会被忽略
|
|
|
|
|
+ - 记下音色 ID(如 `101001`),这就是代码里的 `voice_type`
|
|
|
|
|
+ - 注意每个音色标注的档位(基础/精品/大模型/超自然),直接决定单价
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 三、跑通路径 A(本目录代码)
|
|
|
|
|
+
|
|
|
|
|
+### 1. 装依赖
|
|
|
|
|
+
|
|
|
|
|
+只需要 **libcurl** 和 **OpenSSL** 两个,都是常见库:
|
|
|
|
|
+
|
|
|
|
|
+```powershell
|
|
|
|
|
+git clone https://github.com/microsoft/vcpkg
|
|
|
|
|
+.\vcpkg\bootstrap-vcpkg.bat
|
|
|
|
|
+.\vcpkg\vcpkg install curl:x64-windows openssl:x64-windows
|
|
|
|
|
+# Linux: sudo apt install libcurl4-openssl-dev libssl-dev
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+各自的分工:
|
|
|
|
|
+
|
|
|
|
|
+| 依赖 | 用在哪 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| libcurl | 路径 A 的 REST 请求(`TextToVoice`) |
|
|
|
|
|
+| OpenSSL | ① TC3 签名 / 流式签名的 HMAC;② 流式接口的 WSS 通道(自建,见下) |
|
|
|
|
|
+
|
|
|
|
|
+> 流式接口(WebSocket)**没有用 WinHTTP**,而是用 OpenSSL + 原生 socket 自己实现的,
|
|
|
|
|
+> 原因见 [第九节](#九win7--_win32_winnt0x0601-兼容)。这是 Win7 能跑起来的关键。
|
|
|
|
|
+
|
|
|
|
|
+### 2. 编译
|
|
|
|
|
+
|
|
|
|
|
+```powershell
|
|
|
|
|
+cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=D:/vcpkg/scripts/buildsystems/vcpkg.cmake
|
|
|
|
|
+cmake --build build --config Release
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 3. 运行
|
|
|
|
|
+
|
|
|
|
|
+```powershell
|
|
|
|
|
+set TENCENTCLOUD_SECRET_ID=AKIDxxxxxxxxxxxxxxxx
|
|
|
|
|
+set TENCENTCLOUD_SECRET_KEY=xxxxxxxxxxxxxxxx
|
|
|
|
|
+.\build\Release\tts_demo.exe
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+产出 4 个文件:`out_normal.wav`(默认语气)、`out_happy.wav`(happy×150)、
|
|
|
|
|
+`out_sad.wav`(sad×180)、`out_long.mp3`(长文本分段拼接)。
|
|
|
|
|
+
|
|
|
|
|
+### 4. 接进你的工程
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+#include "TtsClient.h"
|
|
|
|
|
+
|
|
|
|
|
+txcloud::TtsClient client(secret_id, secret_key);
|
|
|
|
|
+
|
|
|
|
|
+txcloud::TtsOptions opt;
|
|
|
|
|
+opt.voice_type = 101001; // 换成你选的多情感音色
|
|
|
|
|
+opt.codec = "wav"; // wav / mp3 / pcm
|
|
|
|
|
+opt.sample_rate = 16000;
|
|
|
|
|
+opt.speed = 0.5f; // [-2, 6]
|
|
|
|
|
+opt.volume = 0.0f; // [-10, 10]
|
|
|
|
|
+opt.emotion_category = "happy"; // 留空 = 默认语气
|
|
|
|
|
+opt.emotion_intensity= 150; // [50, 200]
|
|
|
|
|
+
|
|
|
|
|
+std::string err;
|
|
|
|
|
+client.SynthesizeToFile(u8"今天天气真好,我们出去走走吧。", opt, "a.wav", &err);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+拿到内存数据而非文件,用 `Synthesize()` 返回 `TtsResult::audio`(`std::vector<uint8_t>`),直接喂给你的播放器。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 四、实时播放:不落盘 / 边合成边播
|
|
|
|
|
+
|
|
|
|
|
+两种情况,别混为一谈:
|
|
|
|
|
+
|
|
|
|
|
+| 你想要 | 用哪个 | 延迟 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 合成完立刻播出来,不要文件 | `PlayWavMemory()`(REST + waveOut) | 整段合成完才响,约 1~2 秒 |
|
|
|
|
|
+| 边合成边出声(LLM 逐字、实时播报) | `StreamTtsPlay()`(WebSocket 流式) | 首包约 300ms |
|
|
|
|
|
+
|
|
|
|
|
+### 做法 1:REST 合成 → 内存直接播放
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+txcloud::TtsOptions opt;
|
|
|
|
|
+opt.voice_type = 101001;
|
|
|
|
|
+opt.codec = "wav"; // 关键:wav 可解析出 PCM,mp3 需要额外解码器
|
|
|
|
|
+opt.sample_rate= 16000;
|
|
|
|
|
+opt.emotion_category = "happy";
|
|
|
|
|
+opt.emotion_intensity= 150;
|
|
|
|
|
+
|
|
|
|
|
+txcloud::TtsResult r = client.Synthesize("今天天气真好。", opt);
|
|
|
|
|
+if (r.ok) PlayWavMemory(r.audio); // 阻塞直到播完,全程无临时文件
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+`PcmPlayer` 底层是 waveOut,8 个缓冲区轮转,可随时 `Stop()` 打断:
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+PcmPlayer player;
|
|
|
|
|
+player.Open(16000, 1, 16); // 16kHz / 单声道 / 16bit
|
|
|
|
|
+player.Feed(pcm_data, len); // 可分多次喂
|
|
|
|
|
+player.FlushAndWait(); // 播完剩余
|
|
|
|
|
+player.Close();
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 做法 2:WebSocket 流式,边下边播(推荐做实时场景)
|
|
|
|
|
+
|
|
|
|
|
+网络层是 OpenSSL + 原生 socket 自建的 WSS(`WssClient`),**不用 WinHTTP**,
|
|
|
|
|
+所以 Win7 上照样能跑(WinHTTP 的 WebSocket API 是 Win8+ 才有的)。详见[第九节](#九win7--_win32_winnt0x0601-兼容)。
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+StreamTtsOptions sopt;
|
|
|
|
|
+sopt.appid = 1300000000; // 控制台「账号信息」里的 AppId,流式接口必填
|
|
|
|
|
+sopt.secret_id = secret_id;
|
|
|
|
|
+sopt.secret_key = secret_key;
|
|
|
|
|
+sopt.voice_type = 101001;
|
|
|
|
|
+sopt.codec = "pcm"; // 实时播放必须是 pcm,裸流无需解码,收到即播
|
|
|
|
|
+sopt.sample_rate= 16000;
|
|
|
|
|
+sopt.emotion_category = "story";
|
|
|
|
|
+sopt.emotion_intensity = 120;
|
|
|
|
|
+
|
|
|
|
|
+PcmPlayer player;
|
|
|
|
|
+player.Open(16000, 1, 16);
|
|
|
|
|
+std::string err;
|
|
|
|
|
+StreamTtsResult r = StreamTtsPlay("欢迎使用腾讯云实时语音合成。", sopt, player, nullptr, &err);
|
|
|
|
|
+player.Close();
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+需要自己接管音频(比如送进自研播放器 / 推流 / 落盘),用 `StreamTtsRun()` 拿分片回调:
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+std::atomic<bool> cancel{false};
|
|
|
|
|
+StreamTtsRun(text, sopt,
|
|
|
|
|
+ [](const uint8_t* data, size_t len) { my_player.Feed(data, len); return true; },
|
|
|
|
|
+ &cancel, &err);
|
|
|
|
|
+```
|
|
|
|
|
+回调返回 `false` 或把 `cancel` 置 true,都能立刻中断。
|
|
|
|
|
+
|
|
|
|
|
+### 签名实现说明(重要)
|
|
|
|
|
+
|
|
|
|
|
+流式接口的签名与 REST 完全不同,且**官方文档描述与官方 SDK 实际实现不一致**,本实现按能跑通的 Python SDK
|
|
|
|
|
+(`tencentcloud-speech-sdk-python/tts/speech_synthesizer_ws.py`)对齐:
|
|
|
|
|
+
|
|
|
|
|
+- 签名原文 = `GET` + `tts.cloud.tencent.com` + `/stream_ws?` + 字典序 `k=v&k=v`,**值不做 urlencode**
|
|
|
|
|
+- 签名值 = `Base64(HMAC-SHA1(SecretKey, 签名原文))`
|
|
|
|
|
+- 最终 URL = `wss://tts.cloud.tencent.com/stream_ws?` + 字典序参数(**只有 Text 做 percent-encoding**)
|
|
|
|
|
+ + `&Signature=` + `urlencode(签名值)`
|
|
|
|
|
+- 必填参数:`Action / AppId / SecretId / Timestamp / Expired / SessionId / Text / Codec`
|
|
|
|
|
+
|
|
|
|
|
+常见失败:upgrade 握手失败(HTTP 401/403)→ 99% 是 **AppId 填错、时间戳过期、或 Signature 没 urlencode**。
|
|
|
|
|
+
|
|
|
|
|
+### 流式接口的能力边界
|
|
|
|
|
+
|
|
|
|
|
+- 单次文本:中文 600 汉字 / 英文 1800 字母(比 REST 的 150 字宽裕得多)
|
|
|
|
|
+- 采样率:仅 8000 或 16000;24k 不支持
|
|
|
|
|
+- 音频格式:仅 `pcm` / `mp3`,实时播放选 pcm
|
|
|
|
|
+- 并发:精品 20 路、大模型 20 路、超自然 10 路(与 REST 接口**不共享**)
|
|
|
|
|
+- 超自然音色不支持时间戳
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 五、情感调参要点
|
|
|
|
|
+
|
|
|
|
|
+| 参数 | 取值 | 说明 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `EmotionCategory` | `neutral / sad / happy / angry / fear / news / story / radio / poetry / call / sajiao / disgusted / amaze / peaceful / exciting / aojiao / jieshuo` | 中文音色常用:`happy / sad / angry / fearful→fear / exciting / peaceful / story / news / call / poetry` |
|
|
|
|
|
+| `EmotionIntensity` | `50~200`,默认 100 | 建议先从 100 起,每次 ±25 试听,超过 180 容易"演过头" |
|
|
|
|
|
+
|
|
|
|
|
+提效技巧:**同一段文本,用不同情感值和语速批量合成一批 wav,一次性对比试听**,比单次调参快得多。
|
|
|
|
|
+`demo.cpp` 里的三个输出就是这个思路。
|
|
|
|
|
+
|
|
|
|
|
+补充:腾讯云 TTS 也支持 SSML(控制停顿、局部语速/音调、多音字发音),需要更细的表现力时再上。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 六、工程化注意事项(容易踩的坑)
|
|
|
|
|
+
|
|
|
|
|
+1. **长度限制**:基础语音合成单次 **中文 150 字**(英文 500 字母)。超长必须分段 ——
|
|
|
|
|
+ `SynthesizeLongTextToFile()` 已实现按标点切分 + 拼接。
|
|
|
|
|
+2. **拼接格式**:`wav` 后续分段会自动跳过 44 字节头的 data 块;`mp3` 直接顺序拼接;`pcm` 无头直接追加。
|
|
|
|
|
+ 推荐长文本直接用 `mp3`。
|
|
|
|
|
+3. **并发限制**(默认,超出返回错误码 10002 / 并发超限):
|
|
|
|
|
+ - 基础/精品音色 20 路,大模型音色 20 路,超自然大模型音色 10 路
|
|
|
|
|
+ - 需要更高并发要买并发叠加包(精品 200 元/并发/月,大模型 350 元/并发/月)
|
|
|
|
|
+4. **密钥安全**:`SecretKey` 绝对不能打进客户端。桌面软件请走**后端中转** —— 客户端请求你的服务器,
|
|
|
|
|
+ 服务器持有密钥调腾讯云并返回音频。要放客户端也至少做混淆 + 子账号最小权限 + 用量告警。
|
|
|
|
|
+5. **缓存**:同一文本+参数的结果存本地(按 `md5(text+voice+emotion)` 做 key),成本和延迟都能降一个量级。
|
|
|
|
|
+6. **成本**(后付费,按字符,汉字/字母/标点/空格都算):
|
|
|
|
|
+ - 精品音色 **0.3 元/万字符**
|
|
|
|
|
+ - 大模型音色 1.2 元/万字符起,量大降到 0.55
|
|
|
|
|
+ - 超自然大模型音色 6.5 元/万字符起,量大降到 4.9
|
|
|
|
|
+ - 例:每天 10 万字符的播报量,用精品音色约 **3 元/天**
|
|
|
|
|
+7. **UTF-8**:MSVC 必须加 `/utf-8`(CMakeLists 已配),否则中文字符串按本地 GBK 解析,合成出来是乱码发音。
|
|
|
|
|
+8. **时间戳**:`enable_subtitle=true` 可拿到逐字时间戳,做字幕同步/口型驱动;超自然音色暂不支持。
|
|
|
|
|
+9. **播放侧**:`PcmPlayer` 内部 8 个 8KB 缓冲区,`Feed()` 在缓冲满时会阻塞 —— 这是正常背压,
|
|
|
|
|
+ 不要放到 UI 线程;实时场景建议开一个工作线程专门收数据 + 播放。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 七、排障清单
|
|
|
|
|
+
|
|
|
|
|
+| 现象 | 原因 / 处理 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| `AuthFailure.SignatureExpire` | 本机时间与标准时间偏差过大,开 Windows 时间同步 |
|
|
|
|
|
+| `AuthFailure.SignatureFailure`(REST) | ①`Content-Type` 必须是 `application/json; charset=utf-8`;②参与签名的 header 与实际发送的不一致 |
|
|
|
|
|
+| `InvalidParameterValue.TextTooLong` | 超过 150 字,走分段(或改用流式接口,上限 600 字) |
|
|
|
|
|
+| 返回成功但音频无声 | `Codec` 与播放方式不匹配;pcm 裸流没有文件头,播放器识别不了,先换 wav 验证 |
|
|
|
|
|
+| `FailedOperation.ServiceNotEnabled` | 没开通服务或免费额度没领取 |
|
|
|
|
|
+| 并发超限 | 代码里做请求队列 + 重试退避,或购买并发包 |
|
|
|
|
|
+| 情感没变化 | 音色不是"多情感音色",去音色列表页重新挑一个带"情感"标签的 |
|
|
|
|
|
+| 流式:`WebSocket upgrade rejected ... 403` | 签名错。依次查:AppId 是否为**数字账号 AppId**、Signature 是否 urlencode、Text 编码是否只做了一次 |
|
|
|
|
|
+| 流式:`certificate verify failed ...` | OpenSSL 在 Windows 上找不到根证书。设环境变量 `SSL_CERT_FILE` 指向 `cacert.pem`,或调 `SetCaFile()`;代码默认会尝试导入 Windows 系统根证书库 |
|
|
|
|
|
+| 流式:`TCP connect timeout` | 公司网络/代理拦了 443 出站。本机 `WssClient` 不走系统代理,需要在代理侧放行 `tts.cloud.tencent.com:443` |
|
|
|
|
|
+| 流式:`TLS handshake failed` | 见[第九节](#九win7--_win32_winnt0x0601-兼容)的 TLS 1.2 说明 |
|
|
|
|
|
+| 流式:有数据但无声/像噪音 | `Codec` 不是 `pcm`,或采样率传了 24000(流式只支持 8k/16k) |
|
|
|
|
|
+| 播放卡顿/断续 | `PcmPlayer` 在 UI 线程跑导致背压阻塞,移到工作线程;或把缓冲区调大 |
|
|
|
|
|
+| REST:`Problem with the SSL CA cert` / `SSL peer certificate or SSH remote key was not OK` | 证书校验失败。代码在 Windows + curl ≥ 7.71 下会自动走系统证书库(`CURLSSLOPT_NATIVE_CA`);若你的 curl 更老,下载 `cacert.pem` 后设 `TTS_CA_FILE` 或调 `client.SetCaFile()`。仅本地调试可 `client.SetVerifyPeer(false)` |
|
|
|
|
|
+| REST:Win7 上 TLS 握手失败 | curl 若是 SChannel 后端,Win7 默认没开 TLS 1.2,见[第九节](#九win7--_win32_winnt0x0601-兼容) |
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 八、下一步
|
|
|
|
|
+
|
|
|
|
|
+1. **接实时对话**:LLM 流式输出 → 按句号/分号攒句 → 每攒够一句就起一个 `StreamTtsPlay()`,
|
|
|
|
|
+ 即可实现"边想边说"(官方推荐做法,比等全文生成完再合成体验好很多)。
|
|
|
|
|
+2. **字幕/口型同步**:`enable_subtitle=true`,`StreamTtsRun` 的文本帧里带逐字 `BeginTime/EndTime`,
|
|
|
|
|
+ 用 `on_audio` 累计已播放字节数换算成播放进度,即可驱动字幕高亮或数字人口型。
|
|
|
|
|
+3. **想换官方 SDK**:见 `patch/官方语音SDK增加情感参数.md`,给 `tencentcloud-speech-sdk-cpp`
|
|
|
|
|
+ 补两处即可支持流式 + 情感(代价是要编 Boost/OpenSSL/websocketpp)。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 九、Win7 / `_WIN32_WINNT=0x0601` 兼容
|
|
|
|
|
+
|
|
|
|
|
+工程目标是 Win7(`_WIN32_WINNT=0x0601`)时,有 **两个硬伤** 要处理。下面是全部改动点,
|
|
|
|
|
+按这个改完就能在 Win7 上跑通。
|
|
|
|
|
+
|
|
|
|
|
+### 9.1 硬伤一:WinHTTP 的 WebSocket 是 Win8+ 的 API
|
|
|
|
|
+
|
|
|
|
|
+`WinHttpWebSocketCompleteUpgrade` / `WinHttpWebSocketReceive` 这些函数在 Windows SDK 里
|
|
|
|
|
+被 `#if (NTDDI_VERSION >= NTDDI_WIN8)` 包着。你把 `_WIN32_WINNT` 设成 `0x0601`,
|
|
|
|
|
+它们**直接不声明**,编译期就报「未定义标识符」,没有绕的余地。
|
|
|
|
|
+
|
|
|
|
|
+**解法**:流式接口的 WSS 通道换成 OpenSSL + 原生 socket 自己实现(`WssClient`):
|
|
|
|
|
+
|
|
|
|
|
+| | 原方案 | 现在 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| 传输 | WinHTTP(Win8+) | OpenSSL + `socket()`(Win7 起全支持) |
|
|
|
|
|
+| WebSocket 协议 | 系统实现 | 自己实现 RFC6455 握手 + 帧编解码 |
|
|
|
|
|
+| 依赖 | `winhttp.lib` | `libssl` / `libcrypto` + `ws2_32.lib` |
|
|
|
|
|
+
|
|
|
|
|
+顺带两个好处:不依赖系统 SChannel 的 TLS 版本策略;Win7/Win10/Linux 一份代码。
|
|
|
|
|
+
|
|
|
|
|
+`WssClient` 已实现的细节(省得你再踩):
|
|
|
|
|
+
|
|
|
|
|
+- TLS 下限锁死 1.2(`SSL_CTX_set_min_proto_version`)、设 SNI、做主机名校验
|
|
|
|
|
+- 非阻塞 `connect` + `select` 实现连接超时(`SO_RCVTIMEO` 管不到 connect)
|
|
|
|
|
+- `SSL_read` **不保留 TLS 记录边界** —— 握手时很可能一次就把 HTTP 响应头和紧随其后的
|
|
|
|
|
+ WebSocket 帧一起返回,多出来的字节必须缓存下来给帧解析用,否则首帧音频会被吞掉
|
|
|
|
|
+- 分片(continuation)帧自动拼成完整消息;mask 索引按帧内偏移算(RFC6455 5.3)
|
|
|
|
|
+- 能区分「对方干净关闭」和「超时/中断」(`clean_eof()`),避免超时被误判成播放成功
|
|
|
|
|
+
|
|
|
|
|
+### 9.2 硬伤二:Win7 的 SChannel 默认不开 TLS 1.2
|
|
|
|
|
+
|
|
|
|
|
+腾讯云接口只接受 TLS 1.2+。Win7 的 SChannel 默认只到 TLS 1.0,
|
|
|
|
|
+要打 **KB3140245** 并改注册表 `SslProtocols` 才开得起来 —— 你没法要求每个客户都去打补丁。
|
|
|
|
|
+
|
|
|
|
|
+按你 curl 的编译后端分两种情况:
|
|
|
|
|
+
|
|
|
|
|
+| curl 的 TLS 后端 | Win7 表现 | 处理 |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| **OpenSSL**(推荐) | 正常,OpenSSL 自带协议栈 | 代码里已用 `CURLOPT_SSLVERSION` 锁死 TLS 1.2 |
|
|
|
|
|
+| SChannel | 握手失败 | 装 KB3140245,或换 OpenSSL 后端重编 curl |
|
|
|
|
|
+
|
|
|
|
|
+vcpkg 装 curl 时显式指定后端:
|
|
|
|
|
+
|
|
|
|
|
+```powershell
|
|
|
|
|
+.\vcpkg\vcpkg install curl[openssl]:x64-windows openssl:x64-windows
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> 流式接口(WSS)不受这个影响 —— 它走的是你自己的 OpenSSL,跟 SChannel 没关系。
|
|
|
|
|
+
|
|
|
|
|
+### 9.3 其它改动点
|
|
|
|
|
+
|
|
|
|
|
+| 项 | 说明 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| 签名算法 | 原 Windows 走 BCrypt、其它平台走 OpenSSL 的分支**已删除**,统一走 OpenSSL(`CryptoUtil`)。少链一个 `bcrypt.lib`,也不用维护两套实现 |
|
|
|
|
|
+| `_WIN32_WINNT` | CMakeLists 里原来写死了 `0x0602`,**已删除** —— 那是给 WinHTTP WebSocket 用的,现在不需要了,留着反而会覆盖你工程的设置 |
|
|
|
|
|
+| 链接库 | `winhttp.lib`、`bcrypt.lib` → `ws2_32.lib`、`crypt32.lib`、`libssl`、`libcrypto`。`winmm.lib` 保留(waveOut 播放) |
|
|
|
|
|
+| OpenSSL 版本 | 要求 **>= 1.1.0**(用到 `SSL_CTX_set_min_proto_version` / `SSL_set1_host`),低于此版本会 `#error` 提示 |
|
|
|
|
|
+| 编译器 | VS2013+(`std::strtoll`、C++11)。MSVC 必须加 `/utf-8`,否则中文按 GBK 解析 |
|
|
|
|
|
+| 证书 | Windows 上 OpenSSL 通常找不到根证书,`WssClient` 默认会导入 **Windows 系统根证书库**(`CertOpenSystemStoreA`),一般不用额外配置;真出问题再设 `SSL_CERT_FILE` 或调 `SetCaFile()` |
|
|
|
|
|
+
|
|
|
|
|
+### 9.4 手工接进 VS 工程(不用 CMake)
|
|
|
|
|
+
|
|
|
|
|
+把 `src/` 下这几个文件加进工程即可:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+CryptoUtil.h/.cpp 签名与编解码(OpenSSL)
|
|
|
|
|
+WssClient.h/.cpp 流式接口的 WSS 通道(OpenSSL + socket)
|
|
|
|
|
+StreamTtsPlayer.h/.cpp 流式合成与播放
|
|
|
|
|
+TtsClient.h/.cpp REST 合成(libcurl)
|
|
|
|
|
+PcmPlayer.h/.cpp waveOut 内存播放(仅 Windows)
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+VS 工程属性要改这几处:
|
|
|
|
|
+
|
|
|
|
|
+| 位置 | 值 |
|
|
|
|
|
+|---|---|
|
|
|
|
|
+| C/C++ → 常规 → 附加包含目录 | curl 和 OpenSSL 的 `include` 目录 |
|
|
|
|
|
+| C/C++ → 命令行 → 附加选项 | `/utf-8` |
|
|
|
|
|
+| C/C++ → 预处理器 → 预处理器定义 | `OPENSSL_SUPPRESS_DEPRECATED`(消 OpenSSL 3.0 的 deprecation 警告) |
|
|
|
|
|
+| 链接器 → 输入 → 附加依赖项 | `libcurl.lib` `libssl.lib` `libcrypto.lib` `ws2_32.lib` `crypt32.lib` `winmm.lib` |
|
|
|
|
|
+| 链接器 → 常规 → 附加库目录 | curl 和 OpenSSL 的 `lib` 目录 |
|
|
|
|
|
+
|
|
|
|
|
+最后把 `libcurl.dll`、`libssl-3-x64.dll`、`libcrypto-3-x64.dll` 放到 exe 同目录。
|
|
|
|
|
+
|
|
|
|
|
+### 9.5 验证清单
|
|
|
|
|
+
|
|
|
|
|
+改完按这个顺序验,能快速定位问题在哪一层:
|
|
|
|
|
+
|
|
|
|
|
+1. `TtsClient::SynthesizeToFile()` 能出 `out_normal.wav` → REST + 签名 + curl TLS 都 OK
|
|
|
|
|
+2. `StreamTtsRun()` 收到 `audio_bytes > 0` → WSS 握手 + 签名 OK
|
|
|
|
|
+3. `StreamTtsPlay()` 能听出声音 → 播放链路 OK
|
|
|
|
|
+4. 情感切换有明显语气差异 → 音色是多情感音色,参数生效
|
|
|
|
|
+
|
|
|
|
|
+第 2 步失败时重点看 `err` 里的前缀:`TCP connect` / `TLS handshake` / `certificate verify` /
|
|
|
|
|
+`WebSocket upgrade rejected` 分别对应网络、TLS 版本、证书、签名四个不同环节。
|