一、 蓝牙电话绝对音量
本篇探索一下 蓝牙电话绝对音量相关的话题
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. 车机告诉手机支持绝对音量
c
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\r
Command 0: +BRSF
Command Line Prefix: AT
Command: +BRSF (Bluetooth Retrieve Supported Features)
Type: Action Command (0x003d)
Parameters
HS 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: True
0000 0000 0000 0000 0000 00.. .... .... = Reserved: 0x000000
2. 车机下发音量到 手机
c
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\r
Command 0: +VGS
Command Line Prefix: AT
Command: +VGS (Gain of Speaker)
Type: Action Command (0x003d)
Parameters
Gain: 9/15
3. 车机收到 手机上传的 音量
shell
# 收到手机下发 的通话音量
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
c
/**
* @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++) {
// 调用解析回调函数尝试解析当前 buf
tmp = 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 响应:
shell
+CIND: 1,0
OK
函数执行流程如下:
buf指向"+CIND: 1,0\nOK\n"- 解析器数组依次尝试:
- 第一个解析器识别
+CIND:,处理完后返回指向OK\n的指针;
- 第一个解析器识别
- 循环继续,第二次解析:
- 有解析器识别
OK,处理完后返回末尾;
- 有解析器识别
- 缓冲区解析完毕,循环退出。
对于当前案例, 我们从 btsnoop 中看到我们会收到如下内容:
shell
+VGS: 14
2. bta_hf_client_parse_vgs 分析
- system/bta/hf_client/bta_hf_client_at.cc
c
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
c
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
c
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
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 发送过来的音量。
shell
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
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 侧。
java
mCarAudioManager.setGroupVolume(mVolumeGroupId, +mCommandedSpeakerVolume, 0);
2. 下发车机的通话音量到手机
本节 我们来看 一下, 车机是如何把音量下发到手机的。
1. 蓝牙如何拿到 audio 侧通话的音量
- android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
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 启动成功后,将调用到这里
@Override
public 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 callback
mCarAudioManager.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);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "Car service is disconnected");
}
};
// 3. 当 audio 的接口 设置 通话音量时,此时 onGroupVolumeChanged 将被回调
private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
new CarAudioManager.CarVolumeCallback() {
@Override
public 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 事件
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 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 party
mCommandedSpeakerVolume = -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
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
c
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
c
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;
}
c
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
c
static const tBTA_SYS_REG bta_hf_client_reg = {bta_hf_client_hdl_event,
BTA_HfClientDisable};
c
/*******************************************************************************
*
* 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
c
/*******************************************************************************
*
* 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;
}
}
...
}
c
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
c
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
c
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 就是 车机设置下来的