Echar a andar un headset1 Bluetooth es uno de los mayores dolores de cabeza que se pueden encontrar actualmente en una distribución Linux, como queda demostrado si se echa un vistazo a la cantidad de mensajes en foros solicitando ayuda o al tamaño de la sección dedicada a ello del wiki de Arch Linux2. Y ahora yo también puedo decirlo de primera mano ya que lo he sufrido en mis carnes.
Hay que decir que el soporte de Bluetooth en Linux siempre ha sido un poco "magia negra". Si hace poco me quejaba de la escasa documentación de PulseAudio, el caso de la pila de Bluetooth es mucho peor si cabe. Y para ahondar aún más en el problema, una buena parte de poca información que hay por ahí ha quedado desfasada, como le pasa a la página del wiki de Debian sobre Bluetooth.
Así que, para contar esta historia tengo que empezar explicando que el soporte de Bluetooth en Linux se compone de dos partes:
-
espacio kernel: en los fuentes del kernel se implementan los drivers de acceso al hardware (a los diferentes chipsets como los de Broadcom o Atheros) y también algunos protocolos de bajo nivel. El kernel exporta una serie de dispositivos
hci0
,hci1
,hci2
, ... muy parecidos a los dispositivoseth0
,eth1
, ... que exportan los dispositivos de red. -
espacio de usuario: el resto de los protocolos de alto nivel, así como las herramientas de configuración y demás las implementa BlueZ.
No voy a contar aquí cómo funciona Bluetooth (para eso está la Wikipedia), pero sí que haré un símil para que se entienda fácilmente. Así como la pila de red está formada por un montón de protocolos, desde protocolos de nivel 1 y 2 que se implementan normalmente en el propio hardware —la tarjeta de red— hasta protocolos de alto nivel como SMTP, HTTP y demás, Bluetooth también está formado por una serie de protocolos unos encima de otros. Al fin y al cabo, Bluetooth no es más que otro tipo de red, sólo que una especialmente diseñada para distancias cortas, y donde el consumo energético es una importante restricción.
En Bluetooth hay una serie de protocolos de bajo nivel —que no nos interesan en esta descripción— y luego están el equivalente a los protocolos de alto nivel —a los SMTP y HTTP de la pila de red— que en Bluetooth se llaman perfiles (profiles), y que son los que permiten que un dispositivo funcione para una tarea u otra. En este artículo mencionaré perfiles como A2DP o HSP, ambos relacionados con el audio (que es lo que nos incumbe aquí), pero hay muchos otros. También quiero señalar que cuando un dispositivo Bluetooth es algo simple como unos cascos o un ratón, estos protocolos se implementan en hardware o firmware, por lo que no son actualizables y en concreto los perfiles estarán limitados a lo que venga de fábrica.
En cambio cuando tenemos un aparato complejo, como un ordenador o un smartphone, uno se puede permitir implementar una parte de la pila de Bluetooth en software en vez de en hardware, y esta parte normalmente son los perfiles (o sea, los protocolos de alto nivel). Esa es la parte de la que esencialmente se encarga BlueZ, por sí mismo o con ayuda de otras partes del sistema operativo.
En el caso de unos auriculares o un micrófono Bluetooth —o ambos como es el caso de un headset— BlueZ colabora con el subsistema de sonido (ALSA, PulseAudio, ...) para traernos audio hasta nuestras orejas (o llevar nuestra voz).
Bluez 5 y PulseAudio 5.0 y 6.0
Cuando en Navidades de 2012 salió BlueZ 5 se produjeron un par de cambios relevantes en este software. El primero fue que BlueZ 5 eliminó el soporte para ALSA y empezó a utilizar exclusivamente PulseAudio como subsistema al que derivar las tareas de audio. Los usuarios de BlueZ que hasta ese momento habían usado ALSA no tenían más remedio que migrar a PulseAudio o quedarse anclados en BlueZ 4. Por otro lado, en BlueZ 5 los perfiles HSP (para headsets) y HFP (para manos libres telefónicos), que hasta ese momento se implementaban internamente fueron eliminados en favor de una implementación externa (concretamente por el demonio de PulseAudio uno y el software telefónico oFono el otro):
Remove internal support for telephony (HFP and HSP) profiles. They should be implemented using the new Profile interface preferably by the telephony subsystem of choice (e.g. oFono which already supports this)
Por si fueran pocos cambios, BlueZ 5 introdujo el uso de una nueva
herramienta de configuración, bluetoothctl
, en vez de la variada
colección que había que usar en las versiones previas. Toda la
documentación que había hasta el momento en foros y wikis para
configurar BlueZ quedó obsoleta de un plumazo3. Es lo que le pasa a
la mencionada página de Debian, por ejemplo.
Al mover BlueZ parte de su funcionalidad a PulseAudio, había que actualizar también éste. Pero hasta Marzo de 2014 no aparece la primera versión de PulseAudio con soporte para BlueZ 5, la 5.0, que sólo trae soporte para el perfil A2DP:
PulseAudio now supports BlueZ 5, but only the A2DP profile, which means that in case of headsets, only playback is available, the headset microphone can't be used. BlueZ 4 is still the only way to make the headset and hands-free profiles work.
No es hasta PulseAudio 6.0 aparecida en Febrero de 2015 que no se añade soporte para los perfiles HSP y HFP (y éste último exclusivamente mediante oFono). Esto significa que si usas la combinación BlueZ 5 junto con PulseAudio 5.0 sólo tienes soporte para la parte de auriculares de tu headset y no para el micrófono, mientras que si usas BlueZ 5 junto con PulseAudio 6.0 tienes —o deberías tener— soporte para ambos.
Me diréis que no tiene sentido usar PulseAudio 5.0 teniendo la versión 6.0 disponible, pero es que Debian Jessie lleva PulseAudio 5.0, ya que cuando apareció la versión 6.0 de PulseAudio la distribución estaba en un estado de freeze profundo y ya no se podía actualizar el paquete. Debian Stretch, la actual testing, sí que lleva PulseAudio 6.0, aunque ya veremos que eso no significa que se terminen los problemas.
Configurando el kernel para Bluetooth
Ahora que ya hemos visto el panorama general es hora que pasemos a la parte práctica y a los detalles sucios. Para los que no se quieren manchar las manos hay asistentes gráficos tipo Blueman o Bluedevil, aunque ya os adelanto que si hay problemas no os van a servir para solucionarlos si no entendéis lo que está pasando por debajo.
Lo primero que tuve que hacer en mi caso es compilar un kernel con soporte para Bluetooth. Los que uséis los kernels estándar de vuestra distribución es de suponer que ya vienen con todo compilado, incluso con aquello que nunca usaréis.
El soporte para BNEP es para hacer tethering, así que no lo he incluído. El HIDP sí, por si alguna vez uso un teclado o ratón Bluetooth (en realidad es para enlazar con la parte de dispositivos de entrada HID). Le he puesto además soporte para LE porque mi dongle soporta Bluetooth 4.0.
La opción Bluetooth device drivers
da acceso al submenú donde escoger
qué drivers (módulos) incluir:
Los dongles USB para equipos de sobremesa y los integrados en los
laptops suelen utilizar el módulo btusb
, que es el HCI USB driver
en
este caso, pero algunos portátiles usan otros módulos dependiendo el
hardware que lleven. En mi caso comprobé qué hardware tenía, y si tenía
soporte, consultando los datos USB del dongle:
$ lsusb
...
Bus 003 Device 001: ID 0a5c:21e8 Broadcom Corp. BCM20702A0 Bluetooth 4.0
...
Con el VendorID:ProdID (0a5c:21e8) me fue fácil encontrar que se había
añadido soporte para este dispositivo en el módulo de
btusb
desde el kernel 3.4 4. Pero resulta que en
mi fichero drivers/bluetooth/btusb.c
del kernel 4.0.5 no encontraba
los números en cuestión. No fue hasta que encontré este
commit (github) que me dí cuenta
de lo que había pasado: para no tener que añadir cada ProdID nuevo a la
tabla de identificadores a partir de ese cambio todos los dispositivos
Broadcom (VendorID=0a5c) se tratan como uno solo.
El nuevo kernel con el soporte de Bluetooth compilado y el driver btusb
se veía así en dmsg
tras arrancar el sistema operativo:
[ 39.005283] Bluetooth: Core ver 2.20
[ 39.005293] NET: Registered protocol family 31
[ 39.005295] Bluetooth: HCI device and connection manager initialized
[ 39.005297] Bluetooth: HCI socket layer initialized
[ 39.005299] Bluetooth: L2CAP socket layer initialized
[ 39.005303] Bluetooth: SCO socket layer initialized
Y enchufando el USB dongle:
[ 2996.695737] usb 3-1: new full-speed USB device number 2 using xhci_hcd
[ 2996.826020] usb 3-1: New USB device found, idVendor=0a5c, idProduct=21e8
[ 2996.826026] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 2996.826029] usb 3-1: Product: BCM20702A0
[ 2996.826031] usb 3-1: Manufacturer: Broadcom Corp
[ 2996.826033] usb 3-1: SerialNumber: ------------
[ 2996.869091] usbcore: registered new interface driver btusb
[ 2996.869309] bluetooth hci0: Direct firmware load for brcm/BCM20702A0-0a5c-21e8.hcd failed with error -2
[ 2996.869313] Bluetooth: hci0: BCM: patch brcm/BCM20702A0-0a5c-21e8.hcd not found
Las dos últimas líneas me preocupaban, así que estuve investigando y llegué al siguiente commit "btusb: Add Broadcom patch RAM support" (github):
After hardware reset, some BCM Bluetooth adapters obtain their initial firmware from OTPROM chip. Once this initial firmware is running, the firmware can be further upgraded over HCI interface with .hcd files provided by Broadcom. This is also known as "patch RAM" support. This change implements that.
If the .hcd file is not found in /lib/firmware, BCM Bluetooth adapter continues to operate with the initial firmware.
Antes he dicho que los dispositivos de capacidad reducida (como es el
caso de este dongle) tienden a implementar Bluetooth en hardware o
firmware, por lo que luego no se pueden actualizar. Parece que Broadcom
ha intentado superar esta limitación haciendo que el driver que lo
controla le mande al dispositivo una versión más moderna del firmware
cuando lo inicializa. Esos firmwares más modernos son proporcionados por
el fabricante en forma de ficheros (en el caso de Broadcom con extensión
.hcd
) para que el driver los lea y envíe.
El fichero de firmware que buscaba el driver para mi hardware
(brcm/BCM20702A0-0a5c-21e8.hcd
) no aparecía ni en el paquete
firmware-linux-free (esperable dada la actitud
de Broadcom hacia el software libre) ni en el más general repositorio
git de firmwares en kernel.org (menos esperable) que
contiene firmwares no libres pero sí con permiso de distribución.
Tampoco aparecía ninguna referencia a drivers para Linux en el CD que
viene con el dongle.
Lo que era más preocupante: buscando por ese nombre de fichero en la Web, encontré que se necesitaba el firmware de marras para hacer funcionar los perfiles HSP y HFP. Pero como en ese momento estaba en Debian Jessie con PulseAudio 5.0 (que ya he dicho que carece de soporte para estos perfiles) me dije que de momento podía probar a ver si funcionaba sin el fichero de firmware. Aunque este otro comentario en la lista de correo de linux-bluetooth:
Anyhow, one minor thing that bugs me is this:
[193452.790194] Bluetooth: hci0: BCM: patch brcm/BCM20702A0-0a5c-21e8.hcd not found
While I want the firmware loading available for all Broadcom controllers, I do think we might have to mark some of them as firmware absolutely required since otherwise they will not work. And in that case we should print the error. Otherwise we might just silently not print anything. Thoughts?
parecía dar a entender que en el caso de mi dongle el firmware era imprescindible para que funcionara correctamente. De hecho, lo que en ese hilo se sugiere es implementar que, en los casos en que se sabe positivamente que el firmware es necesario, si no se encuentra el fichero correspondiente el driver emita un error y no inicialice el dispositivo. Pero desde que se había escrito ese mensaje (8 de mayo de 2014) no se había hecho nada en esa línea, y el driver seguía simplemente emitiendo un aviso cuando no encontraba el fichero de firmware (como en mi caso).
A pesar de los malos augurios, me lancé primero a intentar hacer funcionar el headset sin firmwares binarios propietarios opacos.
Configurando Bluetooth en el espacio de usuario
Tener el kernel con el driver cargado es sólo la mitad del trabajo. Ahora es cuando BlueZ, la parte de espacio de usuario, entra en escena. Lo primero es instalarlo, e instalar también el módulo de integración con PulseAudio:
# aptitude install bluez pulseaudio-module-bluetooth
Se instalarán los siguiente paquetes NUEVOS:
bluez libsbc1{a} pulseaudio-module-bluetooth
La versión de BlueZ que lleva Debian Jessie es la 5.23, que es la misma que de momento lleva Stretch, aunque en el instante que escribo esto la versión más reciente de upstream es la 5.31.
La parte fundamental de BlueZ es bluetoothd
, un demonio del sistema
que es el que se encarga de gestionar los interfaces Bluetooth
existentes, y de hablar con los dispositivos que se pongan dentro de su
alcance para presentarlos a otros servicios según qué perfiles
soporten5. Por ejemplo, en mi caso quiero que si un dispositivo
Bluetooth —mi headset— entra en el alcance de mi dongle,
bluetoothd
interrogue al headset para descubrir que soporta los
perfiles A2DP y/o HSP, y a partir de ello informe a PulseAudio que hay
disponibles dos nuevos dispositivos: los auriculares a los que enviar
audio y el micrófono del que recibirlo.
Por lo tanto es obvio que si queremos que los dispositivos Bluetooth
funcionen, debemos asegurarnos que bluetoothd
esté ejecutándose de
contínuo. En Debian, el demonio lo tiene que haber arrancado el sistema
de init que uséis (System V o systemd), y el servicio debe estar en
marcha. Por ejemplo, en systemd:
# systemctl status bluetooth.service
● bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled)
Active: active (running) since mar 2015-05-30 18:43:12 CEST; 36min ago
Docs: man:bluetoothd(8)
Main PID: 3731 (bluetoothd)
Status: "Running"
CGroup: /system.slice/bluetooth.service
└─3731 /usr/lib/bluetooth/bluetoothd
may 30 18:43:12 ******** bluetoothd[3731]: Bluetooth daemon 5.23
may 30 18:43:12 ******** bluetoothd[3731]: Starting SDP server
may 30 18:43:12 ******** bluetoothd[3731]: kernel lacks bnep-protocol support
may 30 18:43:12 ******** bluetoothd[3731]: System does not support network plugin
may 30 18:43:12 ******** bluetoothd[3731]: Failed to open RFKILL control device
may 30 18:43:12 ******** bluetoothd[3731]: Bluetooth management interface 1.9 initialized
Si usáis System V init los mensajes de log estarán en /var/log/daemon.log
.
Dos aclaraciones con respecto a lo que pueden parecer errores:
- Recordad que quité el soporte de BNEP de la configuración de mi kernel (por eso tampoco tengo soporte del plugin de network)
- Un dongle enchufado a un USB evidentemente no tiene botones para encender/apagar el Bluetooth como existen en los portátiles. Por eso no hay un dispositivo RFKILL que abrir (vamos, que no es un error).
Es hora de conectar el dongle a un puerto USB y ver qué pasa:
bluetoothd[3731]: Sap driver initialization failed.
bluetoothd[3731]: sap-server: Operation not permitted (1)
Olvidad de momento estos errores que aparecen en los logs (más adelante
explicaré por qué aparecen y cómo quitarlos) y centrémonos en ver si el
dispositivo Bluetooth está en marcha. Para ello usaremos una herramienta
de bajo nivel de las que vienen con BlueZ: hciconfig
. Esta herramienta
es el equivalente a ifconfig
pero para dispositivos HCI
Bluetooth:
$ hciconfig
hci0: Type: BR/EDR Bus: USB
BD Address: XX:XX:XX:XX:XX:XX ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING
RX bytes:1504 acl:0 sco:0 events:180 errors:0
TX bytes:29730 acl:0 sco:0 commands:179 errors:0
Como podéis observar, ahora tengo un dispositivo hci0
, así que el
dongle ha sido reconocido por el kernel y está funcionando. El
dongle tiene una dirección de red única similar a la de las tarjetas
de red Ethernet6 que yo he preferido ocultar, por lo que aparece como
XX:XX:XX:XX:XX:XX.
Aunque hemos usado hciconfig
para ver que el dispositivo está ahí, no
lo vamos a emplear para configurarlo como se hacía antiguamente. En
BlueZ 5 es el demonio bluetoothd
el que controla los dispositivos HCI
(hci0
, hci1
, etc). Los usuarios le dan órdenes a bluetoothd
mediante la herramienta bluetoothctl
y éste hace lo que haga falta con
el dispositivo en cuestión, almacenando el estado si fuera necesario:
$ bluetoothctl
[NEW] Controller XX:XX:XX:XX:XX:XX ******** [default]
[bluetooth]#
El prompt de bluetoothctl
está esperando comandos que pasarle a
bluetoothd
. help
os dará una lista de los comandos disponibles. Por
ejemplo, si hciconfig
hubiera mostrado el dispositivo como DOWN en vez
de UP RUNNING, podríamos ordenar levantarlo con un power on
. En mi
caso ya está levantado, así que no es necesario a pesar de lo que diga
el Arch Wiki. Los siguientes comandos de esa lista en
cambio sí son necesarios, aunque es suficiente con ejecutarlos una única
vez (la primera), ya que bluetoothd
guarda su estado y los recordará:
[bluetooth]# agent on
Agent registered
[bluetooth]# default-agent
Default agent request successful
Ahora el dongle está listo, pero todavía no está buscando activamente a otros dispositivos Bluetooth que pueda haber en su radio de alcance. Estar activo significa gastar energía, y en Bluetooth el ahorro de energía es una prioridad. Así que debemos decirle explícitamente al dispositivo HCI que active la búsqueda de otros dispositivos Bluetooth:
bluetooth]# scan on
Discovery started
[CHG] Controller XX:XX:XX:XX:XX:XX Discovering: yes
[bluetooth]#
En este momento enciendo el headset y espero hasta que me reconoce el nuevo dispositivo a través de la conexión Bluetooth (paciencia, le cuesta un poco). Cuando aparece:
[NEW] Device YY:YY:YY:YY:YY:YY Energy BT5+
[bluetooth]#
es que el dongle ha encontrado un nuevo dispositivo —el headset—, y además nos dice la dirección de red de éste (que yo he ocultado bajo YY:YY:YY:YY:YY:YY).
Por motivos de seguridad, no es buena idea permitir que cualquier dispositivo sin identificar establezca una conexión sin más con nuestro sistema. Imaginaos el cachondeo que sería que cualquiera con un teclado Bluetooth pudiera conectarlo a nuestro ordenador desde la habitación de al lado y teclear lo que le diera la gana como si fuéramos nosotros7. El usuario tiene de alguna forma que dar el visto bueno al dispositivo remoto que se quiere conectar, y esto se hace mediante el emparejado (pair) de los dos dispositivos:
[bluetooth]# pair YY:YY:YY:YY:YY:YY
Attempting to pair with YY:YY:YY:YY:YY:YY
[CHG] Device YY:YY:YY:YY:YY:YY Connected: yes
[CHG] Device YY:YY:YY:YY:YY:YY UUIDs:
00001108-0000-1000-8000-00805f9b34fb
0000110b-0000-1000-8000-00805f9b34fb
0000110c-0000-1000-8000-00805f9b34fb
0000110e-0000-1000-8000-00805f9b34fb
0000111e-0000-1000-8000-00805f9b34fb
00001200-0000-1000-8000-00805f9b34fb
[CHG] Device YY:YY:YY:YY:YY:YY Paired: yes
Pairing successful
[CHG] Device YY:YY:YY:YY:YY:YY Connected: no
El emparejado ha funcionado, aunque luego el dispositivo se ha desconectado (puede ser simplemente por el ahorro de energía).
Como no queremos estar emparejando manualmente el headset cada vez que
lo encendamos, le vamos a indicar a bluetoothd
que en adelante cuando
vuelva a encontrar al dispositivo YY:YY:YY:YY:YY:YY se fíe de él y haga
el pair automáticamente sin que nosotros le digamos nada. Esto se
consigue mediante el comando trust
:
bluetooth]# trust YY:YY:YY:YY:YY:YY
[CHG] Device YY:YY:YY:YY:YY:YY Trusted: yes
Changing YY:YY:YY:YY:YY:YY trust succeeded
Estoy diciendo que bluetoothd
guarda toda esta información, pero es
mejor que lo veáis por vosotros mismos: está en /var/lib/bluetooth/
(acceso como root). Por cierto, si por cualquier causa queréis purgar
el paquete bluez
para empezar desde cero, tendréis que borrar
manualmente el contenido de ese directorio porque el paquete Debian no
lo hace cuando se desinstala (Bug #511608).
Ahora es cuando finalmente conectamos con el headset:
[bluetooth]# connect YY:YY:YY:YY:YY:YY
Attempting to connect to YY:YY:YY:YY:YY:YY
Failed to connect: org.bluez.Error.Failed
¡¡Error!! Tranquilos, está todo bajo control. Quería mostraros el que creo que es el punto en el que mucha gente se atasca. Veamos lo que dicen los logs:
may 30 19:32:04 ******** bluetoothd[3731]: a2dp-sink profile connect failed for YY:YY:YY:YY:YY:YY: Protocol not available
Lo de "a2dp-sink" es una buena pista de la causa del problema. ¿Os
acordáis que cuando instalé el paquete bluez
instalé también
pulseaudio-module-bluetooth
? Pues lo que pasa es que el módulo en
cuestión no está cargado en el proceso de PulseAudio que ya está
lanzado. Podríamos parar y reiniciar el servicio para que lea la nueva
configuración, pero me cuesta menos decirle a PulseAudio que cargue el
módulo:
$ pactl load-module module-bluetooth-discover
20
$ pactl list short modules | grep bluetooth
20 module-bluetooth-discover
¿Alguién más piensa como yo que el paquete
pulseaudio-module-bluetooth
debería encargarse de ello
automáticamente si el servicio PulseAudio está ya en marcha?
Volviendo a los logs, ahora muestran una cosa bien distinta:
may 30 19:41:29 ******** bluetoothd[3731]: Endpoint registered: sender=:1.42 path=/MediaEndpoint/A2DPSource
may 30 19:41:29 ******** bluetoothd[3731]: Endpoint registered: sender=:1.42 path=/MediaEndpoint/A2DPSink
El demonio bluetoothd
ha sido capaz de registrar dos objetos
D-Bus, uno para los auriculares del headset
(/MediaEndpoint/A2DPSink
) y otro para el micrófono
(/MediaEndpoint/A2DPSource
), que son los que empleará PulseAudio para
enviar/recibir audio. Esto sucede en cuanto se enchufa el dongle, no
hace falta que esté encendido el headset o cualquier otro dispositivo
con perfiles A2DP y/o HSP.
Si encendemos ahora el headset y entramos en bluetoothctl
veremos
que bluetoothd
ha establecido automáticamente la conexión sin que le
hayamos tenido que decir nada (si no fuera así, ejecutad el comando
connect YY:YY:YY:YY:YY:YY
manualmente, ahora no debería dar error;
también puede ser un buen momento para parar el scan con un scan off
si no tenemos más dispositivos que emparejar). Y en los logs aparece:
may 30 19:44:41 ******** bluetoothd[3731]: /org/bluez/hci0/dev_YY_YY_YY_YY_YY_YY/fd0: fd(18) ready
may 30 19:44:41 ******** bluetoothd[3731]: Can't open input device: No such file or directory (2)
may 30 19:44:41 ******** bluetoothd[3731]: AVRCP: failed to init uinput for YY:YY:YY:YY:YY:YY
La primera línea (otro objeto exportado a través de D-Bus) nos confirma que la conexión con el pair remoto se ha establecido. Para las otras dos líneas de error he encontrado una solución en el wiki de Gentoo: compilar el kernel con estas opciones:
Device Drivers --->
Input device support --->
[*] Miscellaneous devices --->
<M> User level driver support
/dev/uinput
es un driver para crear dispositivos de entrada virtuales en
/dev/input/
desde espacio de usuario. bluetoothd
crea un nuevo
dispositivo de entrada /dev/input/eventN
(siendo N un número correlativo)
cuando enciendo el headset, y en los logs del kernel aparece:
input: YY:YY:YY:YY:YY:YY as /devices/virtual/input/input17
AVRCP es un perfil para mandos a distancia de video y de audio, pero ignoro exactamente cuál es el propósito de este dispositivo de input en ese contexto.
En busca del firmware perdido
Me había costado, pero tras hacer todo lo anterior por fin había
conseguido que funcionara el headset en Debian Jessie. Y colorín
colorado, este cuento se ha acabado. Excepto que no se ha acabado,
porque como no me puedo quedar quieto, hace un par de semanas salté a
Stretch, con el desastroso resultado de que el headset me
dejó de funcionar. :-(
He dicho "me dejó de funcionar" pero en realidad lo que pasaba era lo siguiente: cuando se enviaba audio a PulseAudio y el headset estaba encendido, la aplicación que enviaba el audio (exaile, mplayer, etc) se quedaba "clavada" (la reproducción del audio —y del video también en el caso de mplayer— no avanzaba). En el caso de exaile, si intentaba parar la canción la aplicación se quedaba absolutamente congelada. Si apagaba el headset (lo que conmutaba el audio al sink de ALSA), las aplicaciones volvían a la vida y el sonido empezaba a reproducirse normalmente.
Como las diferencias entre Debian Jessie y Debian Stretch son todavía pequeñas, y el cambio fundamental es que Stretch había actualizado PulseAudio de la 5.0 a la 6.0, lo que hice fue probar a hacer un downgrade de los 7 paquetes de PulseAudio que tenía instalados, sustituyendo las versiones 6.0-2 por las 5.0-13 de Jessie.8 El resto de paquetes eran los de Stretch, incluyendo BlueZ (que en este caso da igual porque todavía es exactamente el mismo que el de Jessie). Con los paquetes 5.0-13 el headset volvía a funcionar sin problemas, lo que me permitía acotar el problema a los cambios que había introducido PulseAudio 6.0.
¿Y qué era lo más significativo que había introducido PulseAudio 6.0? Si habéis seguido atentamente las explicaciones del principio ya sabréis la respuesta: el soporte de perfiles HSP y HFP, ¡por algo solté todo el rollo! Y hay otro momento en esta historia en que también se mencionan dichos perfiles: cuando hablé sobre si necesitaba o no cargar el firmware propietario de Broadcom en el driver, y de los bugs que supuestamente éste arreglaba (relacionados con HSP/HFP). Llegados a este punto, el sentido común dictaba que era conveniente probar a cargar el firmware a ver si arreglaba algo.
Sólo restaba encontrar el firmware adecuado, lo que ha sido una pequeña odisea en sí misma. En la página del vendedor de dongles que afirmaba que se necesitaba el firmware para hacer funcionar los perfiles HSP/HFP (que no es la marca de mi dongle pero que debe ser un revendedor análogo porque es clavadito al mío) ofrecía para descargar el siguiente fichero:
-rw-r--r-- 1 ****** ****** 34700 feb 24 2014 fw-0a5c_21e8.hcd
Buscando por el mismo nombre de fichero, encontré otra versión que tenía un tamaño diferente (28758 bytes) lo que me dejaba la duda de cuál era el más reciente. Tampoco me hacía mucha gracia usar un blob opaco descargado de un sitio random de Internet, pero en la página oficial de Broadcom no hay disponible un driver para descargar del que poder extraerlo. Lo más que hay es un ejecutable para detectar qué dispositivo Bluetooth tienes y aparentemente descargarse el driver adecuado, pero como era previsible no funciona con wine (no es capaz de pasar de la fase de detección):
(Tampoco funcionó el truco de sacar los strings
del ejecutable a ver
si entre ellos aparecía la URL desde la que se descargaban los drivers).
Una alternativa que encontré era seguir estas instrucciones
para obtener un .hcd
a partir de los drivers de Windows. En éstos
existen una serie de ficheros .hex
(que parecen simplemente una
versión hexadecimal del firmware) a partir de los cuales se puede
generar el .hcd
correspondiente usando la herramienta
hex2hcd. BlueZ trae esta herramienta (que por cierto en la
versión 5.28 ha sido reescrita) pero Debian a día de hoy no la empaqueta
(Bug 780015) por lo que si queremos usarla hay que bajarse
el fichero hex2hcd.c
y compilarlo a mano.
El problema de este método alternativo es encontrar cuál es el .hex
adecuado, porque hay decenas de ficheros de este tipo en los drivers de
Windows. Hay que buscar la cadena VendorID y ProdID (en mi caso
0A5C:21E8) en los distintos ficheros *.INF
hasta dar con la sección
relevante, donde aparecerá algún nombre de fichero como éste:
BCM20702A1_001.002.014.0187.0188.hex
. Ese fichero (de tamaño 43432
bytes) es concretamente el que encontré en el CD de drivers que
acompañaba a mi dongle. Para añadir más confusión, el .hcd
generado
a partir del mismo tenía un tamaño distinto al de los otros:
-rw-r--r-- 1 ****** ****** 21756 jun 24 16:43 fw-0a5c_21e8-BCM20702A1_001.002.014.0187.0188.hcd
-rw-r--r-- 1 ****** ****** 34700 feb 24 2014 fw-0a5c_21e8.hcd
-rw-r--r-- 1 ****** ****** 28758 jun 24 16:45 otro-fw-0a5c_21e8.hcd
De todas formas, el fichero .hex
extraido del CD de drivers era de
2011, por lo que tenía pinta de ser una versión más antigua del
firmware.
Al final me encontré en una página de soporte de Lenovo unos drivers
Broadcom para Windows de 2013, y en ellos un fichero llamado
BCM20702A1_001.002.014.0889.0896.hex
de 57399 bytes, que convertido a
.hcd
me daba un fichero exactamente igual que el firmware de 28758
bytes que ya me había bajado antes. Esta es la versión que estoy usando:
usb 3-1.6: new full-speed USB device number 5 using ehci-pci
usb 3-1.6: New USB device found, idVendor=0a5c, idProduct=21e8
usb 3-1.6: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 3-1.6: Product: BCM20702A0
usb 3-1.6: Manufacturer: Broadcom Corp
usb 3-1.6: SerialNumber: ------------
Bluetooth: hci0: BCM: chip id 63
Bluetooth: hci0: BCM20702A1 (001.002.014) build 0000
Bluetooth: hci0: BCM20702A1 (001.002.014) build 0896
La última línea es la del "patch RAM", y ya véis su semejanza al nombre del
fichero .hex
utilizado para generarlo.
Lo que no he podido encontrar es un .hex
más moderno de un driver más
reciente que se corresponda con el .hcd
de 34700 bytes.
Perfiles, perfiles everywhere
Una vez puesto el firmware en el path en el que lo busca el driver del kernel, enciendo el headset y el sonido se escucha ¡Por fin! Pero es un sonido de una calidad bajísima y con mucha "fritura"9.
Después de estar un buen rato cavilando qué estaba mal (le estaba echando la culpa a la versión del firmware), por fin caigo: esa debe ser la calidad para perfiles HSP/HFP. Lo único que tengo que hacer es cambiar el perfil:
$ pactl list cards
Card #3
Name: bluez_card.YY_YY_YY_YY_YY_YY
Driver: module-bluez5-device.c
Owner Module: 28
Properties:
device.description = "Energy BT5+"
device.string = "YY:YY:YY:YY:YY:YY"
device.api = "bluez"
device.class = "sound"
device.bus = "bluetooth"
device.form_factor = "headset"
bluez.path = "/org/bluez/hci0/dev_YY_YY_YY_YY_YY_YY"
bluez.class = "0x240404"
bluez.alias = "Energy BT5+"
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: Apagado (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
La sección que nos interesa es la de "Profiles:". Véis que me está detectando dos perfiles (tres si contáis el perfil "off"). Pero el perfil correspondiente al HSP/HFP tiene más prioridad (20) que el de A2DP (10), así que se selecciona por defecto si no le decimos nada. Para cambiar el perfil usamos el siguiente comando, indicando tarjeta y perfil a activar:
$ pactl set-card-profile 3 a2dp_sink
Si no queremos indicar el número de tarjeta (que cambia cada vez que se enciende y apaga el headset) podemos usar el nombre (que permanece constante pero es más largo):
$ pactl set-card-profile bluez_card.YY_YY_YY_YY_YY_YY a2dp_sink
El perfil naturalmente tiene que estar disponible (available: yes
) o
nos devolverá un error bastante críptico (algo así como "Error de
Entrada/Salida").
Si todo ha ido bien, ahora el estado de la tarjeta que corresponde al headset tendrá el nuevo perfil activo (Active Profile):
$ pactl list cards
[ ... ]
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: Apagado (sinks: 0, sources: 0, priority: 0, available: yes)
Active Profile: a2dp_sink
Y ahora el audio volverá a oirse con gran calidad con PulseAudio 6.0. ¡¡Hurra!!
(Sí, me podría haber ahorrado la etapa completa de la búsqueda del
firmware, ya que lo único que necesitaba para que el headset volviera
a funcionar en PulseAudio 6.0 era cambiar el perfil a a2dp_sink
. Los
bugs que corrige el firmware parece que se limitan al perfil HSP/HFP en
este caso —o tal vez no, pero no he notado la diferencia—. Lo único que
puedo decir es que es fácil hablar a toro pasado :-P
).
PulseAudio recuerda el perfil activo de cada tarjeta, así que no hace falta estar seleccionandolo en cada arranque del demonio. Si lo que queremos es volver a pasar al perfil HSP/HFP (por ejemplo para usar el micrófono) hay que volver a cambiar el perfil activo:
$ pactl set-card-profile 3 headset_head_unit
Observad que a2dp_sink
tiene 1 sink y 0 sources, mientras que
headset_head_unit
tiene 1 sink y 1 source. Los sinks (sumideros)
corresponden en terminología PulseAudio a las salidas de audio, mientras
que las sources (fuentes) corresponden a las entradas. El perfil de
alta fidelidad A2DP no tiene entradas, por lo que no se puede usar
el micrófono con este perfil. Si queremos usar la entrada de audio no
nos queda más remedio que seleccionar el perfil HSP/HFP.10
Hay otra forma más visual de escoger el perfil usando la pestaña
"Configuración" de pavucontrol
(PulseAudio Volume Control). Aquí he
preferido usar pactl
para mostraros detalles de bajo nivel (como las
prioridades o la disponibilidad) que son útiles si hay problemas.
pavucontrol
puede ser más atractivo, pero sólo nos permite seleccionar
el perfil sin darnos información adicional:
Con tanta prueba y tanto cambio, en algún momento me ha pasado que un perfil me aparecía como no disponible:
$ pactl list cards
[ ... ]
Profiles:
a2dp_sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 10, available: no)
headset_head_unit: Headset Head Unit (HSP/HFP) (sinks: 1, sources: 1, priority: 20, available: yes)
off: Apagado (sinks: 0, sources: 0, priority: 0, available: yes)
Este tipo de estados no deberían ocurrir (y mucho menos que uno aparezca
como disponible y el otro no), pero al estar jugando con los firmwares y
demás tampoco me extraña que de repente haya una discrepancia entre los
datos de estado almacenados por bluetoothd
y los de PulseAudio.
Comprobad con bluetoothctl
que la conexión está establecida, y si no
es así tratad de establecerla con un connect YY:YY:YY:YY:YY:YY
. Si la
conexión no se establece es cuando hay que empezar a investigar.
Otra posibilidad de recuperación más drástica que se me ocurre es tratar
que ambos demonios olviden el estado que tienen almacenado del
dispositivo, y empezar de nuevo todo el proceso de configuración desde
cero. bluetoothctl
tiene el comando remove
para borrar un
dispositivo remoto, lo que nos permite reemparejarlo de nuevo, pero no
he encontrado ningún método equivalente en PulseAudio. Lo único que se
me ocurre es hacerlo a las bravas: parar el demonio y borrar los
ficheros donde guarda el estado en .config/pulse/*
, pero este método
tiene el serio inconveniente de borrar el estado de todo (tarjetas,
volúmenes, etc). Si alguien sabe un método mejor estaría bien conocerlo.
Últimos retoques
No se me ha olvidado que dije que iba a explicar el origen de estos mensajes de error y cómo deshacerse de ellos:
bluetoothd[3731]: Sap driver initialization failed.
bluetoothd[3731]: sap-server: Operation not permitted (1)
SAP son las siglas de SIM Access Profile, otro perfil más de Bluetooth (y ya van...). Aunque un ordenador raramente va a tener acceso a una tarjeta SIM de teléfono, BlueZ es también la pila Bluetooth usada en Android11. Así que los desarrolladores de BlueZ han incluido este perfil que permite "exportar" la SIM vía Bluetooth a otro dispositivo (sea cual sea la utilidad de algo así).
El soporte de SAP en BlueZ es experimental, pero los mantenedores del
paquete en Debian han decidido compilar BlueZ con el flag
--enable-experimental
, y por eso SAP está incluido en la versión de
bluetoothd
de Jessie y Stretch y por eso aparecen esos mensajes. Lo
que no sería mayor problema si no fuera porque mucha gente los confunde
con el motivo por el que su dispositivo Bluetooth no funciona,
mandándoles tras una pista falsa.
La solución más sencilla es pasar de estos mensajes ya que son
inofensivos. Pero si nos resultan realmente irritantes, hay una manera
de hacerlos desaparecer: decirle bluetoothd
que desactive el plugin de
SAP pasándole el parámetro --noplugin=sap
en el arranque del demonio.
Esto es más fácil o más difícil dependiendo del sistema de arranque que
utilicéis. Por ejemplo, en systemd hay que modificar la unidad
bluetooth.service
para añadir el parámetro a la línea de ExecStart
(solo que en systemd se supone que no hay que modificar los ficheros de
unidades suministrados por los packagers sino añadir un fichero de
unidad nuevo bajo /etc/systemd/system/
que toma precedencia; consultad
la documentación más reciente de systemd en Debian para estar seguros).
Me dan ganas de abrir un bug en el BTS para que sean los propios Debian maintainers los que lo añadan por defecto.
Conclusiones
El protocolo Bluetooth, al intentar cubrir tantos dispositivos diferentes y tantos casos de uso, no es precisamente simple. La implementación en Linux lo empeora con una escasísima documentación y con cambios significativos a lo largo del tiempo. Si a esto le añadimos su interacción con otras piezas problemáticas como PulseAudio, y lo aderezamos todo con bugs hardware sólo solucionables mediante ficheros binarios "mágicos" difíciles de encontrar, entonces uno entiende por qué hay tantísimos problemas con algo que debería ser aparentemente más sencillo.
He dedicado mucho tiempo a poner el headset en marcha —y a escribir
este artículo también—. Me podría haber ahorrado algo de trabajo si
hubiera sido más espabilado, pero al final hubiera tenido que hacerlo
igualmente si quería usar el micrófono. Y me queda también el consuelo
de que me ha servido para terminar aprendiendo un montón de cosas de
Bluetooth y PulseAudio por el camino. ;-)
:wq
-
Se llama headset a una combinación de auriculares y micrófono, sean con cable o inalámbricos. ↩
-
El Wiki de Arch Linux es considerado una de las mejores fuentes de información para configurar cualquier aspecto de Linux. ↩
-
Excepto que todavía uséis una distro con BlueZ 4 o anterior. ↩
-
Los commits en github tienen la pequeña ventaja de mostrar los tags en los que fueron incluidos, los del interfaz git de kernel.org no. ↩
-
La magia la hace Service Discovery Protocol (SDP), que es el protocolo de Bluetooth para consultar qué perfiles soporta el dispositivo remoto. ↩
-
De hecho son otorgadas por el mismo organismo y ambas comparten el espacio de direciones de 48 bits. ↩
-
¡La de bromas que íbais a gastar en la oficina!
}:-P
↩ -
Hay que ser cuidadoso cuando se hacen este tipo de cosas si no se quiere terminar con el sistema medio roto. ↩
-
Argot de radioaficionados. ↩
-
He probado el micro usando la grabación desde audacity y me funciona. ↩
-
Aunque le fastidie a Google (ya que lleva licencia GPL). ↩