Files
Obsidian/项目文档/同力/蓝牙/HFP通话音频切换无声_BSP分析与修复验证_2026-07-28.md
T
2026-11-05 14:35:04 +08:00

7.1 KiB
Raw Blame History

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。采样率和声道转换关系也符合当前实现:

蓝牙 SCO       8000 Hz / 单声道 / S16LE
AudioTrack    16000 Hz / 双声道 / PCM 16-bit
Audio HAL     48000 Hz / 双声道 / S16LE

因此,蓝牙 PCM 输入和 AudioTrack 写入前的数据不是本次无声问题的主要原因,排查范围应落在 AudioTrack 后面的系统音频输出链路:

AudioTrack
  -> AudioFlinger
  -> AudioPolicy
  -> Rockchip Audio HAL
  -> Speaker PCM
  -> Codec / 功放 / 喇叭

4. 异常日志时间线

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次执行以下流程:

get_presentation_position: Operation not permitted
do_out_standby, device=0x2
close device

与此同时,蓝牙AudioTrack仍持续写入且帧数增长。这表明HAL虽然向上层返回了“数据已消费”,但数据没有真正送入Speaker PCM。

5. 系统源码根因

源码文件:

Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c

目标函数:

static int start_output_stream(struct stream_out *out)

原代码约位于第1374行:

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通话下行链路为:

蓝牙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. 修复方案

修改前:

if (adev->mode == AUDIO_MODE_IN_CALL) {
    return 0;
}

修改后:

if ((adev->mode == AUDIO_MODE_IN_CALL) && voice_is_in_call(adev)) {
    return 0;
}

修改含义:

  • 系统处于通话模式,且真实Rockchip Voice Session正在运行时,保持原逻辑,不重复打开声卡。
  • 系统处于通话模式,但HFP Client通过AudioTrack播放时,允许重新打开Speaker PCM。

7. 修复后验证

修复后已完成多轮“手机端/车机端”音频切换,实车确认切回车机后喇叭声音能够恢复。

关键验证时间线一:

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秒再切回:

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状态也为干净:

Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c

本次验证镜像可能来自另一份源码或构建目录,或者已验证修改尚未同步回正式仓库。正式提交和后续重新编译前,应确认实际构建源码路径,并将上述一行条件修改同步到目标仓库,避免修复丢失。