导读
随着移动互联网的发展,行业内衍生了基于移动平台的各类解决方案。其中,设备规模化管理的云控能力是各互联网公司在设备集群控制背景下的诉求。因此涌现了大批提供类似解决方案的平台。如:阿里系的阿里云 MQC、阿里无线和菜鸟 Nimitz 等,阿里之外的有 Testin、百度 MTC、腾讯 WeTest、华为、三星等等。
目前以上平台在云真机的使用上,都存在一个已知的短板 —— 声音。用户看的到画面,能够响应操作,但是涉及到声音播报、语音交互的场景时则无能为力。尤其对于音乐、视听、短视频、直播客户端等这类多媒体属性强的 App,在云真机的使用场景上是受限最大的。
现在回到我们自己的产品。高德地图车机 / 镜版(后面统称 Auto)。其中最常见的导航播报、与系统的多媒体混音交互、以及语音助手多轮对话的交互场景中,这些与声音相关的场景占比高达 25% 以上。所以解决远程场景下的声音双向交互问题,是云真机要成为一个日常化的生产工具之前必须迈过的坎。
挑战
在远程音频的双向通讯解决方案的背景下,满足基本用户体验的方面也存在以下挑战:
- 能力:满足所有车载设备的声音场景的双向交互能力(因为车载设备在声音部分比手机具有更高的定制性,在覆盖车载场景后,手机基本可以无缝适配);
- 延迟:传输延迟低于 500ms(基于一定的网络条件);
- 体验:无明显卡顿、杂音问题。
设想
首先通过下面的一张图来了解一下我们的需求是什么:
- 将声音通过电脑传输到远端的车机设备(车机系统能正常解析处理);
- 将车机通过喇叭播报出的声音传输到用户端。
而实现这两条链路,关键核心的两个因素是:
- 如何获取和写入音频数据;
- 如何实现实时的音频数据在车机和用户设备间的传输链路。
音频获取和写入
Android 系统音频概要
在思考如何进行设备的音频获取前,我们先来了解下 Android 的音频系统架构:
上图描述了音频通信从应用层、Libraries、HAL、到 Driver,最后到硬件模块各层主要实现。而我们也需要从这条链路中去挖掘获取和写入音频数据的思路。
首先,我们考虑的是 Android 对应的音频链路中是否有成熟的支持双向音频的能力。即音频数据在 OS 内部获取到对外传输。
REMOTE_SUBMIX
API 19 新加的 MediaRecorder. AudioSource. REMOTE_ SUBMIX,用于传输系统混音的音频流到远端(在 API 18 也存在,只是属于隐藏属性)。
由于要生效 REMOTE_SUBMIX,需要 Manifest. permission. CAPTURE_ AUDIO_ OUTPUT 权限,而该权限只有系统组件才具备。也就是如果第三方 App 需要的话,需要进行系统签名或者在烧写 OS 版本时就修改对应的权限。作为系统方是可以这么操作的,但显然对于要适配所有系统方的我们来说不适用。
软件 hook
考虑到我们拿到的车载设备中,root 比例高达 80% 以上。因此我们想从在音频数据传输到底层硬件驱动前进行“截胡”。也就是 hook HAL 定义的往驱动写入和读取对应音频数据的方法,来达到音频数据的双向获取。
- hook HAL hardware
hw hook 的是 struct audio_hw_device 的
音频输出:open_output_stream、close_output_stream
音频输入:open_input_stream、close_input_stream
system/lib/hw/audio.primary.*.so (不同的设备有后缀部分差异)
- hook tinyalsa
在实际的调研测试中,我们发现并不是每台设备都能通过 hook hw 来获取到对应的声音数据,尤其是车载设备。于是我们又调研了 ALSA(Advanced Linux Sound Architecture)高级 Linux 声音架构。根据官方的推荐,我们选择了具备 GPL-licensed 的 external/tinyalsa
hook tinyalsa.so
音频输出:pcm_open、pcm_close、pcm_write、pcm_mmap_write
音频输入:pcm_open、pcm_close、pcm_read、pcm_mmap_read
system/lib/libtinyalsa.so
- 问题
在实际摸底验证中,我们发现车机比手机还复杂的原因在于多了功放的概念,而部分车厂选择在设备的 DSP 模块去处理混音。带来的问题就是部分设备如果单纯的通过 hook 播报,对应听到的声音与设备真实通过喇叭播报的效果不同,这也导致我们对于该场景的还原并不真实。
因此,在于 root 设备覆盖不完全且部分设备存在硬件功放处理混音问题的情况下,软件 hook 的方案只能适用于部分设备。
- 成本
hook 自身也会带来一个问题,即针对不同的车机需要每台都进行 hook 处理,使得 hook 带来的成本过高。需要批量一键 hook 来解决这个问题。
分析到这里,我们回顾下音频传输的链路:
基于以上我们对音频获取的这条传输链路上的分析,现在理论上可行的获取途径,就只有硬件的对接或者具体的接收端(喇叭、蓝牙)。
usb 音频
硬件对接部分,在云控场景下,我们的设备通常是通过 USB 线束与我们的节点 PC 连接的。因此音频通过 USB 进行传输的链路,也是一个值得探索的方向。
我们知道,Android 设备在连接 usb 时有三种模式:Host、Development、Accessory Mode:
- 主机模式:可以传输音频,但是 Android 设备作为主机,无法使用 adb 的能力;
- 开发者模式:具备 adb 的能力,但是没有现成的 USB 音频能力;
- 配件模式:既保留了 adb 的能力,在 Android4.1 后的配件模式下,Android 也能自动将其音频输出导向到 USB。
思路:通过实现 AOA 协议,作为主机角色的设备,必须具有能够将 Android 设备从开发模式切换到配件模式的主机控制能力,然后主机从适当的端点传输音频数据
该方案的局限性在于:1、单向传输;2、配件模式取决于设备硬件,但并非所有设备都支持。实测过程中,车机支持配件模式的比例很低,绝大多数都被“阉割”了。
综上,我们无法靠单纯的某种 USB 模式来实现音频的双向交互。但如果是手机集群的场景中,这个方案倒是可以作为单向音频传输的一个优选方案。
蓝牙接收
声音除了可以通过 usb 传输以外,常见的方式还有蓝牙耳机、有线耳机。(这里由于车载设备不存在 3.5mm 孔,所以我们先不讨论有线方式,具体可参考后面「硬件转发」的方案)。
关于蓝牙接收的基本思路就是 PC 端通过安装蓝牙接收器与车机通信。其中蓝牙接收器起到类似于蓝牙耳机的作用。然后对蓝牙接收器的收发数据在 PC 端进行编码处理。
蓝牙耳机:具备了可以听说的能力,也就是双向的音频通信。
摸底验证:部分车机对蓝牙驱动进行了定制,使得蓝牙设备只能作为从设备,无法接入蓝牙耳机功能。我们测试了 35 台,其中 5 台可以用,成功率 14%,收益太低,成本过高。这个方案如果是面向手机集群,倒是一个不错的选择,理论上成功率应该会大大高于车载设备。
硬件转发
上面提到的有线耳机的思路。在车载设备上,不存在 3.5mm 孔或者 type C 口,而是通过主机与功放、音箱外放装置进行连接。在车辆量产上市前的研发阶段,只是一个主机通过线束连接着喇叭的一个过渡状态。所以我们实际是通过将原本接到喇叭上的音频数据通过一种转换装置转接到 PC 上,在 PC 端进行音频编码处理。
大致的参考示意效果如下:
上述方案的优势在于:
- 跨平台,不管是 Android、Linux、QNX 或者 iOS 的设备都适用;
- 解决了混音问题,由于对接的是最终播报出的声音效果,就不存在软件 hook 可能还原不真实的问题;
- 支持双向音频通信。
缺点:
- 部分智能车镜设备,由于集成封装性太强,没有暴露出可对接的线束。这类设备不容易通过该方案覆盖;
- 需要针对车机进行线束定制来实现整体的动态封装性;
- 需要配套的硬件成本。
硬件转发方案中存在几个方面的问题需要注意,比如失真问题、声卡识别问题、usb 兼容上限、声卡与 ehci、xhci 的兼容性问题以及整体封装设计等等。
小结
综合以上音频传输的整条链路的所有方案,我们列举对比下这些方案的优劣(特指在车载场景下):
基于上述情况,考虑到车载的应用场景。最终我们的选型是「软件 hook」+「硬件转发」的组合方案。
音频编码传输
关于音频编码传输这部分的内容,行业中已经有较成熟的解决方案,因此,这部分不展开篇幅讨论,我们仅针对一些方案做选型评估:
综上,从我们的应用场景以及高实时性要求考虑,最终选取了 webRtc 的方案。
最终选型
结合音频的获取和写入以及整体编码传输的方案,最终的技术方案选型图如下:
对应流程图中,也顺带涵盖了远程画面传输的视频流优化的参考链路。
总结
通过软硬件组合的方案来实现音频数据读写的能力,是一种基于特定背景条件下解决方案。但其基本推演的思路和策略,也是适用于手机平台的。而其中硬件的解决方案,理论上是适用于 Android、iOS、Linux、QNX 等平台设备的。
相较来说,手机的硬件转发成本更低。而对于软件的方案,实际的播报效果上仍会有很多细节问题,比如播报声音太小,需要对应设备去调节播报音量比例;出现延迟的场景,可能需要修改采样率;或是需要 hook 的自动化来降低成本等等。最终落地到项目中时,还需要考虑各方面的适配成本,确保整体的投入产出比。
本文作者:高德技术小哥
阅读原文
本文为云栖社区原创内容,未经允许不得转载。