微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

怎么打造车载语音交互:Google 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 没有打开。

重试之后可以听到系统发出 “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

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

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

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

@L_502_2@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 可以设计如下流程进行语音选择:

  1. App 将界面内歌曲列表传递给 VIA 让其播报出来,通过语音提示驾驶员
  2. 当驾驶员听到满意的歌名之后,将其念出来
  3. VIA 将自动识别并匹配上其索引,最后回传给 App
  4. 进而 App 可以依据索引直接选择对应歌曲进行播放

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

 class VoiceInteractionActivity: AppCompatActivity() {
     ...
     private fun testPickup(): VoiceInteractor.Request {
         ...
         return object : VoiceInteractor.PickOptionRequest(prompt,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,Toast.LENGTH_SHORT
                 ).show()
 
                 stopLocalVoiceInteraction()
             }
         }
 }

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

当发出了 “dances with wolves” 的 Speech 之后,它不仅听到了还进行了模糊识别(谁叫自己英语发音不标准呢

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐