使用 Web Speech API

Web Speech API 提供了两个不同领域的功能——语音识别和语音合成(也被称为文本转为语音,或 tts)——这为无障碍和控制机制开启了新的有趣的可能。这篇文章提供了这两个方向的简单介绍,并且都带有例子。

语音识别

语音识别涉及:通过设备的麦克风接收语音,然后语音识别服务会根据语法列表(基本上就是你希望在具体的应用中能够识别出来的词汇)来检查这段语音。当成功识别单词或短语后,结果(或结果列表)会以文本字符串的形式返回,并且可以根据结果进行下一步的操作。

Web Speech API 有一个主要的控制器接口——SpeechRecognition,外加一些与表示语法、结果等等亲密相关的接口。通常,设备都有可使用的默认语音识别系统,大部分现代操作系统都有用于发出语音命令的语音识别系统,比如 Mac OS X 上的 Dictation、iOS 上的 Siri、Win10 上的 Cortana、Android Speech 等等。

备注: 在一些浏览器上,比如 Chrome,在一个网页上使用 Web 语音识别涉及到一个基于服务器的识别引擎。你的音频会被发送到一个 web 服务以进行识别处理,所以它不能在离线状态下工作。

演示

为了展示 Web 语音识别的简单用法,我们编写了一个演示——Speech color changer。点击屏幕之后,说出 HTML 颜色关键字,接下来应用的背景颜色就会变成你说的颜色。

一款名为“Speech color changer”的应用的用户界面。它邀请用户点击屏幕并说出一种颜色,然后它将应用的背景变成该颜色。在本例中,它将背景变成了红色。

要运行这个演示,请在支持的移动端浏览器(比如 Chrome)中打开在线演示 URL

HTML 和 CSS

对于这个应用来说,HTML 和 CSS 部分是无足轻重的。仅仅只有一个标题、一个介绍段落和一个用来输出诊断信息的 div。

html
<h1>Speech color changer</h1>
<p>Tap/click then say a color to change the background color of the app.</p>
<div>
  <p class="output"><em>...diagnostic messages</em></p>
</div>

CSS 也只是提供了简单的响应式样式,跨设备看上去也是 ok 的。

JavaScript

JavaScript 部分会介绍更多细节。

带前缀的属性

浏览器目前通过带有前缀的属性提供语音识别的支持。因此在代码开始部分我们添加了以下代码,以便同时支持带前缀的属性和未来可能支持的不带前缀的属性:

js
const SpeechRecognition =
  window.SpeechRecognition || window.webkitSpeechRecognition;
const SpeechGrammarList =
  window.SpeechGrammarList || window.webkitSpeechGrammarList;
const SpeechRecognitionEvent =
  window.SpeechRecognitionEvent || window.webkitSpeechRecognitionEvent;

语法

代码的下一部分定义了我们希望应用程序识别的语法。定义了以下变量以保存我们的语法:

js
const colors = [
  "aqua",
  "azure",
  "beige",
  "bisque",
  "black",
  "blue",
  "brown",
  "chocolate",
  "coral" /* … */,
];
const grammar = `#JSGF V1.0; grammar colors; public <color> = ${colors.join(
  " | ",
)};`;

语法格式使用的是 JSpeech Grammar FormatJSGF)——你可以在前面的链接中了解更多关于语法格式的规范。不过现在,让我们快速地浏览它:

  • 每一行用分号分隔,和 JavaScript 中一样
  • 第一行——#JSGF V1.0——说的是语法使用的格式和版本。这总是需要首先包括在内
  • 第二行表示我们想要识别的术语类型。public 声明这是一条公共规则,尖括号中的字符串定义需要识别术语的名字(color),等号后面的是这个术语可以被识别和接受的具体值。得注意每一个值如何被一个管道符号分割开的
  • 你可以按照上面的结构,在多行中,想定义多少就定义多少术语,也可以包括相当复杂的语法定义。对于我们这个简单的演示,就把语法定义的简单些。

将语法插入语音识别

接下来是使用 SpeechRecognition() 构造函数,定义一个语音识别实例,控制对于这个应用的识别。还需要使用 SpeechGrammarList() 构造函数,创建一个语音语法列表对象来包含我们的语法。

js
const recognition = new SpeechRecognition();
const speechRecognitionList = new SpeechGrammarList();

使用 SpeechGrammarList.addFromString() 把语法添加到列表 (list),这个方法接收两个参数,第一个是我们想要添加的包含语法内容的字符串,第二个是对添加的这条语法的权重 (权重值范围是 0~1),权重其实是相对于其他语法,这一条语法的重要程度。添加到列表的语法就是可用的,并且是一个SpeechGrammar 实例。

js
speechRecognitionList.addFromString(grammar, 1);

我们然后通过设置 SpeechRecognition.grammars 属性值,把我们的SpeechGrammarList 添加到 speech recognition 实例。在继续往前走之前,我们还需要设置 recognition 实例其他的一些属性:

  • SpeechRecognition.lang :设置识别的是什么语言。这个设定是良好的做好,因此墙裂推荐~
  • SpeechRecognition.interimResults :定义 speech recognition 系统要不要返回临时结果 (interim results),还是只返回最终结果。对于这个简单 demo,只返回最终结果就够了。
  • SpeechRecognition.maxAlternatives :定义每次结果返回的可能匹配值的数量。这有时有用,比如要的结果不明确,你想要用一个列表展示所有可能值,让用户自己从中选择一个正确的。但这里这个简单 demo 就不用了,因此我们设置为 1(1 也就是默认值)。
js
recognition.grammars = speechRecognitionList;
recognition.continuous = false;
recognition.lang = "en-US";
recognition.interimResults = false;
recognition.maxAlternatives = 1;

开始语音识别

在获取输出的<div> 和 html 元素引用之后 (这些我们可以用来待会输出语音识别诊断的结果,更新应用的背景颜色),我们添加了一个onclick 事件处理,作用是当屏幕被点击后,语音识别服务将开启——这通过调用 SpeechRecognition.start() 实现。forEach() 方法内部的工作是,为每个颜色关键字添加一个这个颜色的背景色,这样就直观知道了这个颜色关键字指向什么颜色。

js
const diagnostic = document.querySelector(".output");
const bg = document.querySelector("html");
const hints = document.querySelector(".hints");

let colorHTML = "";
colors.forEach((color, i) => {
  console.log(color, i);
  colorHTML += `<span style="background-color:${color};"> ${color} </span>`;
});
hints.innerHTML = `Tap or click then say a color to change the background color of the app. Try ${colorHTML}.`;

document.body.onclick = () => {
  recognition.start();
  console.log("Ready to receive a color command.");
};

接收、处理结果

一旦语音识别开始,有许多 event handlers 可以用于做结果返回的后续操作,除了识别的结果外还有些零碎的相关信息可供操作 (可查看 SpeechRecognition event handlers list )。最常见会使用的一个是 SpeechRecognition.onresult ,这在收到一个成功的结果时候触发。

js
recognition.onresult = (event) => {
  const color = event.results[0][0].transcript;
  diagnostic.textContent = `Result received: ${color}.`;
  bg.style.backgroundColor = color;
  console.log(`Confidence: ${event.results[0][0].confidence}`);
};

代码中第三行看上去有一点复杂,让我们一步一步解释以下。SpeechRecognitionEvent.results 属性返回的是一个SpeechRecognitionResultList 对象 (这个对象会包含SpeechRecognitionResult 对象们),它有一个 getter,所以它包含的这些对象可以像一个数组被访问到——所以[last] 返回的是排在最后位置 (最新) 的SpeechRecognitionResult对象。每个SpeechRecognitionResult 对象包含的 SpeechRecognitionAlternative 对象含有一个被识别的单词。这些SpeechRecognitionResult 对象也有一个 getter,所以[0] 返回的是其中包含的第一个SpeechRecognitionAlternative 对象。最后返回的transcript属性就是被识别单词的字符串,把背景颜色设置为这个颜色,并在 UI 中报告出这个结果信息。

也使用了 SpeechRecognition.onspeechend 这个 handle 停止语音识别服务 (具体工作在SpeechRecognition.stop()) ,一旦一个单词被识别就不能再说咯 (必须再点击屏幕再次开启语音识别)

js
recognition.onspeechend = () => {
  recognition.stop();
};

处理错误和未能识别的语音

最后两个处理器处理的两种情况,一种是你说的内容不在定义的语法中所以识别不了,另一种是出现了 error。

SpeechRecognition.onnomatch 支持的就是第一种,但是得注意它似乎在 Firefox 或者 Chrome 中触发会有问题;它只是返回任何被识别的内容:

js
recognition.onnomatch = (event) => {
  diagnostic.textContent = "I didn't recognize that color.";
};

SpeechRecognition.onerror 处理的是第二种情况,识别成功了但是有 error 出现——SpeechRecognitionError.error 属性包含的信息就是返回的确切的 error 是什么。

js
recognition.onerror = (event) => {
  diagnostic.textContent = `Error occurred in recognition: ${event.error}`;
};

语音合成

语音合成(也被称作文本转语音或 TTS)包括接收 app 中需要语音合成的文本,再在设备扬声器或音频输出连接中播放出来这两个过程。

Web Speech API 对此有一个主要控制接口——SpeechSynthesis ,外加一些处理如何表示要被合成的文本 (也被称为 utterances),用什么声音来播出 utterances 等工作的相关接口。同样的,许多操作系统都有自己的某种语音合成系统,在这个任务中我们调用可用的 API 来使用语音合成系统。

演示

为了展示 Web 语音合成的简单使用,我们提供了一个例子——Speak easy synthesis。其包含一套用于输入要合成的文本,以及设置朗读文本时使用的音调、语速和声音的表单控件。在输入文本之后,按下 Enter/Return 键使它播放。

UI of an app called speak easy synthesis. It has an input field in which to input text to be synthesised, slider controls to change the rate and pitch of the speech, and a drop down menu to choose between different voices.

想跑这个例子,你可以 git clone Github 仓库中的部分 (或者直接下载),在桌面版支持的浏览器打开 index.html 文件,或者在移动端浏览器直接导向 live demo URL ,像 Chrome 和 Firefox OS。