Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions NaturalVoiceSAPIAdapter/EdgeGec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <string.h>

// Sec-MS-GEC v2, ported from edge-tts 7.3.0 drm.py (LGPL-3.0).
// Keep this independent of Windows/SAPI so the production algorithm can be tested.
namespace EdgeGec {
#include "EdgeGecTables.h"
inline uint32_t Ror(uint32_t x, unsigned n) {
return (x >> n) | (x << (32 - n));
}
inline uint32_t Swap(uint32_t x) {
return Ror(x, 16);
}
inline uint32_t S0(uint32_t x) {
return Ror(x, 7) ^ Ror(x, 18) ^ (x >> 3);
}
inline uint32_t S1(uint32_t x) {
return Ror(x, 17) ^ Ror(x, 19) ^ (x >> 10);
}
inline uint32_t ReadBE(const unsigned char* p) {
return uint32_t(p[0]) << 24 | uint32_t(p[1]) << 16 | uint32_t(p[2]) << 8 | p[3];
}
inline uint32_t ReadLE(const unsigned char* p) {
return uint32_t(p[3]) << 24 | uint32_t(p[2]) << 16 | uint32_t(p[1]) << 8 | p[0];
}
inline void Expand(uint32_t* w, unsigned first) {
for (unsigned i = first; i < 64; i++) w[i] = S1(w[i - 2]) + w[i - 7] + S0(w[i - 15]) + w[i - 16];
}
inline void Compress(uint32_t* state, const uint32_t* w, unsigned first) {
uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4], f = state[5], g = state[6],
h = state[7];
for (unsigned i = first; i < 64; i++) {
uint32_t k = first == 14 && i == 14 ? 0xC9DC06A7 : Rounds[i];
uint32_t t1 = h + (Ror(e, 6) ^ Ror(e, 11) ^ Ror(e, 25)) + (g ^ (e & (f ^ g))) + k + w[i];
uint32_t t2 = (Ror(a, 2) ^ Ror(a, 13) ^ Ror(a, 22)) + ((a & b) | (c & (a | b)));
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}
uint32_t work[] = {a, b, c, d, e, f, g, h};
for (unsigned i = 0; i < 8; i++) state[i] = (first == 14 ? Initial[i] : state[i]) + work[i];
}
// fileTime includes the correction derived from the server's Date header.
// result must have room for 81 ASCII characters plus the terminating NUL.
inline void Generate(uint64_t fileTime, char* result) {
fileTime -= fileTime % 3000000000ULL;
char data[64];
snprintf(data, sizeof(data), "%llu6A5AA1D4EAFF4E9FB37E23D68491D6F4", (unsigned long long)fileTime);
size_t len = strlen(data);
const auto* bytes = reinterpret_cast<const unsigned char*>(data);
// V2 resumes a modified SHA-256 block after a fixed 57-byte prefix.
// These schedule/state adjustments are intentional; ordinary SHA256(data)
// produces the old v1 token and cannot replace this step.
uint32_t w[64] = {};
w[14] = uint32_t(bytes[0]) << 16 | uint32_t(bytes[1]) << 8 | bytes[2];
w[15] = ReadBE(bytes + 3);
for (unsigned i = 16; i < 30; i++) {
uint32_t v = Schedule[i - 16];
if (i - 2 >= 14) v += S1(w[i - 2]) ^ (i == 16 ? 0x000B92C0 : 0);
if (i - 7 >= 14) v += w[i - 7];
if (i - 15 >= 14) v += S0(w[i - 15]) ^ (i == 29 ? 0x059C0B80 : 0);
w[i] = v;
}
w[30] = S1(w[28]) + w[23] + S0(w[15]) + w[14] + 0xC9DC06A7 - Rounds[14];
Expand(w, 31);
uint32_t state[8];
memcpy(state, ResumeState, sizeof(state));
Compress(state, w, 14);
unsigned char padded[128] = {};
memcpy(padded, bytes + 7, len - 7);
padded[len - 7] = 0x80;
size_t total = ((len + 57 + 9 + 63) / 64) * 64 - 64;
uint64_t bits = (len + 57) * 8;
for (unsigned i = 0; i < 8; i++) padded[total - 1 - i] = (unsigned char)(bits >> (i * 8));
for (size_t off = 0; off < total; off += 64) {
for (unsigned i = 0; i < 16; i++) w[i] = ReadBE(padded + off + i * 4);
Expand(w, 16);
Compress(state, w, 0);
}
unsigned char digest[32];
for (unsigned i = 0; i < 32; i++) digest[i] = (unsigned char)(state[i / 4] >> (24 - 8 * (i % 4)));
// CS64 uses little-endian words; SHA-256 emits big-endian words.
uint32_t ka = ReadLE(digest) | 1, kb = ReadLE(digest + 4) | 1;
uint32_t factors[] = {ka * 0x82893933U, kb * 0x82893933U};
uint32_t terms[32] = {}, offsets[32] = {};
unsigned count = 14;
for (unsigned i = 0; i < 14; i++) {
terms[i] = Coefficients[i][0] * factors[i % 2];
offsets[i] = Coefficients[i][1];
}
unsigned char input[72] = {};
memcpy(input + 1, bytes, len);
unsigned words = (unsigned)((len + 1 + 7) / 8) * 2;
for (unsigned i = 0; i < words; i++) {
uint32_t word = ReadLE(input + i * 4);
terms[count] = word * (i % 2 ? kb : ka);
offsets[count] = Swap(word) * (i % 2 ? 0xC31713DBU : 0xEF0569FBU);
if (i == 0) {
terms[count] += 0xF2F90B1AU * factors[0];
offsets[count] += 0x0B1A0000;
}
count++;
}
uint32_t swapAcc = 0, swapLast = 0, s = 0, acc = 0;
for (unsigned i = 0; i < count; i += 2) {
uint32_t x = terms[i], y = terms[i + 1];
uint32_t q = Swap(offsets[i] + x) * 0x76F50F34U;
swapAcc += Swap(q) * 0xC3EFEA97U + q;
q = Swap(offsets[i + 1] + y) * 0x1881CE3CU;
swapLast = Swap(q) * 0x35BD1EC9U + q;
swapAcc += swapLast;
s = s * ka + x;
s = Swap(s) * 0xCF98B111U;
s = Swap(s) * 0x87085B9FU;
s = Swap(s) * 0x12CEB96DU;
s = Swap(s) * 0x257E1D83U;
acc += s;
uint32_t t = s * kb + y;
t = Swap(t) * 0xA27416F5U;
t = Swap(t) * 0xD38396FFU;
s = Swap(Swap(t) * 0x7C932B89U) * 0xBFA49F69U;
acc += s;
}
uint32_t cs[] = {swapLast ^ s, swapAcc ^ acc};
constexpr char hex[] = "0123456789ABCDEF";
for (unsigned i = 0; i < 8; i++) {
unsigned char v = (unsigned char)(cs[i / 4] >> (8 * (i % 4)));
*result++ = hex[v >> 4];
*result++ = hex[v & 15];
}
*result++ = '-';
for (auto v : digest) {
*result++ = hex[v >> 4];
*result++ = hex[v & 15];
}
*result = 0;
}
} // namespace EdgeGec
27 changes: 27 additions & 0 deletions NaturalVoiceSAPIAdapter/EdgeGecTables.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Constants from edge-tts 7.3.0 drm.py (LGPL-3.0). See tests/reference.
static constexpr uint32_t Initial[] = {
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
};
static constexpr uint32_t ResumeState[] = {
0xFD9FBEA2, 0x91243CE7, 0xBE499094, 0xCD2E1E0A, 0x6C006F49, 0x04BD35AB, 0xFEB0A7E4, 0x03EFFBD2,
};
static constexpr uint32_t Schedule[] = {
0xEB24BF9E, 0x1558691F, 0xA0C09EE2, 0x0CFA32BA, 0xA622EE8C, 0xD1DD9491, 0xC0DD25A0,
0xF9E70AB6, 0xF0C31573, 0x7058CE4E, 0x7188E606, 0x00E5AB33, 0xD258B060, 0x72766564,
};
static constexpr uint32_t Rounds[] = {
0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5,
0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174,
0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA,
0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967,
0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85,
0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070,
0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3,
0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2,
};
static constexpr uint32_t Coefficients[][2] = {
{0xE82E91B1, 0xB32700D0}, {0x44DE24C6, 0x88DFF51D}, {0x3BEB65BC, 0x29514038}, {0xC89C5933, 0xB328CBDF},
{0x8D1EC211, 0xD405B4D5}, {0x314EF5D5, 0x26E55F3C}, {0xE92491B1, 0x607801C6}, {0xF0A5B9D5, 0x82B2C1FB},
{0x06064CF3, 0xFA51A3DA}, {0xDA79FA60, 0x207CC464}, {0xBED20560, 0x3C3C74F3}, {0x89F9C9F8, 0xD2D14B61},
{0x35FA38C6, 0xBB04A2C1}, {0xCB49E3C6, 0xC5626167},
};
17 changes: 11 additions & 6 deletions NaturalVoiceSAPIAdapter/NaturalVoiceSAPIAdapter.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<NaturalVoiceRegisterOutput Condition="'$(NaturalVoiceRegisterOutput)' == ''">true</NaturalVoiceRegisterOutput>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IgnoreImportLibrary>true</IgnoreImportLibrary>
<LinkIncremental>true</LinkIncremental>
Expand Down Expand Up @@ -174,7 +177,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>.\NaturalVoiceSAPIAdapter.def</ModuleDefinitionFile>
<RegisterOutput>true</RegisterOutput>
<RegisterOutput>$(NaturalVoiceRegisterOutput)</RegisterOutput>
<PerUserRedirection>false</PerUserRedirection>
<DelayLoadDLLs>Microsoft.CognitiveServices.Speech.core.dll</DelayLoadDLLs>
<AdditionalDependencies>libcrypto.lib;libssl.lib;crypt32.lib;RuntimeObject.lib;%(AdditionalDependencies)</AdditionalDependencies>
Expand Down Expand Up @@ -214,7 +217,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>.\NaturalVoiceSAPIAdapter.def</ModuleDefinitionFile>
<RegisterOutput>true</RegisterOutput>
<RegisterOutput>$(NaturalVoiceRegisterOutput)</RegisterOutput>
<PerUserRedirection>false</PerUserRedirection>
<DelayLoadDLLs>Microsoft.CognitiveServices.Speech.core.dll</DelayLoadDLLs>
<AdditionalDependencies>libcrypto.lib;libssl.lib;crypt32.lib;RuntimeObject.lib;%(AdditionalDependencies)</AdditionalDependencies>
Expand Down Expand Up @@ -255,7 +258,7 @@
<Link>
<SubSystem>Windows</SubSystem>
<ModuleDefinitionFile>.\NaturalVoiceSAPIAdapter.def</ModuleDefinitionFile>
<RegisterOutput>true</RegisterOutput>
<RegisterOutput>$(NaturalVoiceRegisterOutput)</RegisterOutput>
<PerUserRedirection>false</PerUserRedirection>
<DelayLoadDLLs>Microsoft.CognitiveServices.Speech.core.dll</DelayLoadDLLs>
<AdditionalDependencies>libcrypto.lib;libssl.lib;crypt32.lib;RuntimeObject.lib;%(AdditionalDependencies)</AdditionalDependencies>
Expand Down Expand Up @@ -298,7 +301,7 @@
<ModuleDefinitionFile>.\NaturalVoiceSAPIAdapter.def</ModuleDefinitionFile>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<RegisterOutput>true</RegisterOutput>
<RegisterOutput>$(NaturalVoiceRegisterOutput)</RegisterOutput>
<PerUserRedirection>false</PerUserRedirection>
<DelayLoadDLLs>Microsoft.CognitiveServices.Speech.core.dll</DelayLoadDLLs>
<AdditionalOptions>/PDBALTPATH:%_PDB% %(AdditionalOptions)</AdditionalOptions>
Expand Down Expand Up @@ -340,7 +343,7 @@
<ModuleDefinitionFile>.\NaturalVoiceSAPIAdapter.def</ModuleDefinitionFile>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<RegisterOutput>true</RegisterOutput>
<RegisterOutput>$(NaturalVoiceRegisterOutput)</RegisterOutput>
<PerUserRedirection>false</PerUserRedirection>
<DelayLoadDLLs>Microsoft.CognitiveServices.Speech.core.dll</DelayLoadDLLs>
<AdditionalOptions>/PDBALTPATH:%_PDB% %(AdditionalOptions)</AdditionalOptions>
Expand Down Expand Up @@ -383,7 +386,7 @@
<ModuleDefinitionFile>.\NaturalVoiceSAPIAdapter.def</ModuleDefinitionFile>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<RegisterOutput>true</RegisterOutput>
<RegisterOutput>$(NaturalVoiceRegisterOutput)</RegisterOutput>
<PerUserRedirection>false</PerUserRedirection>
<DelayLoadDLLs>Microsoft.CognitiveServices.Speech.core.dll</DelayLoadDLLs>
<AdditionalOptions>/PDBALTPATH:%_PDB% %(AdditionalOptions)</AdditionalOptions>
Expand Down Expand Up @@ -417,6 +420,8 @@
<ClInclude Include="VoiceToken.h" />
<ClInclude Include="VoiceTokenEnumerator.h" />
<ClInclude Include="WSConnectionPool.h" />
<ClInclude Include="EdgeGec.h" />
<ClInclude Include="EdgeGecTables.h" />
<ClInclude Include="WSLogger.h" />
<ClInclude Include="wrappers.h" />
</ItemGroup>
Expand Down
10 changes: 6 additions & 4 deletions NaturalVoiceSAPIAdapter/SpeechServiceConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ constexpr inline const char* EDGE_TRUSTED_CLIENT_TOKEN = "6A5AA1D4EAFF4E9FB37E23

constexpr inline const char* EDGE_VOICE_LIST_URL = "https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4";

constexpr inline const wchar_t* EDGE_WEBSOCKET_URL
= L"wss://api.msedgeservices.com/tts/cognitiveservices/websocket/v1"
L"?Ocp-Apim-Subscription-Key=6A5AA1D4EAFF4E9FB37E23D68491D6F4&Sec-MS-GEC={Sec-MS-GEC}&Sec-MS-GEC-Version=1-142.0.3595.94";
constexpr inline const wchar_t* EDGE_WEBSOCKET_URL =
L"wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1"
L"?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4&Sec-MS-GEC={Sec-MS-GEC}&Sec-MS-GEC-Version=2-143.0.3650.75";

constexpr inline const char* AZURE_TTS_HOST_AFTER_REGION = ".tts.speech.microsoft.com";

constexpr inline const char* AZURE_VOICE_LIST_PATH = "/cognitiveservices/voices/list";

constexpr inline const char* AZURE_WEBSOCKET_PATH = "/cognitiveservices/websocket/v1";

constexpr inline const char* EDGE_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0";
constexpr inline const char* EDGE_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 "
"Edg/143.0.0.0";
Loading
Loading