【android bluetooth 协议分析 14】【HFP详解 2】【蓝牙电话绝对音量详解】

一、 蓝牙电话绝对音量

本篇探索一下 蓝牙电话绝对音量相关的话题

1. 它是什么?

"蓝牙电话绝对音量 "指的是------

当手机和车机通过 HFP(Hands-Free Profile)蓝牙电话协议 连接时,双方使用同一个音量控制通道 ,实现音量同步控制

简单来说,就是:

当你在车机上调节通话音量时,手机的通话音量也会同时变化;

当你在手机上调节通话音量时,车机端的通话声音大小也会对应改变。

这种机制叫 Absolute Volume(绝对音量) ,它最早在 A2DP(音乐播放) 场景中引入,后来扩展到了 HFP(电话) 场景中。

2. 工作原理简述

  1. 当 HFP 链接建立时,车机(HFP AG, Audio Gateway)手机(HFP HF, Hands-Free) 会互相报告各自支持的功能(BRSF 特性位)。

  2. 如果双方都支持 绝对音量功能位(通常由 AT+BIND/AT+BIEV 等命令表示),则:

    • 车机会发送 AT+BIND / AT+BIEV 等命令,建立音量同步信道;(低版本的 协议中是看不到这个流程的)
    • 当用户在任意一端调节音量时,系统会通过 AT 命令(如 AT+VGSAT+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

函数执行流程如下:

  1. buf 指向 "+CIND: 1,0\nOK\n"
  2. 解析器数组依次尝试:
    • 第一个解析器识别 +CIND:,处理完后返回指向 OK\n 的指针;
  3. 循环继续,第二次解析:
    • 有解析器识别 OK,处理完后返回末尾;
  4. 缓冲区解析完毕,循环退出。

对于当前案例, 我们从 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 就是 车机设置下来的

相关推荐
2501_916007477 小时前
Fastlane 结合 开心上架 命令行版本实现跨平台上传发布 iOS App
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张8 小时前
iOS 26 内存占用监控 多工具协同下的性能稳定性分析实战
android·macos·ios·小程序·uni-app·cocoa·iphone
奔跑中的蜗牛6668 小时前
一次崩溃率暴涨 10 倍的线上事故:从“无堆栈”到精准定位,到光速解决
android
Digitally8 小时前
7 种方法:如何将视频从电脑传输到安卓手机
android·电脑·音视频
叶羽西8 小时前
Android15 Camera系统调试操作
android
用户69371750013848 小时前
彻底搞懂api和testImplementation的区别
android
用户69371750013848 小时前
Android闪退数据处理必备:8个优质开源项目推荐
android
用户69371750013848 小时前
Android崩溃前关键数据拯救:从原理到落地的完整方案
android
杜子不疼.8 小时前
【Rust】异步处理器(Handler)实现:从 Future 本质到 axum 实战
android·开发语言·rust