Android 蓝牙(十)A2DP源码分析

转载请注明出处:http://blog.csdn.net/vnanyesheshou/article/details/71811288

上一篇说了下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;
}

接着看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
        }
    }
}

接着查看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;
    }
}

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

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

该方法中很明显是去调用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;
}

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

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

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

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

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状态
}

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

broadcastConnectionState中会向AudioManager中设置A2DP的连接状态,返回值用来延时发送广播。AudioManager设置A2DP的连接状态非常重要,这样音频系统根据当前状态,判断音频从哪里发出(蓝牙a2dp、扬声器、耳机)。

欢迎大家关注、评论、点赞
你们的支持是我坚持的动力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 转载请注明出处:http://blog.csdn.net/vnanyesheshou/article/detail...
    朋永阅读 4,093评论 0 5
  • 蓝牙 注:本文翻译自https://developer.android.com/guide/topics/conn...
    RxCode阅读 8,613评论 11 99
  • Android平台支持蓝牙网络协议栈,实现蓝牙设备之间数据的无线传输。本文档描述了怎样利用android平台提供的...
    Camming阅读 3,280评论 0 3
  • 最近项目使用蓝牙,之前并没有接触,还是发现了很多坑,查阅了很多资料,说的迷迷糊糊,今天特查看官方文档。 说下遇到的...
    King9527阅读 1,780评论 0 1
  • 普通蓝牙设备官方文档 Android 平台包含蓝牙网络堆栈支持,凭借此支持,设备能以无线方式与其他蓝牙设备交换数据...
    sydMobile阅读 69,401评论 5 43