Edge 在线语音 403 问题修复

本文目录

  1. [Edge 在线语音是什么](#Edge 在线语音是什么)
  2. [问题现象:HTTP 403](#问题现象:HTTP 403)
  3. [为什么会 403](#为什么会 403)
  4. 修复方案
  5. 效果与小结

一、Edge 在线语音是什么

「有声小说书屋」客户端里内置了两套朗读引擎:

一套是本地朗读(OneCore),用 Windows 自带语音,完全离线;

另一套就是本文的主角------Edge 在线朗读,它调用微软的神经网络语音,音色自然(晓晓、云希、云健等十几个音色),但需要联网。

Edge 在线朗读的完整链路是这样的:

其中第 ② 步的「WebSocket 握手」最关键------如果握手时服务器不认可你,整条链路在开头就断了,后面都白搭。

二、问题现象:HTTP 403

问题出现时,用户点「朗读」会看到类似这样的报错,或者干脆没声音:

复制代码
Edge 在线语音服务拒绝连接(HTTP 403)。

HTTP 403 的意思是「服务器拒绝访问」------不是网络断了,也不是接口不存在,而是服务器主动拒绝了我们发过去的连接请求。

一句话理解 403服务端收到了请求,但「验明正身」这一关没过,于是把连接关掉了。所以排查方向就是:我们提交给服务器的「身份凭证」哪里不对。

三、为什么会 403

微软这个 readaloud/edge/v1 接口不是公开 API,它是 Edge 浏览器「大声朗读」功能内部使用的一个私有接口。要访问它,必须在请求里带上一串「鉴权参数」,只要任何一个对不上,服务器就返回 403。

这些参数里,最关键的三个:

TrustedClientToken

一个固定令牌,相当于「入门卡」。微软会不定期更换,旧令牌会直接失效。

Sec-MS-GEC

一个签名值,格式 CS64-SHA256。这是最难的一环------它是一套「修改版」摘要算法。

Sec-MS-GEC-Version

签名版本号,跟着 Edge / Chromium 内核版本走,例如 2-143.0.3650.75。

3.1 签名算法变了

微软在 Edge 内核升级时,把 Sec-MS-GEC 从旧版换成了 v2 算法。它不是标准的 SHA-256,而是用了自定义的初始状态、替换过的第 14 轮常量、以及自定义的消息填充 ,另外还拼上一个独立的 CS64 摘要。旧版客户端算出来的签名和服务器对不上,自然被拒。1

3.2 版本号没跟上

签名的版本号 Sec-MS-GEC-Version 要和当前 Edge 内核一致。内核从旧版升级到 143.0.3650.75 之后,如果客户端还报旧版本号,服务器也不认。3

3.3 系统时钟不准(隐藏坑)

这是最隐蔽的一个原因。签名算法里有一个「5 分钟签名窗口」:它先把当前时间戳对齐到 5 分钟的整边界,再参与签名计算。如果本机系统时间不准 (快了几分钟或慢了几分钟),算出来的签名会落在服务器认定的「错误窗口」里,同样返回 403。2

为什么时钟会导致 403服务器只接受「最近 5 分钟」内的签名。本机时钟偏了之后,你以为是现在,服务器却觉得你的签名是「过去」或「未来」发出的,于是拒绝。

四、修复方案

针对上面的三个根因,做了四步修复,核心代码都在 EdgeTtsClient.cpp 里。

4.1 重写 Sec-MS-GEC v2 签名

按新版 edge-tts 的 drm.py 算法,逐常量对齐实现了「修改版 SHA-256 + CS64」,输出 <CS64 16位hex>-<SHA256 64位hex> 格式的签名。1签名输入是「校正后的时间戳 + 固定令牌」:

复制代码
// 取系统时间(100ns 刻度),先校正时钟,再对齐到 5 分钟窗口
ticks += g_clockSkewTicks;                  // 时钟偏移校正
ticks -= ticks % (300ULL * 10000000ULL);    // 对齐到 5 分钟

// 签名输入 = 时间戳字符串 + 固定令牌
std::string strToHash = tickText + kTrustedClientToken;
// 用修改版 SHA-256 + CS64 算出 Sec-MS-GEC

4.2 更新令牌与版本号

把 TrustedClientToken 更新为新令牌,并把 kChromiumVersion 提到 143.0.3650.75,最终请求路径的关键参数长这样:

复制代码
/consumer/speech/synthesize/readaloud/edge/v1
    ?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4
    &ConnectionId=<随机 16 字节 hex>
    &Sec-MS-GEC=<CS64>-<SHA256>
    &Sec-MS-GEC-Version=2-143.0.3650.75

4.3 系统时钟偏移校正

在首次合成时 ,先请求一次 voices/list 接口,读它响应头里的 Date(服务器时间),用它减去本机时间得到偏移量并缓存。之后每次算签名都用校正后的时间,从而稳定落在正确的 5 分钟窗口内:

复制代码
// 通过 voices/list 的 Date 头,计算「服务器时间 - 本地时间」的偏移
g_clockSkewTicks = serverTicks - localTicks;
g_clockSkewValid = true;   // 只算一次,之后复用缓存

4.4 顺手优化:minimp3 解码

修复过程中还发现一个体验问题:原来用 Windows 的 Media Foundation 解码 MP3,首次播放要冷启动数十秒 。于是换成单头文件的 minimp3 纯 C 解码器,直接从内存解 MP3 到 PCM 再交给 WASAPI 播放,首声响应的等待几乎消失。

五、效果与小结

修复之后,Edge 在线朗读恢复了正常:握手拿到 101(协议切换成功),服务器正常返回音频,403 不再出现。

小结

  • 这类「私有接口」比较脆弱,微软一升级内核,签名和版本就要跟着变,否则旧客户端立刻失效。
  • 排查 403 时,别只盯着签名算法,版本号 和系统时钟同样能导致拒绝,时钟偏移是这次最隐蔽的一环。
  • 签名、令牌、版本、时钟四者对齐之后,在线朗读才稳定;离线场景仍可随时切回「本地朗读(OneCore)」,不依赖网络。
根因 表现 修复
Sec-MS-GEC 签名算法升级 旧签名被拒,403 重写 v2 签名(修改版 SHA-256 + CS64)
TrustedClientToken / 版本号过期 凭证失效,403 更新令牌与 Chromium 版本号
系统时钟不准 签名落在错误窗口,403 首次合成时校正时钟偏移并缓存
Media Foundation 解码慢 首次播放等待数十秒 换 minimp3 纯 C 解码器

参考资料(在线)

  1. edge-tts · drm.py 源码 --- Sec-MS-GEC v2 签名与时钟偏移校正的直接实现,代码注释「严格对应 edge_tts/drm.py」所指文件。edge-tts/src/edge_tts/drm.py at master · rany2/edge-tts · GitHub
  2. DeepWiki · rany2/edge-tts · DRM and Security --- 确定问题根因的关键页:系统时钟与微软服务器不同步会返回 403,需用响应的 Date 头计算偏移自动恢复。DRM and Security | rany2/edge-tts | DeepWiki
  3. kothok-edge-tts(crates.io)--- 佐证:微软按 Edge 发布周期轮换 SEC_MS_GEC_VERSION,过期后每次合成返回 HTTP 403,需重新 pin 版本号。https://crates.io/crates/kothok-edge-tts/0.2.1
  4. pyVideoTrans · Common Edge-TTS Errors --- 补充 403 的另一成因:同一 IP 短时间高频请求会触发限流。https://en.pyvideotrans.com/edgetts-error
  5. edge-tts(GitHub 项目)--- Edge 浏览器「大声朗读」私有接口的开源实现,本项目鉴权签名的总来源。GitHub - rany2/edge-tts: Use Microsoft Edge's online text-to-speech service from Python WITHOUT needing Microsoft Edge · GitHub

本地源码

  1. EdgeTtsClient.cpp --- Edge 在线语音客户端:鉴权签名、时钟偏移校正、合成与播放。g:\biancheng\GUDIANSHUWU\GUDIANSHUWU\EdgeTtsClient.cpp
  2. EdgeTtsClient.h --- 客户端类与音色目录(EdgeVoiceCatalog)声明。g:\biancheng\GUDIANSHUWU\GUDIANSHUWU\EdgeTtsClient.h
相关推荐
一碗白开水一1 小时前
Day 5 · 弹性 | 微观
java·前端·数据库
Lsx_1 小时前
微信支付接入全流程:JSAPI支付、小程序支付与H5支付
前端·javascript·全栈
OpenTiny社区1 小时前
React 正式登场!GenUI SDK v1.4.0 三框架渲染、出码与 Agent 生态重磅升级~
前端·ai编程
试图让你心动2 小时前
【无标题】
前端·javascript·vue.js
Hilaku2 小时前
为什么每个前端都应该拥有一个自己的独立博客?
前端·javascript·程序员
燕落南枝2 小时前
CSS 后代选择器与子代选择器:从入门到实战
前端·css
Amos_Web2 小时前
Rspack 源码解析(二十八):HtmlRspackPlugin 如何把模板变成带资源标签的最终产物
前端
马可家的菠萝2 小时前
个人知识库怎么建?从一个正在学的技术主题开始
前端·笔记·知识管理·个人知识库
几何心凉2 小时前
端到端具身交互智能正式上线:让 AI 通过屏幕和机器人,真正走进现实世界
前端·人工智能·实时互动·机器人·交互