Android 藍牙開發(七)hfp音頻連接

轉載請註明出處:http://blog.csdn.net/vnanyesheshou/article/details/71374935

本文已授權微信公衆號 fanfan程序媛 獨家發佈 掃一掃文章底部的二維碼或在微信搜索 fanfan程序媛 即可關注

接着上一篇hfp連接繼續,查看藍牙通話時如何進行處理的。hfp連接有兩個連接,一個是hfp連接(在設置界面顯示的是手機音頻),另一個是藍牙通話時進行的音頻連接。這篇說下第二個連接,音頻連接處理過程。
該文章是基於Android源碼4.3的


1 連接音頻

在手機音頻正常連接時,接通電話,並選擇藍牙通話。從系統應用Phone開始分析。
代碼路徑:packages/apps/Phone/src/com/Android/phone/InCallScreen.Java
手機通話可以選擇揚聲器、聽筒、藍牙,我們選擇藍牙。
這裏寫圖片描述

public void switchInCallAudio(InCallAudioMode newMode) {
    switch (newMode) {
        case SPEAKER: break; //揚聲器    
        case BLUETOOTH: //藍牙
            // 檢查hfp是否連接着(藍牙耳機是否連接可用),檢查藍牙耳機的音頻是否連接
            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() {
    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,接收到消息後處理如下:

case 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耳機通訊,向外發送廣播並向藍牙耳機發送通話狀態。

歡迎掃一掃關注我的微信公衆號,定期推送優質技術文章:

這裏寫圖片描述

發佈了129 篇原創文章 · 獲贊 270 · 訪問量 97萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章