Android 蓝牙hfp初始化、rfcomm连接、slc连接、sco连接源代码分析大全,非常详细的从btif-bta-btm - 点击下载
该文章基于Android 11
一、概述HFP SLC连接成功后,状态转变为connected,然后开始设置device为active状态,同时获取硬件模块。
二、源码分析场景:在手机音频正常连接时,接通电话,在UI上面点选蓝牙通话。audio连接成功后后,状态为audio_on,再次获取音频焦点(force_sco_on),获取到输出设备。
/packages/services/Telecomm/src/com/android/server/telecom/InCallAdapter.java
从通话中应用程序接收呼叫命令和更新,并将它们传递给 CallsManager。此时我们要切换路由。
public void setAudioRoute(int route, String bluetoothAddress) {
383 try {
384 Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerPackageAbbreviation);
385 long token = Binder.clearCallingIdentity();
386 try {
387 synchronized (mLock) {
388 mCallsManager.setAudioRoute(route, bluetoothAddress);
389 }
390 } finally {
391 Binder.restoreCallingIdentity(token);
392 }
393 } finally {
394 Log.endSession();
395 }
396 }
/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
Called by the in-call UI to change the audio route
void setAudioRoute(int route, String bluetoothAddress) {
2803 mCallAudioManager.setAudioRoute(route, bluetoothAddress);
2804 }
/packages/services/Telecomm/src/com/android/server/telecom/CallAudioManager.java
change the audio route,我们切换到蓝牙。
void setAudioRoute(int route, String bluetoothAddress) {
402 Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
403 switch (route) {
404 case CallAudioState.ROUTE_BLUETOOTH:
405 mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
406 CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
407 return;
....
/packages/services/Telecomm/src/com/android/server/telecom/CallAudioRouteStateMachine.java
在状态机中切换
class ActiveHeadsetRoute extends HeadsetRoute {应该在这个状态中
。。。。。
case USER_SWITCH_BLUETOOTH:
594 if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
595 if (mAudioFocusType == ACTIVE_FOCUS
596 || mBluetoothRouteManager.isInbandRingingEnabled()) {
597 String address = (msg.obj instanceof SomeArgs) ?
598 (String) ((SomeArgs) msg.obj).arg2 : null;
599 // Omit transition to ActiveBluetoothRoute until actual connection.
600 setBluetoothOn(address);
601 } else {
602 transitionTo(mRingingBluetoothRoute);
603 }
604 } else {
605 Log.w(this, "Ignoring switch to bluetooth command. Not available.");
606 }
607 return HANDLED;
private void setBluetoothOn(String address) {
1609 if (mBluetoothRouteManager.isBluetoothAvailable()) {首先判断蓝牙是否可用
1610 BluetoothDevice connectedDevice =
1611 mBluetoothRouteManager.getBluetoothAudioConnectedDevice();获取已经连接的蓝牙设备
1612 if (address == null && connectedDevice != null) {
1613 // null means connect to any device, so if we're already connected to some device,
1614 // that means we can just tell ourselves that it's connected.
1615 // Do still try to connect audio though, so that BluetoothRouteManager knows that
1616 // there's an active call.
1617 Log.i(this, "Bluetooth audio already on.");
1618 sendInternalMessage(BT_AUDIO_CONNECTED);
1619 mBluetoothRouteManager.connectBluetoothAudio(connectedDevice.getAddress());
1620 return;
1621 }
1622 if (connectedDevice == null || !Objects.equals(address, connectedDevice.getAddress())) {
1623 Log.i(this, "connecting bluetooth audio: %s", address);
1624 mBluetoothRouteManager.connectBluetoothAudio(address);
1625 }
1626 }
1627 }
/packages/services/Telecomm/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
尝试连接到蓝牙音频。 如果第一次连接尝试同步失败,则安排稍后重试。
public void connectBluetoothAudio(String address) {
535 SomeArgs args = SomeArgs.obtain();
536 args.arg1 = Log.createSubsession();
537 args.arg2 = address;
538 sendMessage(CONNECT_HFP, args);发送内部消息
539 }
发送内部消息
private final class AudioOffState extends State {此时audio应该处于off状态
case CONNECT_HFP:
159 String actualAddress = connectBtAudio((String) args.arg2);
160
161 if (actualAddress != null) {
162 transitionTo(getConnectingStateForAddress(actualAddress,
163 "AudioOff/CONNECT_HFP"));
164 } else {
165 Log.w(LOG_TAG, "Tried to connect to %s but failed to connect to" +
166 " any HFP device.", (String) args.arg2);
167 }
168 break;
private String connectBtAudio(String address) {
614 return connectBtAudio(address, 0);
615 }
尝试连接计数开始为0,向指定的BT地址发起连接
mDeviceManager.connectAudio返回true。
如果是之前默认蓝牙mDeviceManager.connectAudio返回false。
packages/services/Telecomm/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
private String connectBtAudio(String address, int retryCount) {
......
if (!mDeviceManager.connectAudio(actualAddress)) {
boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
shouldRetry ? "retry" : "not retry");
if (shouldRetry) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = Log.createSubsession();
args.arg2 = actualAddress;
args.argi1 = retryCount + 1;
sendMessageDelayed(RETRY_HFP_CONNECTION, args,
mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
mContext.getContentResolver()));
}
return null;
}
return actualAddress;
}
packages/services/Telecomm/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
public boolean connectAudio(String address) {
if (mHearingAidDevicesByAddress.containsKey(address)) {
......
} else if (mHfpDevicesByAddress.containsKey(address)) {
BluetoothDevice device = mHfpDevicesByAddress.get(address);
if (mBluetoothHeadsetService == null) {
Log.w(this, "Attempting to turn on audio when the headset service is null");
return false;
}
boolean success = mBluetoothHeadsetService.setActiveDevice(device);
if (!success) {
Log.w(this, "Couldn't set active device to %s", address);
return false;
}
if (!mBluetoothHeadsetService.isAudioOn()) {
return mBluetoothHeadsetService.connectAudio();
}
return true;
} else {
Log.w(this, "Attempting to turn on audio for a disconnected device");
return false;
}
}
packages/services/Telecomm/src/com/android/server/telecom/BluetoothHeadsetProxy.java
public boolean connectAudio() {
return mBluetoothHeadset.connectAudio();
}
从这里就调用到了hfp 服务中去了:
frameworks/base/core/java/android/bluetooth/BluetoothHeadset.java
public boolean connectAudio() {
final IBluetoothHeadset service = mService;
if (service != null && isEnabled()) {
try {
return service.connectAudio();
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
} else {
Log.w(TAG, "Proxy not attached to service");
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
}
return false;
}
packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java
private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub{
@Override
public boolean connectAudio() {
HeadsetService service = getService();
if (service == null) {
return false;
}
return service.connectAudio();
}
}
boolean connectAudio() {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
synchronized (mStateMachines) {
BluetoothDevice device = mActiveDevice;
if (device == null) {
Log.w(TAG, "connectAudio: no active device, " + Utils.getUidPidString());
return false;
}
return connectAudio(device);
}
}
如果之前默认蓝牙通话,isScoAcceptable返回false。
如果是点选蓝牙会向状态机发送CONNECT_AUDIO。
boolean connectAudio(BluetoothDevice device) {
enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
synchronized (mStateMachines) {
if (!isScoAcceptable(device)) {
Log.w(TAG, "connectAudio, rejected SCO request to " + device);
return false;
}
final HeadsetStateMachine stateMachine = mStateMachines.get(device);
......
stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
}
return true;
}
当前状态机为Connected,接前篇hfp连接。
调用jni connectAudio,最后状态机转到mAudioConnecting
packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
class Connected extends ConnectedBase {
public boolean processMessage(Message message) {
switch (message.what) {
case CONNECT_AUDIO:
stateLogD("CONNECT_AUDIO, device=" + mDevice);
......
if (!mNativeInterface.connectAudio(mDevice)) {
stateLogE("Failed to connect SCO audio for " + mDevice);
// No state change involved, fire broadcast immediately
broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
break;
}
transitionTo(mAudioConnecting);
break;
}
}
}
}
packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
static jboolean connectAudioNative(JNIEnv* env, jobject object,
jbyteArray address) {
......
bt_status_t status = sBluetoothHfpInterface->ConnectAudio((RawAddress*)addr);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed HF audio connection, status: %d", status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
在协议栈中直接调用到了connectAudio()->BTA_AgAudioOpen()->bta_ag_sco_open()来打开sco链路了。具体协议栈连接流程请查看文首链接。
在协议栈中就发起来SCO的连接,暂且不看了:
三、音频连接状态packages/apps/Bluetooth/jni/com_android_bluetooth_hfp.cpp
当音频连接状态改变会回调com_android_bluetooth_hfp.cpp中AudioStateCallback函数。
void AudioStateCallback(bluetooth::headset::bthf_audio_state_t state,
RawAddress* bd_addr) override {
ALOGI("%s, %d for %s", __func__, state, bd_addr->ToString().c_str());
std::shared_lock lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || !mCallbacksObj) return;
ScopedLocalRef addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) return;
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
(jint)state, addr.get());
}
packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java
private void onAudioStateChanged(int state, byte[] address) {
HeadsetStackEvent event =
new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, state,
getDevice(address));
sendMessageToService(event);
}
private void sendMessageToService(HeadsetStackEvent event) {
HeadsetService service = HeadsetService.getHeadsetService();
if (service != null) {
service.messageFromNative(event);
} else {
// Service must call cleanup() when quiting and native stack shouldn't send any event
// after cleanup() -> cleanupNative() is called.
Log.wtfStack(TAG, "FATAL: Stack sent event while service is not available: " + event);
}
}
回调信息会通过messageFromNative发送STACK_EVENT消息转给状态机。
packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java
void messageFromNative(HeadsetStackEvent stackEvent) {
Objects.requireNonNull(stackEvent.device,
"Device should never be null, event: " + stackEvent);
synchronized (mStateMachines) {
HeadsetStateMachine stateMachine = mStateMachines.get(stackEvent.device);
......
stateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent);
}
}
此时状态机为AudioConnecting。AudioConnecting中的processMessage没有STACK_EVENT项,
运行super.processMessage。
class AudioConnecting extends ConnectedBase {
public boolean processMessage(Message message) {
switch (message.what) {
case CONNECT:
case DISCONNECT:
case CONNECT_AUDIO:
case DISCONNECT_AUDIO:
......
break;
case CONNECT_TIMEOUT: {
......
break;
}
default:
return super.processMessage(message);
}
return HANDLED;
}
......
}
private abstract class ConnectedBase extends HeadsetStateBase {
public boolean processMessage(Message message) {
switch (message.what) {
case STACK_EVENT:
HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
stateLogD("STACK_EVENT: " + event);
switch (event.type) {
case HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
processAudioEvent(event.valueInt);
break;
}
}
}
public abstract void processAudioEvent(int state);
}
processAudioEvent由子类实现。
连接成功,状态机变为mAudioOn
class AudioConnecting extends ConnectedBase {
......
public void processAudioEvent(int state) {
switch (state) {
case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
if (!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
stateLogI("Sco connected for call other than CS, check network type");
sendVoipConnectivityNetworktype(true);
} else {
stateLogI("Sco connected for CS call, do not check network type");
}
stateLogI("processAudioEvent: audio connected");
transitionTo(mAudioOn);
break;
}
}
}
class AudioOn extends ConnectedBase {
public void enter() {
super.enter();
......
setAudioParameters();
broadcastStateTransitions();
}
通过broadcastAudioState发送广播ACTION_AUDIO_STATE_CHANGED
private abstract class HeadsetStateBase extends State {
void broadcastStateTransitions() {
......
if (getAudioStateInt() != mPrevState.getAudioStateInt() || (
mPrevState instanceof AudioDisconnecting && this instanceof AudioOn)) {
stateLogD("audio state changed: " + mDevice + ": " + mPrevState + " -> " + this);
broadcastAudioState(mDevice, mPrevState.getAudioStateInt(), getAudioStateInt());
}
if (getConnectionStateInt() != mPrevState.getConnectionStateInt()) {
stateLogD(
"connection state changed: " + mDevice + ": " + mPrevState + " -> " + this);
broadcastConnectionState(mDevice, mPrevState.getConnectionStateInt(),
getConnectionStateInt());
}
}
void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
......
mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState);
Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
HeadsetService.BLUETOOTH_PERM);
}
下一期我们将当SCO链路连接完成后,如何去切换设备输出。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)