# HFP 通话音频切换无声问题分析与修复验证 ## 1. 问题现象 蓝牙通话过程中,先将通话音频切换到手机端,等待车机扬声器输出进入 standby 后,再切回车机端,车机喇叭没有声音。 ## 2. 分析资料 - 异常日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_28_15_59_31.log` - 修复后日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_28_19_50_02.log` - 蓝牙 SCO PCM:`cq_blink_soc_read.pcm` - AudioTrack 写入前 PCM:`cq_blink_play.pcm` - 系统源码:`Y:\tongli\android14.0_rk3562` ## 3. 蓝牙侧确认 蓝牙厂商提供的两份 PCM 数据均正常: - `cq_blink_soc_read.pcm`:蓝牙 SCO 声卡读取到的下行通话数据正常。 - `cq_blink_play.pcm`:经过蓝牙侧处理后,在调用 `AudioTrack.write()` 前的数据正常。 日志显示 AudioTrack 能正常创建、写入并累计 delivered frames。采样率和声道转换关系也符合当前实现: ```text 蓝牙 SCO 8000 Hz / 单声道 / S16LE AudioTrack 16000 Hz / 双声道 / PCM 16-bit Audio HAL 48000 Hz / 双声道 / S16LE ``` 因此,蓝牙 PCM 输入和 AudioTrack 写入前的数据不是本次无声问题的主要原因,排查范围应落在 AudioTrack 后面的系统音频输出链路: ```text AudioTrack -> AudioFlinger -> AudioPolicy -> Rockchip Audio HAL -> Speaker PCM -> Codec / 功放 / 喇叭 ``` ## 4. 异常日志时间线 ```text 16:00:15.147 HAL通过start_output_stream打开Speaker,device=0x2 16:00:18.464 Telecom设置adev_set_mode=2,即AUDIO_MODE_IN_CALL 16:00:27.886 音频切到手机端,蓝牙AudioTrack停止 16:00:30.915 AudioFlinger空闲约3秒,HAL进入standby并关闭Speaker PCM 16:00:36.907 音频切回车机,蓝牙重新创建AudioTrack 16:00:38.054 AudioTrack音量恢复为1.0 16:00:36.420 至16:01:24 HAL持续高频执行do_out_standby/close device,喇叭无声 ``` 异常期间,HAL每秒约90次执行以下流程: ```text get_presentation_position: Operation not permitted do_out_standby, device=0x2 close device ``` 与此同时,蓝牙AudioTrack仍持续写入且帧数增长。这表明HAL虽然向上层返回了“数据已消费”,但数据没有真正送入Speaker PCM。 ## 5. 系统源码根因 源码文件: ```text Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c ``` 目标函数: ```c static int start_output_stream(struct stream_out *out) ``` 原代码约位于第1374行: ```c if (adev->mode == AUDIO_MODE_IN_CALL) { return 0; } ``` 该代码只要发现系统处于 `AUDIO_MODE_IN_CALL`,就直接返回成功,但没有打开扬声器 PCM。 切换到手机端后,原有Speaker PCM会在空闲超时后关闭。再次切回车机时,`start_output_stream()` 因上述判断直接返回,导致 `out->pcm[]` 中没有有效的Speaker PCM。 后续 `out_write()` 的行为如下: 1. 没有找到有效的 `out->pcm[i]`,`ret` 保持为 `-1`。 2. 错误恢复分支调用 `do_out_standby()`,再次关闭输出流。 3. HAL最终仍向AudioTrack返回 `bytes`,所以上层认为写入成功。 4. 下一次写入重复同样过程,形成高频 `standby/close` 循环。 当前HFP Client通话下行链路为: ```text 蓝牙SCO PCM -> 蓝牙处理 -> AudioTrack -> Speaker PCM ``` 该链路不属于Rockchip基带Voice Session。`AUDIO_MODE_IN_CALL` 只表示Framework处于通话模式,不能单独证明基带Voice Session正在占用声卡。 同一文件中的 `out_write()` 和 `do_out_standby()` 已使用 `voice_is_in_call(adev)` 判断真实Voice Session,只有 `start_output_stream()` 的判断条件不一致。 ## 6. 修复方案 修改前: ```c if (adev->mode == AUDIO_MODE_IN_CALL) { return 0; } ``` 修改后: ```c if ((adev->mode == AUDIO_MODE_IN_CALL) && voice_is_in_call(adev)) { return 0; } ``` 修改含义: - 系统处于通话模式,且真实Rockchip Voice Session正在运行时,保持原逻辑,不重复打开声卡。 - 系统处于通话模式,但HFP Client通过AudioTrack播放时,允许重新打开Speaker PCM。 ## 7. 修复后验证 修复后已完成多轮“手机端/车机端”音频切换,实车确认切回车机后喇叭声音能够恢复。 关键验证时间线一: ```text 19:51:19.207 切到手机端,AudioTrack停止 19:51:22.237 Speaker输出进入standby 19:51:22.263 通话模式下成功执行start_output_stream,device=0x2 19:51:22.804 蓝牙重新创建AudioTrack 19:51:23.946 AudioTrack音量恢复为1.0 ``` 关键验证时间线二,Speaker关闭后等待约9秒再切回: ```text 19:52:40.288 adev_set_mode=2,即AUDIO_MODE_IN_CALL 19:52:44.501 切到手机端,AudioTrack停止 19:52:47.501 Speaker输出进入standby 19:52:56.608 成功重新执行start_output_stream,device=0x2 19:52:57.115 蓝牙重新创建AudioTrack 19:52:58.262 AudioTrack音量恢复为1.0 ``` 修复前后日志统计对比: | 日志 | `do_out_standby` | `get_presentation_position`告警 | 完整`start_output_stream` | | --- | ---------------: | ----------------------------: | ----------------------: | | 修复前 | 18648次 | 19645次 | 19次 | | 修复后 | 13次 | 13次 | 13次 | 修复后,高频约90次/秒的 `standby/close` 失败循环已经消失。剩余 `get_presentation_position` 告警只在输出流启动阶段出现一次,不再持续刷屏。 日志中的 `pcm_read error: File descriptor in bad state` 均出现在SCO关闭、蓝牙音频切到手机端之后,属于停止读取过程中的收尾现象,不影响本次修复结论。 ## 8. 责任归属 本问题主责建议归属Android BSP/Rockchip Audio HAL层: - Framework/Telecom设置 `AUDIO_MODE_IN_CALL` 符合通话流程。 - AudioTrack能够正常创建和写入。 - AudioPolicy输出设备为 `AUDIO_DEVICE_OUT_SPEAKER (0x2)`。 - 实际故障发生在Audio HAL重新打开Speaker PCM的状态判断中。 蓝牙厂商需继续配合提供SCO PCM、AudioTrack写入数据及切换时序,但本次无需修改蓝牙PCM处理逻辑。 ## 9. 回归测试建议 1. 通话中执行至少5轮“车机端 -> 手机端 -> 等待超过3秒 -> 车机端”。 2. 确认每次切回车机后均出现 `start_output_stream device=0x2`。 3. 确认不再出现高频 `do_out_standby/close device` 循环。 4. 验证通话下行声音、上行麦克风和音量调节。 5. 回归蓝牙音乐、系统提示音和其他媒体播放。 6. 如果产品支持真实基带通话,回归基带Voice Session,确认 `voice_is_in_call(adev) == true` 时原有声卡独占逻辑不变。 ## 10. 版本管理注意事项 当前可见的以下文件仍显示为修改前代码,Git状态也为干净: ```text Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c ``` 本次验证镜像可能来自另一份源码或构建目录,或者已验证修改尚未同步回正式仓库。正式提交和后续重新编译前,应确认实际构建源码路径,并将上述一行条件修改同步到目标仓库,避免修复丢失。