文本转语音(TTS)速查表

浏览器原生的 Web Speech API 就能把文字读出来,无需后端、不花钱。本页整理朗读的核心参数、SSML 常用标签与移动端/Safari 的兼容坑,帮你把「网页朗读」做得自然稳定。

原理解析

文本转语音(TTS)把文字合成为可播放的语音。两类实现:浏览器内置的 Web Speech API(SpeechSynthesis,零依赖、随开随用,但音色与质量受系统/引擎限制);云端 TTS 服务(如各厂商 API,音色更自然、支持多语言与 SSML 微调)。

SSML(语音合成标记语言)用 XML 描述停顿、语调、发音人等细节,让朗读更自然。输出通常是 MP3/WAV/OGG 等音频,采样率与码率影响体积与清晰度;前端实时朗读常用流式返回以降低延迟。

什么时候该查这张表

这张表在你要给 TTS 调用挑参数时有用。它把语速、音调、音量、采样率、声道、语言、发音人这些参数放在一起,给出含义与调参建议。调语音效果时按表逐项试比查文档快。

不该查的情况:你在做语音内容的可访问性替代,或涉及 TTS 厂商的计费与配额。本表只讲参数含义,不涉及选型、价格、配额与合规。

判断标准:先明确用途再调参。做有声书/长文朗读优先自然度(选更好的发音人、降低语速、增加停顿),做导航播报优先清晰度与速度(语速略快、跳过缩写展开);两种目标的调法相反。

特别注意:参数取值范围与实际效果不是线性的。语速 1.0 听起来正常,1.2 就可能明显失真;音调改变会同时影响音高与音色自然度。调参应小步试探(每次 ±0.1),大幅调整往往得到不如意的结果。

边界与常见陷阱

  • 语速超过 1.2 后可懂度断崖式下降

    多数 TTS 引擎在 1.0–1.1 区间自然度最好,超过 1.3 会出现吞字、辅音脱落、句末语调断裂。真实场景里「用户觉得 1.5 更快更好」往往在 10 秒后才发现听不懂。若确需提速,正确做法是裁剪内容而非加速语音——去掉冗余修饰、拆分长句,比把语速调到 1.5 有效得多。

  • 音调与语速对时长的影响与直觉相反

    多数人以为「音调调高会显得快」。实际相反:音调升高时共振峰上移,同样文本的时长通常会略微变长(音高感知与时长感知不同步);而语速调高会同时改变音高,导致听起来「尖锐而急促」而不是「更快」。若目标是缩短总时长,应只调语速,不要用音调去凑。

  • 数字、缩写与符号会被读错或跳过

    「2024-06-01」「Dr.」「etc.」「&」「$1,200」这类内容,TTS 的表现取决于引擎的文本正规化能力与语言设置。稳妥做法是在送入前做文本正规化:数字转成读法(如 1200 → 一千二百)、符号换成等价词(& → and)、缩写按需展开,并对必须准确的场景(金额、电话、日期)逐条校验输出。

与相邻内容的分工

要比较不同 TTS 引擎的音质与支持语言,属工具选型范畴,需要实际试听与价格评估,本表不覆盖。

要生成或编辑音频文件(裁剪、转码、拼接),用站内音频相关工具;本页只讲合成阶段的参数。

要处理语音识别(STT)与文字转语音的串联流程,属语音系统设计,需要按场景取舍,本表只覆盖 TTS 参数。

若目的是排查某个具体发音不对的问题,先确认语言设置是否匹配——语言选错导致的读音异常比参数问题更常见。

实战片段

要点:① 先做文本正规化(数字转读法、符号换词、缩写展开)再调参数,正规化的收益远大于调音高语速;② 语速超过 1.2 可懂度断崖式下降,要提速应裁剪内容而非加速语音;③ 音调升高会让时长略微变长,与直觉相反,缩短时长只调语速;④ 长文本必须分句合成,否则容易被截断;⑤ Web Speech 的 rate 数值范围到 10 但自然度在 1.2 之后急剧下降。

// ① 文本正规化:数字与符号先转成可读形式,胜过调参数
function normalize(text) {
  return text
    .replace(/[¥$]s?([d,]+)/g, (_, n) => Number(n.replace(/,/g, '')) + ' 元')  // ¥1,200 → 一千二百元
    .replace(/&/g, ' 和 ')
    .replace(/(d{4})-(d{2})-(d{2})/g, '$1 年 $2 月 $3 日')
    .replace(/Dr./g, 'Doctor')
    .replace(/e.g./g, 'for example')
    .replace(/etc./g, 'et cetera')
}
console.log(normalize('2024-06-01 & Dr. Smith ¥1,200 etc.'))

// ② Web Speech API 调参:小步试探,不要一次跳到 1.5
function speak(text, { rate = 1.0, pitch = 1.0, volume = 1.0 } = {}) {
  if (!('speechSynthesis' in window)) throw new Error('不支持 Web Speech')
  const u = new SpeechSynthesisUtterance(normalize(text))
  u.lang = 'zh-CN'
  u.rate = Math.min(Math.max(rate, 0.1), 10)     // 数值合法但 >1.3 自然度差
  u.pitch = Math.min(Math.max(pitch, 0), 2)
  u.volume = Math.min(Math.max(volume, 0), 1)
  speechSynthesis.speak(u)
  return u
}
speak('测试语速', { rate: 1.1 })   // ✅ 先小幅试
speak('测试语速', { rate: 1.5 })   // ⚠️ 往往变成尖锐急促

// ③ 长文要分句合成,否则长文本会被截断或卡住
function speakLong(text, chunk = 200) {
  const s = normalize(text)
  for (let i = 0; i < s.length; i += chunk) {
    speak(s.slice(i, i + chunk), { rate: 1.0 })
  }
}

// ④ 先 stop 再 speak,避免队列堆积
speechSynthesis.cancel()
speak('重来一次')

// ⑤ 服务端 TTS(更可控,但要自己处理 SSML 与采样率)
// POST /tts  { text, voice, rate, pitch, format: 'mp3', sampleRate: 24000 }
// 24kHz 是清晰度与体积的平衡点;16kHz 会明显发闷

实践注解

  • 语速与音高是两条独立曲线,不是音量的替代调快语速会同时压缩停顿,听起来「赶」;单独下调音高会显得沉闷。可读性主要靠语速与句子切分,音高只承担情绪,调参时应分开验证。
  • SSML 支持度因引擎而异同一段 SSML 在浏览器内置语音上多数标签被忽略,在云服务上才生效。若页面依赖 <break> 或 <prosody>,必须提供无 SSML 时的降级文本,否则朗读会连同标签一起被念出来。
  • 长文本需要分片,且分片点应落在句末一次性提交超长文本会触发引擎长度上限或明显卡顿。分片要在句号、问号后切,避免把数字与单位、英文单词从中间切断——这类断点会产生错误的连读。

数据来源

  • W3C SSML 1.1 (Speech Synthesis Markup Language)
  • W3C Web Speech API

上述编号、名称与定义均来自公开规范,本站不主张对规范数据本身的独创性;本页的编排方式、中文释义与实践注解由本站撰写。

核心朗读参数

参数含义调参建议
rate(语速)朗读速度,1 为正常,范围约 0.1–10中文建议 0.9–1.1,过快会吞字;英文可到 1.2
pitch(音高)音调高低,1 为默认,范围约 0–2微调 ±0.1 即可区分角色,别拉太极端
volume(音量)音量,1 为默认,0 为静音网页里通常保持默认,交给系统音量
voice(音色)选择具体发音人,含语种与性别用 speechSynthesis.getVoices() 列出,按 lang 过滤
lang(语种)声明文本语种,影响选音与发音中英文混排时按句切 lang 更自然

常见问题

浏览器自带文本转语音吗,需要后端吗?

现代浏览器内置 Web Speech API 的 SpeechSynthesis,纯前端就能朗读,不需要调任何后端接口,也不产生额外费用。缺点是可用的发音人依赖用户操作系统自带的声音,不同设备音色不一致。

SSML 在网页里都能用吗?

标准不一致。Chrome 桌面端较完整支持 <break>、<prosody> 等常用标签,但移动端和 Safari 支持零散,部分标签会被忽略。生产环境建议先用 feature 检测,不支持 SSML 时退化成纯文本朗读。

为什么有时候 getVoices() 返回空?

声音列表是异步加载的,页面刚打开时可能还没就绪。正确做法是监听 voiceschanged 事件再取,或在拿到非空列表后再初始化 UI,否则会选不到发音人。

长文本朗读会中断怎么办?

部分浏览器对单次 utterance 有长度或时长上限,超长文本会被截断。稳妥做法是按段落切分,监听 onend 后连续朗读下一段,并在边界处适当插入 <break> 让停顿自然。