【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 就是 车机设置下来的

相关推荐
阿巴斯甜15 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker15 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952716 小时前
Andorid Google 登录接入文档
android
黄林晴18 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android