Android 平台使用VIA创建语音交互应用开发指南

科技   2024-09-24 09:30   浙江  

Android 平台使用VIA创建语音交互应用开发指南

要实现语音交互应用(VIA),请按照以下步骤进行:

  1. 1. 创建VIA框架

  2. 2. (Option)实现首次设置/登录流程

  3. 3. (Option)实现“设置”界面

  4. 4. 在清单文件中声明所需权限

  5. 5. 实现语音控制面板界面

  6. 6. 实现语音识别(需包含 RecognitionService API 的实现)。

  7. 7. 实现语音内容(可以选择实现 TextToSpeech API)。

  8. 8. 实现命令执行

下面的各部分将详细介绍如何完成这些步骤。

创建VIA框架

清单
当应用的清单文件包含特定内容时,系统将识别该应用具有语音交互功能。

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myvoicecontrol">

    ...

  <application ... >
    <service android:name=".MyInteractionService"
        android:label="@string/app_name"
        android:permission="android.permission.BIND_VOICE_INTERACTION"
        android:process=":interactor">

      <meta-data
          android:name="android.voice_interaction"
          android:resource="@xml/interaction_service" />

      <intent-filter>
        <action android:name=
          "android.service.voice.VoiceInteractionService" />

      </intent-filter>
    </service>
  </application>
</manifest>

在这个示例中:

  • • VIA 必须公开一个服务,用于扩展 VoiceInteractionService,并为 VoiceInteractionService.SERVICE_INTERFACE(即 "android.service.voice.VoiceInteractionService")提供 intent 过滤器。

  • • 该服务需要拥有 BIND_VOICE_INTERACTION 系统签名权限,以确保与系统的正确交互。

  • • 此外,该服务应包含一个元数据文件,该文件定义为 android.voice_interaction,并存放在 res/xml/interaction_service.xml 中。

<voice-interaction-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:sessionService=
      "com.example.MyInteractionSessionService"
    android:recognitionService=
      "com.example.MyRecognitionService"
    android:settingsActivity=
      "com.example.MySettingsActivity"
    android:supportsAssist="true"
    android:supportsLaunchVoiceAssistFromKeyguard="true"
    android:supportsLocalInteraction="true" />

如需详细了解各字段,请参阅 R.styleable#VoiceInteractionService。 由于所有的 VIA 都属于语音识别服务,您需要在清单文件中添加以下内容:

AndroidManifest.xml

<manifest ...>
  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
  <application ...>
    ...
    <service android:name=".RecognitionService" ...>
      <intent-filter>
        <action android:name="android.speech.RecognitionService" />
        <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
      <meta-data
        android:name="android.speech"
        android:resource="@xml/recognition_service" />

    </service>
  </application>
</manifest>

此外,语音识别服务还需要以下元数据:

res/xml/recognition_service.xml

<recognition-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="com.example.MyRecognizerSettingsActivity" />

接下来,VoiceInteractionService、VoiceInteractionSessionService 和 VoiceInteractionSession 的生命周期如图所示:

VoiceInteraction相关组件生命周期

如前所述,VoiceInteractionService 是 VIA 的核心入口,主要职责包括:

  • • 初始化在 VIA 活跃状态时需要保持运行的进程,如热词检测。

  • • 报告支持的语音操作 。

  • • 从锁定屏幕启动语音交互会话。

下面是一个简单的 VoiceInteractionService 实现示例:

public class MyVoiceInteractionService extends VoiceInteractionService {
    private static final List<String> SUPPORTED_VOICE_ACTIONS =
        Arrays.asList(
            CarVoiceInteractionSession.VOICE_ACTION_READ_NOTIFICATION,
            CarVoiceInteractionSession.VOICE_ACTION_REPLY_NOTIFICATION,
            CarVoiceInteractionSession.VOICE_ACTION_HANDLE_EXCEPTION
    );

    @Override
    public void onReady() {
        super.onReady();
        // TODO: 设置热词检测器
    }

    @NonNull
    @Override
    public Set<String> onGetSupportedVoiceActions(@NonNull Set<String> voiceActions) {
        Set<String> result = new HashSet<>(voiceActions);
        result.retainAll(SUPPORTED_VOICE_ACTIONS);
        return result;
    }
    ...
}

要处理语音助手的功能,您需要实现 VoiceInteractionService#onGetSupportedVoiceActions()。系统会使用 VoiceInteractionSessionService 创建 VoiceInteractionSession 并进行交互,该服务的主要职责是在收到请求时启动新的会话。

public class MyVoiceInteractionSessionService extends VoiceInteractionSessionService {
    @Override
    public VoiceInteractionSession onNewSession(Bundle args) {
        return new MyVoiceInteractionSession(this);
    }
}

大部分逻辑将在 VoiceInteractionSession 中处理。会话实例可以被重复利用,以处理多个用户交互。在 AAOS 中,还提供了一个辅助的 CarVoiceInteractionSession,以实现汽车特定的功能。

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {
    public MyVoiceInteractionSession(Context context) {
        super(context);
    }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        closeSystemDialogs();
        // TODO: 显示 UI 并更新状态
        // TODO: 开始处理音频输入
    }
    ...
}

VoiceInteractionSession 包含多个回调方法,详细介绍可参考相关文档。

实现设置/登录流程

首次设置和登录可在以下情况下进行:设备初始配置期间、语音交互服务交换期间,或应用首次启动时。

在语音服务交换期间,用户可能选择未正确配置的 VIA,原因包括跳过设置向导或选择不同的 VIA。VoiceInteractionService 可通过以下方式鼓励用户完成设置:

  1. 1. 通知提醒:以不干扰用户的方式提示需要设置,并提供导航。注意,设置流程在驾驶时可能受到限制,此时应通过语音说明。

  2. 2. 语音回复:通过 VoiceInteractionSession#onShow() 发起语音提示,告知用户所需操作,并询问是否要启动设置流程。

  3. 3. 首次使用时设置:当 VIA 未配置时,语音告知用户,并询问是否启动设置流程。如果受限,应留通知供用户安全点击。

首次设置和登录界面应作为常规 Activity 开发,确保用户随时能中断和恢复设置。同时,界面设计应与汽车设计系统保持一致,以确保用户体验的一致性。

通知提醒用户完成设置流程

实现“设置”界面

设置界面应作为常规 Android activity 实现。如果已开发该界面,需在 res/xml/interaction_service.xml 中将其入口点声明为 VIA 清单的一部分。这一部分非常适合用于首次设置和登录(若用户尚未完成)或在需要时提供退出账号和切换用户的选项。

与首次设置屏幕相似,这些设置屏幕应具备以下特性:

  • • 提供返回到上一个屏幕(如“汽车设置”)的选项。

  • • 在驾驶时禁止使用,以确保安全。

  • • 符合各类车辆设计系统,保持一致的用户体验。

在清单文件中声明必需的权限

VIA 需要的权限分为三类:

  1. 1. 系统签名权限:仅授予系统签名的预装 APK,用户无法授予。这类权限需由原始设备制造商 (OEM) 在构建时授予,详情请参见授予系统特许权限。

  2. 2. 危险权限:用户必须通过 PermissionsController 对话框授予。这些权限可以预先授予默认的 VoiceInteractionService,但应用应在需要时请求权限。

  3. 3. 其他权限:这些权限不需用户干预,由系统自动授予。

接下来重点介绍危险权限的请求。只有在用户登录或设置屏幕时,才能请求这些权限。

注意:驾车时不允许显示权限请求对话框。如果缺少权限,建议用语音提示说明情况,并通过通知引导用户返回 VIA 设置屏幕。

在“设置”屏幕中,应使用 ActivityCompat#requestPermission() 方法请求危险权限。

通知监听器权限

要实现 TTR 流程,VIA 必须指定为通知监听器。应用可通过以下方式检查获取权限:

  • • (可选)使用 CarAssistUtils#assistantIsNotificationListener() 检查是否为通知监听器。

  • • (必需)响应 VOICE_ACTION_HANDLE_EXCEPTION 和 EXCEPTION_NOTIFICATION_LISTENER_PERMISSIONS_MISSING。

如果未预先授予访问权限,VIA 应用语音提示和通知,引导用户到“汽车设置”的通知权限部分。可以使用以下代码打开设置应用:

private void requestNotificationListenerAccess() {
    Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
    intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
    startActivity(intent);
}

实现语音控制面板界面

在 VoiceInteractionSession 收到 onShow() 回调时,可以显示语音控制面板界面。实现方式有两种:

  1. 1. 覆盖 VoiceInteractionSession#onCreateContentView()

  2. 2. 使用 VoiceInteractionSession#startAssistantActivity() 启动 Activity

使用 onCreateContentView()

这是显示语音控制面板的默认方式。应用需覆盖 VoiceInteractionSession#onCreateContentView() 并返回一个初始不可见的视图。在语音互动开始时,视图在 VoiceInteractionSession#onShow() 上变为可见,并在 onHide() 时再次隐藏。

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {
    private View mVoicePlate;

    @Override
    public View onCreateContentView() {
        mVoicePlate = inflater.inflate(R.layout.voice_plate, null);
        return mVoicePlate;
    }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        mVoicePlate.setVisibility(View.VISIBLE); // 更新UI状态为“监听”
    }

    @Override
    public void onHide() {
        mVoicePlate.setVisibility(View.GONE);
    }
}

建议在 onComputeInsets() 中调整被遮盖区域的处理。

使用 startAssistantActivity()

此方法将处理委托给一个常规 Activity。需在 onPrepareShow() 回调中禁用默认窗口创建,并在 onShow() 时使用 startAssistantActivity() 启动界面。

public class MyVoiceInteractionSession extends CarVoiceInteractionSession {
    @Override
    public void onPrepareShow(Bundle args, int showFlags) {
        super.onPrepareShow(args, showFlags);
        setUiEnabled(false);
    }

    @Override
    protected void onShow(String action, Bundle args, int showFlags) {
        closeSystemDialogs();
        Intent intent = new Intent(getContext(), VoicePlateActivity.class);
        intent.putExtra(VoicePlateActivity.EXTRA_ACTION, action);
        intent.putExtra(VoicePlateActivity.EXTRA_ARGS, args);
        startAssistantActivity(intent);
    }
}

确保 Activity 与 VoiceInteractionSession 之间保持通信,必要时使用内部 Intent 或服务绑定。注意,在 Automotive 中,驾车期间只能显示特殊注解或许可名单中的 Activity,请为 Activity 添加 <meta-data android:name="distractionOptimized" android:value="true"/>

实现语音识别

本节介绍如何通过检测和识别启动指令来实现语音识别。启动指令是用于通过语音启动查询或操作的热词,如“Ok Google”。

DSP 启动指令检测

Android 提供了 AlwaysOnHotwordDetector,可以在 DSP 级别实现始终开启的启动指令检测,具有低 CPU 开销。实现步骤包括:

  1. 1. 实例化 AlwaysOnHotwordDetector

  2. 2. 注册热词声音模型。

使用 VoiceInteractionService#createAlwaysOnHotwordDetector() 创建检测器,并传递所需的启动指令和语言区域。应用会接收 onAvailabilityChanged() 回调,可能的状态有:

  • • STATE_HARDWARE_UNAVAILABLE: DSP 功能不可用。

  • • STATE_HARDWARE_UNSUPPORTED: 不支持给定的指令和语言。

  • • STATE_HARDWARE_ENROLLED: 启动指令检测就绪。

  • • STATE_HARDWARE_UNENROLLED: 声音模型不可用,但可注册。

可用 IVoiceInteractionManagerService#updateKeyphraseSoundModel() 注册声音模型。

软件启动指令检测

若 DSP 检测不可用,使用软件语音识别。为避免干扰其他应用,VIA 必须:

  • • 使用 MediaRecorder.AudioSource.HOTWORD 捕获音频。

  • • 拥有 android.Manifest.permission.CAPTURE_AUDIO_HOTWORD 权限。

这两个常量仅适用于捆绑应用。

管理音频输入和语音识别

音频输入通过 MediaRecorder 实现,语音交互服务需实现 RecognitionService 类。为使用麦克风,VIA 必须拥有 android.permission.RECORD_AUDIO 权限。

在 Android 10 之前,麦克风权限一次只能授予一个应用。从 Android 10 开始,权限可共享。

访问音频输出

在提供语音回复时,必须遵循以下准则:

  • • 请求音频焦点时使用 AudioAttributes#USAGE_ASSISTANT 和 AudioAttributes#CONTENT_TYPE_SPEECH

  • • 在语音识别期间请求 AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 音频焦点。

命令执行

语音识别完成后,相关回调接口会返回结构化数据结构,如json、xml等,通过解析相应字段即可区分业务调用类型及参数,通过相应接口调用或者Intent发起调用,从而将命令词转换成业务调用,完成一次语音交互操作。

示例代码

AOSP源码下提供了创建VIA的示例代码,请参考如下链接: /frameworks/base/tests/VoiceInteraction

参考链接

Android语音互动简介[1] Android语音互动集成流程[2] 语音互动开发[3] Android 标准语音识别框架:SpeechRecognizer 的封装、调用和原理[4] 如何打造车机语音交互:Google Voice Interaction 给你答案[5] 直面原理:5 张图彻底了解 Android TextToSpeech 机制[6]

引用链接

[1] Android语音互动简介: https://source.android.google.cn/docs/automotive/voice/voice_interaction_guide?hl=zh-cn
[2] Android语音互动集成流程: https://source.android.google.cn/docs/automotive/voice/voice_interaction_guide/integration_flows?hl=zh-cn
[3] 语音互动开发: https://source.android.google.cn/docs/automotive/voice/voice_interaction_guide/app_development?hl=zh-cn
[4] Android 标准语音识别框架:SpeechRecognizer 的封装、调用和原理: https://lovecodeboy.blog.csdn.net/article/details/142461117
[5] 如何打造车机语音交互:Google Voice Interaction 给你答案: https://lovecodeboy.blog.csdn.net/article/details/142458948
[6] 直面原理:5 张图彻底了解 Android TextToSpeech 机制: https://lovecodeboy.blog.csdn.net/article/details/142461117


虎哥Lovedroid
Android技术达人 近10年一线开发经验 关注并分享Android、Kotlin新技术,新框架 多年Android底层框架修改经验,对Framework、Server、Binder等架构有深入理解
 最新文章