Web 语音技术全景
系统梳理 Web 语音能力的技术体系,覆盖语音识别与语音合成在 Web 语音系统中的整体链路、浏览器语音能力的实现机制,以及主流开源工具与语音服务提供商。

在过去很长一段时间里,人机交互几乎都围绕“键盘 + 鼠标”展开,信息的输入与输出被牢牢限制在视觉与文本层面。但语音的出现,正在慢慢把这种交互方式拉回更自然的状态——像人和人说话一样去与机器沟通。
语音技术的发展,本质上是让机器具备“听懂”和“说话”的能力,这对应着两条核心能力链路:语音识别(ASR, Automatic Speech Recognition)与语音合成(TTS, Text To Speech)。前者负责把声音转成文字,让机器理解人类语言;后者则把文字转回声音,让机器具备表达能力。这两项能力共同构成了现代语音交互系统的基础。
早期的语音技术受限于算力与算法,识别准确率低、延迟高、对环境噪声极其敏感,只能在特定场景中勉强使用,比如:简单指令控制或实验性输入法。但随着深度学习的发展,尤其是端到端语音模型的成熟,语音识别与合成的能力发生了质变。今天的语音系统已经可以在嘈杂环境中保持较高识别率,并支持多语言、口音适配甚至情感表达。
在应用层面,语音技术早已不再局限于“语音输入法”这种单一形态,而是渗透到更广泛的产品形态中:智能助手、车载系统、智能客服、会议转写、无障碍辅助设备,以及近几年快速发展的 AI 对话系统,都离不开语音能力的支撑。语音正在成为连接人类与数字世界的重要桥梁之一。
在 Web 领域,这一趋势也同样明显。浏览器逐渐开始提供原生语音能力接口,同时结合云端与本地模型,使得网页应用也能够具备实时语音识别与合成能力。这种能力的加入,让 Web 不再只是“看和点”的媒介,而逐步演变为可以“听与说”的交互平台。
因此,理解 Web 语音技术,不只是了解一套 API 或工具,而是理解一整套从信号采集、模型处理到交互设计的完整链路。本篇文章将从语音技术的基础原理出发,逐步梳理其在浏览器中的实现方式、主流语音服务提供商与开源方案以及实际工程落地方案,帮助你建立一个完整的“Web 语音能力图谱”。
基础原理
从整体来看,Web 语音能力并不是一个单一的技术点,而是一套由多层结构组成的技术体系。它的核心目标是实现人与机器之间更自然的交互方式,而这一过程通常需要经过“声音信号 → 音频采集 → 语音识别 → 语义理解与响应生成 → 语音合成 → 声音输出”的完整链路。
关键层次
从技术结构上,可以将 Web 语音系统拆解为四个关键层次:
音频采集层是整个语音链路的起点,它负责把现实世界中的连续声音信号,转换为计算机可以处理的数字音频数据。
从本质上看,人类的语音是一种连续变化的模拟信号,它由空气分子的振动形成声波传播,这种变化在时间上是连续的,在幅度上也是连续的。但计算机的核心问题在于:它只能处理离散的数字信息,而无法直接表示“无限连续变化”的物理量。无论是存储、计算还是传输,本质上都建立在有限精度的二进制结构之上,因此必须通过“采样”的方式,将连续信号转换为离散数据。
在物理链路上,这一过程首先发生在麦克风内部。当声波进入麦克风时,会推动振膜产生微小振动,这种机械运动会进一步转换为连续变化的模拟电信号(电压变化)。但此时信号仍然是模拟的,计算机依然无法直接处理。
真正的关键一步发生在模拟数字转换器(ADC,Analog-to-Digital Converter)中,它会对连续信号进行三个核心操作:
- 采样(Sampling):在固定时间间隔内对信号进行“截取”,例如:每秒 16000 次(16kHz),把连续时间变成离散时间点。
- 量化(Quantization):将每个时间点上的模拟幅度映射为有限的数字值,例如 16-bit PCM 中的 -32768 到 32767。
- 编码(Encoding):将这些数值按顺序组织为可传输的数据结构,形成连续的音频帧流。
经过这一过程,原本连续的声波信号就被转换为计算机可处理的数字音频流(Digital Audio Stream),在 Web 场景中最常见的底层形式是 PCM 原始数据,或经过压缩后的 Opus 等流式音频格式。
在浏览器环境中,这一过程并不是开发者直接控制硬件完成的,而是由系统音频栈负责完成整个“硬件到软件”的转换链路:声波 → 麦克风振膜 → 模拟电信号 → ADC 转换 → 操作系统音频驱动 → 浏览器音频接口。
当用户授权麦克风权限后,浏览器通过 navigator.mediaDevices.getUserMedia({ audio: true }) 向操作系统申请音频输入能力,操作系统会打开麦克风设备,并将采集到的音频信号经过系统音频栈处理后,以数字音频流的形式交由浏览器。浏览器随后将该音频流抽象为 MediaStream 对象,其中音频已经在底层以固定采样率被分帧处理(frame-based processing),但这一过程对开发者是透明的。开发者通过 Web Audio API 或 MediaStreamTrack 接口对其进行进一步处理(如分析、编码或传输)。
因此,这一层的核心作用是:将现实世界的连续声波,稳定、可靠地转换为浏览器可处理的数字音频流,为后续语音识别与语义理解提供输入基础。
语音识别层是整个语音系统中承接音频输入与语义理解的关键环节,它的核心任务是将音频采集层输出的数字音频流,转换为结构化文本。
从系统链路来看,这一层的输入并不是原始声波,而是经过音频采集层处理后的数字音频数据流(例如 PCM 或 Opus 编码的音频帧)。这些数据本质上是对声音信号在时间维度上的数字化采样结果,以连续的时间片(audio frame)形式不断生成与传输。
ASR 的核心并不是“理解语言”,而是完成一个更基础的映射过程:将音频流所携带的声学信息中隐含的语言结构,通过概率建模还原为最可能的文本序列。
在这一过程中,系统通常会经历几个关键步骤:
- 声学特征提取:音频帧流首先会被转换为更适合模型处理的特征表示,例如:梅尔频谱(Mel Spectrogram)或频谱图(Spectrogram)。这一过程的本质,是将随时间变化的音频信号转换为“时间 × 频率”的二维结构,使模型能够更稳定地捕捉语音中的变化模式。
- 声学建模推理:在获得特征表示之后,模型会对每一段音频进行概率判断,推测其更可能对应哪些语言单位,例如:音素(phoneme)或子词(subword)。这一阶段并不是在“翻译整句话”,而是在不断逼近“这段声音像什么”。
- 语言约束与解码:由于语音信号本身存在噪声、口音差异以及连读现象,仅依赖声学信息往往不够稳定,因此系统通常会引入语言模型或解码策略,对初步结果进行修正,使输出更符合自然语言的表达习惯。
- 文本输出生成:最终,系统会输出结构化文本结果,从而完成从“连续音频信号”到“离散语言表示”的转换。
在现代 ASR 系统中,这一过程正在逐渐被端到端模型所替代,即直接从音频序列映射到文本序列,中间不再显式拆分声学模型与语言模型,从而在简化结构的同时提升整体鲁棒性。
在 Web 场景中,这一能力通常有两种实现路径:一种是依赖云端服务进行实时推理,另一种是将轻量化模型部署在端侧,通过 WebAssembly 或浏览器运行时完成本地识别。两者的差异主要体现在延迟、成本以及隐私控制等方面。
这一层是语音系统从“识别出文字”走向“理解用户意图”的关键阶段。
如果说语音识别(ASR)解决的是“用户说了什么”,那么这一层要解决的,是更接近系统行为的问题:用户想要系统做什么,以及如何将这一需求映射到具体的业务能力上。
在语音链路中,这一阶段已经脱离了纯粹的信号处理,开始进入“语义建模与系统决策”的范畴。输入不再是音频或声学特征,而是经过 ASR 转换后的结构化文本,但这一层的输出,也不再只是另一段自然语言文本,而是可以直接驱动系统行为的结构化语义结果。
因此,这一层的核心任务并不是“理解句子本身”,而是将自然语言转换为一种系统可执行的中间表达形式,使后续流程能够直接对接具体的业务模块或能力接口,而无需再对文本进行二次解析。
举个简单的例子:
-
用户说:“呃…嗯…帮我那个…查一下明天上海的天气(混合背景噪音)”
-
ASR 输出:“帮我查一下明天上海的天气”
-
NLP / Logic 层输出:
{ "intent": "get_weather", "slots": { "date": "tomorrow", "location": "Shanghai" }, "action": "call_weather_api" }
可以看到,这一层的结果已经不再是语言本身,而是一个可以直接进入系统执行链路的结构化对象。在这一过程中,系统不仅完成语义解析,还会根据识别出的意图,将请求映射到具体的能力模块,从而进入后续处理流程。在具体实现中,这一层通常包含几个核心任务:
-
意图识别:用于判断用户的整体目标,例如:是查询信息、执行操作还是进行对话。例如:“帮我订一张明天去北京的机票”,其意图可能被识别为
book_flight。 -
实体抽取:用于从文本中提取关键业务信息,例如:时间、地点、对象或数值。例如:在“订一张明天去北京的机票”中,会抽取出
date = tomorrow、destination = Beijing。 -
对话状态管理:用于维护多轮交互中的上下文信息,使系统能够记住前一轮内容。例如:用户先说“帮我订一张机票”,再说“明天的”,系统仍然能够补全完整语义。
-
语义到动作映射:这一部分会将解析后的语义结构进一步映射到具体的系统能力或服务接口,例如:API 调用或函数调用,从而决定请求最终由哪个模块执行。例如:
{ "function": "createOrder", "args": { "type": "ticket", "date": "2026-02-21", "destination": "Beijing" } } -
结构化语义输出:最终,这一层输出的不再是自然语言文本,而是结构化结果(Intent / Slots / Action),供后续业务系统或语音合成模块使用。
在现代 Web 应用中,这一层的边界正在发生变化。随着大模型能力的引入,传统的“意图识别 + 规则系统 + 路由逻辑”正在逐渐被统一的语义理解模型所替代,使系统可以直接从文本生成结构化语义结果,并同时完成能力选择与执行路径规划,从而减少中间解析步骤,让语音交互逐步从“文本驱动系统”演进为“语义驱动系统”。
语音合成层负责将系统生成的文本转化为可播放的语音信号。
从系统链路来看,这一层的输入通常是语义处理层输出的结构化文本或自然语言结果,而输出则是连续的语音波形信号,也就是最终可以被播放设备直接使用的音频数据。
从本质上看,这一过程并不是简单的“文字转语音”,而是一个从离散符号到连续信号的生成过程:系统需要将文本中的语言信息逐步转化为具有时间结构的语音波形,使其不仅能够“被读出来”,还能够表达语气、节奏与情感变化。
举个更贴近实际的例子:
- 输入文本:“明天上海的天气是晴天”
- 系统输出语音:“明天上海的天气是晴天(语气自然上扬)”
在这个过程中,语音并不是直接逐字播放,而是会先被转换为可发音的语言序列,并在不同语境下自动确定更合理的读法与表达方式。
在语音合成的整体流程中,系统通常会经历以下几个关键阶段:
- 文本规范化与语言解析:系统首先会对输入文本进行处理,将数字、符号和特殊表达转换为标准语言形式,并进一步拆解为发音单元(phoneme)。例如:“2026年”这种表达,由于数字在自然语言中存在多种读法(如“二零二六年”或“两千零二十六年”),系统必须结合上下文先进行规范化处理,使其变成唯一确定的发音路径,否则后续语音生成阶段将无法稳定建模。这一步的本质是消除文本中的歧义,使后续语音生成路径唯一确定。
- 语音特征建模:在获得语言结构之后,模型会进一步生成语音的“表达方式”,包括:语速、停顿、重音以及语调变化等信息。这一阶段并不是基于规则逐条控制,而是模型根据句子结构与语义重点自动预测得到的。例如:“帮我查一下明天上海的天气”中,“明天”和“上海的天气”通常会被赋予更高的表达权重,而“查一下”则会自然降低语速,从而形成更接近人类表达习惯的语音节奏。该阶段通常生成中间声学表示,例如 Mel 频谱,用于描述语音在时间与频率维度上的变化结构。
- 声波生成(声码器阶段):最后一步是将声学特征转换为连续的语音波形,使其可以被音频设备直接播放。该过程通常由声码器(vocoder)完成,其本质是将前面生成的抽象语音特征还原为真实可听的声音信号,使数字化表达最终转化为物理声波输出。
在现代 TTS 系统中,这一流程已经从传统的分阶段拼接式语音合成,逐渐演进为基于神经网络的端到端生成模型,使系统能够直接学习“文本结构 → 语音表达”的整体映射关系,从而提升语音自然度与一致性。
在 Web 场景中,这一能力通常通过云端 API 或端侧轻量模型实现。云端方案通常在语音质量与表达能力上更强,而端侧方案则更强调低延迟与隐私保护,两者在实际系统中根据具体应用场景进行权衡选择。
整体链路
将上述四层串联起来,可以得到一个完整的 Web 语音交互流程:
用户语音输入 → 音频采集 → 语音识别(ASR)→ 语义理解(NLP / LLM)→ 生成响应文本 → 语音合成(TTS)→ 音频播放输出
从整体来看,这条链路本质上是在完成一件事:将连续的物理声波信号,逐步转换为可计算的语义表示,再反向生成可感知的语音信号。整个过程形成了一个典型的“语音 ↔ 语义”双向转换闭环。
但需要注意的是,这条链路并不是一个固定不变的标准结构,而是一个可以根据具体业务需求进行裁剪的能力组合。在不同的应用场景中,实际使用的链路往往并不完整。例如:
- 如果系统只需要做语音转文字(如会议转写、字幕生成),那么链路通常只需要“音频采集 → ASR”,语义理解(NLP / LLM)以及语音合成(TTS)都可以被完全省略。
- 如果系统只需要做语音播报(如导航提示音、系统通知),那么链路可能是“文本输入 → TTS → 音频播放”,甚至不需要 ASR。
语义理解层(NLP / LLM)并不是语音系统的必选组件,它更多取决于系统是否需要“理解用户意图”以及“执行复杂业务逻辑”。在没有明确业务需求的情况下,语音系统完全可以只存在于“输入识别”或“输出合成”的单向能力中。
因此,从系统设计角度来看,这条链路更合理的理解方式并不是“必须完整执行的一条流水线”,而是一个由多个能力模块组成的组合结构:根据业务目标选择性启用不同的语音能力,从而构建出不同复杂度的语音系统形态。
这一链路的关键特点主要体现在以下几个方面:
- 实时性要求:语音交互通常依赖低延迟反馈,系统需要在毫秒到秒级时间内完成识别、理解与合成,否则会明显影响交互体验。
- 流式处理能力:输入与输出通常是连续数据流,而不是一次性任务。例如:ASR 可以边输入边识别,TTS 也可以边生成边播放,从而降低整体等待时间。
- 跨端协同架构:浏览器负责音频采集与播放,云端或本地模型负责语音识别与生成,不同计算单元之间通过流式数据或 API 协作完成整体链路。
- 模型能力依赖强:ASR 的准确率与 TTS 的自然度,直接决定语音系统的基础体验,而 NLP / LLM 则只在需要“理解与决策”的场景中才发挥作用。
从系统视角来看,这条链路并不是线性流水线,而更接近一个“可裁剪的实时数据流系统”:每一层既是输入的处理者,也是下一层的供给者,但是否需要完整链路,取决于具体的产品目标与业务复杂度。
能力体系
如果说基础原理回答的是 Web 语音“如何运行”,那么能力体系回答的就是另一件更贴近实际的问题:它到底“能做什么”。
在真实的语音交互系统中,这些能力通常不会以单一形态存在,而是由多个相对独立的模块组合而成。每一个模块解决一个具体问题,而它们共同构成完整的语音交互体验。
语音识别
语音识别是整个语音体系中最基础的一环,它负责将语音信号转换为文本。
从交互角度来看,它解决的是一个最关键的问题:用户说了什么。
在 Web 场景中,语音识别通常承担几个典型角色,比如:语音输入、实时字幕生成,以及 AI 对话系统的语音入口。无论是哪一种应用,本质上都是先把“声音”转化为“可计算的文本”。
如果没有这一层,后续的语义理解与交互就无从谈起,因此它几乎是所有语音系统的起点。
语音合成
与语音识别相对应的是语音合成,它解决的是输出问题。
如果说 ASR 是“听懂”,那么 TTS 就是“表达”。
它的作用是将文本转换为自然语音,使系统具备可听见的反馈能力。在实际应用中,这一能力广泛存在于智能助手回复、页面朗读、导航播报等场景中。
随着神经网络模型的发展,语音合成已经不再只是机械式的朗读,而是可以在一定程度上模拟语气、停顿甚至情绪变化,从而让交互体验更接近真实对话。
语音唤醒
在完整的语音交互中,并不是所有时间都需要系统持续响应,因此就引入了语音唤醒机制。
它解决的是一个很现实的问题:什么时候开始进入语音交互状态。
比如,当用户说出“Hey Siri”或“OK Google”时,系统才被激活,进入后续识别流程。这种设计可以减少无效计算,同时避免误触发。
在 Web 环境中,这一能力通常受到浏览器权限与运行机制限制,因此实现方式往往比移动端更复杂,一般依赖云端服务或轻量端侧模型配合实现。
声纹识别
除了内容本身,语音还包含另一个重要维度:身份信息。
声纹识别就是用来解决“谁在说话”的问题。
它通过分析语音中的声学特征(例如音色、共振峰结构、发音习惯等),将不同说话人的语音进行区分,从而实现身份级别的识别能力。
从系统角度来看,这一能力处理的并不是“语音内容”,而是“语音来源”。也就是说,它关注的不是“说了什么”,而是“是谁在说”。
在具体应用中,它通常以两种形式存在:
- 一种是用于身份验证,例如:通过语音确认是否为注册用户(语音登录、语音支付确认等)
- 另一种是用于说话人区分,例如:在多人语音环境中标记不同说话人的语音片段
相比 ASR 和 TTS,它并不参与语音内容的生成与理解,而是更偏向“身份层能力”,用于为语音系统提供额外的身份维度信息。
浏览器中的语音能力实现机制
在 Web 语音体系中,浏览器扮演的是一个“运行时中枢”的角色:它既负责音频数据的采集与播放,也承担了部分语音能力的封装与调度,使得开发者可以在不直接接触底层音频系统的情况下构建语音交互应用。
从能力层面来看,现代浏览器主要提供三类与语音相关的核心能力:
音频输入能力
这是浏览器语音系统的入口能力,主要用于获取用户的语音数据。
浏览器通过调用底层系统的音频设备接口,将麦克风输入转换为可处理的音频流。在这一过程中,开发者通常使用 navigator.mediaDevices.getUserMedia 获取音频权限,并接收实时音频流数据。
浏览器在这一阶段主要负责:
- 麦克风设备访问与权限管理
- 音频采样流的生成
- 音频数据的实时传输(MediaStream)
- 基础的设备兼容性处理
这一能力本质上对应语音体系中的“音频采集层”,但在 Web 端被进一步封装为标准 API,使开发者无需直接处理底层采样与硬件细节。
在工程中,最常见的方式是通过 getUserMedia 获取麦克风输入:
const stream = await navigator.mediaDevices.getUserMedia({
audio: true
});这里返回的并不是音频数据本身,而是一个 MediaStream 对象。它本质上是浏览器对麦克风输入的抽象封装,代表一条持续生成音频数据的输入轨道,而不是某一段可以直接使用或传输的音频内容。
因此,这个 stream 不能直接发送给语音识别服务。原因在于网络传输需要的是“离散的数据帧”,而 MediaStream 是浏览器内部的“实时音频管道”,它只负责不断产生音频数据,但并不提供可序列化的输出格式。
在实际工程中,如果要接入语音识别服务,通常需要先把这条音频流“拆出来”,转换为可传输的音频帧数据。这一步一般通过 Web Audio API 或 AudioWorklet 完成:
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);AudioContext 在这里的作用,是把 MediaStream 接入浏览器的音频处理图(Audio Graph)中,使其进入一个可持续处理的管道。在这个管道里,浏览器会将麦克风输入拆分为连续的音频 buffer,并按固定时间片不断输出。
但需要注意的是,这里的 source 仍然不是最终数据,它只是音频处理图中的一个输入节点。真正可以被发送到后端的,是从这个节点下游不断“取出”的音频帧数据,例如:Float32Array 或 PCM chunk。
在实际实现中,最常见的做法是使用 AudioWorklet 持续捕获音频帧,然后通过 WebSocket 将数据实时发送到语音识别服务:
class PCM extends AudioWorkletProcessor {
// 每一帧音频都会自动进到这里
process(inputs) {
// inputs 是一个二维数组:
// inputs[0] = 第一个音频输入源
// inputs[0][0] = 单声道音频数据(Float32Array)
const input = inputs[0][0];
// 如果有音频数据
if (input) {
// 把这一小段音频数据发送到主线程
// input 是一帧音频(约10~30ms)
this.port.postMessage(input);
}
// return true = 让这个 processor 一直运行(不断流)
return true;
}
}
// 注册一个音频处理器,名字叫 "pcm"
// main.js 里会用这个名字来创建节点
registerProcessor("pcm", PCM);const socket = new WebSocket("wss://your-asr-service");
// 收到 ASR 返回结果(文本)
socket.onmessage = (e) => {
// 服务端返回 JSON,比如:{ text: "今天天气很好", type: "final" }
const data = JSON.parse(e.data);
console.log("ASR识别结果:", data.text);
};
// 1. 打开麦克风
const stream = await navigator.mediaDevices.getUserMedia({
audio: true
});
// 2. 创建音频处理环境(Audio Graph系统)
const audioContext = new AudioContext({
sampleRate: 16000 // ASR常用采样率
});
// 3. 把麦克风接入 AudioContext
const source = audioContext.createMediaStreamSource(stream);
// 4. 加载 AudioWorklet(你刚刚那个PCM处理器)
await audioContext.audioWorklet.addModule("processor.js");
// 5. 创建音频处理节点(连接 PCM processor)
const node = new AudioWorkletNode(audioContext, "pcm");
// 6. 收到音频帧(关键!这里才是“真正数据”)
node.port.onmessage = (e) => {
// e.data = Float32Array(10~30ms音频片段)
const pcmChunk = e.data;
// WebSocket 必须是打开状态才发送
if (socket.readyState === 1) {
// 发送的是“二进制音频帧”
socket.send(pcmChunk);
}
};
// 7. 连接音频链路(启动流动)
source.connect(node);
// 有些浏览器要求必须 connect 到 destination 才会启动
node.connect(audioContext.destination);在这个过程中,浏览器并不是一次性提供音频数据,而是持续不断地将麦克风输入拆分为音频帧,并通过 AudioWorklet 推送到应用层。开发者所做的事情,本质上就是从这个持续流动的音频管道中不断获取数据,并转发到语音识别服务。
从整体上看,这一链路可以理解为:浏览器负责构建“音频输入管道”,而开发者负责从管道中提取“可传输的音频帧”,并将其持续发送给外部 ASR 服务,从而实现实时语音识别能力。
音频处理与播放能力
除了输入能力,浏览器同样提供完整的音频输出能力,用于播放语音合成(TTS)结果或其他音频内容。
这一部分主要依赖 Web Audio API,它不仅可以用于简单的音频播放,还可以构建完整的音频处理与播放链路,例如:音频解码、音频节点控制以及实时音频处理等能力。在语音系统中,这一能力主要用于 TTS 输出阶段,将语音合成服务返回的音频数据转换为可播放的声音,并最终输出给用户。
需要注意的是,在实际工程中,TTS 服务返回的音频并不一定是一个“完整的 mp3 文件”。根据系统设计不同,常见的返回形式包括:
- 完整音频文件(如 MP3 / WAV):适用于非实时场景,例如:语音播报或一次性生成语音内容。
- 流式音频数据(PCM / Opus chunk):适用于实时 TTS 场景,音频会随着生成过程不断返回并播放。
- WebRTC 音频轨道:适用于低延迟语音交互系统,直接以音频流形式接入播放链路。
因此,在 Web 语音系统中,“播放音频”并不等同于“播放一个文件”,更常见的是对连续音频数据流进行解码与实时输出。
在具体实现中,如果是最基础的文件播放,可以直接使用 HTMLAudioElement:
const audio = new Audio("output.mp3");
audio.play();如果需要更底层的控制能力,例如:对音频进行解码或接入自定义音频处理链路,则通常使用 Web Audio API:
const audioContext = new AudioContext();
const response = await fetch("tts-output.mp3");
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioContext.destination);
source.start();在更复杂的实时语音系统中,音频数据通常不会以完整文件形式存在,而是以流式数据持续输入播放管道,浏览器则通过 AudioContext 构建的 Audio Graph 将其实时输出,从而实现低延迟的语音播放能力。
在这种模式下,TTS 服务通常会通过 WebSocket 或 HTTP chunked response 持续返回音频数据(例如 PCM 或 Opus 分片),浏览器则需要一边接收、一边解码、一边播放:
const audioContext = new AudioContext({ sampleRate: 24000 });
// 创建播放节点(用于连续写入音频数据)
const bufferQueue = [];
// 当前播放时间基准
let nextTime = audioContext.currentTime;
// WebSocket 接收 TTS 流式音频
const socket = new WebSocket("wss://your-tts-stream");
socket.binaryType = "arraybuffer";
socket.onmessage = async (event) => {
const arrayBuffer = event.data;
// 假设服务端返回的是 PCM Float32 或可解码音频块
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer.slice(0));
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
// 关键:保证音频连续播放(避免断裂)
source.connect(audioContext.destination);
// 排队播放
if (nextTime < audioContext.currentTime) {
nextTime = audioContext.currentTime;
}
source.start(nextTime);
nextTime += audioBuffer.duration;
};原生语音能力 API
除了底层音频能力,浏览器还提供了一套更高层的语音接口:Web Speech API。它主要包含两个核心模块:
用于将用户语音直接转换为文本,浏览器内部会自动完成:
- 音频采集
- 语音识别模型推理
- 文本结果输出
开发者只需监听结果事件,即可获取识别后的文本内容。
<button id="start">开始语音识别</button>
<button id="stop">停止</button>
<div id="output"></div>
<script>
function initASR() {
const SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRecognition) {
console.warn("浏览器不支持语音识别");
return;
}
const recognition = new SpeechRecognition();
recognition.lang = "zh-CN";
recognition.interimResults = true;
recognition.continuous = false;
const output = document.getElementById("output");
const startBtn = document.getElementById("start");
const stopBtn = document.getElementById("stop");
if (!output || !startBtn || !stopBtn) return;
recognition.onresult = (event) => {
let text = "";
for (let i = event.resultIndex; i < event.results.length; i++) {
text += event.results[i][0].transcript;
}
output.innerText = text;
};
startBtn.onclick = () => {
output.innerText = "正在识别...";
recognition.start();
};
stopBtn.onclick = () => {
recognition.stop();
};
}
/* 初始化 */
initASR();
</script>
用于将文本转换为语音播放,浏览器内部会自动完成:
- 文本解析
- 音色选择
- 语音生成
- 音频播放
开发者只需传入文本,即可触发语音输出。
<input id="text" value="你好,这是语音合成测试" />
<button id="speak">播放语音</button>
<script>
function initTTS() {
const input = document.getElementById("text");
const btn = document.getElementById("speak");
if (!input || !btn) return;
btn.onclick = () => {
const text = input.value;
if (!text) return;
speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = "zh-CN";
utterance.rate = 1;
utterance.pitch = 1;
speechSynthesis.speak(utterance);
};
}
initTTS();
</script>
需要注意的是,这一层 API 属于“高层封装能力”,其内部实现通常依赖浏览器厂商提供的云服务或系统级语音能力,因此在不同浏览器之间可能存在一致性差异。
主流语音服务与开源方案
在 Web 语音系统的实际落地中,语音能力通常不会由单一技术完成,而是由一整套外部生态共同支撑。不同团队在构建语音能力时,面对的第一个问题并不是“用什么 API”,而是更本质的选择:语音能力应该放在云端、开源模型,还是端侧执行?
这个选择背后,本质是在四个约束之间做权衡:识别准确率、系统延迟、成本结构,以及对数据的可控性。
从当前工业实践来看,语音方案大致可以分为三条路径:云端语音服务、开源语音框架,以及逐渐兴起的端侧语音能力。
云端语音服务
云端语音服务是目前最主流的实现方式,它的特点非常直接:效果稳定、接入简单,并且已经高度工程化。
这一类服务通常将语音识别(ASR)和语音合成(TTS)封装为标准 API,开发者只需要上传音频或文本,就可以获得对应结果。在体验上,它更像是一种“即插即用”的能力层。
在国际生态中,这类能力主要由云厂商提供,例如:Google Cloud、Microsoft Azure 等,它们的优势在于多语言支持、模型稳定性以及流式处理能力。
在中文语境下,这一层能力同样非常成熟,其中科大讯飞在语音识别与语音合成领域长期处于领先位置,尤其是在中文语音的自然度与稳定性方面表现突出;百度智能云提供了覆盖识别、合成与语音增强的一体化方案,更偏向平台化能力;阿里云则更强调实时语音交互与业务系统的结合;腾讯云在实时转写与在线语音场景中也有较强覆盖。
从工程角度看,云端方案的优势非常清晰:它几乎不需要模型层面的维护,就可以获得稳定的语音能力,并且在复杂噪声环境下依然能保持较高的识别效果。
但它的问题同样明确:语音必须上传到云端处理,这意味着网络延迟不可避免,同时成本会随着调用量线性增长。在一些对隐私敏感或极致低延迟的场景中,这会成为明显限制。
开源语音方案
与云端方案相对的是开源语音体系,它更强调“可控性”,而不是“即用性”。
这一类方案通常以模型或框架的形式存在,允许开发者在本地或私有服务器上部署语音能力,从而摆脱对第三方服务的依赖。
在语音识别领域,Whisper 体系以及 wav2vec 2.0 代表了当前较高质量的开源路线,而 Vosk 和 Kaldi 则更偏向轻量或传统工程方案。在语音合成方面,Coqui TTS、FastSpeech、Tacotron 等模型构成了主流开源生态。
相比云端服务,这类方案的优势在于完全可控:数据可以完全在本地处理,不依赖外部服务,同时可以根据特定业务场景进行模型微调,例如:客服语音、行业术语识别等。
但代价也非常现实。开源方案通常意味着更高的工程复杂度,包括:模型部署、推理优化以及算力管理。在没有 GPU 或优化推理框架的情况下,实时性往往难以保证。
因此在实际项目中,开源方案更多出现在“对数据敏感”或“对成本极度敏感”的系统中,而不是轻量 Web 应用。
端侧语音能力
如果说云端强调稳定性、开源强调可控性,那么端侧语音能力解决的是另一个问题:如何在浏览器本地完成语音处理。
随着 WebAssembly 与轻量模型的发展,语音识别与语音合成开始逐步向端侧迁移。例如:Whisper 的轻量化版本 Whisper.cpp,以及基于 ONNX Runtime Web 的浏览器推理能力,都已经可以在一定条件下实现本地语音识别。
这一方向的优势非常明显:由于不需要上传音频到云端,它可以显著降低延迟,同时增强隐私保护能力,并支持离线使用。
但目前阶段的限制也同样明显,包括:模型体积较大、对设备性能依赖较强,以及整体工程生态仍不成熟。因此它更多是作为云端能力的补充,而不是完全替代。