接着上一篇hfp鏈接繼續,查看藍牙通話時如何進行處理的。hfp鏈接有兩個鏈接,一個是hfp鏈接(在設置界面顯示的是手機音頻),另外一個是藍牙通話時進行的音頻鏈接。這篇說下第二個鏈接,音頻鏈接處理過程。 java
該文章是基於Android源碼4.3的android
1 鏈接音頻 |
在手機音頻正常鏈接時,接通電話,並選擇藍牙通話。從系統應用Phone開始分析。
代碼路徑:packages/apps/Phone/src/com/Android/phone/InCallScreen.Java
手機通話能夠選擇揚聲器、聽筒、藍牙,咱們選擇藍牙。
數組
public void switchInCallAudio(InCallAudioMode newMode) {
app
switch (newMode) {
函數
case SPEAKER: break; //揚聲器
ui
case BLUETOOTH: //藍牙
this
// 檢查hfp是否鏈接着(藍牙耳機是否鏈接可用),檢查藍牙耳機的音頻是否鏈接
spa
if (isBluetoothAvailable() && !isBluetoothAudioConnected()) {
線程
if (PhoneUtils.isSpeakerOn(this)) { //關閉揚聲器
代理
PhoneUtils.turnOnSpeaker(this, false, true);
}
connectBluetoothAudio(); //鏈接藍牙音頻
}
break;
case EARPIECE:break; //聽筒
default: break;
}
updateInCallTouchUi(); //更新ui
}
藍牙通話時選擇藍牙,會調到switchInCallAudio(),對於藍牙通話模式,檢查是否鏈接藍牙耳機 headset(手機音頻),檢查藍牙通話音頻是否鏈接,若是有鏈接的藍牙耳機,而且沒有鏈接藍牙音頻(這個鏈接並非設置界面中的手機音頻鏈接,這是通話是須要的鏈接,該鏈接的前提是須要進行手機音頻的鏈接),則知足條件。
若是揚聲器開着,則先關閉揚聲器,而後鏈接藍牙音頻。接着看connectBluetoothAudio()函數。
/* package */ void connectBluetoothAudio() {
/* package */ void connectBluetoothAudio() {
if (mBluetoothHeadset != null) { mBluetoothHeadset.connectAudio(); } //注意:藍牙鏈接不會當即發生;connectAudio()調用當即返回,但實際它在另外一個線程中工做。 //mBluetoothConnectionPending標誌只是一個標誌,以確保屏幕UI當即更新。 mBluetoothConnectionPending = true; mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();}
mBluetoothHeadset是經過getProfileProxy獲取的BluetoothHeadset代理對象。經過代理對象鏈接音頻。mBluetoothHeadset.connectAudio()會跳到應用Settings中HeadsetService內部類BluetoothHeadsetBinder中的connectAudio()方法,而後又跳到HeadsetService的connectAudio()函數中。
HeadsetService的connectAudio()函數以下:
boolean connectAudio() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!mStateMachine.isConnected()) { //檢查手機音頻是否鏈接
return false;
}
if (mStateMachine.isAudioOn()) { //檢查音頻是否鏈接
return false;
} //向狀態機發送消息
mStateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO);
return true;
}
在HeadsetService的connectAudio()函數中檢查headset是否鏈接,音頻是否鏈接。向狀態機發送鏈接音頻的消息。此時headset是鏈接的,HeadsetStateMachine中的狀態是Connected。
接收到後CONNECT_AUDIO的消息進行以下處理:
//mCurrentDevice表示狀態改變前鏈接的設備。
connectAudioNative(getByteAddress(mCurrentDevice));
mCurrentDevice表示狀態改變前鏈接的設備。經過getByteAddress獲取該設備的藍牙地址。而後調用native方法connectAudioNative鏈接音頻,該方法會調用jni目錄下的
com_android_bluetooth_hfp.cpp中的connectAudioNative函數。
static jboolean connectAudioNative(JNIEnv *env, jobject object, jbyteArray address) {
jbyte *addr;
bt_status_t status;
if (!sBluetoothHfpInterface) return JNI_FALSE;
//將byte數組類型的地址轉換爲jbyte*類型
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
//鏈接audio
if ( (status = sBluetoothHfpInterface->connect_audio((bt_bdaddr_t *)addr)) !=
BT_STATUS_SUCCESS) {
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
將byte數組類型的地址轉換成jbyte*類型,而後向hardware、協議棧下進行鏈接。
2 音頻鏈接狀態 |
當音頻鏈接狀態改變會回調com_android_bluetooth_hfp.cpp中audio_state_callback函數。
audio_state_callback函數以下:
static void audio_state_callback(bthf_audio_state_t state, bt_bdaddr_t* bd_addr) {
jbyteArray addr;
CHECK_CALLBACK_ENV
//獲取藍牙地址
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
//調用method_onAudioStateChanged對應的方法。
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, (jint) state, addr);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
audio_state_callback中參數state表示音頻鏈接狀態,address表示藍牙的地址。將address轉換爲jbyteArray類型,而後調用java層代碼,調用HeadSetStateMachine中的onAudioStateChanged函數。onAudioStateChanged代碼以下:
private void onAudioStateChanged(int state, byte[] address) {
StackEvent event = new StackEvent(EVENT_TYPE_AUDIO_STATE_CHANGED);
event.valueInt = state;
event.device = getDevice(address);
sendMessage(STACK_EVENT, event); //發送消息
}
onAudioStateChanged向狀態機發送消息。此時狀態機處於Connected狀態,收到該消息調用processAudioEvent(event.valueInt, event.device)函數。processAudioEvent代碼以下:
private void processAudioEvent(int state, BluetoothDevice device) {
if (!mCurrentDevice.equals(device)) { //查看是不是以前鏈接的設備
return;
}
switch (state) {
case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTED;
//設置藍牙SCO進行通訊。
mAudioManager.setBluetoothScoOn(true);
broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTED,
BluetoothHeadset.STATE_AUDIO_CONNECTING);
transitionTo(mAudioOn); //切換到AudioOn狀態
break;
case HeadsetHalConstants.AUDIO_STATE_CONNECTING:
mAudioState = BluetoothHeadset.STATE_AUDIO_CONNECTING;
broadcastAudioState(device, BluetoothHeadset.STATE_AUDIO_CONNECTING,
BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
break;
default:
break;
}
}
音頻鏈接回調,狀態是HeadsetHalConstants.AUDIO_STATE_CONNECTING或HeadsetHalConstants.AUDIO_STATE_CONNECTED,向外發送audio鏈接狀態改變的廣播。狀態是HeadsetHalConstants.AUDIO_STATE_CONNECTED,經過AudioManager設置藍牙SCO進行音頻通訊,將狀態機切換到AudioOn狀態。
能夠經過廣播接收者註冊BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,監聽音頻到鏈接狀態的改變。
3 音頻斷開鏈接 |
藍牙通話狀態下,切換到聽筒、揚聲器或者中止通話,都會將音頻斷開鏈接。在應用Phone中的InCallScreen.java中調用disconnectBluetoothAudio,代碼以下:
/* package */ void disconnectBluetoothAudio() {
if (mBluetoothHeadset != null) {
//斷開音頻鏈接
mBluetoothHeadset.disconnectAudio();
}
mBluetoothConnectionPending = false;
}
mBluetoothHeadset.disconnectAudio()經過代理對象調用disconnectAudio(),跳轉到應用Bluetooth的HeadSetService內部類BluetoothHeadsetBinder中的disconnectAudio()中,而後跳到HeadSetService的disconnectAudio()函數中。
boolean disconnectAudio() {
enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
//判斷狀態機狀態是否處於AudioOn狀態
if (!mStateMachine.isAudioOn()) {
return false;
} //發送DISCONNECT_AUDIO消息
mStateMachine.sendMessage(HeadsetStateMachine.DISCONNECT_AUDIO);
return true;
}
此時HeadsetStateMachine狀態爲AudioOn,接收到消息後處理以下:
ase DISCONNECT_AUDIO:
if (disconnectAudioNative(getByteAddress(mCurrentDevice))) {
mAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
//音頻管理關閉藍牙SCO。
mAudioManager.setBluetoothScoOn(false);
//發送廣播
broadcastAudioState(mCurrentDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
BluetoothHeadset.STATE_AUDIO_CONNECTED);
}
break;
disconnectAudioNative爲native方法,調用到jni關閉音頻鏈接。關閉藍牙SCO耳機通信,向外發送廣播並向藍牙耳機發送通話狀態。