Echar a andar un headset 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 Linux. 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 dispositivos eth0
, 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 plumazo. 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 . 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
soporten. 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 Ethernet 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 nosotros.
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. 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".
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.
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 Android. 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