Android 藍(lán)牙(十)A2DP源碼分析

轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/vnanyesheshou/article/details/71811288

上一篇說了下A2DP的一些基本操作星立,這篇分析下系統(tǒng)應(yīng)用取试、系統(tǒng)源碼是如何操作A2DP的贾铝。尤其是其連接過程,基于Android4.3源碼均唉。Andorid手機(jī)一般都是做為A2DP Audio Source端。


1 連接過程

媒體音頻也就是A2DP,首先連接的藍(lán)牙設(shè)備需要支持A2DP協(xié)議(并且做為A2DP Audio Sink端)辱挥,并且需要與該設(shè)備進(jìn)行配對(duì),如何進(jìn)行藍(lán)牙配對(duì)這里就不細(xì)說了边涕,可以參照我的其他文章晤碘。主要分析下其連接過程褂微。
對(duì)于系統(tǒng)自帶應(yīng)用Settings中已配對(duì)的藍(lán)牙設(shè)備界面(如下圖所示):


這里寫圖片描述

其對(duì)應(yīng)文件路徑:
packages/apps/Settings/src/com/android/settings/bluetooth/DeviceProfilesSettings.Java
點(diǎn)擊媒體音頻進(jìn)行連接,調(diào)用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); //獲取對(duì)應(yīng)的profile
        onProfileClicked(prof, (CheckBoxPreference) preference);
        return false;   // checkbox will update from onDeviceAttributesChanged() callback
    } else {
        return false;
    }
    return true;
}

接著看onProfileClicked()函數(shù)處理

private void onProfileClicked(LocalBluetoothProfile profile, CheckBoxPreference profilePref) {
    BluetoothDevice device = mCachedDevice.getDevice(); //獲取配對(duì)的藍(lán)牙設(shè)備
    int status = profile.getConnectionStatus(device);  //獲取profile的連接狀態(tài)
    boolean isConnected =
            status == BluetoothProfile.STATE_CONNECTED;
    if (isConnected) { //如果是連接狀態(tài)則斷開連接
        askDisconnect(getActivity(), profile);
    } else { //沒有連接
        if (profile.isPreferred(device)) { //獲取profile是否是首選
            // profile is preferred but not connected: disable auto-connect
            profile.setPreferred(device, false); //設(shè)置對(duì)應(yīng)profile的PRIORITY 為off蕊梧,防止自動(dòng)連接
            refreshProfilePreference(profilePref, profile); //刷新check box狀態(tài)
        } else {
            profile.setPreferred(device, true); //設(shè)置對(duì)應(yīng)profile的PRIORITY 為on
            mCachedDevice.connectProfile(profile); //連接指定profile
        }
    }
}

接著查看CachedBluetoothDevice中的connectProfile函數(shù)連接某一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) {
    //查看是否配對(duì)腮介,如果沒有配對(duì)則進(jìn)行配對(duì)肥矢,配對(duì)后進(jìn)行連接,
    //如果配對(duì)則直接連接
    if (!ensurePaired()) { 
        return;
    }
    if (profile.connect(mDevice)) {//連接
        return;
    }
}

connectProfile() ——>connectInt()
connectInt()函數(shù)中會(huì)先判斷是否配對(duì)叠洗,如果沒有配對(duì)則開始配對(duì)甘改,配對(duì)成功后連接profile。
如果已經(jīng)配對(duì)則直接連接profile灭抑。
對(duì)于profile.connect(mDevice)會(huì)根據(jù)profile調(diào)用各自對(duì)應(yīng)的connect方法十艾。(如手機(jī)音頻則對(duì)應(yīng)HeadsetProfile,媒體音頻對(duì)應(yīng)A2dpProfile)腾节。這里查看手機(jī)音頻的連接A2dpProfile忘嫉。

public boolean connect(BluetoothDevice device) {
    if (mService == null) return false;
    //獲取連接hfp的設(shè)備
    List<BluetoothDevice> sinks = mService.getConnectedDevices();
    if (sinks != null) {
        for (BluetoothDevice sink : sinks) {
            mService.disconnect(sink); //斷開連接
        }
    } //連接hfp。
    return mService.connect(device);
}

A2dpProfile.java中的connect()方法案腺,mService是通過getProfileProxy獲取的BluetoothA2DP代理對(duì)象庆冕,通過其進(jìn)行A2DP相關(guān)操作。
mService.connect跳到Bluetooth應(yīng)用中劈榨,
代碼路徑:packages/apps/Bluetooth/src/com/android/bluetooth/a2dp/A2dpService.java
先調(diào)用到內(nèi)部類BluetoothA2dpBinder的connect方法访递。

public boolean connect(BluetoothDevice device) {
    A2dpService service = getService();
    if (service == null) return false;
    return service.connect(device);
}

該方法中很明顯是去調(diào)用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; //檢查連接狀態(tài)
    }

    mStateMachine.sendMessage(A2dpStateMachine.CONNECT, device);
    return true;
}

A2dpService的connect()函數(shù)會(huì)對(duì)priority和連接狀態(tài)進(jìn)行必要的檢查同辣,不符合條件則返回false拷姿。符合條件則向狀態(tài)機(jī)發(fā)送消息A2dpStateMachine.CONNECT。
此時(shí)A2dpStateMachine中狀態(tài)應(yīng)該是Disconnected旱函,所以查看Disconnected state中的處理

BluetoothDevice device = (BluetoothDevice) message.obj;
//發(fā)送廣播响巢,正在連接A2DP
broadcastConnectionState(device, BluetoothProfile.STATE_CONNECTING,
               BluetoothProfile.STATE_DISCONNECTED);
//連接遠(yuǎn)端設(shè)備。
if (!connectA2dpNative(getByteAddress(device)) ) {
    //連接失敗陡舅,向外發(fā)送連接失敗廣播
    broadcastConnectionState(device, BluetoothProfile.STATE_DISCONNECTED,
                   BluetoothProfile.STATE_CONNECTING);
    break;
}

synchronized (A2dpStateMachine.this) {
    mTargetDevice = device; //mTargetDevice要連接的設(shè)備
    transitionTo(mPending); //切換到pending狀態(tài)
} //超時(shí)處理
sendMessageDelayed(CONNECT_TIMEOUT, 30000);

A2DPStateMachine調(diào)用connectA2dpNative()函數(shù)來進(jìn)行媒體音頻的連接抵乓。connectA2dpNative是native方法,跳轉(zhuǎn)到com_android_bluetooth_a2dp.cpp中靶衍,調(diào)用對(duì)應(yīng)的方法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;
}

其中sBluetoothA2dpInterface->connect會(huì)跳到hardware灾炭、藍(lán)牙協(xié)議棧進(jìn)行連接,這就先不進(jìn)行分析了颅眶。


2 狀態(tài)回調(diào)##

當(dāng)協(xié)議棧連接狀態(tài)改變會(huì)回調(diào)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);
}

bta2dp_connection_state_callback方法中會(huì)從cpp層調(diào)用到j(luò)ava層,對(duì)應(yīng)于A2DPStateMachine中的onConnectionStateChanged函數(shù)

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);
}

onConnectionStateChanged函數(shù)中發(fā)送消息STACK_EVENT(攜帶狀態(tài)和藍(lán)牙地址)涛酗,此時(shí)是Pending state铡原,收到該消息調(diào)用processConnectionEvent偷厦。
正常連接成功應(yīng)該會(huì)先收到CONNECTION_STATE_CONNECTING狀態(tài),然后收到CONNECTION_STATE_CONNECTED狀態(tài)燕刻。

//發(fā)送廣播只泼,連接成功
broadcastConnectionState(mTargetDevice, BluetoothProfile.STATE_CONNECTED,
                         BluetoothProfile.STATE_CONNECTING);
synchronized (A2dpStateMachine.this) {
    mCurrentDevice = mTargetDevice;//mCurrentDevice表示已連接的設(shè)備
    mTargetDevice = null; //mTargetDevice表示要連接的設(shè)備
    transitionTo(mConnected);//切換到Connected狀態(tài)
}

收到CONNECTION_STATE_CONNECTED狀態(tài),后向外發(fā)送連接成功的廣播卵洗,狀態(tài)機(jī)切換到Connected狀態(tài)请唱。

private void broadcastConnectionState(BluetoothDevice device, int newState, int prevState) {
    //AudioManager設(shè)置A2DP的連接狀態(tài)
    int delay = mAudioManager.setBluetoothA2dpDeviceConnectionState(device, newState);
    mWakeLock.acquire();
    //延時(shí)處理,發(fā)送廣播 
    mIntentBroadcastHandler.sendMessageDelayed(mIntentBroadcastHandler.
        obtainMessage(MSG_CONNECTION_STATE_CHANGED,
           prevState,newState,device),
           delay);
}

broadcastConnectionState中會(huì)向AudioManager中設(shè)置A2DP的連接狀態(tài)过蹂,返回值用來延時(shí)發(fā)送廣播十绑。AudioManager設(shè)置A2DP的連接狀態(tài)非常重要,這樣音頻系統(tǒng)根據(jù)當(dāng)前狀態(tài)酷勺,判斷音頻從哪里發(fā)出(藍(lán)牙a2dp本橙、揚(yáng)聲器、耳機(jī))脆诉。

歡迎大家關(guān)注甚亭、評(píng)論、點(diǎn)贊击胜。
你們的支持是我堅(jiān)持的動(dòng)力狂鞋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市潜的,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌字管,老刑警劉巖啰挪,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嘲叔,居然都是意外死亡亡呵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門硫戈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锰什,“玉大人,你說我怎么就攤上這事丁逝≈ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵霜幼,是天一觀的道長(zhǎng)嫩码。 經(jīng)常有香客問我,道長(zhǎng)罪既,這世上最難降的妖魔是什么铸题? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任铡恕,我火速辦了婚禮,結(jié)果婚禮上丢间,老公的妹妹穿的比我還像新娘探熔。我一直安慰自己,他們只是感情好烘挫,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布诀艰。 她就那樣靜靜地躺著,像睡著了一般墙牌。 火紅的嫁衣襯著肌膚如雪涡驮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天喜滨,我揣著相機(jī)與錄音捉捅,去河邊找鬼。 笑死虽风,一個(gè)胖子當(dāng)著我的面吹牛棒口,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辜膝,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼无牵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼臣镣!你這毒婦竟也來了崖飘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤墓懂,失蹤者是張志新(化名)和其女友劉穎忱辅,沒想到半個(gè)月后七蜘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡墙懂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年橡卤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片损搬。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碧库,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出巧勤,到底是詐尸還是另有隱情嵌灰,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布踢关,位于F島的核電站伞鲫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏签舞。R本人自食惡果不足惜秕脓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一柒瓣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吠架,春花似錦芙贫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拐辽,卻和暖如春拣挪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俱诸。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工菠劝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人睁搭。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓赶诊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親园骆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子舔痪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/vnanyesheshou/article/detail...
    朋永閱讀 4,146評(píng)論 0 5
  • 藍(lán)牙 注:本文翻譯自https://developer.android.com/guide/topics/conn...
    RxCode閱讀 8,651評(píng)論 11 99
  • Android平臺(tái)支持藍(lán)牙網(wǎng)絡(luò)協(xié)議棧,實(shí)現(xiàn)藍(lán)牙設(shè)備之間數(shù)據(jù)的無線傳輸锌唾。本文檔描述了怎樣利用android平臺(tái)提供的...
    Camming閱讀 3,303評(píng)論 0 3
  • 最近項(xiàng)目使用藍(lán)牙锄码,之前并沒有接觸,還是發(fā)現(xiàn)了很多坑晌涕,查閱了很多資料巍耗,說的迷迷糊糊,今天特查看官方文檔渐排。 說下遇到的...
    King9527閱讀 1,790評(píng)論 0 1
  • 普通藍(lán)牙設(shè)備官方文檔 Android 平臺(tái)包含藍(lán)牙網(wǎng)絡(luò)堆棧支持,憑借此支持灸蟆,設(shè)備能以無線方式與其他藍(lán)牙設(shè)備交換數(shù)據(jù)...
    sydMobile閱讀 69,457評(píng)論 5 43