Android车载技术之蓝牙通讯——如何蓝牙播放音乐

蓝牙是一种无线技术标准,可实现设备间短距离数据交换。 蓝牙可以以一定的周期发送广播,手机端接收到广播后,解析广播包,可做设备识别、配对,事件通知以及指令控制等。低精度定位根据设备的信号强度,可以估算出大概方位和距离。

蓝牙通信

前置条件:

首先,要操作蓝牙,先要在AndroidManifest.xml里加入权限 <uses-permissionandroid:name=“android.permission.BLUETOOTH_ADMIN” /> <uses-permissionandroid:name=“android.permission.BLUETOOTH” />

除了蓝牙权限外,如果需要BLE feature则还需要声明uses-feature:

按时required为true时,则应用只能在支持BLE的Android设备上安装运行;required为false时,Android设备均可正常安装运行,需要在代码运行时判断设备是否支持BLE feature:

通信流程:

发现设备->配对/绑定设备->建立连接->数据通信

发现设备

经典蓝牙设备发现其它经典蓝牙设备的方式是调用BluetoothAdapter的startDiscovery()方法,这个方法只能够发现经典蓝牙设备。

低功耗蓝牙中则有一个主设备(Central)和从设备(Peripheral,也叫外围设备)的概念。主设备作为发现方(一般为手机)调用发现设备的方法,通过BluetoothAdapter的startLeScan()方法实现。从设备则作为被发现方(穿戴设备,便携设备等),发出广播,以供发现。同样,这个startLeScan()方法也仅能够发现低功耗蓝牙从设备,在整个搜索过程中,功耗是比较大的,应尽快结束搜索。

发现设备比较重要,实践中有2个比较重要的点:

1 、经典蓝牙配对前,如果没有扫描过程,通过蓝牙地址直接配对,配对对话框可能不出现,以消息通知栏的方式呈现。如果扫描过,则会弹出配对对话框。

2 、ble连接前,如果没有扫描过程,通过蓝牙地址直接连接,可能连不上。如果扫描并发现外围设备,则会连接成功。

配对/绑定

配对指的是BLE设备的配对。配对的作用在于和设备做相互确认,一方面是确定要操作的设备,另一方面是考虑到安全因素。 经典蓝牙可通过createRfcommSocketToServiceRecord 和 Method creMethod = BluetoothDevice.class.getMethod(“createBond”); creMethod.invoke(mBluetoothDevice);

ble蓝牙在连接的过程中 自动配对,但不弹出配对对话框。

建立连接

在建立连接的方式上,两者就千差万别了。

蓝牙小知识

在蓝牙设备中,存在着物理地址,我们也叫作蓝牙的MAC地址,这个地址是唯一的,就像咱们网络上的IP地址。同时还存在着一个叫做UUID的东西,可以把它理解为是IP地址中的端口号。正如知道了IP地址和端口号,就知道了怎么链接到目标网络服务器位置,知道了蓝牙设备的MAC地址和UUID也就能够确定到具体是哪一台蓝牙设备了,这两者合起来就是蓝牙的唯一身份标识。BLE扫描的过程,设备会生成随机地址。根据随机地址也能进行连接。

经典蓝牙建立连接的方式实际上就是Socket的连接的建立。只不过这里不是直接用Socket,而是BluetoothSocket。获取BluetoothSocket的方式也很简单,利用搜索找到的BluetoothDevice,调用其方法createRfcommSocketToServiceRecord(UUID)。最后,使用获取到的BluetoothDevice调用其方法connect()就建立了经典蓝牙设备之间的连接通道。

蓝牙连接耳机播放音乐

A2dp的连接过程,在蓝牙搜索结果列表连接一个蓝牙耳机,既然是从设备列表开始,所以起步代码自然是这个了

  DevicePickerFragment.java (settings\src\com\android\settings\bluetooth)     3884     2013-6-26
      void onClicked() {
        int bondState = mCachedDevice.getBondState();
        if (mCachedDevice.isConnected()) {
            askDisconnect();
        } else if (bondState == BluetoothDevice.BOND_BONDED) {
            mCachedDevice.connect(true);
        } .......
    }
      void connect(boolean connectAllProfiles) {
        if (!ensurePaired()) {  //要先确保配对
            return;
        }
        mConnectAttempted = SystemClock.elapsedRealtime();
        connectWithoutResettingTimer(connectAllProfiles);//没别的了,只能看到这里
    }

代码路径这里packages/apps/Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java,具体代码看下面

  // Try to initialize the profiles if they were not.
       ...........
        // Reset the only-show-one-error-dialog tracking variable
        mIsConnectingErrorPossible = true;

        int preferredProfiles = 0;
        for (LocalBluetoothProfile profile : mProfiles) {
            if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
                if (profile.isPreferred(mDevice)) {
                    ++preferredProfiles;
                    connectInt(profile);//连接在这里,
                }
            }
        }
       .............

connectInt的实现很简单,直接跳过看里面的profile.connect(mDevice),这里的profile是指A2dpProfile,所以connet()方法的具体实现在

    public boolean connect(BluetoothDevice device) {
        if (mService == null) return false;
        List<BluetoothDevice> sinks = getConnectedDevices();
        if (sinks != null) {
            for (BluetoothDevice sink : sinks) {
                mService.disconnect(sink);
        }}
        return mService.connect(device);
    }

下面是 BluetoothA2dp.java (frameworks\base\core\java\android\bluetooth) ,为什么是这样看下这个private BluetoothA2dp mService;就知道了

  public boolean connect(BluetoothDevice device) {
        if (mService != null && isEnabled() &&
            isValidDevice(device)) {
            try {
                return mService.connect(device);
            } catch (RemoteException e) {
                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
                return false;
            }
        }...........
        return false;

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

    }

之后的跳转和第一部分蓝牙接听电话跳转过程类似,就不重复了,最后会来到packages/apps/Bluetooth/jni/com_android_bluetooth_a2dp.cpp的connectA2dpNative,同样到下面的代码,我们能看到的开放的代码也就是这些,再下面要看vendor的具体实现了。

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

    ALOGI("%s: sBluetoothA2dpInterface: %p", __FUNCTION__, sBluetoothA2dpInterface);
    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) {
        ALOGE("Failed HF connection, status: %d", status);
    }
    env->ReleaseByteArrayElements(address, addr, 0);
    return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

本文主要讲了车载中的蓝牙通信基础,如何连接蓝牙耳机代码实现。车载除了这有许多要学习的。车载学习资料可以点击获取方式;有BYD车载高级工程师拟制。初步学习感觉内容讲的很细致。这里推荐一下。

比喻:

  • 串口
  • DLNA
  • Automotive
  • 车载进程通信
  • CarLauncher
  • 车载多媒体
  • 车载空调系统
  • 车载系统开发

等等。。。

Android车载是未来10年的黄金赛道,把握住这次机会就能稳住程序员的饭碗,所以学习是必要的。

猜你喜欢

转载自blog.csdn.net/m0_71524094/article/details/126876514