bluetooth在linux应用开发
linux内Bluetooth的协议栈为BlueZ,http://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之上有两个协议被较广地使用着:RFCOMM和BNEP,前者用于取代传统的串行口,包括串行口上的各种应用,比如,传真和拨号上网、打印机、文件图片等数据传输;后者则可以提供一个以太网接口,更适于计算机组网。自然地,对于手机和计算机之间,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设备。若使用CSR的chip,通过串口和cpu通讯的,芯片默认使用BCSP作为通讯协议,所以选择HCI UART driver和BCSP protocol support
若是通过usb接口使用蓝牙适配器,需要选择HCI USB driver。
用户态部分
用户态程序及工具集:应用程序接口和bluez工具集。
bluez软件包名称:bluez,提供bluetoothd守护进程。
bluez工具集:bluez-utils,提供bluetoothctl命令。
可用bluetoothctl完成蓝牙设备配对,步骤如下:
-
(optional) Select a default controller with select MAC_address.
-
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.
-
Enter devices to get the MAC Address of the device with which to pair.
-
Enter device discovery mode with scan on command if device is not yet on the list.
-
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.
-
Enter pair MAC_address to do the pairing (tab completion works).
-
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.
-
Enter connect MAC_address to establish a connection.
一个操作示例如下,连接蓝牙音箱(bluetoothctl命令下):
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
Bluetooth的一个很重要特性,就是所有的Bluetooth产品都无须实现全部的Bluetooth规范。为了更容易的保持Bluetooth设备之间的兼容,Bluetooth规范中定义了Profile。Profile定义了设备如何实现一种连接或者应用,你可以把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),音频输出是音乐播放器,而音频输入是无线耳机或无线立体声音响。
此配置文件定义了音频设备的两个角色:输出和输入。
输出(SRC,source):音频的输入端对音频数据进行编码,发送到Sink端。
输入(SNK,sink):接收到音频数据后,进行解码操作还原出音频。
A2DP定义了在ACL信道实现高品质音频内容的单声道或立体声分发协议和程序。 因此, “高级音频”与“蓝牙音频”应该区别开来,后者是指根据基带规格定义的SCO信道中分发窄幅波段的语音。
此配置文件建立在GAVDP基础上。它包括对复杂程度低的次频宽编解码技术(SBC)的必备支持和对MPEG-1,2音频、 MPEG-2,4 AAC和自适应声学转换编码技术(ATRAC)的可选支持。
音频数据按适当的格式进行压缩后能在有限频宽中正常使用。环绕声的分发不在此配置文件的范围。
PulseAudio
https://www.freedesktop.org/wiki/Software/PulseAudio/
Bluetooth的音频应使用软件pulseaudio,要使用蓝牙耳机或音响的话要先安装pulseaudio-bluetooth或pulseaudio-module-bluetooth。PulseAudio 5.x 开始默认支持 A2DP。
PulseAudio是一个开源的、跨平台的、支持网络的sound server。声音服务器基本上就是您的声音应用的代理者。它可以支持从一个或多个source(进程或音频采集设备)输入声音并重定向它到一个或多个sink(声卡,远程网络PulseAudio server或其他进程)。PulseAudio的目的之一就是通过它来reroute所有的音频流。
为了支持Bluetooth audio source,PulseAuido还实现了动态检测蓝牙音频设备的功能。这个功能和BlueZ A2DP Sink都是由João Paulo实现的,可以在它的blog《BlueZ 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是通过那个socket从BlueZ的进程传递到PulseAudio的进程的。最后,使用完毕,调用bt_audio_service_close()来关闭socket。PulseAudio通过D-bus和BlueZ进行通信,进行参数的读取和设置,决定合适的读取时机,发送读取的状态。
PulseAudio从fd读出的音频数据流是经过SBC压缩编码的(对于采用其他编码,如MPEG-1,的情况,本文不做讨论),PulseAudio还需要对这些音频数据流进行解码。在BlueZ中已经实现了SBC编解码,源文件位于bluez/sbc。PluseAudio直接使用了这些源代码,把它们放在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
至此,蓝牙音响可播放出声音。
参考: