1. 背景描述
- 手机A连着车机蓝牙。
- 手机A 拨打10086 后,并接通, 此时 手机B: 13632375734 拨打手机A。(三方电话)
- 此时车机 上调节 电话音量, 发现此时 嘟嘟嘟的来电铃声无法调节音量。
1.1 问题拆分
- 此时嘟嘟声音是 车机自己的 telecom 中触发的声音还是 通过 hfp sco 传递过来的声音?
- 为何此时从车机侧调节电话铃声的音量,无效?
接下来我们将从这两个角度展开来讨论。
2. 嘟嘟声的来源
- 要搞清楚 嘟嘟声的来源, 首先需要判断清楚, 在电话 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 链路 传递到 我们的车机。那为何 无法调节该铃声的音量呢?
- 车机没有下发 音量到 手机 A?
- 还是手机A 收到了车机的音量,但是没有处理呢?
带着上面两个疑问,继续展开。
3. 音量调节
3.1 hfp 远程音量调节协议说明
我们先补充一点hfp 音量调节的知识,看看 SIG官方是如何定义远程音量调节的。
根据hfp 协议和场景,车机和手机的角色定义如下:
- HF(车机)
- AG (手机)
3.1.1 HF和AG互换支持的特性
手机 A 和 车机 在建立 hfp 连接时:
- HF -> AG : 通过 AT+BRSF 命令告诉 AG 此时HF支持那些特性, 其中有一条就是 是否支持 远程音量调节
- 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