bluetooth在linux应用开发

时间:2024-02-19 08:46:41

linuxBluetooth的协议栈为BlueZhttp://www.bluez.org/4.46上,BlueZ实现了对A2DP Sink的支持,而之前的版本只支持A2DP Source


主机实现到HCI层,底层由蓝牙芯片实现。HCI层实现的是蓝牙芯片与主机通讯的方式。目前一般是串口或者USB通讯。所谓的USB也不是真正意义上的USB通讯,而是类似与USB转串口的方式,即通过驱动模拟USB设备实现串口通讯。目前USB蓝牙适配器基本都是这种设备模式。PC端实现了L2CAP, SDP, RFCOMM协议,以及USB转串口的驱动。Windows XP SP2操作系统以上版本的都内置了这些协议栈,还有如WIDCOMM等公司提供的第三方协议栈。

实际中只需在市场上购买这种蓝牙适配器(USB接口),然后通过配置内核蓝牙的接口驱动(即上图中的HCI层驱动),这样相应的蓝牙协议(linux官方版本是bluez)就已经在内核中了,这就相当于内核驱动中已经支持了相应的蓝牙协议(SDP,RFCOMM...),有了驱动就需要接口库提供给应用程序使用,这里用到的接口库是开源的bluez,其实就是要在内核之上移植bluez及工具bluez-utils

bluez分为两部分:内核代码和用户态程序及工具集。

内核部分

内核代码:bluez核心协议和驱动程序等模块组成。自从linux2.4.6开始linux内核集成bluez

HCI. 这个是最底层的了,称为 Host Control Interface. 之所以称为 HCI 是源于蓝牙的应用模型的。蓝牙是连接智能外设的无线接口,接口的一侧是设备,另一侧就是主机 (Host) 了,采用类似记法的还有 USB, IEEE1394,所以,从设计初衷来看,这几个东东都是针对差不多的市场的,当然,各有所长了。一个蓝牙适配器是否能被驱动起来,就看 HCI 的支持性了。最常见的蓝牙适配器就是笔者持有的这类 USB 接口的了,对于大部分标准的蓝牙设备,它的驱动模块是: hci-usb,对于我们的 2.6 内核,插入这个适配器,该模块就被自动加载了。

L2CAP之上有两个协议被较广地使用着:RFCOMMBNEP,前者用于取代传统的串行口,包括串行口上的各种应用,比如,传真和拨号上网、打印机、文件图片等数据传输;后者则可以提供一个以太网接口,更适于计算机组网。自然地,对于手机和计算机之间,RFCOMM 总是更常被用到。

内核蓝牙配置:

[*] Networking support --->

<*> Bluetooth subsystem support ---> //蓝牙子系统必须选择

<*> L2CAP protocol suppor //逻辑链路控制和适配协议。

<*> SCO links support //蓝牙语音和耳机支持

<*> RFCOMM protocol suppor //面向流的传输协议,支持拨号网络等

[*] RFCOMM TTY support //

<*> BNEP protocol support //蓝牙网络封装协议,自组网支持

[*] Multicast filter support //蓝牙多播,支持BNEP

[*] Protocol filter support

<*> HIDP protocol support //基本支持协议

Bluetooth device drivers --->

<*> HCI USB driver //USB蓝牙模块支持

<M>HCI UART driver //基于串口,CF卡或PCMCIA的蓝牙

<*> HCI BlueFRITZ! USB driver

<*> HCI VHCI (Virtual HCI device) driver

此外,在Bluetooth device drivers里选上你所需要支持的Bluetooth设备。若使用CSRchip,通过串口和cpu通讯的,芯片默认使用BCSP作为通讯协议,所以选择HCI UART driverBCSP protocol support

若是通过usb接口使用蓝牙适配器,需要选择HCI USB driver。

用户态部分

用户态程序及工具集:应用程序接口和bluez工具集。

bluez软件包名称:bluez,提供bluetoothd守护进程。

bluez工具集:bluez-utils,提供bluetoothctl命令。

可用bluetoothctl完成蓝牙设备配对,步骤如下:

  1. (optional) Select a default controller with select MAC_address.

  2. Enter power on to turn the power to the controller on. It is off by default and will turn off again each reboot, see #Auto power-on after boot.

  3. Enter devices to get the MAC Address of the device with which to pair.

  4. Enter device discovery mode with scan on command if device is not yet on the list.

  5. Turn the agent on with agent on or choose a specific agent: if you press tab twice after agent you should see a list of available agents, e.g. DisplayOnly KeyboardDisplay NoInputNoOutput DisplayYesNo KeyboardOnly off on.

  6. Enter pair MAC_address to do the pairing (tab completion works).

  7. If using a device without a PIN, one may need to manually trust the device before it can reconnect successfully. Enter trust MAC_address to do so.

  8. Enter connect MAC_address to establish a connection.

一个操作示例如下,连接蓝牙音箱:

select 48:51:B7:DE:56:DD //48:51:B7:DE:56:DD为网关蓝牙模块的地址

power off

power on

agent on

default-agent

scan on //搜索蓝牙设备,等待,直到待连接设备被搜索到

pair FC:58:FA:5B:7E:DD //配对

connect FC:58:FA:5B:7E:DD //连接,会提示连接成功

exit //退出bluetoothctl

蓝牙音频Audio

蓝牙profile

蓝牙profile协议概览.pdf   

Bluetooth的一个很重要特性,就是所有的Bluetooth产品都无须实现全部的Bluetooth规范。为了更容易的保持Bluetooth设备之间的兼容,Bluetooth规范中定义了ProfileProfile定义了设备如何实现一种连接或者应用,你可以把Profile理解为连接层或者应用层协议

比如,如果一家公司希望它们的Bluetooth芯片支援所有的Bluetooth耳机,那么它只要支持HeadSet Profile即可,而无须考虑该芯片与其它Bluetooth设备的通讯与兼容性问题。如果你想购买Bluetooth产品,你应该了解你的应用需要哪些Profile来完成,并且确保你购买的Bluetooth产品支持这些Profile

之所以把Profile翻译为配置文件,是为避免和JavaME中的简表混淆,配置文件也是蓝牙 SIG官方网站给出的标准翻译。

想要使用蓝牙无线技术,设备必须能够翻译特定蓝牙配置文件,配置文件定义了可能的应用。蓝牙配置文件表达了一般行为,蓝牙设备可以通过这些行为与其他设备进行通信。

蓝牙技术定义了广泛的配置文件,描述了许多不同类型的使用安全。按蓝牙规格中提供的指导,开发商可创建应用程序以用来与其他符合蓝牙规格的设备协同工作。在最低限度下,各配置文件规格应包含下列主题的相关信息:

1)与其他配置文件的相关性。

2)建议的用户界面格式。

3)配置文件使用的蓝牙协议堆栈的特定部分。

为执行其任务,每个配置文件都使用堆栈各层上的特定选项和参数。若需要,也可包括必需的服务记录概要。

在所有的Profile中,有四种是基本的Profile,这些Profile会被其它的Profile使用。它们是:

GAP Profile: Generic Access Profile,该Profile保证不同的Bluetooth产品可以互相发现对方并建立连接。

SDAP Profile: Service Discovery Application Profile,通过该Profile,一个Bluetooth设备可以找到其它Bluetooth设备提供的服务,以及查询相关的信息。

SPP Profile: Serial Port Profile,模拟串口通讯。

GOEP Profile: Generic Object Exchange Profile,通用对象交换。这个Profile的名字有些费解,它定义的是数据的传输,包括同步,文件传输,或者推送其它的数据。你可以把它理解为内容无关的传输层协议,可以被任何应用用来传输自己定义的数据对象

Bluetooth还定义了9种应用(usage)Profile

CTP Profile: Cordless Telephone Profile,无绳电话。

IP Profile: Intercom Profile,这是在两个设备之间建立语音连接,换句话说,把两个昂贵的蓝牙设备变成廉价的对讲机。

HS Profile: HeadSet Profile,用于连接耳机。

DNP Profile: Dial-up Networking Profile,用于为PC提供拨号网络功能。

FP Profile: Fax Profile,传真功能。

LAP Profile: LAN Access Profile,使用PPP协议建立局域网。

OPP Profile: Object Push Profile,用于设备之间传输数据对象。

FTP Profile: File Transfer Profile,用于文件传输。

SP Profile: Synchronization Profile,用于不同的Bluetooth设备同步,保持数据的一致性。

目前常用的配置蓝牙配置(profile)是A2DP

A2DP全名是Advanced Audio Distribution Profile 高级音频分发配置文件,描述了立体声音频如何从媒体输出(source)传输至输入(sink)。A2DP是能够采用耳机内的芯片来堆栈数据,达到声音的高清晰度。然而并非支持A2DP的耳机就是蓝牙立体声耳机,立体声实现的基本要求是双声道,所以单声道的蓝牙耳机是不能实现立体声的。

使用场景:简单来说,对于一个蓝牙音乐播放器(MP3),音频输出是音乐播放器,而音频输入是无线耳机或无线立体声音响。

此配置文件定义了音频设备的两个角色:输出和输入。

输出(SRCsource):音频的输入端对音频数据进行编码,发送到Sink端。

输入(SNKsink):接收到音频数据后,进行解码操作还原出音频。

A2DP定义了在ACL信道实现高品质音频内容的单声道或立体声分发协议和程序。 因此, 高级音频蓝牙音频应该区别开来,后者是指根据基带规格定义的SCO信道中分发窄幅波段的语音。

此配置文件建立在GAVDP基础上。它包括对复杂程度低的次频宽编解码技术(SBC)的必备支持和对MPEG-1,2音频、 MPEG-2,4 AAC和自适应声学转换编码技术(ATRAC)的可选支持。

音频数据按适当的格式进行压缩后能在有限频宽中正常使用。环绕声的分发不在此配置文件的范围。

PulseAudio

https://www.freedesktop.org/wiki/Software/PulseAudio/

Bluetooth的音频应使用软件pulseaudio,要使用蓝牙耳机或音响的话要先安装pulseaudio-bluetoothpulseaudio-module-bluetoothPulseAudio 5.x 开始默认支持 A2DP

PulseAudio是一个开源的、跨平台的、支持网络的sound server声音服务器基本上就是您的声音应用的代理者。它可以支持从一个或多个source(进程或音频采集设备)输入声音并重定向它到一个或多个sink(声卡,远程网络PulseAudio server或其他进程)。PulseAudio的目的之一就是通过它来reroute所有的音频流。

为了支持Bluetooth audio sourcePulseAuido还实现了动态检测蓝牙音频设备的功能。这个功能和BlueZ A2DP Sink都是由João Paulo实现的,可以在它的blogBlueZ now has A2DP Sink support》中找到相关信息。

PulseAudio如何从BlueZ得到音频数据

虽然BlueZ内部对A2DP Sink的实现较为复杂,但是暴露给外部的数据接口确非常简单。在bluez/audio/ipc.c中实现了三个bt_audio_service函数。PulseAudio使用bt_audio_service_open()打开一个socket,然后调用bt_audio_service_get_data_fd()得到音频数据文件描述符fd。这个fd是通过那个socketBlueZ的进程传递到PulseAudio的进程的。最后,使用完毕,调用bt_audio_service_close()来关闭socketPulseAudio通过D-busBlueZ进行通信,进行参数的读取和设置,决定合适的读取时机,发送读取的状态。

PulseAudiofd读出的音频数据流是经过SBC压缩编码的(对于采用其他编码,如MPEG-1,的情况,本文不做讨论),PulseAudio还需要对这些音频数据流进行解码。在BlueZ中已经实现了SBC编解码,源文件位于bluez/sbcPluseAudio直接使用了这些源代码,把它们放在pulseadio/src/modules/bluetooth/sbc中。

sudo apt install pulseaudio-module-bluetooth

Reading package lists... Done

Building dependency tree

Reading state information... Done

The following additional packages will be installed:

libasound2-plugins libpulsedsp libspeexdsp1 pulseaudio pulseaudio-utils rtkit

Suggested packages:

pavumeter pavucontrol paman paprefs

The following NEW packages will be installed:

libasound2-plugins libpulsedsp libspeexdsp1 pulseaudio pulseaudio-module-bluetooth

pulseaudio-utils rtkit

0 upgraded, 7 newly installed, 0 to remove and 238 not upgraded.

Need to get 32.2 kB/1,387 kB of archives.

After this operation, 6,764 kB of additional disk space will be used.

Do you want to continue? [Y/n]

Get:1 http://mirrors.ustc.edu.cn/raspbian/raspbian stretch/main armhf rtkit armhf 0.11-4+deb9u1 [32.2 kB]

Fetched 32.2 kB in 0s (112 kB/s)

Selecting previously unselected package libspeexdsp1:armhf.

(Reading database ... 126798 files and directories currently installed.)

Preparing to unpack .../0-libspeexdsp1_1.2~rc1.2-1_armhf.deb ...

Unpacking libspeexdsp1:armhf (1.2~rc1.2-1) ....................................................]

Selecting previously unselected package libasound2-plugins:armhf...............................]

Preparing to unpack .../1-libasound2-plugins_1.1.1-1_armhf.deb ...

Unpacking libasound2-plugins:armhf (1.1.1-1) ..................................................]

Selecting previously unselected package libpulsedsp:armhf......................................]

Preparing to unpack .../2-libpulsedsp_10.0-1+deb9u1_armhf.deb ...

Unpacking libpulsedsp:armhf (10.0-1+deb9u1) ...................................................]

Selecting previously unselected package pulseaudio-utils.......................................]

Preparing to unpack .../3-pulseaudio-utils_10.0-1+deb9u1_armhf.deb ...

Unpacking pulseaudio-utils (10.0-1+deb9u1) ....................................................]

Selecting previously unselected package pulseaudio.............................................]

Preparing to unpack .../4-pulseaudio_10.0-1+deb9u1_armhf.deb ...

Unpacking pulseaudio (10.0-1+deb9u1) ...#####..................................................]

Selecting previously unselected package rtkit.####.............................................]

Preparing to unpack .../5-rtkit_0.11-4+deb9u1_armhf.deb ...

Unpacking rtkit (0.11-4+deb9u1) ...#################...........................................]

Selecting previously unselected package pulseaudio-module-bluetooth............................]

Preparing to unpack .../6-pulseaudio-module-bluetooth_10.0-1+deb9u1_armhf.deb ...

Unpacking pulseaudio-module-bluetooth (10.0-1+deb9u1) ...#.....................................]

Setting up libpulsedsp:armhf (10.0-1+deb9u1) ...##############.................................]

Setting up pulseaudio-utils (10.0-1+deb9u1) ...####################............................]

Setting up rtkit (0.11-4+deb9u1) ...###################################........................]

Created symlink /etc/systemd/system/graphical.target.wants/rtkit-daemon.service → /lib/systemd/system/rtkit-daemon.service.

Processing triggers for man-db (2.7.6.1-2) ...#############################....................]

Processing triggers for dbus (1.10.26-0+deb9u1) ...

Setting up libspeexdsp1:armhf (1.2~rc1.2-1) ...

Setting up libasound2-plugins:armhf (1.1.1-1) ...###############################...............]

Setting up pulseaudio (10.0-1+deb9u1) ...###########################################...........]

Adding user pulse to group audio######################################################.........]

Setting up pulseaudio-module-bluetooth (10.0-1+deb9u1) ...##############################.......]

Processing triggers for dbus (1.10.26-0+deb9u1) ...#########################################...]

启动pulseaudio:

/usr/bin/pulseaudio --start --log-target=syslog 

pulseaudio安装完成后,蓝牙音响设备pair/connect完成后,可以通过其提供的命令pactl查看蓝牙设备,pacmd设置profile、sink等。

#pactl list cards
Card #0
    Name: bluez_card.FC_58_FA_5B_7E_DD
    Driver: module-bluez5-device.c
    Owner Module: 20
    Properties:
        device.description = "A3"
        device.string = "FC:58:FA:5B:7E:DD"
        device.api = "bluez"
        device.class = "sound"
        device.bus = "bluetooth"
        device.form_factor = "headset"
        bluez.path = "/org/bluez/hci0/dev_FC_58_FA_5B_7E_DD"
        bluez.class = "0x260404"
        bluez.alias = "A3"
        device.icon_name = "audio-headset-bluetooth"
        device.intended_roles = "phone"
    Profiles:
        headset_head_unit: Headset Head Unit (HSP/HFP) (sinks: 1, sources: 1, priority: 20, available: yes)
        a2dp_sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 10, available: yes)
        off: Off (sinks: 0, sources: 0, priority: 0, available: yes)
    Active Profile: headset_head_unit
    Ports:
        headset-output: Headset (priority: 0, latency offset: 0 usec)
            Part of profile(s): headset_head_unit, a2dp_sink
        headset-input: Headset (priority: 0, latency offset: 0 usec)
            Part of profile(s): headset_head_unit
            
# pactl list sinks
Sink #1
    State: SUSPENDED
    Name: bluez_sink.FC_58_FA_5B_7E_DD
    Description: A3
    Driver: module-bluez5-device.c
    Sample Specification: s16le 1ch 8000Hz
    Channel Map: mono
    Owner Module: 20
    Mute: no
    Volume: mono: 65536 / 100%
            balance 0.00
    Base Volume: 65536 / 100%
    Monitor Source: bluez_sink.FC_58_FA_5B_7E_56.monitor
    Latency: 0 usec, configured 0 usec
    Flags: HARDWARE HW_VOLUME_CTRL LATENCY 
    Properties:
        bluetooth.protocol = "headset_head_unit"
        device.intended_roles = "phone"
        device.description = "A3"
        device.string = "FC:58:FA:5B:7E:DD"
        device.api = "bluez"
        device.class = "sound"
        device.bus = "bluetooth"
        device.form_factor = "headset"
        bluez.path = "/org/bluez/hci0/dev_FC_58_FA_5B_7E_DD"
        bluez.class = "0x260404"
        bluez.alias = "A3"
        device.icon_name = "audio-headset-bluetooth"
    Ports:
        headset-output: Headset (priority: 0)
    Active Port: headset-output
    Formats:
        pcm

pacmd获取到音响card索引号和sink索引号后,可通过pactl设置profile和默认输出:

pacmd set-card-profile 0 a2dp_sink        // Card #0
pacmd set-default-sink 1                  // Sink #1

设置完成后,可用pulseaudio自带的paplay播放wav格式音频文件:

paplay test.wav

至此,蓝牙音响可播放出声音。

 

参考:

    1. 嵌入式linux开发板使用pulseaudio连接蓝牙耳机播放音频文件

    2. bluetooth archlinux wiki

    3. 蓝牙核心技术概述(一):蓝牙概述

    4. 蓝牙设备开发的三种方式

    5. 对蓝牙profile的理解

    6. ARM平台上蓝牙协议栈Bluez的移植使用和配置

    7. UbuntuBluetooth A2DP receiver实现分析

    8. Bluetooth headset (简体中文)

    9. Turn Your Raspberry Pi Into a Wireless Portable Bluetooth Audio System A2DP

    10. 用树莓派玩转蓝牙

    11. Playing Audio over Bluetooth on Rasbperry Pi (Using Bluealsa, Command Line)

    12. 树莓派连接天猫精灵蓝牙音箱-1