【蓝牙电话】【蓝牙绝对音量】【案例一:三方通话时无法调节铃声音量】

1. 背景描述

  1. 手机A连着车机蓝牙。
  2. 手机A 拨打10086 后,并接通, 此时 手机B: 13632375734 拨打手机A。(三方电话)
  3. 此时车机 上调节 电话音量, 发现此时 嘟嘟嘟的来电铃声无法调节音量。

在这里插入图片描述

1.1 问题拆分

  1. 此时嘟嘟声音是 车机自己的 telecom 中触发的声音还是 通过 hfp sco 传递过来的声音?
  2. 为何此时从车机侧调节电话铃声的音量,无效?

接下来我们将从这两个角度展开来讨论。

2. 嘟嘟声的来源

  1. 要搞清楚 嘟嘟声的来源, 首先需要判断清楚, 在电话 B 到来时,手机A和车机 hfp 有没有断开 sco.
    • 场景一:如果电话B 在响铃的过程中, hfp 断开了 sco. 那么这个 嘟嘟声音就是车机自己 Telecom 服务根据电话的状态自己播放的铃声。那么此时 铃声音量的控制 和 手机A 无关。 是车机本地自己 audio 出现了问题。
    • 场景二:如果电话 B 在响铃的过程中, hfp 并没有断开 sco. 那么此时就要继续来判断, 车机是否给手机A下发了调节电话音量的命令。 如果下发了命令,手机是否响应,并是否真正的调节手机铃声音量。

2.1 logcat 日志分析

下面是我们从 logcat 中抓到的日志

# 手机拨打 10086 的时间点
01-08 22:14:04.909927  1635  3838 I Telecom : CallsManager: setCallState NEW -> DIALING, call: [Call id=TC@18, state=NEW, tpac=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, cmgr=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, handle=tel:***86, vidst=A, childs(0), has_parent(false), cap=[ sup_hld mut], prop=[]], voip=false: (...->CS.crCo->H.CS.crCo)->CSW.hCCC(cab/cast)@E-E-CsY

# 此时建立了 sco
01-08 22:14:04.957468  2004  5255 I bt_stack: [INFO:bta_av_main.cc(913)] bta_av_sco_chg_cback: status=BTA_SYS_SCO_OPEN, num_links=1
01-08 22:14:05.013229  2004  5255 I bt_stack: [INFO:bta_av_main.cc(913)] bta_av_sco_chg_cback: status=BTA_SYS_SCO_OPEN, num_links=1
01-08 22:14:05.016343  2004  5596 I HeadsetClientStateMachine: hfp_enable=true

# 此时 10086 处于接通的状态
01-08 22:14:07.643549  1635  4644 I Telecom : CallsManager: setCallState DIALING -> ACTIVE, call: [Call id=TC@18, state=DIALING, tpac=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, cmgr=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, handle=tel:***86, vidst=A, childs(0), has_parent(false), cap=[ sup_hld mut], prop=[]], voip=false: CSW.sA(cab)@CtU


# 此时电话 B 响铃
01-08 22:14:10.506814  1635 21125 I Telecom : CallsManager: setCallState NEW -> RINGING, call: [Call id=TC@19, state=NEW, tpac=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, cmgr=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, handle=tel:*********34, vidst=A, childs(0), has_parent(false), cap=[ sup_hld mut], prop=[]], voip=false: (...->CS.crCo->H.CS.crCo)->CSW.hCCC->CILH.sL->CILH.oQC->BCF.gBS->BCF.gBS->ICFG.sF->ICFG.sF->ICFG.sF->ICFG.sF->ICFG.sF->ICFG.sF->ICFG.sF(cast)@E-E-Ctg

# 此时电话 B 结束
01-08 22:14:36.726512  1635  1681 I Telecom : CallsManager: setCallState RINGING -> DISCONNECTED, call: [Call id=TC@19, state=RINGING, tpac=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, cmgr=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, handle=tel:*********34, vidst=A, childs(0), has_parent(false), cap=[ sup_hld mut], prop=[]], voip=false: CSW.sDc(cab)@CxY


# 此时 sco 断开
01-08 22:14:39.076439  2004  5255 I bt_stack: [INFO:bta_av_main.cc(913)] bta_av_sco_chg_cback: status=BTA_SYS_SCO_CLOSE, num_links=0
01-08 22:14:39.076500  2004  5255 I bt_stack: [INFO:bta_av_main.cc(913)] bta_av_sco_chg_cback: status=BTA_SYS_SCO_CLOSE, num_links=0
01-08 22:14:39.076764  2004  5596 I HeadsetClientStateMachine: hfp_enable=false

# 10086 电话结束
01-08 22:14:39.111735  1635  4577 I Telecom : CallsManager: setCallState DISCONNECTING -> DISCONNECTED, call: [Call id=TC@18, state=DISCONNECTING, tpac=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, cmgr=ComponentInfo{com.android.bluetooth/com.android.bluetooth.hfpclient.HfpClientConnectionService}, ***, UserHandle{0}, handle=tel:***86, vidst=A, childs(0), has_parent(false), cap=[ sup_hld mut], prop=[]], voip=false: CSW.sDc(cab)@Cyk

2.2 btsnoop 日志分析

通过btsnoop 我们再次来看一下这个过程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 从logcat 和 btsnoop 中我们可以清楚的看到 , 是我们场景二描述的情况。 也就是 手机B来电时, 车机 和 手机 A 并没有断开 sco (音频通路).

这里确定了嘟嘟声的来源是 手机 A通过 sco 链路 传递到 我们的车机。那为何 无法调节该铃声的音量呢?

  1. 车机没有下发 音量到 手机 A?
  2. 还是手机A 收到了车机的音量,但是没有处理呢?

带着上面两个疑问,继续展开。

3. 音量调节

3.1 hfp 远程音量调节协议说明

我们先补充一点hfp 音量调节的知识,看看 SIG官方是如何定义远程音量调节的。

根据hfp 协议和场景,车机和手机的角色定义如下:

  • HF(车机)
  • AG (手机)

3.1.1 HF和AG互换支持的特性

手机 A 和 车机 在建立 hfp 连接时:

  1. HF -> AG : 通过 AT+BRSF 命令告诉 AG 此时HF支持那些特性, 其中有一条就是 是否支持 远程音量调节
  2. AG -> HF: 同时 AG 也会通过 +BRSF 告知HF, 此时 AG 支持那些特性。

在这里插入图片描述

在这里插入图片描述

从上面的协议来看, HF 需要通过 AT+BRSF 告知 AG 自己支持
Remote audio volume control.

3.1.2 手机调节车机音量

在这里插入图片描述

AG 发送+VGM:n来通知microphone音量变化,发送+VGS:n来通知speaker音量变化(n取值范围0-15)。

3.1.3 车机来调节手机音量

在这里插入图片描述

车机 通过 AT+VGS/VGM 来控制手机音量,具体参考AT+VGS =n和 AT+VGM=n (n取值范围0-15)。

3.2 实际情况

在这里插入图片描述

在这里插入图片描述

  • HF -> AG: 车机向手机下发了调节音量的命令, 并且 AG->HF 回复了ok.

也就是说, 车机正常向手机下发了音量调节, 手机也回复收到了, 但是手机自己没有去处理 自己的音量。

这个问题是手机本身的问题, 和车机没有任何关系。

其实分析到这里, 这篇文章本身可以结束了。 但是有些朋友比较好奇, HF -> AG 下发音量变化的代码实现是什么样子的。 好奇害死猫啊。哈哈。出于这个欲望, 我们进一步来简单看一下对应代码的实现。

4. 具体代码分析

  • android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
HeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService,
Looper looper, NativeInterface nativeInterface) {

	...
	if (mService.isAutomotive()) {
		mCar = Car.createCar(context, mConnection);
		mCar.connect();
	}

	...
}

  • 在 HeadsetClientStateMachine 构造函数里面 会去连接 CarServer 此时注册了一个回调 mConnection
    private final ServiceConnection mConnection = new ServiceConnection() {
        @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);

				// 这个位置会注册  mVolumeChangeCallback
				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);
            }
        }
        
}
  • 当 HeadsetClientSM 和 CarServer连接之后, 会调用这里的 onServiceConnected
  • 此时 mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback); 会注册 音量改变回调

当 CarAudioManager 中改变音量时,就会调用这里的回调


    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);
                    mAudioManager.setParameters("hfp_volume=" + hfVol);
                    // 此时会触发 SET_SPEAKER_VOLUME 事件
                    sendMessage(SET_SPEAKER_VOLUME, streamValue);
                }
            }
        };

...
}
    class Connected extends State {
    
    
        public synchronized boolean processMessage(Message message) {
                        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;        
        }
    
    }

最终会通过 mNativeInterface.setVolume 去触发native 协议栈来去设置音量。

  • android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
public boolean setVolume(BluetoothDevice device, int volumeType, int volume) {

return setVolumeNative(getByteAddress(device), volumeType, 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;
  }

	// 最终音量通过这里设置下去
  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;
}
/*******************************************************************************
 *
 * Function         volume_control
 *
 * Description      volume control
 *
 * Returns          bt_status_t
 *
 ******************************************************************************/
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:
	  // 这个地方就是组合 的地方 AT+VGS = 1 
      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;
}
/*******************************************************************************
 *
 * Function         BTA_HfClientSendAT
 *
 * Description      send AT command
 *
 *
 * Returns          void
 *
 ******************************************************************************/
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);
}

在这里插入图片描述

# 1. CarAudioManager 中音量改变后, 会触发这里的回调
01-08 22:14:34.713261  2004  2004 I HeadsetClientStateMachine: zoneId:0, groupId:3
01-08 22:14:34.713394  2004  2004 I HeadsetClientStateMachine: AM -> HF 4 1
01-08 22:14:34.713427  2004  2004 I HeadsetClientStateMachine: Setting volume to audio manager: 4 hands free: 4
01-08 22:14:34.717214  2004  5596 I HeadsetClientStateMachine: AudioOn process message: 8
01-08 22:14:34.717231  2004  5596 I HeadsetClientStateMachine: Connected process message: 8
01-08 22:14:34.717244  2004  5596 I HeadsetClientStateMachine: AM -> HF 4 1
01-08 22:14:34.717251  2004  5596 I HeadsetClientStateMachine: Volume4:-1
01-08 22:14:34.717435  2004  5255 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: bta_sys_event: Event 0x1b0e
01-08 22:14:34.717470  2004  5255 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: bta_hf_client_hdl_event: BTA_HF_CLIENT_SEND_AT_CMD_EVT (0x1b0e)
01-08 22:14:34.717479  2004  5255 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: HF Client evt : State 2 (BTA_HF_CLIENT_OPEN_ST), Event 0x1b0e (BTA_HF_CLIENT_SEND_AT_CMD_EVT)
01-08 22:14:34.717487  2004  5255 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: bta_hf_client_send_at_cmd: at cmd: 10

# 这里组合好 AT+VGS = 1 
01-08 22:14:34.717495  2004  5255 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: bta_hf_client_send_at_vgs
01-08 22:14:34.717503  2004  5255 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: bta_hf_client_send_at 14
01-08 22:14:34.717510  2004  5255 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: bta_hf_client_send_at: writing port data to 8

# 最终通过 rfcomm 发送出去
01-08 22:14:34.717518  2004  5255 I bt_rfcomm: packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: PORT_WriteData() max_len:9
01-08 22:14:34.717527  2004  5255 I bt_rfcomm: packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: PORT_WriteData 9 bytes
01-08 22:14:34.717533  2004  5255 I bt_rfcomm: packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: PORT_Write : Data is being sent
01-08 22:14:34.760756  2004  5255 I bt_rfcomm: packages/modules/Bluetooth/system/main/bte_logmsg.cc:201 LogMsg: RFCOMM_BufDataInd: Handling UIH event, buf_len=6, credit=0
01-08 22:14:34.760772  2004  5255 I bt_rfcomm: packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: PORT_DataInd with data length 6, p_mcb:0x73029df920,p_port:0x73029de7e8,dlci:16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值