如何打造车载语音交互:Android Voice Interaction 给你答案

简介: 如何打造车载语音交互:Android Voice Interaction 给你答案

在某些场景下进行图形交互显得有些困难、甚至危险,比如驾驶汽车。那么在这些场景下可以适当加入语音交互,在解放手眼的同时可以增强安全、避免分心。

概述

语音交互并不是一个新事物,很早就有了。比如 Apple 设备的 Siri、Amazon 的 Alxea、Google 的 Google Assistant 等等。


它们大多是系统的内置服务,由热词唤醒或按键触发,之后只通过语音指令即可完成完整的交互。可这些交互场景往往覆盖了系统服务或系统 App,而对第三方 App 的支持有限或者鲜少针对第三方 App 完成完整的语音交互逻辑。


第三方 App 除了被动等待系统语音服务的调度,当然可以选择主动支持。可是完全依靠自己实现的话,需要考虑监听、识别、理解、分析、调度等诸多复杂逻辑和流程,耗时耗力、可能还入不敷出。


那有没有简单办法来快速切入、试试水呢?


在 Android 生态当中,我们可以选择 Voice Interaction 来完成。Voice Interaction,简称 VI,是 Android 平台特有的语音交互 API,第三方 App 可以通过它来接入系统的语音服务。


这些服务称作 Voice Interaction App,简称 VIA。Android 设备一般都会内置一个或多个 VIA 服务,比如 Pixel 设备默认内置了 Google Assistant、Samsung 设备默认的 Bixby。


当第三方 App 接入它们之后,可以便捷地实现一些语音交互功能。比如在删除某项数据的时候,App 可以调度这些服务发起语音提示,并等待用户发出确认或取消的语音指令,其识别之后自动将结果返回回来,App 接棒完成后续的处理。


后面将着重演示如何使用 VI API 在 Pixel 模拟器上调度 Google Assistant 完成几个语音交互的示例。

Confirmation Request

Android 的 Activity 组件提供了发起和停止 VI 调用的方法:startLocalVoiceInteraction() 和 stopLocalVoiceInteraction()。

class VoiceInteractionActivity: AppCompatActivity() {
    ...
    fun onButtonClick(view: View?) {
        when (view?.id) {
            R.id.btn_confirm->{
                val bundle = Bundle().apply {
                    putString("name", "Test Voice Interaction")
                }
                startLocalVoiceInteraction(bundle)
            }
        }
    }
}

调用被发起后 Activity 的 onLocalVoiceInteractionStarted() 会被回调,在这里 App 可以获取到向 VIA 请求的入口即 VoiceInteractor

class VoiceInteractionActivity: AppCompatActivity() {
    ...
    override fun onLocalVoiceInteractionStarted() {
        val request = testConfirmation()
        voiceInteractor.submitRequest(request)
    }
}

接着可以创建 Request 实例,并使用得到的 VoiceInteractor 向系统发出去。


Request 的类型有很多,比如适用于上面提到的确认交互场景的 ConfirmationRequest。而且为便于用户准确理解,Request 还可以指定友好的提示说明,用 Prompt 实例构建。

class VoiceInteractionActivity: AppCompatActivity() {
    ...
    private fun testConfirmation(): VoiceInteractor.Request {
        val prompt = VoiceInteractor.Prompt(resources.getString(R.string.vi_confirmation_prompt))
        return object : VoiceInteractor.ConfirmationRequest(prompt, null) { ... }
    }
}

系统收到 Request 后会按照提示调用 TTS 进行朗读,并等待用户的后续语音指令,当用户发出不同指令或指令超时的时候,Request 的相应回调将被系统触发:


YES:onConfirmationResult() 被回调并且 confirmed 参数为 true


NO:onConfirmationResult() 被回调但 confirmed 参数为 false


超时:onCancel() 被回调


这里演示当点击删除 Button 之后,App 通过 VIA 发出询问用户是否要删除该首歌曲的语音提示。用户发出 Yes 之后弹出 Toast 的同时将该首歌曲的 TextView 隐藏。

class VoiceInteractionActivity: AppCompatActivity() {
    ...
    private fun testConfirmation(): VoiceInteractor.Request {
        val prompt = VoiceInteractor.Prompt(resources.getString(R.string.vi_confirmation_prompt))
        return object : VoiceInteractor.ConfirmationRequest(prompt, null) {
            override fun onConfirmationResult(confirmed: Boolean, result: Bundle?) {
                val stringId =
                    if (confirmed) R.string.vi_confirmation_confirmed else R.string.vi_confirmation_cancelled
                Toast.makeText(
                    this@VoiceInteractionActivity,
                    stringId,
                    Toast.LENGTH_SHORT
                ).show()
                if (confirmed)
                    confirmTv?.visibility = View.INVISIBLE
                stopLocalVoiceInteraction()
            }
            override fun onCancel() {
                Toast.makeText(
                    this@VoiceInteractionActivity,
                    R.string.vi_confirmation_timeout,
                    Toast.LENGTH_SHORT
                ).show()
                stopLocalVoiceInteraction()
            }
        }
    }
}

一开始发现点击 Button 之后没有任何反应:虽然日志上显示 onLocalVoiceInteractionStarted() 能回调,但既没有收到系统的语音提示,发出 YES 或者 NO 也没有收到 Request 的回调。


经过调查发现模拟器的音量和 Microphone 没有打开。

1832b220aa754cd18c504acc7686a560.png

重试之后可以听到系统发出 “Are you sure you want to delete this song?” 的语音提示了,但我发出的指令仍然没有反馈。


在模拟器上打开了 Online Test Mic 发现发出的语音模拟器是能收到的,即麦克风没有问题。那么必然是识别那块除了问题。重新取了日志,果然发现了问题:ASR 识别连接发生了错误,虽然我已经连上了网。

06-21 22:41:51.307  1506  8756 W ErrorReporter: reportError [type: 211, code: 65561, bug: 0]: errorCode: 65561, engine: 2
06-21 22:41:51.307  1506  8756 I NetworkRecognitionRnr: Using pair HTTP connection
06-21 22:41:51.311  1506  7017 I PairHttpConnection: [Upload] Connected
06-21 22:41:51.317  1506  1990 W CronetNetworkRqstWrppr: Upload request without a content type.
06-21 22:41:51.324  1506  1972 I S3RecognizerInfoBuilder: S3PreambleType 0

一顿折腾之后,模拟器能够科学上网了,再试果然成功了。

2Zmh5D.gif

录屏可以看到点击了 “Delete that song” Button 之后,Google Assistant 弹出了 UI 说明,GIF 无法展示,事实上还播放了对应的语音提示。


在此之后,当发出了 “Yes” 的 Voice 之后,被它成功地识别了,并回调了我们的 Delete 逻辑,最终隐藏了目标歌曲。

Pick Option Request

除了借助 VI 帮忙做 YES 或 NO 的判断题,还可以通过 PickOptionRequest 让 VI 帮忙做选择题。发起和回调的处理差不多,区别在于 Request 的部分,需要传入选项 Array

class VoiceInteractionActivity: AppCompatActivity() {
    ...
    private fun testPickup(): VoiceInteractor.Request {
        val prompt = VoiceInteractor.Prompt(resources.getString(R.string.vi_pick_prompt))
        val optionList = arrayOf(
            VoiceInteractor.PickOptionRequest.Option(optionsArray[0], 0),
            VoiceInteractor.PickOptionRequest.Option(optionsArray[1],1),
            VoiceInteractor.PickOptionRequest.Option(optionsArray[2], 2)
        )
        return object : VoiceInteractor.PickOptionRequest(prompt, optionList, null) { ... }
    }
}

这里模拟一个场景,当驾驶员搜索或者打开歌单出现一堆歌曲的时候,App 可以设计如下流程进行语音选择:


App 将界面内歌曲列表传递给 VIA 让其播报出来,通过语音提示驾驶员

当驾驶员听到满意的歌名之后,将其念出来

VIA 将自动识别并匹配上其索引,最后回传给 App

进而 App 可以依据索引直接选择对应歌曲进行播放

另外要注意,选择后有其特有的回调即 onPickOptionResult()。

class VoiceInteractionActivity: AppCompatActivity() {
    ...
    private fun testPickup(): VoiceInteractor.Request {
        ...
        return object : VoiceInteractor.PickOptionRequest(prompt, optionList, null) {
            override fun onPickOptionResult(
                finished: Boolean,
                selections: Array<out Option>?,
                result: Bundle?
            ) {
                if (finished && selections?.size == 1) {
                    val index = selections[0].index
                    Toast.makeText(
                        this@VoiceInteractionActivity,
                        "${resources.getString(R.string.vi_pick_selected_prefix)} ${optionList[index].label}",
                        Toast.LENGTH_SHORT
                    ).show()
                    var selectedItem: View? = when (index) {
                        0 -> optionTv1
                        1 -> optionTv2
                        2 -> optionTv3
                        else -> { null }
                    }
                    selectedItem?.isPressed = true
                }
                stopLocalVoiceInteraction()
            }
            override fun onCancel() {
                Toast.makeText(
                    this@VoiceInteractionActivity,
                    R.string.vi_confirmation_timeout,
                    Toast.LENGTH_SHORT
                ).show()
                stopLocalVoiceInteraction()
            }
        }
}

image.gif

可以看到点击 “Choose a song” Button 之后,Google Assistant 弹出了 “Which song do you want?” 的 UI 提示,以及同等的语音提示。


当发出了 “dances with wolves” 的 Speech 之后,它不仅听到了还进行了模糊识别(谁叫自己英语发音不标准呢 😂)并成功回调了 Select 目标 Item 的逻辑。

其他 Request

除了用于确认的 ConfirmationRequest、用于选择的 PickOptionRequest,还有其他 Request:


Command Request,用于向 VIA 发送预设的 Command String(比如控制导航、媒体、车辆、通信等特殊 Command),可在 onCommandResult() 里回调,命令执行与否在 isCompleted 参数中体现

Complete Voice Request,用于通知 VIA 已经成功通过 Voice Interaction 完成交互逻辑,在 onCompleteResult() 回调里可以关闭 Activity

Abort Voice Request,用于通知 VIA 无法通过 Voice Interaction 完成交互,在收到 onAbortResult() 回调里可以开启传统的 UI 操作 Activity 以继续完成交互。

VI Flow

1832b220aa754cd18c504acc7686a560.png

如同 AccessibilityService,VIA 的核心服务 VoiceInteractionService 依赖 SystemService 的调度,该服务名为 VoiceInteractionManagerService。


在 VIA 设置为 Default Digital Assistant App 之后或重启之后,VoiceInteractionManagerService 会绑定 VIA 的 VoiceInteractionService 并进行 ASR、NLU、NLG、TTS 等服务或 Engine 的初始化,同时开启对 Hotword 的探测。


当 Client App 通过 VI 发出 Request 后,VoiceInteractionManagerService 会绑定 VoiceInteractinoSessionService 并开启一个 VoiceInteractionSession 进行处理。


该 Session 收到具体的的 Request,在展示 UI 的同时会依据传入的 Prompt 文本调用 TTS 进行朗读。之后调用 MediaRecorder 进行录音,并将数据交由 ASR 和 NLU 进入语音识别和语义分析。


当识别到的结果和目标意图符合或模糊匹配上的话,将会回调 Request 的相应 Callback。

注意点

在使用 VI API 实战的时候需要留意如下几点:


确保麦克风打开


确保扬声器音量足够大


确保网络正常,可以下载必要的语音包的


尽量科学上网,否则可能无法识别语音(虽然我觉得基础指令的解析本可以在本地完成)


确保设备中存在 VIA 并且设置为默认的 Digital Assistant App(如果设备中没有,可以考虑下载、安装 Google Assistant & Google,并设置为默认 App)

如果在实战过程中发现一些问题,可以查看如下日志以帮助分析失败的原因:

adb logcat -s GoogleTTSServiceImpl -s VoiceDataDownloader -s VoiceDataManager -s VoiceGenerator -s TextToSpeech -s GoogleTTSService -s GoogleTTSServiceImpl

结语

和语音助手一样,Voice Interaction API 也早就出现了,准确的是在 Android 6 推出的,可是鲜少有朋友了解或使用过。


VI 这套 API 可以免去自行集成 ASR、NLU、NLG、TTS 这些复杂模块的步骤,而且随着 AOSP 的版本升级未来还可以便捷地支持更多功能、无需自行扩展架构。


如果为了体验或者给 App 提供基础的语音交互功能,不妨从接入 VoiceInteraction 开始!当然作为 VI 的实现方 VIA 才是语音交互的精髓,后续将从原理、实战进行更完整地探讨。

参考资料

相关实践学习
达摩院智能语音交互 - 声纹识别技术
声纹识别是基于每个发音人的发音器官构造不同,识别当前发音人的身份。按照任务具体分为两种: 声纹辨认:从说话人集合中判别出测试语音所属的说话人,为多选一的问题 声纹确认:判断测试语音是否由目标说话人所说,是二选一的问题(是或者不是) 按照应用具体分为两种: 文本相关:要求使用者重复指定的话语,通常包含与训练信息相同的文本(精度较高,适合当前应用模式) 文本无关:对使用者发音内容和语言没有要求,受信道环境影响比较大,精度不高 本课程主要介绍声纹识别的原型技术、系统架构及应用案例等。 讲师介绍: 郑斯奇,达摩院算法专家,毕业于美国哈佛大学,研究方向包括声纹识别、性别、年龄、语种识别等。致力于推动端侧声纹与个性化技术的研究和大规模应用。
相关文章
|
3月前
|
Android开发
Android实现语音播报的两种方式
Android实现语音播报的两种方式
217 0
|
3月前
|
安全 API Android开发
Android网络和数据交互: 解释Retrofit库的作用。
Android网络和数据交互: 解释Retrofit库的作用。
58 0
|
3月前
|
JSON Android开发 数据格式
android与Web服务器交互时的cookie使用-兼谈大众点评数据获得(原创)
android与Web服务器交互时的cookie使用-兼谈大众点评数据获得(原创)
60 2
|
16天前
|
XML Android开发 UED
"掌握安卓开发新境界:深度解析AndroidManifest.xml中的Intent-filter配置,让你的App轻松响应scheme_url,开启无限交互可能!"
【8月更文挑战第2天】在安卓开发中,scheme_url 通过在`AndroidManifest.xml`中配置`Intent-filter`,使应用能响应特定URL启动或执行操作。基本配置下,应用可通过定义特定URL模式的`Intent-filter`响应相应链接。
44 12
|
2月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
3月前
|
安全 物联网 测试技术
构建未来:Android与IoT设备的无缝交互深入探索软件自动化测试的未来趋势
【5月更文挑战第30天】在物联网(IoT)技术快速发展的当下,Android系统因其开放性和广泛的用户基础成为了连接智能设备的首选平台。本文将探讨如何通过现代Android开发技术实现智能手机与IoT设备的高效、稳定连接,并分析其中的挑战和解决方案。我们将深入挖掘Android系统的底层通信机制,提出创新的交互模式,并通过实例演示如何在Android应用中集成IoT控制功能,旨在为开发者提供一套可行的指导方案,促进IoT生态系统的进一步发展。
|
3月前
|
XML JSON API
转Android上基于JSON的数据交互应用
转Android上基于JSON的数据交互应用
27 1
|
3月前
|
Java Android开发
Android桌面快捷方式图标生成与删除 使用Intent与launcher交互
Android桌面快捷方式图标生成与删除 使用Intent与launcher交互
57 1
|
3月前
|
JSON 自然语言处理 Java
Android App开发语音处理之系统自带的语音引擎、文字转语音、语音识别的讲解及实战(超详细 附源码)
Android App开发语音处理之系统自带的语音引擎、文字转语音、语音识别的讲解及实战(超详细 附源码)
224 0
|
3月前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
25 0