基于Purple_Pi_OH2开发板前言HFPHands - Free Profile是蓝牙技术联盟Bluetooth SIG定义的一种蓝牙协议用于实现免提通信功能。HFP ClientHFP 客户端则是遵循 HFP 协议的设备或软件组件它可以与支持 HFP 的蓝牙设备如车载蓝牙免提系统、蓝牙耳机等进行通信以实现免提通话等功能。蓝牙 HFP 协议规定通话双方必须有两个角色AG音频网关Audio Gateway就是你的手机。它是音频数据的“源头”和“控制中心”拨号、挂断、调节音量都在这里。HF免提设备Hands-Free就是你的蓝牙耳机或车载中控。它是音频数据的“终点”和“执行者”播放对方声音、拾取你的说话声。严格来说“HFP Client客户端”指的就是上面的 HF免提设备也就是你的蓝牙耳机/车载机器里的那个软件协议栈。一、硬件连接硬件需要适配支持蓝牙PCM蓝牙模组的PCM接口连接主控I2S。要实现RK3576与蓝牙模块间的 HFP 通话硬件上必须连接 RK3576 的 SAI 接口配置为 PCM 从/主模式与蓝牙模块的 PCM 音频接口同时需保证UART 控制通道、电源使能以及时钟源MCLK的正确供给。RK3576 SAI 接口 (I2S/PCM 模式)蓝牙模块 PCM/I2S 接口信号说明SAI_SCLK(串行时钟)PCM_CLK / I2S_SCK位时钟由主设备提供SAI_FS(帧同步/字选择)PCM_SYNC / I2S_WS帧同步信号或左右声道选择SAI_SDI(串行数据输入)PCM_DO / I2S_DO数据输出RK3576 从蓝牙模块接收数据SAI_SDO(串行数据输出)PCM_DI / I2S_DI数据输入RK3576 向蓝牙模块发送数据PCM脉冲编码调制你可以把它理解为蓝牙芯片和主控之间“约定好的对话规则”。它是数字音频的原始裸数据格式未压缩的。蓝牙耳机通话时麦克风录到的是模拟信号蓝牙芯片会把它转成 PCM 数字信号然后通过几根电线发给主控RK3576。关键点PCM 是 HFP 通话的专用通道走的是实时语音流跟听歌的 I2S 不一样I2S 是给高保真音乐用的。SAI串行音频接口你可以把它理解为RK3576 芯片内部的一个“硬件耳朵”。它是 Rockchip 主控里专门用来接收/发送数字音频的物理模块。SAI可以配置成多种模式比如 I2S 模式听歌、PCM 模式通话、TDM 模式多通道。你设备树里写的sai2就是 RK3576 上的第 2 个 SAI 控制器。PCM你不用管它是什么芯片你只需要知道蓝牙模块上有 4 个引脚分别叫 PCM_CLK、PCM_SYNC、PCM_DI、PCM_DO。SAI你只需要在 RK3576 的原理图上找到SAI2_M0 那 4 个引脚SCLK、LRCK、SDI、SDO。你的工作把这 4 对引脚用 PCB 走线或飞线一对一焊起来这就是硬件连接。二、dts配置蓝牙 HFP免提协议通话的音频路径 用于实现蓝牙电话功能。整个配置分为三个核心部分/ { /* ... 你原有的其他节点 ... */ bt_sco: bt-sco { status okay; compatible delta,dfbmcs320; #sound-dai-cells 1; }; bt_sound: bt-sound { status okay; compatible simple-audio-card; simple-audio-card,format dsp_a; simple-audio-card,bitclock-inversion; simple-audio-card,mclk-fs 256; simple-audio-card,name rockchip,bt; simple-audio-card,cpu { sound-dai sai2; }; simple-audio-card,codec { sound-dai bt_sco 1; }; }; }; sai2 { status okay; pinctrl-names default; pinctrl-0 sai2m0_lrck sai2m0_sclk sai2m0_sdi sai2m0_sdo; };2.1、架构总览┌─────────────────────────────────────────────────────────────┐ │ Android 系统 │ │ 蓝牙协议栈 (BT Stack) ←→ TinyALSA ←→ Audio HAL │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Linux 内核 │ │ bt_sound (simple-audio-card) │ │ ┌──────────┴──────────┐ │ │ ▼ ▼ │ │ CPU端 (SAI2) 编解码器端 (bt_sco) │ │ (I2S 接口) (delta,dfbmcs320) │ └─────────────────────────────────────────────────────────────┘ │ ▼ 蓝牙芯片 (SCO 链路)2.2、第一部分 bt_sco 节点编解码器端bt_sco: bt-sco { status okay; compatible delta,dfbmcs320; #sound-dai-cells 1; };2.3、第二部分 bt_sound 节点音频卡bt_sound: bt-sound { status okay; compatible simple-audio-card; simple-audio-card,format dsp_a; simple-audio-card,bitclock-inversion; simple-audio-card,mclk-fs 256; simple-audio-card,name rockchip,bt; simple-audio-card,cpu { sound-dai sai2; }; simple-audio-card,codec { sound-dai bt_sco 1; }; };CPU 端和编解码器端simple-audio-card,cpu { sound-dai sai2; // 连接到 CPU 的 SAI2 接口 }; simple-audio-card,codec { sound-dai bt_sco 1; // 连接到 bt_sco 的第 1 个 DAI };- CPU 端 使用 sai2 即 Rockchip 芯片的 SAI2Serial Audio Interface作为 I2S 主设备- 编解码器端 使用 bt_sco 1 即蓝牙 SCO 编解码器的第 1 个 DAI 接口 #sound-dai-cells 1 定义了需要索引2.4、第三部分 sai2 节点CPU 音频接口sai2 { //启用 CPU 端的 SAI2 接口 status okay; pinctrl-names default; pinctrl-0 sai2m0_lrck sai2m0_sclk sai2m0_sdi sai2m0_sdo; };2.5、配置原理总结bt_sco 定义蓝牙 SCO 编解码器作为音频数据的终点/起点bt_sound 使用 simple-audio-card 框架建立 CPUSAI2和编解码器bt_sco之间的音频链路sai2 启用 CPU 端的 SAI2 接口并配置正确的引脚这样配置后Android 系统就能通过 TinyALSA 访问名为 rockchip,bt 的音频设备实现蓝牙通话的音频输入输出。三、系统配置3.1 设置主板为A2DP.sink 和avrcp.controllerA2DPAdvanced Audio Distribution Profile是一种重要的协议用于在两个蓝牙设备之间传输高质量的立体声音频。A2DP 中有 Sink接收器和 Source源两种角色。设置主板作为音频数据接收者它通过蓝牙接收 A2DP Source 发送过来的编码音频数据然后对其进行解码将解码后的音频信号转换为模拟音频信号最终通过音频输出设备如扬声器播放出来。AVRCPAudio/Video Remote Control Profile音频 / 视频远程控制配置文件它定义了蓝牙设备之间如何进行音频和视频播放的远程控制。一个支持 AVRCP 的设备可以作为控制器Controller向目标设备Target发送控制命令如播放、暂停、切换曲目等。主板端配置为AVRCP.controller控制端用于控制连接的手机播放音乐。--- a/device/rockchip/common/modules/bluetooth.mk b/device/rockchip/common/modules/bluetooth.mk -30,11 30,14 PRODUCT_PACKAGES \ # Set supported Bluetooth profiles to enabled PRODUCT_PRODUCT_PROPERTIES \ bluetooth.profile.asha.central.enabled?true \ - bluetooth.profile.a2dp.source.enabled?true \ - bluetooth.profile.avrcp.target.enabled?true \ bluetooth.profile.a2dp.source.enabled?false \ bluetooth.profile.avrcp.controller.enabled?true \ bluetooth.profile.avrcp.target.enabled?false \ bluetooth.profile.a2dp.sink.enabled?true \ bluetooth.profile.bas.client.enabled?true \3.2 配置a2dp_sink 自动聚焦packages/modules/Bluetooth/android/app/res/values/config.xml!-- If true, device requests audio focus and start avrcp updates on source start or play -- bool namea2dp_sink_automatically_request_audio_focustrue/bool当 A2DP Sink 设备如蓝牙耳机或音箱连接到 Android 设备并开始接收音频流时系统会自动为该 A2DP Sink 设备请求音频焦点。这意味着系统会暂停当前正在播放音频的其他应用程序确保 A2DP Sink 设备能够正常播放接收到的蓝牙音频。例如当你将蓝牙耳机连接到手机并开始通过蓝牙播放音乐时如果手机上原本有其他应用如音乐播放器或视频应用正在播放音频这些应用会自动暂停蓝牙耳机开始播放音乐。默认修改为BTIF_A2DP_SINK_FOCUS_GRANTED连接后默认认为获取到焦点开始尝试播放音乐。--- a/packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc b/packages/modules/Bluetooth/system/btif/src/btif_a2dp_sink.cc -93,7 93,7 class BtifA2dpSinkControlBlock { decode_alarm(nullptr), sample_rate(0), channel_count(0), - rx_focus_state(BTIF_A2DP_SINK_FOCUS_NOT_GRANTED), rx_focus_state(BTIF_A2DP_SINK_FOCUS_GRANTED), audio_track(nullptr), decoder_interface(nullptr) {} -108,7 108,7 class BtifA2dpSinkControlBlock { alarm_free(decode_alarm); decode_alarm nullptr; rx_flush false; - rx_focus_state BTIF_A2DP_SINK_FOCUS_NOT_GRANTED; rx_focus_state BTIF_A2DP_SINK_FOCUS_GRANTED; sample_rate 0; channel_count 0; decoder_interface nullptr; -552,7 552,7 static void btif_a2dp_sink_avk_handle_timer() { if (btif_a2dp_sink_cb.rx_focus_state BTIF_A2DP_SINK_FOCUS_NOT_GRANTED) { APPL_TRACE_DEBUG(%s: skipping frames since focus is not present, __func__); - return; //return; } /* Play only in BTIF_A2DP_SINK_FOCUS_GRANTED case */ if (btif_a2dp_sink_cb.rx_flush) {3.3 设置蓝牙设备类型BTA_DM_COD 修改设置为 {0x5A, 0x04, 0x06} // 手机端搜索到蓝牙显示带耳麦的耳机图标0x5A 服务类0x04 主设备类代表音频 / 视频设备0x06 次设备类表示特定类型的音频设备像耳机或者扬声器。#define BTA_AV_SINK_INCLUDED TRUE //配置为A2DP Sink设备#define ESCO_DATA_PATH_PCM 6博通方案中, ESCO_DATA_PATH_PCM 为1代表蓝牙芯片作为pcm master, 6作为pcm slave.--- a/device/rockchip/rk3562/bluetooth/bdroid_buildcfg.h b/device/rockchip/rk3562/bluetooth/bdroid_buildcfg.h -18,7 18,10 #define _BDROID_BUILDCFG_H #define BTM_DEF_LOCAL_NAME rk3562 -#define BTA_DM_COD {0x1A, 0x01, 0x10} #define BTA_DM_COD {0x5A, 0x04, 0x06} #define BTA_AV_SINK_INCLUDED TRUE #define ESCO_DATA_PATH_PCM 63.4 设置蓝牙音频支持8/16KHz采样率--- a/kernel-5.10/sound/soc/codecs/bt-sco.c b/kernel-5.10/sound/soc/codecs/bt-sco.c -27,14 27,14 static struct snd_soc_dai_driver bt_sco_dai[] { .stream_name Playback, .channels_min 1, .channels_max 2, - .rates SNDRV_PCM_RATE_8000, .rates SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .formats SNDRV_PCM_FMTBIT_S16_LE, }, .capture { .stream_name Capture, .channels_min 1, .channels_max 2, - .rates SNDRV_PCM_RATE_8000, .rates SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, .formats SNDRV_PCM_FMTBIT_S16_LE, }, },3.5 配置hfp角色为hf- bluetooth.profile.hfp.ag.enabled?true \ bluetooth.profile.hfp.ag.enabled?false \ bluetooth.profile.hfp.hf.enabled?true \HFPHands - Free Profile即蓝牙免提配置文件它定义了蓝牙设备间实现免提通话功能的通信标准。HFP 中有两个关键角色HFP HFHands - Free Unit和 HFP AGHands - Free Audio Gateway--- a/overlay/frameworks/base/core/res/res/values/config.xml b/overlay/frameworks/base/core/res/res/values/config.xml -20,7 20,7 resources !-- This device is not voice capable; its data-only. -- - bool nameconfig_voice_capablefalse/bool bool nameconfig_voice_capabletrue/bool !-- This device does not allow sms service. -- bool nameconfig_sms_capablefalse/bool