1. 程式人生 > >Android 藍芽開發(十)A2DP原始碼分析

Android 藍芽開發(十)A2DP原始碼分析

上一篇說了下A2DP的一些基本操作,這篇分析下系統應用、系統原始碼是如何操作A2DP的。尤其是其連線過程,基於Android4.3原始碼。Andorid手機一般都是做為A2DP Audio Source端。

1 連線過程

媒體音訊也就是A2DP,首先連線的藍芽裝置需要支援A2DP協議(並且做為A2DP Audio Sink端),並且需要與該裝置進行配對,如何進行藍芽配對這裡就不細說了,可以參照我的其他文章。主要分析下其連線過程。 
對於系統自帶應用Settings中已配對的藍芽裝置介面(如下圖所示): 
這裡寫圖片描述 
其對應檔案路徑: 
packages/apps/Settings/src/com/Android

/settings/bluetooth/DeviceProfilesSettings.Java 
點選媒體音訊進行連線,呼叫onPreferenceChange。

public boolean onPreferenceChange(Preference preference, Object newValue) {
    if (preference == mDeviceNamePref) { //重新命名
        mCachedDevice.setName((String) newValue);
    } else if (preference instanceof CheckBoxPreference) {//check box
LocalBluetoothProfile prof = getProfileOf(preference); //獲取對應的profile onProfileClicked(prof, (CheckBoxPreference) preference); return false; // checkbox will update from onDeviceAttributesChanged() callback } else { return false; } return true; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

接著看onProfileClicked()函式處理

private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
    BluetoothDevice device = mCachedDevice.getDevice(); //獲取配對的藍芽裝置
    int status = profile.getConnectionStatus(device);  //獲取profile的連線狀態
    boolean isConnected =
            status == BluetoothProfile.STATE_CONNECTED;
    if (isConnected) { //如果是連線狀態則斷開連線
        askDisconnect(getActivity(), profile);
    } else { //沒有連線
        if (profile.isPreferred(device)) { //獲取profile是否是首選
            // profile is preferred but not connected: disable auto-connect
            profile.setPreferred(device, false); //設定對應profile的PRIORITY 為off,防止自動連線
            refreshProfilePreference(profilePref, profile); //重新整理check box狀態
        } else {
            profile.setPreferred(device, true); //設定對應profile的PRIORITY 為on
            mCachedDevice.connectProfile(profile); //連線指定profile
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

接著檢視CachedBluetoothDevice中的connectProfile函式連線某一profile。

void connectProfile(LocalBluetoothProfile profile) {
    mConnectAttempted = SystemClock.elapsedRealtime();
    // Reset the only-show-one-error-dialog tracking variable
    mIsConnectingErrorPossible = true;
    connectInt(profile); //連線profile
    refresh();    // 重新整理ui
}

synchronized void connectInt(LocalBluetoothProfile profile) {
    //檢視是否配對,如果沒有配對則進行配對,配對後進行連線,
    //如果配對則直接連線
    if (!ensurePaired()) { 
        return;
    }
    if (profile.connect(mDevice)) {//連線
        return;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

connectProfile() ——>connectInt() 
connectInt()函式中會先判斷是否配對,如果沒有配對則開始配對,配對成功後連線profile。 
如果已經配對則直接連線profile。 
對於profile.connect(mDevice)會根據profile呼叫各自對應的connect方法。(如手機音訊則對應HeadsetProfile,媒體音訊對應A2dpProfile)。這裡檢視手機音訊的連線A2dpProfile。

public boolean connect(BluetoothDevice device) {
    if (mService == null) return false;
    //獲取連線hfp的裝置
    List<BluetoothDevice> sinks = mService.getConnectedDevices();
    if (sinks != null) {
        for (BluetoothDevice sink : sinks) {
            mService.disconnect(sink); //斷開連線
        }
    } //連線hfp。
    return mService.connect(device);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

A2dpProfile.java中的connect()方法,mService是通過getProfileProxy獲取的BluetoothA2DP代理物件,通過其進行A2DP相關操作。 
mService.connect跳到Bluetooth應用中, 
程式碼路徑:packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java 
先呼叫到內部類BluetoothA2dpBinder的connect方法。

public boolean connect(BluetoothDevice device) {
    A2dpService service = getService();
    if (service == null) return false;
    return service.connect(device);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

該方法中很明顯是去呼叫A2dpService的connect方法。接著看A2dpService中的connect

public boolean connect(BluetoothDevice device) {
    enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                   "Need BLUETOOTH ADMIN permission");
    if (getPriority(device) == BluetoothProfile.PRIORITY_OFF) {
        return false; //檢查priority
    }

    int connectionState = mStateMachine.getConnectionState(device);
    if (connectionState == BluetoothProfile.STATE_CONNECTED ||
        connectionState == BluetoothProfile.STATE_CONNECTING) {
        return false; //檢查連線狀態
    }

    mStateMachine.sendMessage(A2dpStateMachine.CONNECT, device);
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

A2dpService的connect()函式會對priority和連線狀態進行必要的檢查,不符合條件則返回false。符合條件則向狀態機發送訊息A2dpStateMachine.CONNECT。 
此時A2dpStateMachine中狀態應該是Disconnected,所以檢視Disconnected state中的處理

BluetoothDevice device = (BluetoothDevice) message.obj;
//傳送廣播,正在連線A2DP
broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
               BluetoothProfile.STATE_DISCONNECTED);
//連線遠端裝置。
if (!connectA2dpNative(getByteAddress(device)) ) {
    //連線失敗,向外傳送連線失敗廣播
    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
                   BluetoothProfile.STATE_CONNECTING);
    break;
}

synchronized (A2dpStateMachine.this) {
    mTargetDevice = device; //mTargetDevice要連線的裝置
    transitionTo(mPending); //切換到pending狀態
} //超時處理
sendMessageDelayed(CONNECT_TIMEOUT, 30000);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

A2DPStateMachine呼叫connectA2dpNative()函式來進行媒體音訊的連線。connectA2dpNative是native方法,跳轉到com_android_bluetooth_a2dp.cpp中,呼叫對應的方法connectA2dpNative

static jboolean connectA2dpNative(JNIEnv *env, jobject object, jbyteArray address) {
    jbyte *addr;
    bt_bdaddr_t * btAddr;
    bt_status_t status;
    if (!sBluetoothA2dpInterface) return JNI_FALSE;

    addr = env->GetByteArrayElements(address, NULL);
    btAddr = (bt_bdaddr_t *) addr;
    if (!addr) {
        jniThrowIOException(env, EINVAL);
        return JNI_FALSE;
    }

    if ((status = sBluetoothA2dpInterface->connect((bt_bdaddr_t *)addr)) != BT_STATUS_SUCCESS) {
    }
    env->ReleaseByteArrayElements(address, addr, 0);
    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

其中sBluetoothA2dpInterface->connect會跳到hardware、藍芽協議棧進行連線,這就先不進行分析了。

2 狀態回撥

當協議棧連線狀態改變會回撥com_android_bluetooth_a2dp.cpp中的方法bta2dp_connection_state_callback。

static void bta2dp_connection_state_callback(btav_connection_state_t state, bt_bdaddr_t* bd_addr) {
    jbyteArray addr;
    if (!checkCallbackThread()) {                                       \
        return;                                                         \
    }
    addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (!addr) {
        checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
        return;
    }
    sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*) bd_addr);
    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, (jint) state,
                                 addr);
    checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
    sCallbackEnv->DeleteLocalRef(addr);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

bta2dp_connection_state_callback方法中會從cpp層呼叫到java層,對應於A2DPStateMachine中的onConnectionStateChanged函式

private void onConnectionStateChanged(int state, byte[] address) {
    StackEvent event = new StackEvent(EVENT_TYPE_CONNECTION_STATE_CHANGED);
    event.valueInt = state;
    event.device = getDevice(address);
    sendMessage(STACK_EVENT, event);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

onConnectionStateChanged函式中傳送訊息STACK_EVENT(攜帶狀態和藍芽地址),此時是Pending state,收到該訊息呼叫processConnectionEvent。 
正常連線成功應該會先收到CONNECTION_STATE_CONNECTING狀態,然後收到CONNECTION_STATE_CONNECTED狀態。

//傳送廣播,連線成功
broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
                         BluetoothProfile.STATE_CONNECTING);
synchronized (A2dpStateMachine.this) {
    mCurrentDevice = mTargetDevice;//mCurrentDevice表示已連線的裝置
    mTargetDevice = null; //mTargetDevice表示要連線的裝置
    transitionTo(mConnected);//切換到Connected狀態
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

收到CONNECTION_STATE_CONNECTED狀態,後向外發送連線成功的廣播,狀態機切換到Connected狀態。

private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
    //AudioManager設定A2DP的連線狀態
    int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState);
    mWakeLock.acquire();
    //延時處理,傳送廣播 
    mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.
        obtainMessage(MSG_CONNECTION_STATE_CHANGED,
           prevState,newState,device),
           delay);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

broadcastConnectionState中會向AudioManager中設定A2DP的連線狀態,返回值用來延時傳送廣播。AudioManager設定A2DP的連線狀態非常重要,這樣音訊系統根據當前狀態,判斷音訊從哪裡發出(藍芽a2dp、揚聲器、耳機)。

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