本章面向终端开发者,说明如何接入真实音频硬件,以及 APP 在 Android / iOS 上能够共享哪些声音。使用 SDK 1.0.0 / package revision 27 / ABI 8 的配套头文件与静态库;先完成首次连接。
选择实际支持的音频路径
| 路径 | 型号能力 | 终端需要实现 | 声音来源 |
|---|---|---|---|
| 蓝牙播放 | audio.a2dp.sink,bit15 |
真实 A2DP Sink 音频模块、驱动及输出 | 手机系统选择的蓝牙媒体音频路由 |
| Wi-Fi 播放 | audio.wifi,bit16,同时启用 network.wifi |
独立 TLS 接收器、PCM sink 及实际扬声器输出 | 用户授权且系统允许采集的播放音频 |
| 通话 | calls 与实际 HFP/通话驱动 |
通话音频硬件和驱动 | 见通话接入 |
经典蓝牙不保证实现 A2DP Sink,不能只勾选经典蓝牙就声明可播放声音。network.bluetooth.classic 选择业务通信方式;audio.a2dp.sink 描述音频接收硬件,两者独立。BLE 业务控制可以与独立经典蓝牙 A2DP 模块共存。
SDK 以真实硬件能力、后台授权和客户端认识的能力取交集。PROFILE key36 给出硬件、授权和协商后的音频路径;它不表示正在播放,也不能替手机切换系统路由。只声明已经接通的硬件,音频能力不能标记为模拟;不要把勾选能力、蓝牙业务认证或成功收包当成听音成功。
Wi-Fi 音频格式与来源
Wi-Fi 使用独立 TCP 5006 TLS 通道,格式为 16 kHz、单声道 PCM S16LE,每帧 160 个样本 / 10 ms / 320 字节。这是面向语音的格式,不是高保真音乐通道,也不代替 HFP 通话。音频不混入视频 UDP 队列。
首页使用一个投屏入口,按当前车辆的能力决定是否附加 Wi-Fi 音频:
| 当前条件 | APP 行为 |
|---|---|
| 型号未开通 Wi-Fi 音频 | 纯视频投屏,不申请声音权限;车辆管理隐藏音频连接区与独立共享按钮 |
| 已开通且运行就绪 | PROFILE36 的 Wi-Fi hardware / authorized / negotiated 均有效,主业务 wifiConnected=true 且 PROFILE37 audioRuntime=true 时附加音频 |
| 系统拒绝采集或来源不允许 | 保留可用的视频路径,按实际采集状态提示 |
Android 10+ 使用系统播放采集;iOS 使用 ReplayKit audioApp,忽略麦克风音频。系统权限、音频来源和源应用的采集策略共同决定样本是否可用。
APP 不内置 TTS,也不生成替代导航语音。 高德公开播报接口只有文本,没有原始 PCM,不能据此提供 Wi-Fi 导航原声。当前内置导航使用手机系统 / A2DP 音频路径,与屏幕镜像互斥。没有允许采集的样本时,应显示来源受限或静音,不宣称已播放。
Managed:启动前接入 sink
Managed 由 SDK 任务管理音频 TLS、授权和清理。完成真实驱动后,填写 moto/audio.h 中的 moto_audio_port,在 create 成功后、start 前调用:
// terminal 已创建;sink 的四个回调已接通真实音频驱动。
moto_result rc = moto_terminal_set_audio(terminal, &sink, NULL);
if (rc == MOTO_OK) {
rc = moto_terminal_start(terminal);
}
// 检查 rc;失败时不继续启用输入,按生命周期规则清理。
第三个参数为 NULL 时复用 config.network。使用高级外部主传输时,须显式传入完整的 moto_network_port,供独立音频接收器使用。函数复制端口结构;sink 与网络的上下文必须保持有效到 terminal 销毁。
配置由同一个生命周期管理任务串行完成;运行中调用返回 MOTO_BUSY,成功 stop 后再次 start 保留配置。不要通过 moto_terminal_post 在运行中挂接音频。需要 iOS 独立镜像时,还须在启动前调用 moto_terminal_set_mirror(terminal, true),具体前提见视频投屏。
实现四个音频回调
| 回调 | 驱动职责 |
|---|---|
begin(user, token) |
准备本次音频流;硬件不可用时返回错误 |
write(user, token, pcm) |
接收本流 PCM;异步播放必须在回调返回前复制到有界队列 |
stop(user, token) |
立即使该 token 的待播数据失效,防止旧声音进入下一次播放 |
status(user, token, &played, &queued) |
返回已提交给驱动及仍排队的样本数 |
四个回调必须齐全,由 SDK 所属线程调用,不能阻塞或重入 SDK。pcm 只借用到回调返回,不能保存其指针供播放任务稍后读取。驱动按 token 区分流,停止时同时处理尚未播放的驱动缓冲。
MOTO_FULL 丢弃当前实时帧;MOTO_IO 终止当前流。音频不重传过期帧,不通过扩大无界队列掩盖慢驱动。played 表示已提交给驱动的样本数,不证明扬声器已经发声;延迟与音质需要目标硬件测量。
Core:复用通用音频接收器
使用 Core 的产品在 moto_create 后、连接前调用 moto_audio_attach(sdk, &sink)。它复制端口但不创建任务或 socket;sink 上下文保持到 SDK 销毁。
推荐使用 moto/audio_server.h 的 moto_audio_server_create/poll/stop/destroy,链接包内 moto_tls 组件。配置借用主 SDK、网络端口和安全随机源;这些对象及回调上下文须比接收器活得更久。创建失败时不继续启动音频接收。
在同一个 SDK 所有者线程每 10–20 ms 调用 moto_audio_server_poll(server, now_ms),使用单调时钟。接收器仅在有效授权期监听 5006,管理单条活动连接、TLS、解析和背压;客户不必再实现音频报文解析器。先停止所有调用者并销毁接收器,再销毁主 SDK。需要自行适配传输时,按交付包的 moto_audio_wifi_* 契约处理链路,不与通用接收器重复接管同一流。
断开与验收
停止共享、授权撤销、owner 切换、网络失效或租约超时都必须停止旧流。主 TCP 意外断开时,只有已经委派且仍有效的镜像租约可以按镜像生命周期继续;这不代表整网断开后仍能播放。临时音频凭据仅属于当前 owner、会话和 token,不记录或持久化。
Android 和 iOS 分别验证:允许采集的来源、被禁止采集的来源、真实扬声器播放、系统 A2DP 切换、停止与重新共享、网络断开及旧缓冲清除。切到 A2DP 后检查 Wi-Fi 音频已停止,避免重复播放。没有 A2DP 实机时保留该项待验收,不用协商结果替代。编译、收包和 played 计数均不能替代实际听音。按现象排查见联调与排障。
