Chromium 语音合成接口 SpeechSynthesis 实现分析
背景
语音合成(Text To Speech,TTS)可以实现网页文章的阅读。通过它,我们可以不需要看文字,就可以听新闻、听小说。
目前国内的主流的语音合成解决方案无非是以下几种:
- 科大讯飞
- 云服务(阿里云、腾讯云……)
上述的方案,音质不错,语音包选择多。但是都有一定的免费配额或者语言的限制(如科大讯飞的试用语音包),没有完全免费的方案。
开源免费的方案的效果比较差,而且需要利用自己的服务器资源,需要考虑额外的成本。
那有没有什么免费可以使用的方案呢?还是有的!
这里就要搬出来 Web API 里面的 SpeechSynthesis。这个接口是浏览器原生支持的语音合成。
基本上现在主流的现代浏览器都支持这个接口。具体的浏览器支持情况可以查阅上面链接里的文档。
如何使用?
下面就是一个简单例子:
1 | const ss = new window.SpeechSynthesisUtterance(); |
SpeechSynthesisUtterance
是配置语音合成内容和参数的类,有以下参数可以配置:
lang
:文本所属语言,默认使用<html>
里面的lang
pitch
:音调(0
-2.0
),默认是1
rate
:播放速率(0.1
-10.0
),默认是1
text
:需要说出来的文本voice
:语音包名称volume
:音量大小(0
-1.0
),默认是1
同时可以配置以下事件:
onerror
:出现错误onstart
:开始播放onboundary
:句子结束时触发onend
:播放完成onmark
:SSML 的 mark 标记触发onpause
:被暂停播放onresume
:被恢复播放
speechSynthesis
类实现语音合成的方法:
SpeechSynthesis.getVoices()
:获取全部可以播放的语音包(含语言)SpeechSynthesis.speak()
:按SpeechSynthesisUtterance
配置播放合成语音SpeechSynthesis.cancel()
:移除SpeechSynthesis
播放队列里面的全部待播放语音SpeechSynthesis.pause()
:暂停播放某个语音SpeechSynthesis.resume()
:恢复播放某个语音
具体的文档可以查看 MDN。
以 macOS 下的 Google Chrome 为例,通过 window.speechSynthesis.getVoices()
可以获取全部语音包,下面是一部分的输出:
1 | [{ |
上述数组是 SpeechSynthesisVoice
的实例数组,SpeechSynthesisVoice
的参数如下:
voiceURI
:语音合成服务的URLname
:语音包名称lang
:语言(符合BCP 47规范)localService
:是本地语言包(离线)还是远程语言包(网络)default
:是否为默认的语音包
通过观察,可以发现,Google Chrome 在 macOS 下可以使用的语音包有 macOS 自带的本地语音包和谷歌提供的在线语音包。如苹果的「Ting-Ting」和谷歌的「Google 普通话(中国大陆)」。
在构建 SpeechSynthesisUtterance
的时候,传入支持的语音包的 lang
和 voice
(对应 SpeechSynthesisVoice
的 name
)就可以选择语音包了。
通过这个接口,我们可以给使用 Google Chrome 的浏览器用户提供来自谷歌的语音合成功能。
探索 SpeechSynthesis
的原理
下面,我们通过 Chromium 的源代码探索 Google Chrome 的 SpeechSynthesis
是怎么实现的。
Chromium 里面的 SpeechSynthesis 类都是在 speech 模块 中实现的。其中,该模块包含 语音识别(speechRecognizer)和 语音合成 (SpeechSynthesis)两大功能的实现。
看语音合成的头文件就大概知道有哪些功能:
speech_synthesis_impl.h
:SpeechSynthesis
实现的后端,分派任务给TTS Conreoller
tts_controller_impl.h
: 语音合成的控制器,集成了获取语音包、设置引擎、播放、暂停等功能tts_platform_impl.h
: 管理平台相关的语音包,具体的实现在
语音包怎么来的?
我们可以先看 tts_controller_impl
里面的 GetVoices
实现:
1 | void TtsControllerImpl::GetVoices(BrowserContext* browser_context, |
简单来说,SpeechSynthesis
的语音包来自两大地方——操作系统自带和浏览器自带。所以我们才发现 window.speechSynthesis.getVoices()
拿到的语音包存在一部分是 Apple 提供的,一部分是 Google 提供的。
本地语音包的实现
下面分开看不同的语音包是怎么调用的,先看操作系统自带的。
观察源代码,我们发现 tts_platform_impl.h
的实现是跟操作系统有关,不同的操作系统下的实现不一样:
tts_android.cc
:安卓本地语音的实现,本质上是通过JNI调用android.speech.tts.TextToSpeech
,具体实现点我查看,该接口与安卓系统「语音引擎」设置有关,原生安卓系统是Pico TTS
,国内大多是科大讯飞引擎tts_fuchsia.cc
:Fuchsia 本地语音的实现tts_linux.cc
:Linux 本地语音的实现,本质上是使用libspeechd.so.2
库tts_mac.mm
:macOS 本地语音的实现,本质上是调用 NSSpeechSynthesizerDelegate,集成了 Apple 的语音包
上述代码通过不同的操作系统下的本地语音合成调用方法完成了本地语音包的调用,此处不着墨过多,有兴趣的小伙伴可以看源代码深入了解。
在线语音包的实现
浏览器自带的在线语音包是通过 TtsEngineDelegate
类封装的。这个东西在 Chromium 里面是由 tts_extension
实现的。因此,在线的语音包是以插件的形式存在于 Chrome 里面。
我们可以查阅 tts_engine_extension_api.cc 的 GetVoicesInternal
实现,发现在线语音包的获取方式。
1 | // Get the voices for an extension, checking the preferences first |
大概锁定到这个在线语音包的扩展在 pref
里面存储语音包的信息。
那么我们可以去 chrome://prefs-internals/ 找一下这个扩展。然后就会惊奇发现到这个隐藏的插件:
1 | { |
你会发现这里的 voices
和 window.speechSynthesis.getVoices()
里面的谷歌的语音包一模一样,所以就是这个插件负责了 Google Chrome 的在线语音包。
Google Network Speech 这个扩展的源代码可以在 Chromium 的项目里面找到,源代码在此自取。
本质上,这个在线语音包调用的是 Google Cloud 的语音合成模块。
接口是 https://www.google.com/speech-api/v2/synthesize
谷歌中国 www.google.cn
也是有这个接口可以使用的(明显是为了解决国内用户无法调用语音合成模块的问题)。
Query 参数分别是:
- key: Google API Key,请到 Google Developers Console 的凭据页面申请,并开通文字到语音的接口权限
- text: 需要合成语音的文本,对应
SpeechSynthesisUtterance.text
- lang: 语言,对应
SpeechSynthesisUtterance.lang
- name: 语音包名称,对应
SpeechSynthesisUtterance.name
(可选) - speed: 播放速率,对应
SpeechSynthesisUtterance.rate
(可选) - pitch: 音调,对应
SpeechSynthesisUtterance.pitch
(可选) - enc: 默认是 mpeg
- client: 默认是 chromium
每个用户每个月可以享受400万字符的免费配额(一个中文字也算一个字符),配额超过后每100万字符收4美元。
关于这个服务的更多介绍,可以查看 Google Cloud 上的介绍信息。
总结
- 对于谷歌浏览器的用户,我们可以使用
window.speechSynthesis
享受原生的免费语音合成 - 对于其他浏览器的场景,我们可以使用 Google Cloud 的语音合成模块实现语音合成,有一定的免费配额
- 针对苹果用户,苹果系统自带了各种离线语音包,可以免费使用
- 针对安卓用户,安卓系统设置的语音包决定了
window.speechSynthesis
的能力,如华为内置的科大讯飞离线语音包
除非注明,麦麦小家文章均为原创,转载请以链接形式标明本文地址。