安卓语音应用开发-全-
安卓语音应用开发(全)
原文:
zh.annas-archive.org/md5/ADB9095CFEE0752E2C9902904FAEC11A
译者:飞龙
前言
能够与计算机交谈的想法长期以来吸引着许多人。然而,直到最近,这看起来还像是科幻小说的内容。现在情况已经改变,拥有智能手机或平板电脑的人可以使用语音在设备上执行许多任务——你可以发送短信、更新日历、设置闹钟,以及提出你之前会输入到搜索框中的问题。在小型设备上,语音输入通常更为方便,尤其是在物理限制使得打字和点击更加困难的情况下。
本书为 Android 设备开发语音应用提供了实用指南,使用谷歌语音 API 进行文本到语音(TTS)和自动语音识别(ASR)以及其他开源软件。尽管有许多书籍涵盖了 Android 编程的通用内容,但没有一个资源全面处理为 Android 开发基于语音的应用程序。
开发语音用户界面与开发更传统界面的许多特性是相同的,但也存在一些特定的要求使得语音应用开发与众不同,开发者进入这一领域时,了解常见的陷阱和困难是很重要的。本书提供了一些入门材料,以覆盖那些可能不为主流计算背景的专业人士所熟悉的方面。然后详细展示了如何从简单的程序逐步构建起复杂的应用程序。通过在书中示例的基础上进行实践,尝试描述的技术,你将能够为你的 Android 应用带来语音的力量,使它们更智能、更直观,提升用户的移动体验。
本书涵盖的内容
第一章,Android 设备上的语音,讨论了如何在 Android 设备上使用语音,并概述了涉及的技术。
第二章,文本到语音合成,涵盖了文本到语音合成技术以及如何使用谷歌 TTS 引擎。
第三章,语音识别,概述了语音识别技术以及如何使用谷歌语音识别引擎。
第四章,简单语音交互,展示了如何构建用户与应用程序可以进行对话的简单交互,以获取一些信息或执行操作。
第五章,表单填充对话框,说明了如何创建与传统网页应用中表单填充类似的语音对话框。
第六章,对话语法,介绍了如何使用语法来解释用户的输入,这些输入超出了单个词和短语。
第七章,多语言和多模态对话,探讨了如何构建使用不同语言和模态的应用程序。
第八章,与虚拟个人助手的对话,展示了如何构建一个支持语音的个人助手。
第九章,进一步探索,展示了如何开发更高级的虚拟个人助手。
您需要这本书的内容
要运行代码示例并开发您自己的应用程序,您需要安装 Android SDK 和平台工具。一个包含必要的 Android SDK 组件以及内置 ADT(Android 开发者工具)版本的 Eclipse IDE 和教程的完整捆绑包可以在developer.android.com/sdk/
下载。
您还需要一个 Android 设备来构建和测试示例,因为 Android ASR(语音识别)在虚拟设备(模拟器)上无法工作。
本书的目标读者
本书面向所有对语音应用开发感兴趣的人,包括语音技术和移动计算专业的学生。我们假设您具有一般的编程背景,特别是在 Java 方面。我们还假设您对 Android 编程有一定的了解。
约定
在这本书中,您会发现多种文本样式,这些样式区分了不同类型的信息。以下是一些样式示例,及其含义的解释。
文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 处理程序会以下列形式显示:"以下代码行创建了一个实现onInitListener
接口的onInit
方法的TextToSpeech
对象。"
代码块设置如下:
TextToSpeech tts = new TextToSpeech(this, new OnInitListener(){
public void onInit(int status){
if (status == TextToSpeech.SUCCESS)
speak("Hello world", TextToSpeech.QUEUE_ADD, null);
}
}
当我们需要引起您对代码块中特定部分的注意时,相关的行或项目会以粗体显示:
Interpret field i:
Play prompt of field i
Listen for ASR result
Process ASR result:
If the recognition was successful, then save recognized
keyword as value for the field i and move to the next field
If there was a no match or no input, then interpret field i
If there is any other error, then stop interpreting
Move to the next field:
If the next field has already a value assigned, then move to the next one
If the last field in the form is reached,thenendOfDialogue=true
新术语和重要词汇以粗体显示。您在屏幕上看到的词,例如菜单或对话框中的,会在文本中以这样的形式出现:“请说出专辑名称的一个词”。
注意
警告或重要注意事项会以这样的框显示。
提示
提示和技巧会像这样出现。
读者反馈
我们始终欢迎读者的反馈。告诉我们您对这本书的看法——您喜欢或可能不喜欢的地方。读者的反馈对我们来说非常重要,帮助我们开发出您真正能够充分利用的标题。
如果您想要给我们发送一般性反馈,只需发送电子邮件至<feedback@packtpub.com>
,并在邮件的主题中提及书名。
如果您在某个主题上有专业知识,并且有兴趣撰写或参与书籍编写,请查看我们在www.packtpub.com/authors上的作者指南。
客户支持
既然您已经自豪地拥有了一本 Packt 图书,我们有许多方法可以帮助您充分利用您的购买。
下载示例代码
您可以从您的账户中下载您购买的所有 Packt 图书的示例代码,访问地址为www.packtpub.com
。如果您在别处购买了这本书,可以访问www.packtpub.com/support
进行注册,我们会将文件通过电子邮件直接发送给您。
图书网页
本书有一个网页lsi.ugr.es/zoraida/androidspeechbook
,其中包含额外的资源,包括练习和项目的想法,进一步阅读的建议,以及有用的网页链接。
勘误
尽管我们已经竭尽全力确保内容的准确性,但错误仍然会发生。如果您在我们的书中发现了一个错误——可能是文本或代码中的错误——如果您能将其报告给我们,我们将不胜感激。这样做可以节省其他读者的时间,并帮助我们在后续版本中改进这本书。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata
,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情。一旦您的勘误被验证,您的提交将被接受,勘误将在我们网站的相应标题下的勘误部分上传或添加到现有的勘误列表中。任何现有的勘误都可以通过从www.packtpub.com/support
选择您的标题来查看。
盗版
互联网上版权材料的盗版是一个所有媒体都面临的持续问题。在 Packt,我们非常重视保护我们的版权和许可。如果您在互联网上以任何形式遇到我们作品非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
如果您发现了疑似盗版材料,请通过<copyright@packtpub.com>
联系我们,并提供相关链接。
我们感谢您保护我们的作者,以及我们为您带来有价值内容的能力。
问题
如果您在书的任何方面遇到问题,可以通过<questions@packtpub.com>
联系我们,我们会尽力解决。
第一章:安卓设备上的语音
你是否想过创建可以在你自己的安卓设备上运行的语音应用;可以与你对话并对你回应的应用?本章介绍了如何使用谷歌的开源 API 在安卓设备上进行文本语音合成和语音识别。在简要概述了语音用户界面(VUIs)的世界之后,本章概述了交互式语音应用(或虚拟个人助手)的组成部分。
到本章结束时,你应该对使用谷歌免费提供的资源创建基于语音的应用所需的条件有一个很好的了解。
在安卓设备上使用语音
安卓设备提供了内置的语音识别和文本语音转换功能。以下是一些基于语音的安卓应用实例:
语音识别
通过语音识别,安卓设备用户可以在需要文本输入的任何文本框中进行语音输入,例如电子邮件、短信和搜索。键盘控制包含一个带有麦克风符号的按钮和两个表示语言输入设置的字母,用户可以更改这些设置。按下麦克风按钮后,会弹出一个窗口提示用户现在说话。用户的口语输入会自动转录成文字。然后用户可以决定如何处理转录的文本。
由于一方面使用了大规模的基于云计算的语音识别资源,另一方面设备通常紧靠用户嘴边以获得更可靠的声学信号,小型设备上的语音听写准确率已显著提高。语音听写的主要挑战之一是输入不可预测——用户可以说字面上的任何内容——因此需要一个大的通用词汇表来覆盖所有可能的输入。其他挑战包括处理背景噪音、含糊的发音和不熟悉的口音。
文本语音转换
文本语音转换(TTS)用于将文本转换为语音。各种应用可以利用 TTS。例如,通过辅助功能选项可用的 TalkBack,使用 TTS 帮助盲人和视力受损的用户描述被触摸、选择和激活的项。TalkBack 还可以用于在 Google Play Books 应用中阅读书籍。TTS 功能在 Android Kindle 以及谷歌地图上提供逐步驾驶指令时也可用。还有大量第三方应用使用 TTS,也有可用的替代 TTS 引擎。
语音搜索
语音搜索在安卓设备上提供与传统的谷歌搜索相同的功能,不同之处在于用户不是输入查询,而是说出查询。用户可以通过谷歌搜索小部件中的麦克风使用语音搜索。在语音搜索中,识别的文本被传递给搜索引擎,并以与输入查询相同的方式执行。
语音搜索的一个新功能是,除了返回一系列链接外,还会返回一个对查询的语音回应。例如,对于问题“埃菲尔铁塔有多高?”,应用会回答:“埃菲尔铁塔高 324 米。”还可以使用代词提出后续问题,例如,“它是何时建造的?”。这种附加功能是通过将谷歌的知识图谱——一个被谷歌使用的知识库——与其会话搜索技术相结合,提供更具对话式的交互风格来实现的。
安卓语音动作
用户也可以通过谷歌搜索小部件中的麦克风访问安卓语音动作。语音动作允许用户使用语音命令控制他们的设备。语音动作需要匹配特定结构的输入,如下面来自谷歌网页的列表所示:www.google.co.uk/intl/en_uk/mobile/voice-actions/
。注意:带有*的项目是可选的。斜体字是需要说出的词语。
语音动作 | 结构 | 示例 |
---|---|---|
发送短信 | 给[收件人][信息]*发送短信 | 给艾莉森·米勒发送短信,内容为“我迟到了。我将在大约 9 点回家” |
给商家打电话 | 给[商家名称][位置]*打电话 | 给伦敦的 Soho 比萨店打电话 |
查看地图 | [地址/城市]的地图 | 伦敦的地图 |
谷歌搜索 | [你的查询] | 日落时分的巨石阵图片 |
获取方向 | 导航至[地址/城市/商家名称] | 导航至伦敦的英国博物馆或导航至 24 号磨坊街 |
给联系人打电话 | 给[联系人姓名][电话类型]*打电话 | 给艾莉森·米勒家里打电话 |
访问网站 | 前往[网站] | 前往维基百科 |
语音动作中的结构允许它们映射到设备上可用的动作。例如,关键词call表示电话通话,而关键短语go to表示要启动的网站。需要额外的处理来提取动作的参数,如联系人姓名和网站。
虚拟个人助手
最激动人心的基于语音的应用之一是虚拟个人助手(VPA),它像一个私人助手一样执行一系列任务,如查找有关当地餐馆的信息;执行涉及设备上应用程序的命令,例如使用语音设置闹钟或更新日历;以及进行一般性对话。至少有 20 个 VPA 可用于 Android 设备(请参阅本书的网页)尽管最著名的 VPA 是 Siri,自 2011 年起它就已经在 iPhone iOS 上可用。你可以在苹果公司的网站上找到与 Siri 的互动示例,这些示例与 Android VPAs 执行的操作类似 www.apple.com/uk/ios/siri/
。许多 VPA,包括 Siri,都被赋予了个性,并能够以幽默的方式回应恶搞问题和可疑的输入,从而增加了它们的娱乐价值。可以在www.sirifunny.com
查看示例以及 YouTube 上的众多视频剪辑。
值得一提的是,以下解释的许多技术具有与 VPAs(虚拟个人助手)某些相似的特点:
对话系统,在学术研究中有着悠久传统,其基于开发能够以自然语言(最初是文本,但最近更多是语音)与人类交流的系统的愿景。最初系统关注于获取信息,例如航班时间或股票报价。第二代系统允许用户进行某种形式的交易,如银行业务或预订旅行,而较新的系统则致力于故障排除,例如指导用户在设置设备时遇到的困难。实现对话系统使用了各种各样的技术,包括基于规则和统计的对话处理。
语音用户界面(VUIs),与对话系统相似,但更侧重于商业部署。这类系统通常专注于特定用途,如呼叫路由、电话簿查询和交易对话,例如旅行、酒店、航班、汽车租赁或银行余额查询。许多当前的 VUIs 是使用基于 XML 的 VoiceXML 标记语言设计的。VoiceXML 脚本随后在语音浏览器上解释,该浏览器还提供所需的语音和电话功能。
聊天机器人,传统上被用来模拟人类对话。最早的聊天机器人可以追溯到 20 世纪 60 年代,约瑟夫·魏森鲍姆(Joseph Weizenbaum)编写的著名 ELIZA 程序模拟了一位罗杰斯心理治疗师——常常以令人信服的方式。近年来,聊天机器人在教育、信息检索、商业、电子商务以及自动化的客服中心等领域得到了应用。聊天机器人使用复杂的模式匹配算法来匹配用户的输入并检索适当的回应。尽管大多数聊天机器人是基于文本的,但基于语音的聊天机器人正在逐渐出现(详见第八章,与虚拟个人助手的对话)。
具身对话代理(ECAs),是计算机生成的动画角色,结合面部表情、身体姿态、手势和语音,提供一种丰富的通信渠道。通过增强面对面互动的视觉维度,具身对话代理可以显得更加值得信赖和可信,同时也更有趣和娱乐性。具身对话代理已被用于诸如互动语言学习、虚拟培训环境、虚拟现实游戏节目以及互动小说和讲故事系统等应用中。它们越来越多地被用于电子商务和电子银行,以提供友好和有帮助的自动化帮助。例如,在宜家网站上的代理 Anna,请访问www.ikea.com/gb/en/
。
虚拟个人助手与这些技术不同之处在于,它们允许用户使用语音执行移动设备上许多可用的功能,例如发送短信、查看和更新日历、或设置闹钟。它们还提供了访问网络服务的功能,例如寻找餐厅、跟踪快递、预订航班,或使用诸如知识图谱、Wolfram Alpha 或维基百科等信息服务。由于它们可以访问设备上的上下文信息,如用户的位置、时间和日期、联系人以及日历等,VPA 可以提供与用户位置和偏好相关的餐厅推荐等信息。
设计和开发语音应用
语音应用设计与软件设计的一般特性有许多共同之处,但也存在一些独特的方面,特别是对于语音界面——例如,处理语音识别始终不可能达到 100%准确,因此与使用图形用户界面(GUI)输入相比,其可靠性较低。另一个问题是,由于语音是短暂的,特别是在没有视觉显示的设备上,与 GUI 应用相比,对用户的记忆提出了更高的要求。
语音应用的可使用性受到许多因素的影响。为了确定系统的需求,重要的是要进行广泛的使用案例分析,考虑以下问题:应用是否替代或补充现有应用;语音是否适合作为输入/输出的媒介;应用将提供的服务类型;将使用该应用的用户类型;以及应用的通用部署环境。
为什么选择谷歌语音?
以下是我们使用谷歌语音的原因:
-
Android 设备的普及:“截至 2012 年第三季度,Android 在全球智能手机市场的份额为 75%,总共有 75 亿台设备被激活,每天有 150 万台设备被激活。”(来自
www.idc.com/getdoc.jsp?containerId=prUS23771812 2013 年 9 月 7 日检索
)。 -
Android SDK 是开源的:Android SDK 是开源的,这使得它比其他一些操作系统更容易被开发者和爱好者用于创建应用。任何人都可以使用如 Eclipse 这样的免费开发环境开发自己的应用,并将其上传到 Android 设备上,供个人使用和享受。
-
谷歌语音 API:谷歌语音 API 在 Android 设备上免费使用。这意味着对于希望在不投资昂贵的商业可用替代品的情况下尝试语音的开发者来说,语音 API 非常有用。由于谷歌雇佣了许多顶尖的语音科学家,他们的语音 API 在性能上可以与商业提供的相媲美。
提示
你也可以尝试…
Nuance NDEV Mobile 支持多种语言的文本语音合成和语音识别,并提供 PhoneGap 插件,使开发人员能够在不同的平台上实现他们的应用(dragonmobile.nuancemobiledeveloper.com
)。
AT&T 语音混合应用支持基于语音的应用开发,并使用 W3C 标准的语音识别语法。
构建虚拟个人助理需要什么?
下图展示了构建一个支持语音的虚拟个人助理所需的各个组件。
对于一个虚拟个人助理(VPA)的基本要求是它能够说话和理解语言。文本到语音的合成,提供了说话的能力,在第二章,文本到语音合成中进行讨论,而语音识别则在第三章,语音识别中介绍。然而,尽管这些功能对于带语音功能的助手来说是根本性的,但它们还不够。参与对话并与网络服务和设备功能连接的能力也是个人助理的基础。要做到这些,VPA 需要以下能力:
-
一种控制对话的方法,确定谁应该采取对话的主动权以及他们应该涵盖哪些话题。在实际中,这可以通过一次性的交互简化,用户只需说出他们的查询,应用程序就会响应。一次性交互在第四章,简单的语音交互中介绍。系统引导的对话,即应用程序提出一系列问题——如在基于网络的表单填写(例如,预订酒店或租车)中,这在第五章,表单填写对话中进行讨论。
-
识别用户输入后对其进行解释的方法。这是口语语言理解组件的任务,它除了提供其他功能之外,还提供了一个语义解释,表示用户所说内容的含义。由于在许多商业系统中,输入被限制为单个单词或短语,因此解释相对简单。两种不同的方法将在第六章,对话语法中进行说明:如何创建一个手工制作的语法,涵盖用户可能说出的单词和短语;以及如何使用统计语法来涵盖更广泛的输入并提供更健壮的解释。如果语音输入和输出不可能或性能不佳,它还提供了不同的模态。如果需要,VPA 还应该具备使用不同语言的能力。这些主题在第七章,多语言和多模态对话中进行讨论。
-
确定相关动作并生成适当响应。这些对话管理和响应生成方面的内容在第七章,多语言和多模态对话中以及第八章,与个人虚拟助理的对话中进行描述。
在第二章和第三章中介绍的基本文本到语音合成和语音识别技术的基础上,第 4-8 章涵盖了一系列技术,使开发者能够进一步使用这些基本技术,并使用谷歌语音 API 创建基于语音的应用程序。
概述
本章节为 Android 设备上的语音技术提供了引论。我们研究了目前在 Android 设备上可用的各种语音应用程序。我们还探讨了为什么我们决定将重点放在谷歌语音 API 作为开发者的工具上。最后,我们介绍了创建虚拟个人助理所需的主要技术。这些技术将在本书的剩余章节中进行讲解。
在下一章中,我们将向您介绍文本到语音合成(TTS),并展示如何使用谷歌 TTS API 开发能够发音的应用程序。
第二章:文本到语音合成
你是否曾好奇过,你的移动设备是如何大声朗读你最喜欢的电子书或最后的电子邮件的?在本章中,你将了解文本到语音合成技术(TTS),以及如何使用谷歌 TTS 引擎开发能够发声的应用程序。涵盖的主题包括:
-
文本到语音合成技术
-
谷歌文本到语音合成
-
使用文本到语音合成开发应用程序
到本章结束时,你应该能够在 Android 设备上开发使用文本到语音合成的应用程序。
介绍文本到语音合成
文本到语音合成(通常缩写为 TTS)是一种技术,它可以将书面文本转换为语音。TTS 已经被广泛用于为视障人士提供屏幕阅读,同时也用于有严重言语障碍的用户。或许最著名的语音合成技术用户是物理学家斯蒂芬·霍金,他患有运动神经元病,当他的言语变得无法理解时,使用 TTS 作为他的语音。在单词预测技术的帮助下,他能够构建一个句子,然后将其发送到内置的 TTS 系统(更多信息请参见:www.hawking.org.uk/the-computer.html
)。
TTS 还广泛应用于用户手或眼忙碌的情况下,例如,在驾驶导航系统时,车辆沿着路线行进时,系统会朗读方向。TTS 的另一个广泛用途是在公共广播系统中,例如在机场或火车站。TTS 还用于基于电话的呼叫中心应用程序,以及一般语音对话系统,用于朗读系统的提示,并与网站上的对话头像结合使用,以提供客户帮助和支持。
TTS 系统的质量对用户的感知有很大影响。用户可能会对听起来机械的系统或错误发音如地名或地址等单词的系统感到烦恼。然而,只要 TTS 的输出可以理解,这至少应该能允许系统充分地执行功能。
文本到语音合成技术
文本到语音合成主要有两个阶段:
-
文本分析,即对要合成的文本进行分析和准备,以便进行口语输出。
-
波形生成,即分析后的文本被转换为语音。
文本分析阶段可能会遇到很多问题。例如,单词staring的正确发音是什么?是基于单词star + ing的组合还是stare + ing的组合?要确定这个问题的答案需要对单词结构进行复杂分析;在这种情况下,需要确定像stare这样的单词的词根形式如何通过添加如ing这样的后缀而改变。
还有一些单词根据它们在特定句子中的使用具有不同的发音。例如,作为动词的live与give押韵,但作为形容词时与five押韵。词性还会影响单词内的重音分配;例如,名词record的发音为'record(重音在第一个音节上),作为动词时为re'cord(重音在第二个音节上)。
另一个问题涉及到将数字值转换成适合语音输出的形式(这被称为规范化)。例如,项目12.9.13如果它代表一个日期,就不应该读作twelve dot nine dot thirteen,而应该是December 9th, two thousand thirteen。需要注意的是,使用 Google TTS API 的应用程序开发者无需担心这些问题,因为这些问题已经内置到 TTS 引擎中了。
转向波形生成,早期系统中使用的主要方法要么是发音合成,试图模拟人类产生语音的物理过程,或者是共振峰合成,它模拟声学信号的特征。
现在使用的则是拼接语音合成,在这种合成中,预先录制的语音单元存储在语音数据库中,在生成语音时选择并连接这些单元。这些单元的大小各不相同;单个声音(或音素),相邻的声音对(双音素),这会产生更自然的输出,因为一个音素的发音会根据周围音素而变化;音节、单词、短语和句子;已经开发出复杂的算法来选择最佳的候选单元链,并将它们平滑地连接起来以产生流畅的语音。一些系统的输出常常与真实的人类语音无法区分,特别是在有效使用语调的情况下。语调包括语调、音高、响度、速度和节奏,用于传达意义上的差异以及态度。
使用预先录制的语音而不是 TTS
尽管近年来 TTS 的质量有了显著的提高,但许多商业企业仍然倾向于使用预先录制的语音,以确保输出高质量。通常被称为声音天才的专业艺术家被雇佣来录制系统的提示音。
预录提示的缺点是它们不能用于输出文本不可预测的地方——如在阅读电子邮件、短信或新闻的应用程序中,或者在客户名单不断添加新名字的应用程序中。即使文本可以被预测,但涉及大量组合——如在机场的航班公告中——输出元素的不同部分必须从预录片段拼接起来,但在许多情况下,结果却是生硬和不自然的。另一种情况是可能需要提供其他语言的输出。可以聘请声音人才以各种语言录制输出,但对于更大的灵活性,使用不同语言版本的 TTS 可能成本更低,且足够满足目的。
关于 TTS 与预录语音的问题,已经进行了大量的研究。例如,请参阅James R. Lewis所著的《实用语音用户界面设计》,CRC Press。
使用谷歌文本语音合成
TTS 自 Android 1.6(API 级别 4)起在 Android 设备上可用。谷歌 TTS API(包android.speech.tts
)的组件在developer.android.com/reference/android/speech/tts/package-summary.html
有文档记录。接口和类被列出,点击这些可以获取更多详细信息。
启动 TTS 引擎
启动 TTS 引擎涉及创建TextToSpeech
类的一个实例以及当 TTS 引擎初始化时将执行的方法。通过一个名为OnInitListener
的接口来检查 TTS 是否已初始化。如果 TTS 初始化完成,将调用onInit
方法。
下面的代码行创建了一个实现onInitListener
接口的onInit
方法的TextToSpeech
对象。
TextToSpeech tts = new TextToSpeech(this, new OnInitListener(){
public void onInit(int status){
if (status == TextToSpeech.SUCCESS)
speak("Hello world", TextToSpeech.QUEUE_ADD, null);
}
}
提示
下载示例代码
你可以从你在www.packtpub.com
的账户下载你购买的所有 Packt 图书的示例代码文件。如果你在其他地方购买了这本书,可以访问www.packtpub.com/support
注册,文件会直接通过电子邮件发送给你。
你也可以访问这本书的网页:lsi.ugr.es/zoraida/androidspeechbook
在示例中,当 TTS 正确初始化后,将调用speak
方法,该方法可能包括以下参数:
-
QUEUE_ADD
:新条目被放置在播放队列的末尾。 -
QUEUE_FLUSH
:播放队列中的所有条目被丢弃,并由新条目替换。
由于某些设备的存储空间有限,可能并非所有支持的语言实际上都安装在特定设备上。因此,在创建 TextToSpeech
对象之前,检查特定语言是否可用非常重要。这样,如有必要,可以下载并安装所需的特定语言资源文件。这是通过发送带有 ACTION_CHECK_TTS_DATA
动作的 Intent
来实现的,它是 TextToSpeech.Engine
类的一部分,如下面的代码所示:
Intent intent = newIntent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(intent,TTS_DATA_CHECK);
如果语言数据已经正确安装,onActivityResult
处理程序将接收到 CHECK_VOICE_DATA_PASS
,这时我们应该创建 TextToSpeech
实例。如果数据不可用,将执行 ACTION_INSTALL_TTS_DATA
动作,如下面的代码所示:
Intent installData = new Intent (Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installData);
你可以在 TTSWithIntent
应用程序中查看完整代码,该应用程序在代码捆绑包中提供。
使用 Google TTS 开发应用程序
为了避免在多个地方重复代码,并能够随着我们过渡到更复杂的应用程序而专注于新部分,我们将最常使用的 TTS 功能封装到一个名为 TTSLib
的库中(请参阅源代码中的 sandra.libs.tts
),该库在不同的应用程序中使用。
TTS.java
类是按照单例设计模式创建的。这意味着此类只能有一个实例,因此使用该库的应用程序将使用一个单一的 TTS 对象来合成所有消息。这具有多种优势,例如优化资源并防止开发者在同一应用程序中无意创建多个 TextToSpeech
实例。
TTSWithLib 应用程序 – 读取用户输入
下图显示了此应用程序的启动屏幕,用户在此屏幕上输入文本,选择语言,然后按下按钮以使设备开始或停止阅读文本。默认情况下,选中选项是设备中的默认语言,如下面的屏幕截图所示:
TTSWithLib.java
文件中的代码主要初始化视觉用户界面中的元素,并控制所选语言(setLocaleList
方法),以及当用户按下说话(setSpeakButton
)和停止(setStopButton
)按钮时要执行的操作。从展示的代码中可以看出,主要功能是调用TTSLib
库中TTS.java
文件里的相应方法。在TTS.java
中(参见代码包中的TTSLib
项目),有三个名为setLocale
的方法用于设置地区。第一个方法接收两个参数,分别对应语言和国家代码。例如,对于英式英语,语言代码是EN
,国家代码是GB
;而对于美式英语,它们分别是EN
和US
。第二个方法仅设置语言代码。第三个方法不接收任何参数,仅设置设备的默认语言。可以观察到,如果第一个或第二个方法的任何参数为 null,则会调用第二个和第三个方法。
其他重要方法是负责开始(speak
方法)和停止(stop
方法)合成,而shutdown
方法释放 TTS 引擎使用的本地资源。调用shutdown
方法是一个好习惯,我们在调用活动的onDestroy
方法中执行它;例如,在TTSDemo.java
文件中)。
TTSReadFile 应用 – 大声朗读文件
文本到语音合成的更现实场景是朗读一些文本,特别是在用户的眼和手忙碌时。与上一个示例类似,应用程序检索一些文本,用户按下说话按钮来听它。提供了一个停止按钮,以防用户不想听完全部文本。
这种应用程序的一个潜在用例是用户访问网络上的文本,例如新闻条目、电子邮件或体育报道。这需要额外的代码来访问互联网,这超出了当前应用程序的范围(例如,参见第五章中的 MusicBrain 应用程序,表单填充对话框)。因此,为了简化问题,文本预先存储在Assets
文件夹中,并从那里检索。将文本从其他来源检索并传递给 TTS 朗读留给读者作为一个练习。以下截图显示了打开屏幕:
TTSReadFile.java
文件与TTSWithLib.java
文件类似。如代码所示,主要区别在于它使用英语作为默认语言(与存储的文件匹配)并从文件中获取文本,而不是从用户界面(参见speakbutton
的onClickListener
方法和代码包中的getText
方法)。
提示
在书中详细讨论了一些更高级的问题:《专业 Android™传感器编程,Greg Milette 和 Adam Stroud,Wrox,第十六章》。有一些方法可以根据特定设备上可用的内容选择不同的声音。例如,TTS API 提供了额外的帮助您播放不同类型文本的方法。
总结
本章节展示了如何使用谷歌 TTS API 在设备上实现文本到语音的合成。首先提供了文本到语音合成背后的技术概览,随后介绍了谷歌 TTS API 的各个元素。通过两个示例演示了文本到语音合成的基础知识。在后续章节中,将开发更为复杂的方法。
下一章节将处理语音硬币的另一面:语音到文本(或语音识别)。
第三章:语音识别
你是否曾在设备上点击多个菜单和选项,直到你能够完成你想要的操作?你是否希望只需说几个字就能让它工作?本章探讨的是自动语音识别(ASR),它将说出的单词转换为书面文字的过程。涵盖的主题包括以下内容:
-
语音识别技术
-
使用谷歌语音识别
-
利用谷歌语音识别 API 开发应用
到本章结束时,你应该能够很好地理解在应用中使用语音识别所涉及的问题,并应该能够使用谷歌语音 API 开发简单的应用程序。
语音识别技术
以下是语音识别的两个主要阶段:
-
信号处理:这一阶段包括将麦克风录入的单词通过模拟-数字转换器(ADC)转换成计算机可以处理的数字数据。ADC 处理数字数据以去除噪音,并执行其他如回声消除等过程,以便能够提取对语音识别有关键作用的特点。
-
语音识别:信号被分割成微小的片段,与待识别语言的音素进行匹配。音素是语音的最小单位,大致相当于字母表中的字母。例如,单词"cat"中的音素是 /k/, /æ/, 和 /t/。在英语中,例如,大约有 40 个音素,这取决于所讲英语的变体。
在语音识别中最成功的方法是统计建模语音,这样处理的结果是一系列猜测(或假设)用户可能说的话,并根据计算出的概率进行排序。这一统计建模使用复杂的概率函数。其中最常用的模型是隐马尔可夫模型,但神经网络也被使用,有时还使用这两种方法的混合过程。这些模型通过使用大量训练数据的训练过程进行调优。通常,需要使用数百到数千小时的音频来处理人类语音的变异性和复杂性。结果是产生了一个表示语言中声音和单词不同发音方式的声学模型。
仅仅依靠声学模型对于高性能的语音识别是不够的。语音识别系统的另一个部分是另一个统计模型,即语言模型。这个模型包含了关于允许的单词序列和给定序列中哪些单词更可能出现的知识。例如,尽管从声学角度来说,to和two听起来是一样的,但在短语I went to the shops中前者更可能出现,在短语I bought two shirts中则是后者。语言模型有助于返回每个短语中正确的单词。
语音识别的输出是根据识别器对识别正确性的置信度(从 0 到 1 的区间)排列的一系列识别结果(称为N-best 列表)。接近 1.0 的值表示对识别正确性有很高的置信度,而接近 0.0 的值表示置信度较低。N-best 列表和置信度分数很有用,因为可能的情况是,首选识别的字符串并不是用户实际所说的内容,而其他选项可能提供更合适的结果。置信度分数也很有用,因为它们可以为决定系统应该如何继续提供依据,例如,是否确认已识别的短语。
使用谷歌语音识别
自从 Android 2.1(API 级别 7)起,语音识别服务就已经在 Android 设备上可用。其中一个可以使用识别功能的地方就是 Android 键盘上的麦克风图标。点击麦克风按钮激活谷歌语音识别服务,如下面的截图所示。红色的麦克风和文字提示表明系统正在等待接收语音:
如果没有检测到语音,系统会生成一个重新提示对话框,要求用户再次尝试说话。另一种可能是无法为用户的语音输入找到合适的匹配项。在这种情况下,屏幕会显示未找到匹配项的消息。最后,当没有可用的网络连接时,会显示连接问题的消息。
可以通过设置 | 语言和输入 | 语音 | 语音搜索调整内置语音识别服务的参数,或者根据设备的不同,点击屏幕右上角的工具图标。会展示出多种可用的语言。检查语言的可用性以及选择语言也可以通过编程实现。这将在第七章,多语言和多模态对话中进行介绍。
使用谷歌语音识别 API 开发应用
谷歌语音 API(包android.speech
)的组件在developer.android.com/reference/android/speech/package-summary.html
进行了文档化。这里列出了接口和类,点击它们可以获得更多详细信息。
在 Android 设备上有两种进行语音识别的方法:仅基于RecognizerIntent
的方法,或者创建SpeechRecognizer
的实例。前者提供了一个易于编程的机制,通过启动 Intent 类并处理其结果,可以创建使用语音识别的应用程序。按照此方案的应用程序将显示一个对话框,为用户提供关于 ASR 是否准备就绪或在识别过程中出现不同错误的信息反馈。使用SpeechRecognizer
为开发人员提供了关于识别相关事件的不同通知,从而允许更细致地处理语音识别过程。这种方法不会显示对话框,为开发人员在应用程序 GUI 上提供更多控制。
在以下章节中,我们将介绍两个应用程序(ASRWithIntent
和ASRWithLib
),它们识别用户说的话并以 N-best 列表的形式展示识别结果及置信度得分。实际上,它们是相同的应用程序,但是它们是按照前面描述的两种不同方法开发的:ASRWithIntent
使用RecognizerIntent
方法,而ASRWithLib
使用我们在 ASRLib 库中编程的SpeechRecognizer
方法(代码包中的sandra.libs.asr.asrlib
)。
带有 Intent 的 ASR 应用
这个简单的应用程序说明了以下内容:
-
用户选择语音识别的参数。
-
用户按下按钮并说一些话。
-
识别出的词语及其置信度得分以列表形式显示。
在打开的屏幕上,有一个带有消息按下按钮说话的按钮。当用户按下按钮时,将启动语音识别功能,使用用户选择的参数。激活语音识别实例并说出贝尔法斯特的天气如何的词组的结果在下图中显示:
用户按下启动语音识别的按钮是通过引用asrwithintent.xml
中指定的按钮设置的,你可以在代码包中找到它(在ASRWithIntent
项目的res/layout
文件夹中):
//Gain reference to speak button
Button speak = (Button) findViewById(R.id.speech_btn);
//Set up click listener
speak.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
//Speech recognition does not currently work on simulated devices,
//it is the user attempting to run the app in a simulated device
//they will get a Toast
if("generic".equals(Build.BRAND. toLowerCase())){
Toast toast = Toast.makeText(getApplicationCon text(),"ASR is not supported on virtual devices", Toast.LENGTH_SHORT);
toast.show();
Log.d(LOGTAG, "ASR attempt on virtual device");
}
else{
setRecognitionParams(); //Read parameters from GUI
listen(); //Set up the recognizer and start listening
}
}
});
当用户按下按钮时,将调用listen()
方法,在其中设置RecognizerIntent
的详细信息。在ASRWithIntent
中,通过发送一个带有ACTION_RECOGNIZE_SPEECH
动作的 Intent 来支持语音识别,使用startActivityForResult(Intent,int)
方法,其中int
值是由开发者定义的请求代码。如果它大于 0,当活动退出时,此代码将在onActivityResult()
中返回。请求代码作为标识符,用于区分在应用程序中可能调用的不同意图产生的特定结果。
以下代码启动了一个识别语音的活动:
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
// Specify language model
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
// Specify how many results to receive. Results listed in order of confidence
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, numberRecoResults);
// Start listening
startActivityForResult(intent, ASR_CODE);
正如代码所示,与ACTION_RECOGNIZE_SPEECH
关联有几个额外的参数。其中一个是必需的,即EXTRA_LANGUAGE_MODEL
。其他的是可选的。顾名思义,EXTRA_LANGUAGE_MODEL
指定了在识别过程中要使用的语言模型。它支持以下两个选项:
-
LANGUAGE_MODEL_FREE_FORM
:这个语言模型基于自由形式的语音识别,用于识别自由形式的语音,例如,在电子邮件的口述中。 -
LANGUAGE_MODEL_WEB_SEARCH
:这个语言模型基于网络搜索词汇,用于模拟更为受限的输入形式,如较短的、类似搜索的短语,例如,飞往伦敦,马德里的天气等等。
可以观察到,这两个选项都意味着相当不受限制的输入。要构建一个只接受特定关键词的应用程序,或者使用识别语法,目前需要处理识别结果,以匹配预期的模式。关于如何执行此操作的一些示例将在第六章,对话语法中展示。
其他额外的参数在 Android 文档的RecognizerIntent
类中有描述。以下是一些更常用的可选额外参数:
-
EXTRA_PROMPT
:这提供了一个文本提示,当要求用户说话时向用户展示。 -
EXTRA_MAX_RESULTS
:这个整数值指定了返回结果的最大数量限制。如果省略,识别器将决定返回多少结果。这些结果是用户输入的不同可能的文本,并从最可能到最不可能排序(本章前面讨论的最佳 N 列表)。 -
EXTRA_LANGUAGE
:这指定了可以代替设备上提供的默认语言的语言。其他语言的使用在第七章,多语言和多模态对话中进行介绍。
可以让用户从这些选项中选择,或者可以程序化地设置。ASRWithIntent
应用会提示用户选择语言模型和最大结果数。如果没有提供这些信息,它会使用在两个常量中指示的默认值(请参阅asrwithintent.java
文件中的showDefaultValues
和setRecognitionParams
方法)。
语音识别的结果会通过onActivityResults(int, int, Intent)
中的活动结果返回。此时,识别已经完成。然而,通常我们会希望查看结果或以某种方式使用它们。为此需要执行的额外步骤在以下来自ASRWithIntent
的注释代码中说明:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ASR_CODE) {
if (resultCode == RESULT_OK) {
//Retrieves the N-best list and the confidences from the ASR result
ArrayList<String> nBestList =
data.getStringArrayListE xtra(RecognizerIntent.EXTRA_RESULTS);
float[] nBestConfidences =
data.getFloatArrayExtra(RecognizerIntent.EXTRA_CONFIDENCE_SCORES;
/** Creates a collection of strings, each one with a recognition result and its confidence, e.g. "Phrase matched (conf: 0.5)" */
ArrayList<String> nBestView = new ArrayList<String>();
for(int i=0; i<nBestList.size(); i++){
if(nBestConfidences[i]<0)
nBestView.add(nBestList.get(i) + " (no confidence value available)");
else
nBestView.add(nBestList.get(i) + " (conf: " + nBestConfidences[i] + ")");
}
//Includes the collection in the listview of the GUI
setListView(nBestView);
//Adds information to log
Log.i(LOGTAG, "There were : "+ nBestView. size()+" recognition results");
}
else {
//Reports error in log
Log.e(LOGTAG, "Recognition was not successful");
}
}
}
如先前所讨论的,识别结果通过意图返回。可以使用getStringArrayListExtra
方法,并传入RecognizerIntent.EXTRA_RESULTS
作为参数来访问匹配的句子。同样,可以使用getFloatArrayExtra
和RecognizerIntent.EXTRA_CONFIDENCE_SCORES
获取带有置信度分数的数组。
结果保存在一个新的ArrayList<String>
中,其元素结合了匹配的字符串和浮点数组中的分数(表示为字符串)。请注意,有可能置信度分数是-1;这是置信度不可用时的情况。然后,在setListView
方法中调用ArrayAdapter
,将格式化的字符串插入到 GUI 的ListView
中。
也可能发生识别无法满意进行的情况(在之前的代码中resultCode!=RESULT_OK
)。在RecognizerIntent
类中为主要的错误情况定义了不同的常量:RESULT_AUDIO_ERROR
、RESULT_CLIENT_ERROR
、RESULT_NETWORK_ERROR
、RESULT_SERVER_ERROR
和RESULT_NOMATCH
。它们都对应于物理设备或网络的错误,最后一个对应于没有短语与音频输入匹配的情况。开发者可以使用结果代码对这些错误进行详细处理。然而,显示的对话框已经实现了一个简单的错误处理,对于较简单的应用来说可能已经足够。在这种情况下,用户会收到关于错误的反馈,并且在适用的情况下,他们会要求用户重复其话语。
最后,由于应用需要访问互联网以执行语音识别,因此需要在manifest
文件中设置权限:
<uses-permission android:name="android.permission.INTERNET" />
ASRWithLib 应用
ASRWithLib
应用与ASRWithIntent
具有完全相同的功能,但不是只使用RecognizerIntent
类,而是创建SpeechRecognizer
类的实例。为了使代码在所有使用 ASR 的应用程序中可用,我们建议使用库,就像上一章中为 TTS 所做的那样。在这种情况下,库位于sandra.libs.asr.asrlib
中,只包含一个类ASR
,它实现了RecognitionListener
接口,因此所有处理不同识别事件的方法,即onResults
、onError
、onBeginningOfSpeech
、onBufferReceived
、onEndOfSpeech
、onEvent
、onPartialResults
、onReadyForSpeech
和onRmsChanged
。
SpeechRecognizer
实例是在createRecognizer
方法中创建的,在检查设备中是否可用 ASR 引擎之后:
List<ResolveInfo> intActivities = packManager.queryIntentActivities(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
if (intActivities.size() != 0) {
myASR = SpeechRecognizer.createSpeechRecognizer(ctx);
myASR.setRecognitionListener(this);
}
这段代码查询PackageManager
类以检查是否支持识别。如果intActivities
.size()的值大于零,则支持语音识别,然后创建SpeechRecognizer
的实例并将其保存在属性myASR
中。识别的监听器通过引用this
来设置,因为 ASR 类实现了RecognitionListener
接口。
listen
方法用于通过RecognizerIntent
类开始监听。尽管额外的内容与ASRWithIntent
应用中的相同,但启动识别的方式略有不同。ASRWithIntent
使用了startActivityForResult
,而在这个案例中,SpeechRecognizer
对象负责通过接收意图作为参数来启动识别。
public void listen(String languageModel, int maxResults) throws Exception{
if((languageModel.equals(RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) || languageModel.equals(RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH)) && (maxResults>=0){
Intent intent =
new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
// Specify the calling package to identify the application
intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, ctx. getPackageName());
// Specify language model intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
languageModel);
// Specify how many results to receive.
// Results listed in order of confidence
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
// Start recognition
myASR.startListening(intent);
}
else
throw new Exception("Invalid parameters for listen method");
}
在ASRWithIntent
应用中,所有代码都在同一个类中,识别参数(languageModel
和maxResults
)的正确性测试也在那个单独的类中进行。然而,这里我们正在构建一个将在许多应用中使用的库,因此不能想当然地认为在调用listen
方法之前会检查参数。这就是为什么该方法会检查它们的值,如果它们不正确,则会抛出异常。
有几种方法用来响应 ASR 引擎可能引发的不同事件。在库中实现这些事件的响应并不是一个好的策略,首先,这意味着所有使用该库的应用程序都必须对事件进行相同的处理;其次,因为大多数时候,对事件的响应涉及到向用户显示消息或以其他方式使用图形用户界面,而将应用程序的逻辑与其界面分离是一个基本的设计原则。
这就是为什么ASR
类使用抽象方法的原因。抽象方法只声明头部,具体子类负责为它们提供代码。这样,负责响应 ASR 事件的每个方法都调用一个抽象方法,而 ASR 的每个子类中这些方法的行为都不同。在ASRWithLib
示例中,ASRWithLib
类是 ASR 的一个子类,并以特定方式实现抽象方法。如果我们有另一个应用程序,想要开发不同的行为,可以为这些方法编写单独的代码。
例如,当引擎找到与用户所说内容相匹配的句子时,会调用ASR.java
类中的onResults
方法。ASR.java
中此方法的代码如下所示。请注意,为了获取结果,它使用了SpeechRecognizer
类的静态方法,而不是像ASRWithIntent
中那样使用RecognizerIntent
:
public void onResults(Bundle results) {
processAsrResults(results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION),results.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES));
}
此方法调用了抽象方法processAsrResults
。在ASRWithLib
类中实现了此方法,指出了如何处理结果(在这种情况下,是通过填充列表视图,如同ASRWithIntent
中那样)。
如您可能已经观察到的,ASRWithLib
应用程序没有显示识别对话框。这对于执行连续语音识别的应用程序(ASR 始终作为后台服务活动)可能是可取的,因为此类反馈可能会使用户感到烦恼。然而,对于其他应用程序,需要向用户显示一些反馈,以便他们知道应用程序正在听。这是通过onAsrReadyForSpeech
方法完成的。当 ASR 引擎准备好开始听时,执行此方法,这是来自ASRLib
的抽象方法,在ASRWithLib.java
中通过更改语音按钮的颜色和信息来实现(文本和颜色都不是硬编码的,而是从res/values
文件夹中获取的):
Button button = (Button) findViewById(R.id.speech_btn); button.setText(getResources().getString(R.string.speechbtn_listening));
button.setBackgroundColor(getResources().getColor(R.color.speechbtn_listening));
最后,需要在manifest
文件中设置权限,以便使用 ASR 访问互联网和录制音频:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
概括
本章展示了如何使用谷歌语音 API 来实现语音识别服务,前提是检查设备上是否提供该服务。系统会提示用户说出一些词语,识别结果,即识别出的字符串及其置信度分数,将显示在屏幕上。用户可以选择识别的语言模型以及要检索的最大结果数。这一功能是通过ASRWithIntent
和ASRWithLib
应用程序中的两种不同方法来实现的。
ASRWithIntent
应用程序是一个简单易开发的基本示例,所有代码都包含在同一个类中。ASR 是通过使用RecognizerIntent
类来完成的,并且有一个自动生成的对话框,提供反馈信息,指示引擎是否在听或是否出现错误。
ASRWithLib
应用程序展示了如何模块化并创建一个语音识别库,该库可以在许多应用程序中使用。它不是仅依赖于 RecognizerIntent
类,而是使用 SpeechRecognizer
类的实例,并使用抽象方法实现 RecognitionListener
接口,提供了一个灵活的实现,能够响应广泛的 ASR 事件。
虽然这两个示例并不是特别有用的应用程序,但这里展示的代码几乎可以原封不动地用在任何使用语音识别的应用程序中。本书后续的示例将基于此代码构建。下一章将展示如何结合 TTS(文本到语音)和 ASR(自动语音识别)来执行简单的语音交互,用户可以请求信息或向设备发出命令。
第四章:简单的语音交互
如果你可以仅通过向移动设备说话来获取信息或让它做事,那不是很好吗?本章介绍简单的语音交互,让你能够这样做。两个教程示例将向你展示如何实现查询信息以及请求启动设备上的一个应用程序。
语音识别并不完美,因此实施一些机制来选择最佳的识别结果很有意义。在前面的章节中,我们学习了如何获得置信度度量。在本章中,我们将介绍两种新机制:相似度度量,用于将识别的输入与用户所说的内容进行比较,以及确认机制,直接询问用户系统是否正确理解。
到本章结束时,你应该能够开发简单的语音交互,以请求信息并在设备上执行命令。你也应该了解如何使用相似度度量并确认用户所说的话。
语音交互
正如在第一章,安卓设备上的语音中讨论的,谷歌语音动作是简单的交互,用户说出一个问题或命令,应用程序通过动作或语音回应(或两者的组合)进行响应。
注意
以下是具有简单结构和少量交互回合的类似交互示例:
示例 1
用户:BBC 新闻
应用程序:(启动 BBC 新闻)
示例 2
应用程序:你的查询是什么?
用户:法国的首都是什么?
应用程序:(返回关于巴黎和法国的网页)
以下交互方式简单:
-
有限的对话管理:交互最多由两到三个回合组成。
-
有限的语音理解能力:用户仅限于由单个单词或短语组成的输入,例如网站名称或应用程序名称,或可以由谷歌搜索引擎处理的文本段落。
VoiceSearch 应用程序
这个应用程序说明了以下内容:
-
点击按下按钮说话选项时,提示用户说一些词语。
-
用户说出一些词语。
-
VoiceSearch
根据用户的语音发起搜索查询。
开屏界面有一个按钮,提示用户按下并说话。按下按钮后,下一界面显示谷歌语音提示你的查询是什么?结果会在浏览器窗口中显示。
在此案例中,应用程序使用了之前开发的两个库:TTSLib
(见第二章,文本到语音合成)和ASRLib
(见第三章,语音识别)。它们的jar
文件包含在VoiceSearch
项目的libs
文件夹中。ASR 方法用于识别用户输入并将其用作搜索条件。TTS 用于向用户提供关于应用程序状态的语音反馈。
这个应用程序结合了TTSWithLib
(第二章,语音合成)和ASRWithLib
(第三章,语音识别)应用程序中已经呈现的代码。它使用与TTSWithLib
中相同的 TTS 实例,以及ASRWithDemo
中的相同 ASR 方法。它还通过改变按钮的颜色和文字,提供视觉反馈以指示应用程序是否正在监听用户。
一旦 ASR 产生结果,它将被用于执行谷歌搜索,使用一个网页搜索意图,如processAsrResults
方法所示:
public void processAsrResults(ArrayList<String> nBestList, float[] nBestConfidences) {
String bestResult = nBestList.get(0);
indicateSearch(bestResult); //Provides feedback to the user that search
//is going to be started
//Carries out a web search with the words recognized
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, bestResult);
startActivity(intent);
}
可以观察到,应用程序总是使用最佳结果启动网络搜索。然而,系统可能误解了用户说的话,在这种情况下,启动的搜索可能是错误的。为了避免这种情况,可以使用确认。本章末尾将介绍确认的引入以及使用VoiceSearch
应用程序的一个简单示例。
VoiceLaunch 应用程序
这个应用程序的功能如下:
-
当用户点击按下说话按钮时,系统会提示用户说出一个应用程序的名称。
-
用户说出一个应用程序的名字。
-
VoiceLaunch
会将识别的输入与设备上安装的所有应用程序的名称进行比较,并启动名称最相似的应用程序。
像 VoiceLaunch 这样的应用程序不需要任何界面,因为用户只需说出他们想要启动的应用程序名称即可。然而,为了说明起见,我们创建了一个简单的界面,用户可以在其中选择两个参数的值:一个相似度阈值和一个相似度准则,如下面的截图所示。截图显示了用户要求启动邮件的场景。VoiceLaunch
显示了图中的屏幕并启动了邮件应用程序(在这种情况下是相似度最高的,为1.00):
我们介绍了相似性准则的技术,以展示如何改善 ASR 的结果。例如,当你有一个需要识别的关键词列表,但你不能限制 ASR 只监听这些词时,识别结果可能并不完全等同于你所期望的关键词。比如,如果你的关键词是冰淇淋口味,你可能考虑巧克力、草莓和香草,但如果你的 ASR 没有限制,对草莓的识别结果可能是蔓越莓。如果你的应用程序期待一个完全匹配,它会丢弃识别结果。然而,有了相似性度量,你的应用程序会知道用户说了与草莓相似的内容。
在VoiceLaunch
中,相似度标准用于控制应用如何测量识别的名称与设备中安装的应用名称之间的相似度。使用相似度阈值,以便如果应用的名称与用户输入的识别内容不够相似,则不会启动该应用。
我们考虑以下两种计算相似度的选项:
-
正字法相似度:
VoiceLaunch
计算单词之间的 Levenshtein 距离。这个度量标准将单词 A 和 B 之间的距离视为在 A 中执行插入、删除或替换以获得 B 所需的最小字符数。VoiceLaunch
将距离值转换为 0 到 1 之间的相似度值。 -
语音相似度:
VoiceLaunch
使用Soundex算法来计算名称之间的语音相似度。所使用的实现仅对英语有效,这就是为什么VoiceLaunch
应用仅提供英语版本。这样,即使拼写不同,发音相同的单词也会被认为是相似的。相似度也使用从 0 到 1 的区间进行测量。
备注
考虑的距离度量有很多:欧几里得距离、杰卡德指数、汉明距离、索伦森相似度指数或梅特 afone。
通常同音词的拼写相似,因此正字法和语音相似度的值几乎相同,例如,to和too,或flower和flour。可以观察到,当处理 ASR 结果时,语音相似度更为方便,而当处理文本结果时,正字法相似度更好,因为文本中拼写错误更常见。以下表格显示了一些示例:
相似度标准 | 示例单词对 |
---|---|
Addition – Edition | |
正字法相似度 | 0.75 |
语音相似度 | 0.75 |
备注
一本配套的小型 Java 项目可在书籍网页上找到,你可以使用ComparisonTest
java 项目来创建自己的测试。为了使用它,你必须包含 Soundex 的 apache 库,并将要比较的单词作为运行参数。在 Eclipse 中包含库,请右键点击项目 | 属性 | 库 | 添加外部 JARs。要运行项目,请右键点击运行方式 | 运行配置,在程序参数标签下,用空格分隔两个单词(例如,to too
)。
处理 ASR 和 TTS 的代码与 VoiceSearch
应用相似。然而,在识别完成后,VoiceLaunch
必须找到与用户识别词相似的应 用,并启动最相似的一个。这通过在 processAsrResults
方法中完成(在代码包的 VoiceLaunch.java
文件中查看),该方法调用 getSimilarAppsSorted
和 launchApp
方法,这些方法将在以下页面中描述。
提示
你是否注意到…
所有应用中使用的语音识别(ASR)库使它们具有相似的结构,其中在 processAsrResults
方法中处理识别信息。例如,在 VoiceSearch
应用中,它使用识别条件开始搜索,而在 VoiceLaunch
中,它获取已安装的类似应用列表并启动最相似的一个。
此外,VoiceLaunch
必须存储和管理关于设备上可用应用的信息。为了高效地完成这项工作同时保持代码的可读性,我们创建了一个名为 MyApp
的辅助类(你可以在代码包中找到,位于 VoiceLaunch
项目的 VoiceLaunch.java
文件中),对于每个应用,我们保存其用户友好的名称(例如,Adobe Reader),其被 Android 可识别的包名(例如,com.adobe.reader),以及其与用户输入的相似度(从 0 到 1 的值,例如,0.7)。
由于需要排序已安装的应用,我们还为 MyApp
类的对象定义了一个比较器。比较器如果第一个元素小于第二个元素返回负数,如果它们相等返回 0,如果第一个元素大于第二个元素返回正数。然后它们可以用来将集合从最小值排序到最大值。在我们的案例中,我们希望从最高相似度到最低相似度排序 MyApps
的集合。因此,我们将相似度值的比较器结果乘以-1,以得到反向排序的结果:
private class AppComparator implements Comparator<MyApp>{
@Override
public int compare(MyApp app1, MyApp app2) {
return (- Double.compare(app1.getSimilarity(), app2\. getSimilarity())); // Multiply by -1 to get reverse ordering (from most to least similar)
}
}
如前所述,VoiceLaunch
使用 getSimilarAppsSorted
方法来获取设备上按相似度排序的应用列表,但它只考虑那些与识别名称相似度高于指定阈值的应用。
如代码包所示(在 VoiceLaunch
项目下的 VoiceLaunch.java
文件中),该过程通过以下步骤进行:
-
从包管理器中检索所有已安装应用的列表。
-
对于列表中的每个应用,使用用户在 GUI 中选择的算法计算其与识别名称的相似度。如果获得的相似度值高于阈值,通过创建
MyApp
类的实例来保存应用的名称、包名和相似度值,该实例被添加到similarApps
集合中。 -
similarApps
集合是使用我们的相似度比较器进行排序的:Collections.sort(similarApps, new AppComparator());
-
这些信息被保存在日志中,以便开发者了解在相似度方面考虑了哪些应用。
public void processAsrResults(ArrayList<String> nBestList, float[] nBestConfidences) { //Obtains the best recognition result String bestResult = nBestList.get(0); //Read the values for the similarity parameters from the GUI readGUIParameters(); //Obtains the apps installed in the device sorted from most to least similar name regarding the user input //String[] = [0] = name, [1] = package, [2] = similarity ArrayList<MyApp> sortedApps = getSimilarAppsSorted(bestRes ult); //Shows the matching apps and their similarity values in a list showMatchingNames(sortedApps); //Launches the best matching app (if there is any) if(sortedApps.size()<=0) { Toast toast = Toast.makeText(getApplicationContex t(),"No app found with sufficiently similar name", Toast.LENGTH_ SHORT); toast.show(); Log.e(LOGTAG, "No app has a name with similarity > "+similarityThreshold); } else launchApp(sortedApps.get(0)); }
提示
你也可以尝试…
可以使用另一种方法,使系统不仅仅选择一个相似度度量,而是综合考虑两种方法的结果来选择最相似的应用,例如,使用加权投票方法。
根据所使用的相似度算法,getSimilarAppsSorted
调用以下方法之一:compareOrthographic
或comparePhonetic
(见代码捆绑包中VoiceLaunch
项目下的VoiceLaunch.java
)。前者使用 Levenshtein 度量计算正字法距离,后者使用 Soundex 算法计算语音距离。
为了计算 Levenshtein 距离,我们使用了com.voicedemos
包中的LevenshteinDistance.java
类。它基于维基教科书提供的代码,该代码可以在en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Java
找到,我们在此基础上添加了将距离转换为 0 到 1 之间相似度值的代码。
为了计算语音距离,我们使用了 Apache Commons 提供的 Soundex 实现,可在commons.apache.org/proper/commons-codec/index.html
找到。为此,我们将对应的commons-codec-1.8 lib
的jar
文件添加到了VoiceLaunch
项目的libs
文件夹中。
这两个相似度计算方法的输入参数是两个字符串,分别对应考虑中的应用名称和识别的输入。这些字符串之前通过删除空格并使用小写进行了标准化处理:
private String normalize(String text){
return text.trim().toLowerCase();
}
注意事项
可以进行更复杂的标准化处理,以应对用户只说了一个双词名称中的一个词的情况,例如,说kindle而不是kindle reader。
应用排序后,最相似的应用通过使用launchApp
方法启动,该方法使用带有应用包名的launchintent
(你可以在代码捆绑包中的VoiceLaunch.java
文件中找到它)。
VoiceSearchConfirmation 应用
确认是事务性对话中非常重要的一个方面,人类在服务交易中也广泛使用以确信一切都已被正确理解。由于当前的语音识别技术不能保证应用确切听到了用户所说的内容,应用应该确认用户的意图,尤其是如果下一个动作可能导致不可恢复的后果时。然而,确认应该谨慎使用,因为它们会延长交互时间,如果过度使用可能会让用户感到烦恼。
VoiceSearchConfirmation
应用程序与VoiceSearch
功能相同,但在执行搜索之前会确认搜索条件。与该应用程序的两个示例交互如下:
-
确认情景:此情景的特点如下:
-
用户按下按钮说话,并说出了贝尔法斯特的天气。
-
系统理解了贝尔法斯特的天气并询问你是说贝尔法斯特的天气吗?
-
用户按下按钮说话,并说是的。
-
系统使用贝尔法斯特的天气作为条件启动搜索。
-
-
否定情景:其特点如下:
-
用户按下按钮说话,并说格拉纳达的天气。
-
系统理解了加拿大的天气并询问你是说加拿大的天气吗?
-
用户按下按钮说话,并说不。
-
对话返回到步骤 1,直到用户满意为止。
-
processResults
方法在 ASR 识别出用户输入的任何内容时都会被调用。因此,为了给应用程序提供确认功能,需要区分方法是识别搜索条件后调用,还是识别确认请求的“是/否”响应。为此,我们引入了新的属性searchCriterion
来存储已识别的条件。如果为空,我们会尝试识别新的条件,如果不是,则确认其值。您可以查看VoiceSearchConfirmation.java
文件中的代码,该文件位于VoiceSearchConfirmation
项目的代码包中。
提示
其他相似度衡量方法和技术
其他相似度衡量方法和用于增强谷歌语音识别器返回结果的技巧在 Greg Milette 和 Adam Stroud 所著的《Professional Android™ Sensor Programming》第十七章中有所描述。讨论的技巧包括:
使用词干提取来提高词汇识别,即通过去掉词尾来将单词简化为词根,例如,将 walk、walks、walked 和 walking 都简化为同一个词根。
音韵索引,即匹配在发音上相似的词汇,例如,如果识别器返回发音相似的词appeal,能够返回apple。
使用 Lucene 进行匹配,这是一个为文本搜索而设计的搜索引擎库。
总结
在本章中,我们展示了如何使用谷歌语音识别和 TTS API 开发简单的语音交互。第一个示例说明了如何从用户那里获取一些词语作为输入并启动搜索查询。第二个示例涉及使用语音在设备上启动应用程序。这里我们介绍了使用相似度度量来比较用户输入的识别结果与可能说出内容的技术。文中展示了两种不同的度量方法:正字法相似性和音韵相似性。最后一个示例展示了如何使用确认来检查系统是否正确识别了输入。这些技术,加上前一章引入的置信度分数,对于开发支持语音的应用程序非常有用。
然而,这些交互在两个方面受到限制。首先,它们没有涉及使用对话状态信息来控制交互,以及确定应用程序应该说什么和做什么。应用程序的行为在特定的语音动作中是硬编码的。其次,这些交互限制了用户只能输入一个简单单词或短句。更复杂的对话需要对话状态的表示以及更高级的口语语言理解。第五章展示了如何包含对话状态的表示,以提供更灵活的对话管理,而第六章则展示了如何使用语法来允许更高级的口语语言理解。
就确认而言,我们针对只需确认单个数据的情况提出了一种非常简单的解决方案。在接下来的章节中,我们将研究如何创建更为复杂的行为,以便可以确认多个数据,同时考虑识别置信度的确认策略。我们还将介绍一种无需按下按钮即可进行对话的方法。
第五章:表单填写对话
许多启用语音的应用程序使用前一章描述的一键式对话。你觉得语音界面可以比这走得更远吗?你能想象更复杂的交互,其中需要从用户那里获取多个信息项,用于各种目的,例如启动应用程序、查询数据库、启动网络服务或网络服务混搭,以及更多?
这类对话与传统的网络应用程序中的表单填写相似。在本章结束时,你应该能够实现简单的表单填写对话,以便获取访问网络服务所需的数据。
表单填写对话
表单填写对话可以看作是多个待填写的槽位。例如,在航班预订应用程序的情况下,系统可能需要填写五个槽位:目的地、到达日期、到达时间、出发日期和出发时间。在简单的表单填写对话中,每个槽位逐一处理,并提出相关问题,直到所有槽位都被填写。此时,应用程序可以查找所需的航班,并将结果呈现给用户。以下是一个对话可能进行的方式的示例,以及对话进行时槽位状态的变化。
应用程序:欢迎来到航班信息服务。你想要去哪里旅行?
通话者:伦敦。
槽位 | 目的地 | 到达日期 | 到达时间 | 出发日期 | 出发时间 |
---|---|---|---|---|---|
--- | --- | --- | --- | --- | --- |
值 | 伦敦 | 未知 | 未知 | 未知 | 未知 |
应用程序:你想要哪一天飞往伦敦?
通话者:7 月 10 日。
槽位 | 目的地 | 到达日期 | 到达时间 | 出发日期 | 出发时间 |
---|---|---|---|---|---|
--- | --- | --- | --- | --- | --- |
值 | 伦敦 | 07/10/2013 | 未知 | 未知 | 未知 |
框架的槽位也可以附加条件,例如,如果是往返旅程,那么还需要填写返回旅程的槽位值。
在更复杂的对话中,用户可以一次填写多个槽位,如下面的示例所示:
应用程序:欢迎来到航班信息服务。你想要去哪里旅行?
通话者:我想在周五飞往伦敦。
槽位 | 目的地 | 到达日期 | 到达时间 | 出发日期 | 出发时间 |
---|---|---|---|---|---|
--- | --- | --- | --- | --- | --- |
值 | 伦敦 | 21/06/2013 | 未知 | 未知 | 未知 |
由于用户在回答目的地问题时已经提供了到达日期,因此可以填写该槽位,系统可以跳过到达时间的问题。然而,为了处理本例中通话者的输入,需要更复杂的语法,如第六章中讨论的对话语法。
实现表单填写对话
为了实现表单填写对话,需要:
-
创建一个数据结构来表示系统必须从用户那里获取信息的槽位。
-
开发一个算法来处理槽位,提取每个槽位所需的提示语。
VoiceXML(www.w3.org/TR/voicexml20/
)为这项任务提供了一个有用的结构,即包含表示完成表单所需不同信息项(槽位)的表单字段。以下代码是一个示例:
<form id = "flight">
<field name="destination">
<prompt>where would you like to travel to?</prompt>
<grammar src = "destinations.grxml"/>
</field>
<field name="date">
<prompt>what day would you like to travel?</prompt>
<grammar src = "days.grxml"/>
</field>
</form>
前面的示例展示了一个应用,该应用需要获取两项信息:目的地和日期。为了询问目的地,它使用了提示语 where would you like to travel to?
,而询问日期时,它合成了 what day would you like to travel?
。每一条数据都是通过重复提示用户直到收集完所有信息的方式来顺序获取的。在 VoiceXML 中,这是通过使用表单解释算法(FIA)完成的,具体描述见www.w3.org/TR/voicexml20/#dml2.1.6
。
尽管在本书中完全实现 VoiceXML 表单填充的方法超出了范围,但在本章的剩余部分,我们将展示如何创建一个简单的表单填充应用,该应用使用了类似的数据结构和算法。
提示
你也可以尝试…
你可以使用Voxeo Evolution(evolution.voxeo.com/
)来创建自己的 VoiceXML 对话。你可以注册一个免费的开发者账户,然后你就可以创建自己的 VoiceXML 应用,并将分配一个 Skype 号码,你可以使用它与应用互动。请注意,这对于 Android 应用来说并不直接有用,但可以让你了解 VoiceXML 以及它作为对话脚本语言是如何工作的。
你可能还想研究使用 JVoiceXML(jvoicexml.sourceforge.net/
)来构建一个完整的 Java VoiceXML 解析器。针对 Android 的初步实现代码可以在这里找到:sourceforge.net/p/jvoicexml/code/HEAD/tree/branches/android/
。
线程处理
在剩余章节中,我们将出于各种目的使用 XML 文件,并将公共代码封装在XMLLib
库中。一个重要的问题涉及到线程处理。启动应用时,会创建一个线程来运行代码。这个线程负责涉及更新用户界面的操作,因此有时被称为UI 线程。在 UI 线程中执行非常耗时的操作,如下载文件、执行 HTTP 请求、打开套接字连接或访问数据库,可能会使 UI 线程长时间阻塞,导致界面无响应并冻结更新。因此,从 Android 3(HoneyComb)开始,当尝试在 Android 应用的 主线程上执行网络操作时,会引发android.os.NetworkOnMainThreadException
异常。
Android 提供了多种方法来实现后台线程与 UI 线程之间的通信,具体解释请参见:developer.android.com/guide/components/processes-and-threads.html
其中一种方法是使用异步任务(AsyncTask
)。AsyncTask
始终在后台运行耗时的操作,并在 UI 线程中发布结果。关于AsyncTask
的文档可以在这里找到:developer.android.com/reference/android/os/AsyncTask.html
简而言之,当执行异步任务时,它会经历四个阶段,可以通过以下方法进行控制:onPreExecute
在任务执行之前,doInBackground
在onPreExecute
之后立即执行后台计算,onProgressUpdate
显示操作进度(例如,在用户界面中的进度条),以及onPostExecute
,当后台计算完成时调用。
XMLLib
在我们的库中(代码包中的sandra.libs.util.xmllib
),RetrieveXMLTask
(见RetrieveXMLTask.java
)负责从网上获取 XML 文件并将其内容保存为字符串以供进一步处理。它是以下列方式声明的:
class RetrieveXMLTask extends AsyncTask<String, Void, String>
它被定义为一个异步任务(AsyncTask
),接收一个字符串集合作为输入参数。它不产生任何类型的进度值(void),并以字符串形式产生后台计算的结果(<parameters, progress, result>)。在我们的案例中,字符串输入是获取 XML 文件的 URL,字符串结果是文件中的 XML 代码。从指定 URL 读取 XML 文件作为doInBackground
方法中的后台任务,该方法使用其他私有方法来打开 HTTP 连接和读取字节流(saveXmlInString
和readStream
)。查看代码包中的doInBackground
和saveXMLInString
方法(XMLLib
项目,RetrieveXMLTask.java
)。这里有一个关于如何进行异步 HTTP 请求的好教程:mobiledevtuts.com/android/android-http-with-asynctask-example/
可以观察到,RetrieveXMLTask
中的异常处理以一种特殊的方式进行,因为有一个名为exception
的属性,用于保存连接或读取操作(doInBackground
方法)期间可能引发的异常。一旦异步任务完成(在onPostExecute
方法中),就会处理此类异常。
一旦后台任务完成,我们需要使用onPostExecute
方法将结果返回给调用类。为此,我们定义了接口XML AsyncResponse
(见XML AsyncResponse.java
),其中包含抽象方法processXMLContents
。这个想法是调用异步任务的类实现该接口,并提供processXMLContents
方法的代码,而onPostExecute
将输出处理委托给此方法(见代码包中的onPostExecute
方法:sandra.libs.util.xmllib.RetrieveXMLTask
)。
表单填充库
要构建一个表单填充应用程序,我们必须指定一个类似于飞行示例中的数据结构。为此,我们定义了两个类:Form
和Field
。如 UML 图所示,Form
拥有字段集合,而Field
具有五个属性:一个名称,一个表示应用程序将用于请求该数据片段的提示字符串,以及两个表示当应用程序不理解用户对初始提示的响应(nomatch
)或听不到(noinput
)时使用的提示字符串,以及应用程序理解的值。
例如,Field
飞行设置可能为其属性具有以下值:
name: 目的地
prompt: 你的目的地是哪里?
nomatch: 抱歉,我没理解你说的话
noinput: 抱歉,我听不到你的声音
value: 罗马(当用户对系统提示回答罗马时)
这种结构足以构建我们本章讨论类型的程序。只需创建与待填充槽位数量相等的Field
类对象,以及一个包含所需字段集合的Form
。这可以通过 Java 编程轻松实现,然而它对读者不太友好,程序员也很难更改一些如系统提示之类的参数。
为解决此问题,我们将实现 VoiceXML 标准的一个子集,以创建易于阅读的 XML 文件,其中包含对话的结构。然后,我们将使用VXMLParser
类自动解析 XML 文件,并将它们转换为Field
和Form
类的对象。最后,DialogInterpretation
类将使用这些对象来管理交互。
注意
这并不意味着 VoiceXML 设计完全独立于应用程序。应用程序需要知道哪些信息需要处理,这些信息在 VoiceXML 文件中作为字段指出。因此,开发者可以更改 VoiceXML 中的任何内容,除了字段的数量和名称。
VXML 解析器
在 Android 中实现解析器有不同的方法,主要是使用 DOM、SAX 或 Pull 解析器。Android 推荐使用XMLPullParser
类(developer.android.com/reference/org/xmlpull/v1/XmlPullParser.html
),因为它设计得更为高效和简单。XML 的 Pull 解析允许流式解析,处理过程可以随时中断和恢复。
为了解析 VXML 文件,我们创建了一个XMLPullParser
来读取 XML 内容,并不断调用next()
方法来获取下一个事件,直到事件为END_DOCUMENT
。这样,我们处理START_TAG
、TEXT
和END_TAG
事件,分别对应系统遇到开始标签、处理标签内的文本或遇到结束标签的情况。
当系统遇到