Web Speech API:浏览器中的语音识别与语音合成指南

学习如何使用原生 Web Speech API 为 Web 应用添加语音识别 (Speech-to-Text) 与语音合成 (Text-to-Speech) 功能,包含代码实战与跨浏览器兼容性策略。

Web Speech API:浏览器中的语音识别与语音合成指南
6 分钟阅读
本页内容

摘要

Web 交互界面已经超越了传统的鼠标点击与键盘输入。由 W3C 制定的 Web Speech API 提供了两个独立的子系统:SpeechRecognition(语音转文本)用于捕获并转录用户的语音输入,而 SpeechSynthesis(文本转语音)则使用系统内置的语音库朗读文本。

本文详细介绍了这两个子系统的架构设计、代码实战以及跨浏览器兼容性解决方案(包括 Chrome 15 秒中断 Bug 和 iOS 限制)。


1. 两个独立的子系统

该 API 划分为两个完全解耦的模块:

子系统交互方向核心接口兼容性现状
SpeechRecognition用户 → 应用(麦克风输入)SpeechRecognition / webkitSpeechRecognition受限(主要是 Chromium 核心)
SpeechSynthesis应用 → 用户(音频输出)window.speechSynthesis + SpeechSynthesisUtterance全面支持 (Baseline)

注意:SpeechSynthesis 在现代浏览器中已获得全面支持。然而 SpeechRecognition 仍处于实验阶段。在使用前请务必进行特性检测。


2. 语音识别 (Speech-to-Text)

2.1 特性检测与实例化

由于厂商前缀的存在,构造函数的获取应采取防御性写法:

const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;

if (!SpeechRecognition) {
  console.error('当前浏览器不支持 SpeechRecognition。');
}

2.2 配置属性

实例化后,可通过以下四个核心属性对识别引擎进行配置:

属性类型描述
langstringBCP 47 语言标签(如 "zh-CN"、"en-US")。默认使用文档的 lang 属性。
continuousboolean若为 true,将在返回结果后继续监听;默认值为 false(返回单次结果后自动停止)。
interimResultsboolean若为 true,将在识别过程中实时返回临时中间结果。
maxAlternativesnumber每个结果片段返回的最大替代匹配数量。默认值为 1。
const recognition = new SpeechRecognition();
recognition.lang = 'zh-CN';
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 1;

2.3 识别结果的层级结构

返回的识别结果并非纯字符串,而是一个具有三层结构的嵌套对象:

  1. SpeechRecognitionResultList — 可通过 event.results 访问的结果列表集合
  2. SpeechRecognitionResult — 包含 isFinal 状态标识及一个或多个替代匹配
  3. SpeechRecognitionAlternative — 包含转录文本 transcript 与置信度分值 confidence(0 到 1)
recognition.onresult = (event) => {
  for (let i = event.resultIndex; i < event.results.length; i++) {
    const result = event.results[i];
    const transcript = result[0].transcript;
    const confidence = result[0].confidence;

    if (result.isFinal) {
      console.log(`最终结果: "${transcript}" (${(confidence * 100).toFixed(1)}%)`);
    } else {
      console.log(`临时结果: "${transcript}"`);
    }
  }
};

2.4 事件生命周期

识别引擎按照确定的顺序触发事件:

start → audiostart → soundstart → speechstart
  ↓
  result(在说话过程中持续触发)
  ↓
speechend → soundend → audioend → end

需要重点处理的关键事件:

recognition.onspeechstart = () => {
  console.log('检测到语音 — 正在实时转录...');
};

recognition.onerror = (event) => {
  switch (event.error) {
    case 'not-allowed':
      console.error('麦克风权限被用户或安全策略拒绝。');
      break;
    case 'network':
      console.error('无法连接到云端语音识别服务。');
      break;
    case 'no-speech':
      console.warn('未检测到语音 — 仅检测到静音或背景噪音。');
      break;
    case 'aborted':
      console.warn('语音识别已被中断。');
      break;
  }
};

recognition.onnomatch = () => {
  console.warn('检测到声音,但无法识别为有效文字。');
};

recognition.onend = () => {
  console.log('语音识别会话已结束。');
};

2.5 完整示例:实时语音听写

const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;

if (!SpeechRecognition) {
  document.getElementById('status').textContent = '当前浏览器不支持语音识别。';
} else {
  const recognition = new SpeechRecognition();
  recognition.lang = 'zh-CN';
  recognition.continuous = true;
  recognition.interimResults = true;

  const finalOutput = document.getElementById('final');
  const interimOutput = document.getElementById('interim');
  let finalTranscript = '';

  recognition.onresult = (event) => {
    let interim = '';

    for (let i = event.resultIndex; i < event.results.length; i++) {
      const transcript = event.results[i][0].transcript;

      if (event.results[i].isFinal) {
        finalTranscript += transcript;
      } else {
        interim += transcript;
      }
    }

    finalOutput.textContent = finalTranscript;
    interimOutput.textContent = interim;
  };

  recognition.onerror = (event) => {
    document.getElementById('status').textContent = `错误: ${event.error}`;
  };

  document.getElementById('start-btn').addEventListener('click', () => {
    finalTranscript = '';
    recognition.start();
    document.getElementById('status').textContent = '正在倾听...';
  });

  document.getElementById('stop-btn').addEventListener('click', () => {
    recognition.stop();
    document.getElementById('status').textContent = '已停止。';
  });
}

3. 语音合成 (Text-to-Speech)

3.1 单例控制器

与 SpeechRecognition 不同,语音合成引擎通过一个全局单例进行访问:

const synth = window.speechSynthesis;

该控制器管理着一个 FIFO 朗读队列,并暴露了三个只读状态标识:

属性描述
synth.pending若队列中有等待朗读的任务则返回 true
synth.speaking若当前正在播放语音则返回 true
synth.paused若播放已被暂停则返回 true

控制方法:

synth.speak(utterance);
synth.pause();
synth.resume();
synth.cancel();

3.2 创建朗读任务 (Utterance)

SpeechSynthesisUtterance 对象是朗读的基本单元,包含待朗读的文本及其声学属性:

属性取值范围描述
textString待朗读的文本内容
voiceSpeechSynthesisVoice指定使用的语音包
langString (BCP 47)覆盖默认语言设置
pitch0 – 2音调(1 表示正常音调)
rate0.1 – 10语速(1 表示正常语速)
volume0 – 1音量(1 表示最大音量)
const utterance = new SpeechSynthesisUtterance('你好,欢迎使用本应用。');
utterance.lang = 'zh-CN';
utterance.pitch = 1;
utterance.rate = 1;
utterance.volume = 0.8;

synth.speak(utterance);

3.3 异步加载语音包

浏览器加载语音库通常是异步的(特别是在 Chromium 中)。如果在页面刚加载时同步调用 getVoices(),往往会返回一个空数组。应该监听 voiceschanged 事件:

const synth = window.speechSynthesis;
let voices = [];

const loadVoices = () => {
  voices = synth.getVoices();
  console.log(`已成功加载 ${voices.length} 个语音包。`);
};

loadVoices();

if (synth.onvoiceschanged !== undefined) {
  synth.onvoiceschanged = loadVoices;
}

每个 SpeechSynthesisVoice 对象包含:

voices.forEach((voice) => {
  console.log(`${voice.name} (${voice.lang}) ${voice.default ? '— 默认' : ''}`);
});

3.4 完整示例:带声音选择的文本朗读器

const synth = window.speechSynthesis;
let voices = [];

const voiceSelect = document.getElementById('voice-select');
const textInput = document.getElementById('text-input');

const populateVoices = () => {
  voices = synth.getVoices();
  voiceSelect.innerHTML = '';

  voices.forEach((voice, index) => {
    const option = document.createElement('option');
    option.value = index;
    option.textContent = `${voice.name} (${voice.lang})`;
    voiceSelect.appendChild(option);
  });
};

populateVoices();
if (synth.onvoiceschanged !== undefined) {
  synth.onvoiceschanged = populateVoices;
}

document.getElementById('speak-btn').addEventListener('click', () => {
  synth.cancel();

  const utterance = new SpeechSynthesisUtterance(textInput.value);
  utterance.voice = voices[voiceSelect.value];
  utterance.pitch = parseFloat(document.getElementById('pitch').value);
  utterance.rate = parseFloat(document.getElementById('rate').value);

  utterance.onstart = () => {
    document.getElementById('status').textContent = '正在朗读...';
  };

  utterance.onend = () => {
    document.getElementById('status').textContent = '朗读完毕。';
  };

  utterance.onerror = (event) => {
    document.getElementById('status').textContent = `错误: ${event.error}`;
  };

  synth.speak(utterance);
});

document.getElementById('stop-btn').addEventListener('click', () => {
  synth.cancel();
  document.getElementById('status').textContent = '已取消。';
});

4. 云端处理 vs. 端侧离线推理

SpeechRecognition 可以通过两种截然不同的管道路由音频:

4.1 云端处理(默认模式)

默认情况下,Chrome 会将音频流序列化并发送至 Google 的云端服务器进行转录。这种方式准确率高(依托深度神经网络模型与大规模训练数据集),但也带来了以下权衡:

  • 延迟:网络往返会降低实时交互体验
  • 隐私:原始音频数据会离开设备
  • 离线:在无网络连接时完全无法使用

4.2 端侧本地推理(实验性)

现代浏览器正在引入基于端侧模型的本地推理能力。这消除了网络延迟,支持离线使用,并确保音频数据保留在本地:

const recognition = new SpeechRecognition();

const availability = await SpeechRecognition.available();

if (availability === 'available') {
  recognition.start();
} else if (availability === 'downloadable') {
  await SpeechRecognition.install();
  recognition.start();
} else {
  console.warn('不支持端侧识别,降级使用云端服务。');
  recognition.start();
}

注意:SpeechRecognition.available() 与 SpeechRecognition.install() 为实验性 API,可用于检查语言模型是否已缓存在本地并触发其下载。目前仅在较新版本的 Chromium 中受支持。


5. 安全与权限模型

为防止被动窃听,Web Speech API 实施了严格的安全限制:

  • 必须由用户手势触发:recognition.start() 必须在用户主动触发的事件(点击、轻触)中调用。在没有用户手势的情况下以编程方式调用会抛出错误。
  • 仅限 HTTPS:整个 API 仅在安全上下文 (Secure Context) 中可用,HTTP 页面无法访问麦克风。
  • 视觉指示器:麦克风处于激活状态时,浏览器会显示持续且无法隐藏的录音指示(标签页图标、状态栏提示)。
  • Permissions-Policy:服务器管理员可以通过 HTTP 响应头禁止跨源 iframe 访问麦克风:
Permissions-Policy: microphone=()

6. 浏览器兼容性 (2026)

浏览器SpeechSynthesis (TTS)SpeechRecognition (STT)备注
Chrome✅ 完全支持 (v33+)✅ 完全支持 (v33+)主要实现者,默认基于云端。
Edge✅ 完全支持 (v14+)⚠️ 不稳定暴露了接口,但相关事件可能永远不会触发。
Firefox✅ 完全支持 (v49+)❌ 未实现原则上反对将语音生物特征数据发送至第三方云服务。
Safari✅ 完全支持 (v7+)⚠️ 部分支持 (v14.1+)需要 webkit 前缀,依赖于 Siri 是否启用。
Opera✅ 完全支持 (v21+)❌ 未实现与 Edge/Firefox 的限制一致。
Brave⚠️ 视情况而定❌ 已拦截出于隐私考虑拦截云端 STT 连接。

6.1 防御性特性检测

在使用任一子系统之前,务必先验证浏览器支持情况:

const hasSpeechRecognition = 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;
const hasSpeechSynthesis = 'speechSynthesis' in window;

if (!hasSpeechRecognition) {
  console.warn('SpeechRecognition 不可用。可考虑使用云端降级方案(Google Cloud STT、Azure、AWS Transcribe)。');
}

if (!hasSpeechSynthesis) {
  console.warn('SpeechSynthesis 不可用。');
}

7. 已知问题与解决方案

7.1 Chrome 语音合成 15 秒中断问题

在 Chrome 中使用云端语音时,时长超过约 15 秒的朗读会被中止,且不会触发 end 或 error 事件,导致界面一直停留在“朗读中”状态。解决方法是将长文本切分为较短的朗读任务:

const speakLongText = (text) => {
  const synth = window.speechSynthesis;
  synth.cancel();

  const sentences = text.match(/[^.!?。!?]+[.!?。!?]+/g) || [text];

  sentences.forEach((sentence) => {
    const utterance = new SpeechSynthesisUtterance(sentence.trim());
    utterance.voice = voices[0];
    synth.speak(utterance);
  });
};

7.2 iOS Safari:语音合成需要“预热”手势

iOS 上的 Safari 会在用户触发手势之前阻止 TTS 音频播放。解决方法是在首次用户交互时静默地“预热”音频上下文:

document.addEventListener('click', () => {
  const primer = new SpeechSynthesisUtterance('');
  window.speechSynthesis.speak(primer);
}, { once: true });

完成预热后,后续对 synth.speak() 的调用无需额外的用户手势即可正常工作。

7.3 iOS PWA 中的 SpeechRecognition

当网页在 iOS 上被“添加到主屏幕”后,webkitSpeechRecognition 会被完全禁用。所有调用都会直接抛出 NotAllowed 错误,甚至不会显示权限请求提示。Web Speech API 内部没有任何规避方法,必须改用基于云端的 STT 服务。


8. 无障碍收益

Web Speech API 能够直接提升以下用户群体的可访问性:

  • 行动障碍用户:语音指令可替代键盘与鼠标操作
  • 视障用户:TTS 可朗读页面内容,作为屏幕阅读器的补充
  • 读写能力有限的用户:音频反馈降低了对阅读能力的依赖
  • 免手操作场景:烹饪、驾驶或医疗环境等不便于触控或键盘输入的场合

在无障碍界面中集成语音功能的最佳实践:

  • 收起虚拟键盘:在识别开始时调用 element.blur(),以在移动设备上释放视口空间
  • 显示临时结果:以可视方式展示临时结果,让用户在听写过程中获得实时反馈
  • 使用 TTS 的 boundary 事件:在朗读时高亮对应文本,提供视听同步的体验

9. 总结

Web Speech API 在语音与 Web 平台之间建立了有效的桥梁。SpeechSynthesis 已可在所有浏览器中用于生产环境,而 SpeechRecognition 由于支持程度参差不齐,需要谨慎的特性检测与降级策略。

建议从 TTS 入手改善可访问性,它在所有浏览器中均可使用。对于 STT,应以使用云端识别的 Chromium 浏览器为目标,实现完善的错误处理,并评估云端降级服务(Google Cloud Speech-to-Text、Azure Cognitive Services、AWS Transcribe)以覆盖更多浏览器。


参考来源:W3C Web Speech API 规范、MDN Web 文档、Chromium Issue Tracker、浏览器兼容性数据 (caniuse.com)。示例对应截至 2026 年 3 月的 API 状态。

拖动可移动图示。按 + 和 − 缩放,方向键移动,0 重置视图。

一起合作

软件工程师、高级全栈开发工程师,常驻玻利维亚Santa Cruz,远程办公。欢迎新的工作机会。