Android 蓝牙(十)A2DP源码分析

作者: 朋永 | 来源:发表于2017-07-20 17:36 被阅读0次

    转载请注明出处: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、扬声器、耳机)。

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

    相关文章

      网友评论

        本文标题:Android 蓝牙(十)A2DP源码分析

        本文链接:https://www.haomeiwen.com/subject/evdjkxtx.html