【android bluetooth 协议分析 14】【HFP详解 2】【蓝牙电话绝对音量详解】
一、 蓝牙电话绝对音量
本篇探索一下 蓝牙电话绝对音量相关的话题
1. 它是什么?
“蓝牙电话绝对音量”指的是——
当手机和车机通过 HFP(Hands-Free Profile)蓝牙电话协议 连接时,双方使用同一个音量控制通道,实现音量同步控制。
简单来说,就是:
当你在车机上调节通话音量时,手机的通话音量也会同时变化;
当你在手机上调节通话音量时,车机端的通话声音大小也会对应改变。
这种机制叫 Absolute Volume(绝对音量),它最早在 A2DP(音乐播放) 场景中引入,后来扩展到了 HFP(电话) 场景中。
2. 工作原理简述
-
当 HFP 链接建立时,车机(HFP AG, Audio Gateway) 和 手机(HFP HF, Hands-Free) 会互相报告各自支持的功能(BRSF 特性位)。
-
如果双方都支持 绝对音量功能位(通常由 AT+BIND/AT+BIEV 等命令表示),则:
- 车机会发送
AT+BIND/AT+BIEV等命令,建立音量同步信道;(低版本的 协议中是看不到这个流程的) - 当用户在任意一端调节音量时,系统会通过 AT 命令(如
AT+VGS、AT+VGM)实时同步到对方; - 最终使得车机与手机的通话音量保持一致。
- 车机会发送
举个例子👇:
你在车机上调节通话音量 → 车机发送 AT+VGS=10 → 手机接收并更新自身的通话音量 → 手机回 ACK。
3. 为何车机上“需要”它?
车机通常希望具备 一致的音量体验,原因如下:
| 场景 | 没有绝对音量会怎样 | 有绝对音量后改进点 |
|---|---|---|
| 通话中 | 车机音量和手机音量分离,调一个可能声音仍太大或太小 | 双方同步调整,用户听感更自然 |
| 切换蓝牙设备 | 手机记忆不同设备的音量状态,切换时差异大 | 保持统一的通话响度 |
| 多麦克风降噪系统 | 车机可根据实际输出音量调整回声消除(AEC)参数 | 提升语音清晰度和稳定性 |
| 因此,大多数 高端车机 或 OEM 系统 都会启用 电话绝对音量功能。 |
4. 为何有时“不需要”或“要关闭”?
在某些系统中,启用电话绝对音量反而会引发问题:
| 问题类型 | 原因说明 |
|---|---|
| 🔉 音量双重控制 | 手机和车机都再调音量,导致增益叠加或削弱 |
| 📱 手机实现不一致 | 不同品牌手机(尤其是部分国产 Android)对 AT 命令支持不完整,会造成同步错误 |
| 🔇 通话音太小或太大 | 当车机音量和手机音量的线性范围不一致时,同步导致失真或偏移 |
| 🎧 特殊场景冲突 | 某些车机在通话中共用音频通道(如 A2DP + HFP),绝对音量同步会扰乱整体音量曲线 |
因此,很多厂商在车机设置中会:
- 禁用电话绝对音量,改用车机端独立音量;
- 或在系统层面通过配置(如
persist.bluetooth.disable_abs_vol=true)关闭绝对音量。
二、日志欣赏
1. btsnoop
1. 车机告诉手机支持绝对音量
9562 2025-01-02 11:26:44.724160 ec:a7:ad:c2:01:06 (SA8295 Cockpit) vivoMobi_91:b0:62 (iQOO Neo3) HFP 26 Sent AT+BRSF=767 Bluetooth HFP Profile[Role: HS - Headset (2)]AT Stream: AT+BRSF=767\rCommand 0: +BRSFCommand Line Prefix: ATCommand: +BRSF (Bluetooth Retrieve Supported Features)Type: Action Command (0x003d)ParametersHS supported features bitmask: 767.... .... .... .... .... .... .... ...1 = EC and/or NR function: True.... .... .... .... .... .... .... ..1. = Call waiting or 3-way calling: True.... .... .... .... .... .... .... .1.. = CLI Presentation: True.... .... .... .... .... .... .... 1... = Voice Recognition Activation: True.... .... .... .... .... .... ...1 .... = Remote Volume Control: True // 支持绝对音量.... .... .... .... .... .... ..1. .... = Enhanced Call Status: True.... .... .... .... .... .... .1.. .... = Enhanced Call Control: True.... .... .... .... .... .... 1... .... = Codec Negotiation: True.... .... .... .... .... ...0 .... .... = HF Indicators: False.... .... .... .... .... ..1. .... .... = eSCO S4 (and T2) Settings Support: True0000 0000 0000 0000 0000 00.. .... .... = Reserved: 0x000000
2. 车机下发音量到 手机
9690 2025-01-02 11:26:44.927943 ec:a7:ad:c2:01:06 (SA8295 Cockpit) vivoMobi_91:b0:62 (iQOO Neo3) HFP 23 Sent AT+VGS=9 Frame 9690: 23 bytes on wire (184 bits), 23 bytes captured (184 bits)
Bluetooth
Bluetooth HCI H4
Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth RFCOMM Protocol
Bluetooth HFP Profile[Role: HS - Headset (2)]AT Stream: AT+VGS=9\rCommand 0: +VGSCommand Line Prefix: ATCommand: +VGS (Gain of Speaker)Type: Action Command (0x003d)ParametersGain: 9/15
3. 车机收到 手机上传的 音量
# 收到手机下发 的通话音量
10605 2025-01-02 11:29:24.959755 vivoMobi_91:b0:62 (iQOO Neo3) ec:a7:ad:c2:01:06 (SA8295 Cockpit) HFP 25 Rcvd +VGS: 14
三、源码分析
本节从 aosp 源码的角度出发,探讨一下 绝对音量的流程。
1. 收到手机侧音量变化
当我们收到 手机发给 车机的 命令时, 我们将调用 bta_hf_client_at_parse_start 来解析该命令, 那我们从 它开始分析我们车机在收到 手机的音量变化后的处理流程。
1.bta_hf_client_at_parse_start 讲解
- system/bta/hf_client/bta_hf_client_at.cc
/*** @brief 开始解析 HFP Client 收到的 AT 命令或响应。* * 该函数会循环解析 client_cb->at_cb.buf 中的内容,* 按顺序尝试多个解析器(bta_hf_client_parser_cb 数组中的回调),* 直到所有 AT 命令都被识别或跳过。* * 如果解析失败或数据异常(如无法跳过未知命令),* 会主动触发断开连接。*/
static void bta_hf_client_at_parse_start(tBTA_HF_CLIENT_CB* client_cb) {// 指向待解析 AT 命令字符串缓冲区char* buf = client_cb->at_cb.buf;// 打印函数名到日志(调试用)APPL_TRACE_DEBUG("%s", __func__);#ifdef BTA_HF_CLIENT_AT_DUMP// 如果开启了调试宏,则打印当前 AT 缓冲区的内容(用于分析 AT 数据)bta_hf_client_dump_at(client_cb);
#endif// 开始循环解析整个缓冲区内容(直到字符串结束符 '\0')while (*buf != '\0') {int i;char* tmp = NULL; // 用于记录解析函数返回的新指针位置// 遍历注册的所有 AT 解析器回调函数// 这些解析器对应不同类型的 AT 命令,如 +CIND、+BRSF、OK、ERROR 等for (i = 0; i < bta_hf_client_parser_cb_count; i++) {// 调用解析回调函数尝试解析当前 buftmp = bta_hf_client_parser_cb[i](client_cb, buf);// 如果返回 NULL,说明解析失败(命令格式不识别或出错)if (tmp == NULL) {APPL_TRACE_ERROR("HFPCient: AT event/reply parsing failed, skipping");// 尝试跳过未知命令(比如设备发了未定义的 AT)tmp = bta_hf_client_skip_unknown(client_cb, buf);break; // 跳出循环,准备进入下一轮}/*** tmp != buf 表示解析器“成功识别并消费”了一部分 AT 字符串,* 返回值 tmp 指向下一个未处理的字符位置。** tmp == buf 表示当前解析器认为这段数据不属于它负责的类型,* 需要换下一个解析器继续尝试。*/if (tmp != buf) {buf = tmp; // 移动 buf 到下一段未解析的数据起始处break;}}/*** 如果 tmp == NULL,说明:* 1. 所有解析器都无法识别该数据;* 2. bta_hf_client_skip_unknown() 也未能跳过;* 通常意味着收到的 AT 数据已损坏或设备异常。* 此时,为避免协议混乱,主动断开连接。*/if (tmp == NULL) {APPL_TRACE_ERROR("HFPCient: could not skip unknown AT event, disconnecting");// 清空 AT 解析器状态机bta_hf_client_at_reset(client_cb);// 构造关闭事件消息tBTA_HF_CLIENT_DATA msg = {};msg.hdr.layer_specific = client_cb->handle;// 通知状态机执行“断开”事件,关闭 HFP Client 连接bta_hf_client_sm_execute(BTA_HF_CLIENT_API_CLOSE_EVT, &msg);return;}// 更新 buf,继续解析剩余数据buf = tmp;}
}
举个通俗例子:
假设车机(HFP Server)发来一串 AT 响应:
+CIND: 1,0
OK
函数执行流程如下:
buf指向"+CIND: 1,0\nOK\n"- 解析器数组依次尝试:
- 第一个解析器识别
+CIND:,处理完后返回指向OK\n的指针;
- 第一个解析器识别
- 循环继续,第二次解析:
- 有解析器识别
OK,处理完后返回末尾;
- 有解析器识别
- 缓冲区解析完毕,循环退出。
对于当前案例, 我们从 btsnoop 中看到我们会收到如下内容:
+VGS: 14
2. bta_hf_client_parse_vgs 分析
- system/bta/hf_client/bta_hf_client_at.cc
static const tBTA_HF_CLIENT_PARSER_CALLBACK bta_hf_client_parser_cb[] = {bta_hf_client_parse_ok, bta_hf_client_parse_error,bta_hf_client_parse_ring, bta_hf_client_parse_brsf,bta_hf_client_parse_cind, bta_hf_client_parse_ciev,bta_hf_client_parse_chld, bta_hf_client_parse_bcs,bta_hf_client_parse_bsir, bta_hf_client_parse_cmeerror,bta_hf_client_parse_vgm, bta_hf_client_parse_vgme,bta_hf_client_parse_vgs /*这里*/, bta_hf_client_parse_vgse, bta_hf_client_parse_bvra, bta_hf_client_parse_clip,bta_hf_client_parse_ccwa, bta_hf_client_parse_cops,bta_hf_client_parse_binp, bta_hf_client_parse_clcc,bta_hf_client_parse_cnum, bta_hf_client_parse_btrh,bta_hf_client_parse_bind, bta_hf_client_parse_busy,bta_hf_client_parse_delayed, bta_hf_client_parse_no_carrier,bta_hf_client_parse_no_answer, bta_hf_client_parse_rejectlisted,bta_hf_client_process_unknown};static char* bta_hf_client_parse_vgs(tBTA_HF_CLIENT_CB* client_cb,char* buffer) {AT_CHECK_EVENT(buffer, "+VGS:"); // 这里会检查我们的事件是否为 +VGS:return bta_hf_client_parse_uint32(client_cb, buffer,bta_hf_client_handle_vgs /*对 +VGS: 的处理函数*/);
}// 收到 mic 大小的,命令
static void bta_hf_client_handle_vgm(tBTA_HF_CLIENT_CB* client_cb,uint32_t value) {APPL_TRACE_DEBUG("%s: %lu", __func__, value);if (value <= BTA_HF_CLIENT_VGM_MAX) {bta_hf_client_evt_val(client_cb, BTA_HF_CLIENT_MIC_EVT, value);}
}// 收到改变 spk 大小, 从 btsnoop 中看到,我们收到的是 VGS, 也就是改变扬声器的大小
static void bta_hf_client_handle_vgs(tBTA_HF_CLIENT_CB* client_cb,uint32_t value) {APPL_TRACE_DEBUG("%s: %lu", __func__, value);if (value <= BTA_HF_CLIENT_VGS_MAX) {bta_hf_client_evt_val(client_cb, BTA_HF_CLIENT_SPK_EVT, value);}
}
3. btif_hf_client_upstreams_evt
- system/btif/src/btif_hf_client.cc
static void btif_hf_client_upstreams_evt(uint16_t event, char* p_param) {tBTA_HF_CLIENT* p_data = (tBTA_HF_CLIENT*)p_param;btif_hf_client_cb_t* cb = btif_hf_client_get_cb_by_bda(p_data->bd_addr);...switch (event) {...case BTA_HF_CLIENT_MIC_EVT:HAL_CBACK(bt_hf_client_callbacks, volume_change_cb, &cb->peer_bda,BTHF_CLIENT_VOLUME_TYPE_MIC, p_data->val.value);break;case BTA_HF_CLIENT_SPK_EVT: // 这里会触发该事件HAL_CBACK(bt_hf_client_callbacks, volume_change_cb, &cb->peer_bda,BTHF_CLIENT_VOLUME_TYPE_SPK, p_data->val.value);// 向 jni 层通知 音量变化break;...}
}
4. volume_change_cb
- android/app/jni/com_android_bluetooth_hfpclient.cpp
static bthf_client_callbacks_t sBluetoothHfpClientCallbacks = {sizeof(sBluetoothHfpClientCallbacks),connection_state_cb,audio_state_cb,vr_cmd_cb,network_state_cb,network_roaming_cb,network_signal_cb,battery_level_cb,current_operator_cb,call_cb,callsetup_cb,callheld_cb,resp_and_hold_cb,clip_cb,call_waiting_cb,current_calls_cb,volume_change_cb, // 这里cmd_complete_cb,subscriber_info_cb,in_band_ring_cb,last_voice_tag_number_cb,ring_indication_cb,unknown_event_cb,
};static void volume_change_cb(const RawAddress* bd_addr,bthf_client_volume_type_t type, int volume) {std::shared_lock<std::shared_mutex> lock(callbacks_mutex);CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid() || mCallbacksObj == NULL) return;ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));if (!addr.get()) return;// 上报到 java 层sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeChange, (jint)type,(jint)volume, addr.get());
}static void classInitNative(JNIEnv* env, jclass clazz) {...method_onVolumeChange = env->GetMethodID(clazz, "onVolumeChange", "(II[B)V");...
}
- android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
void onVolumeChange(int type, int volume, byte[] address) {// 封装为 协议栈事件StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_VOLUME_CHANGED);event.valueInt = type;event.valueInt2 = volume;event.device = getDevice(address);if (DBG) {Log.d(TAG, "onVolumeChange: event " + event);}HeadsetClientService service = HeadsetClientService.getHeadsetClientService();if (service != null) {service.messageFromNative(event); // 通过他上报到 状态机} else {Log.w(TAG, "onVolumeChange: Ignoring message because service not available: " + event);}}
5. HeadsetClientStateMachine.java
收到 AG 发送过来的音量。
01-02 11:29:29.459170 3854 4984 I HeadsetClientStateMachine: Connected process message: 100
01-02 11:29:29.459178 3854 4984 I HeadsetClientStateMachine: Connected: event type: 16
01-02 11:29:29.459199 3854 4984 I HeadsetClientStateMachine: AM volume set to 6
01-02 11:29:29.459233 3854 4984 W CAR.L : setGroupVolume zoneId(0) groupId(3) index(6) flags(0) com.android.bluetooth
- android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
class Connected extends State {int mCommandedSpeakerVolume = -1;...public synchronized boolean processMessage(Message message) {logD("Connected process message: " + message.what);if (DBG) {if (mCurrentDevice == null) {Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");return NOT_HANDLED;}}switch (message.what) {...case StackEvent.STACK_EVENT: // 收到 协议栈底层数据Intent intent = null;StackEvent event = (StackEvent) message.obj;logD("Connected: event type: " + event.type);switch (event.type) {...case StackEvent.EVENT_TYPE_VOLUME_CHANGED: // 收到音量变化后if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {if (mDisableHfpAbsoluteVolume) { // 如果绝对音量是关的, 直接退出logD("StackEvent.EVENT_TYPE_VOLUME_CHANGED disable absolute volume.");break;}mCommandedSpeakerVolume = event.valueInt2;logD("AM volume set to " + mCommandedSpeakerVolume);if (mService.isAutomotive()) { // 如果是车机try {// 直接调用 CarAudioManager.setGroupVolume 去设置音量mCarAudioManager.setGroupVolume(mVolumeGroupId, +mCommandedSpeakerVolume, 0);} catch (CarNotConnectedException e) {Log.e(TAG, "Car is not connected!", e);}} } else if (event.valueInt== HeadsetClientHalConstants.VOLUME_TYPE_MIC) {mAudioManager.setMicrophoneMute(event.valueInt2 == 0);}break;}break;default:return NOT_HANDLED;}return HANDLED;}}
最后我们 从手机 传递过来的 通话音量将通过, 如下,告知 audio 侧。
mCarAudioManager.setGroupVolume(mVolumeGroupId, +mCommandedSpeakerVolume, 0);
2. 下发车机的通话音量到手机
本节 我们来看 一下, 车机是如何把音量下发到手机的。
1. 蓝牙如何拿到 audio 侧通话的音量
- android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
// 1. HeadsetClientStateMachine 构造函数HeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService,Looper looper, NativeInterface nativeInterface) {super(TAG, looper);mService = context;mNativeInterface = nativeInterface;mAudioManager = mService.getAudioManager();mHeadsetService = headsetService;if (mService.isAutomotive()) {mCar = Car.createCar(context, mConnection); // 这里回去 监听 carserver 的启动状态mCar.connect();}}private final ServiceConnection mConnection = new ServiceConnection() {// 2. 当 carserver 启动成功后,将调用到这里@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {try {int amVol = 0;int hfVol = 0;mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);mVolumeGroupId = mCarAudioManager.getVolumeGroupIdForUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION);logD("mVolumeGroupId:" + mVolumeGroupId);sMaxAmVcVol = mCarAudioManager.getGroupMaxVolume(mVolumeGroupId);sMinAmVcVol = mCarAudioManager.getGroupMinVolume(mVolumeGroupId);// 这个里面 会向 CarAudioManager 中注册 volume callbackmCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);amVol = mCarAudioManager.getGroupVolume(mVolumeGroupId);hfVol = amToHfVol(amVol);logD("Setting volume to audio manager: " + amVol+ " hands free: " + hfVol);mAudioManager.setParameters("hfp_volume=" + hfVol);} catch (CarNotConnectedException e) {Log.e(TAG, "Car is not connected!", e);}}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.e(TAG, "Car service is disconnected");}};// 3. 当 audio 的接口 设置 通话音量时,此时 onGroupVolumeChanged 将被回调private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =new CarAudioManager.CarVolumeCallback() {@Overridepublic void onGroupVolumeChanged(int zoneId, int groupId, int flags) {logD("zoneId:" + zoneId + ", groupId:" + groupId);if (zoneId == CarAudioManager.PRIMARY_AUDIO_ZONE && groupId == mVolumeGroupId){int streamValue = 0;try {streamValue = mCarAudioManager.getGroupVolume(zoneId, groupId);} catch (CarNotConnectedException e) {Log.e(TAG, "Car is not connected", e);} catch (NullPointerException e) {Log.e(TAG, "mCarAudioManager is NULL!", e);}int hfVol = amToHfVol(streamValue);if (mSyncHfpVolWithRing) {hfVol = streamValue;}logD("Setting volume to audio manager: " + streamValue+ " hands free: " + hfVol);// 4. 此时包装为 SET_SPEAKER_VOLUME 进行处理sendMessage(SET_SPEAKER_VOLUME, streamValue);}}};
2. 处理 SET_SPEAKER_VOLUME 事件
class Connected extends State {int mCommandedSpeakerVolume = -1;public synchronized boolean processMessage(Message message) {logD("Connected process message: " + message.what);if (DBG) {if (mCurrentDevice == null) {Log.e(TAG, "ERROR: mCurrentDevice is null in Connected");return NOT_HANDLED;}}switch (message.what) {...case SET_SPEAKER_VOLUME:// This message should always contain the volume in AudioManager max normalized.if (mDisableHfpAbsoluteVolume) {break;}int amVol = message.arg1;int hfVol = amToHfVol(amVol);if (amVol != mCommandedSpeakerVolume) {logD("Volume" + amVol + ":" + mCommandedSpeakerVolume);// Volume was changed by a 3rd partymCommandedSpeakerVolume = -1;// 这里将音量 直接设置到 底层if (mNativeInterface.setVolume(mCurrentDevice,HeadsetClientHalConstants.VOLUME_TYPE_SPK, hfVol)) {addQueuedAction(SET_SPEAKER_VOLUME);}}break;break;default:return NOT_HANDLED;}return HANDLED;}}
3. setVolumeNative
- android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
public boolean setVolume(BluetoothDevice device, int volumeType, int volume) {return setVolumeNative(getByteAddress(device), volumeType, volume);}private static native boolean setVolumeNative(byte[] address, int volumeType, int volume);
- android/app/jni/com_android_bluetooth_hfpclient.cpp
static jboolean setVolumeNative(JNIEnv* env, jobject object, jbyteArray address,jint volume_type, jint volume) {std::shared_lock<std::shared_mutex> lock(interface_mutex);if (!sBluetoothHfpClientInterface) return JNI_FALSE;jbyte* addr = env->GetByteArrayElements(address, NULL);if (!addr) {jniThrowIOException(env, EINVAL);return JNI_FALSE;}// 1. 设置 音量bt_status_t status = sBluetoothHfpClientInterface->volume_control((const RawAddress*)addr, (bthf_client_volume_type_t)volume_type, volume);if (status != BT_STATUS_SUCCESS) {ALOGE("FAILED to control volume, status: %d", status);}env->ReleaseByteArrayElements(address, addr, 0);return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
4. volume_control
- system/btif/src/btif_hf_client.cc
static bt_status_t volume_control(const RawAddress* bd_addr,bthf_client_volume_type_t type, int volume) {btif_hf_client_cb_t* cb = btif_hf_client_get_cb_by_bda(*bd_addr);if (cb == NULL || !is_connected(cb)) return BT_STATUS_FAIL;CHECK_BTHF_CLIENT_SLC_CONNECTED(cb);switch (type) {case BTHF_CLIENT_VOLUME_TYPE_SPK: // 调用这里BTA_HfClientSendAT(cb->handle, BTA_HF_CLIENT_AT_CMD_VGS, volume, 0, NULL);break;case BTHF_CLIENT_VOLUME_TYPE_MIC:BTA_HfClientSendAT(cb->handle, BTA_HF_CLIENT_AT_CMD_VGM, volume, 0, NULL);break;default:return BT_STATUS_UNSUPPORTED;}return BT_STATUS_SUCCESS;
}
void BTA_HfClientSendAT(uint16_t handle, tBTA_HF_CLIENT_AT_CMD_TYPE at,uint32_t val1, uint32_t val2, const char* str) {tBTA_HF_CLIENT_DATA_VAL* p_buf =(tBTA_HF_CLIENT_DATA_VAL*)osi_malloc(sizeof(tBTA_HF_CLIENT_DATA_VAL));p_buf->hdr.event = BTA_HF_CLIENT_SEND_AT_CMD_EVT; // 注意这个事件p_buf->uint8_val = at;p_buf->uint32_val1 = val1;p_buf->uint32_val2 = val2;if (str) {strlcpy(p_buf->str, str, BTA_HF_CLIENT_NUMBER_LEN + 1);p_buf->str[BTA_HF_CLIENT_NUMBER_LEN] = '\0';} else {p_buf->str[0] = '\0';}p_buf->hdr.layer_specific = handle;bta_sys_sendmsg(p_buf);
}
bta_sys_sendmsg : 将 BTA_HF_CLIENT_SEND_AT_CMD_EVT 事件提交给 bta 层处理。
5. Bta 层处理
- system/bta/hf_client/bta_hf_client_api.cc
static const tBTA_SYS_REG bta_hf_client_reg = {bta_hf_client_hdl_event,BTA_HfClientDisable};
/********************************************************************************* Function bta_hf_client_hdl_event** Description Data HF Client main event handling function.*** Returns bool*******************************************************************************/
bool bta_hf_client_hdl_event(BT_HDR_RIGID* p_msg) {APPL_TRACE_DEBUG("%s: %s (0x%x)", __func__,bta_hf_client_evt_str(p_msg->event), p_msg->event);bta_hf_client_sm_execute(p_msg->event, (tBTA_HF_CLIENT_DATA*)p_msg);return true;
}
6. bta_hf_client_sm_execute
- system/bta/hf_client/bta_hf_client_main.cc
/********************************************************************************* Function bta_hf_client_sm_execute** Description State machine event handling function for HF Client*** Returns void*******************************************************************************/
void bta_hf_client_sm_execute(uint16_t event, tBTA_HF_CLIENT_DATA* p_data) {.../* execute action functions */for (i = 0; i < BTA_HF_CLIENT_ACTIONS; i++) {action = state_table[event][i];if (action != BTA_HF_CLIENT_IGNORE) {(*bta_hf_client_action[action])(p_data); // 调用 对应的处理函数} else {break;}}...
}
const tBTA_HF_CLIENT_ACTION bta_hf_client_action[] = {/* BTA_HF_CLIENT_RFC_DO_CLOSE */ bta_hf_client_rfc_do_close,/* BTA_HF_CLIENT_START_CLOSE */ bta_hf_client_start_close,/* BTA_HF_CLIENT_START_OPEN */ bta_hf_client_start_open,/* BTA_HF_CLIENT_RFC_ACP_OPEN */ bta_hf_client_rfc_acp_open,/* BTA_HF_CLIENT_SCO_LISTEN */ NULL,/* BTA_HF_CLIENT_SCO_CONN_OPEN */ bta_hf_client_sco_conn_open,/* BTA_HF_CLIENT_SCO_CONN_CLOSE*/ bta_hf_client_sco_conn_close,/* BTA_HF_CLIENT_SCO_OPEN */ bta_hf_client_sco_open,/* BTA_HF_CLIENT_SCO_CLOSE */ bta_hf_client_sco_close,/* BTA_HF_CLIENT_FREE_DB */ bta_hf_client_free_db,/* BTA_HF_CLIENT_OPEN_FAIL */ bta_hf_client_open_fail,/* BTA_HF_CLIENT_RFC_OPEN */ bta_hf_client_rfc_open,/* BTA_HF_CLIENT_RFC_FAIL */ bta_hf_client_rfc_fail,/* BTA_HF_CLIENT_DISC_INT_RES */ bta_hf_client_disc_int_res,/* BTA_HF_CLIENT_RFC_DO_OPEN */ bta_hf_client_rfc_do_open,/* BTA_HF_CLIENT_DISC_FAIL */ bta_hf_client_disc_fail,/* BTA_HF_CLIENT_RFC_CLOSE */ bta_hf_client_rfc_close,/* BTA_HF_CLIENT_RFC_DATA */ bta_hf_client_rfc_data,/* BTA_HF_CLIENT_DISC_ACP_RES */ bta_hf_client_disc_acp_res,/* BTA_HF_CLIENT_SVC_CONN_OPEN */ bta_hf_client_svc_conn_open,/* BTA_HF_CLIENT_SEND_AT_CMD */ bta_hf_client_send_at_cmd, // 这里将会被调用
};
7. bta_hf_client_send_at_cmd
- system/bta/hf_client/bta_hf_client_at.cc
void bta_hf_client_send_at_cmd(tBTA_HF_CLIENT_DATA* p_data) {tBTA_HF_CLIENT_CB* client_cb =bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific);if (!client_cb) {APPL_TRACE_ERROR("%s: cb not found for handle %d", __func__,p_data->hdr.layer_specific);return;}tBTA_HF_CLIENT_DATA_VAL* p_val = (tBTA_HF_CLIENT_DATA_VAL*)p_data;char buf[BTA_HF_CLIENT_AT_MAX_LEN];APPL_TRACE_DEBUG("%s: at cmd: %d", __func__, p_val->uint8_val);switch (p_val->uint8_val) {...case BTA_HF_CLIENT_AT_CMD_VGS:bta_hf_client_send_at_vgs(client_cb, p_val->uint32_val1);break;...}
}
8. bta_hf_client_send_at_vgs
- system/bta/hf_client/bta_hf_client_at.cc
void bta_hf_client_send_at_vgs(tBTA_HF_CLIENT_CB* client_cb, uint32_t volume) {char buf[BTA_HF_CLIENT_AT_MAX_LEN];int at_len;APPL_TRACE_DEBUG("%s", __func__);at_len = snprintf(buf, sizeof(buf), "AT+VGS=%u\r", volume);if (at_len < 0) {APPL_TRACE_ERROR("%s: AT command Framing error", __func__);return;}bta_hf_client_send_at(client_cb, BTA_HF_CLIENT_AT_VGS, buf, at_len);
}
这里将 给手机 下发 AT+VGS=11: 11 就是 车机设置下来的
