My multimedia and home theater system has mutated various ways, and presently, the house server (the only machine running 24/7) is on the west side of the room while the speakers and amplifier are on the south side. I have an ugly cable running between them. I would like to replace the cable.
The server has a factory-installed Realtek RTL8723AE 802.11 (WiFi) and Bluetooth NIC. I bought a Logitech Bluetooth Audio Adapter, which accepts a connection from a sound source, converts the payload to analog audio, and sends it to the amplifier via RCA jacks or a 3.5mm stereo jack. It works nicely from my Android devices…
But Bluetooth on desktop Linux is badly integrated and badly documented.
My goals for this device are:
The Grivet Music Player (executing as wwwrun on the server) should be able to play its music over Bluetooth on the remote speakers. Manual intervention would definitely impair the WAF of this hardware.
Sound by the console user (e.g. Skype) should still function on the local speakers, served by a USB (or PCI) sound card.
If in the future I got a Bluetooth mouse or keyboard, these should function for the console user.
Using the Logitech Bluetooth Audio Adapter was very simple, taking under ten minutes:
The equivalent process on the server, running OpenSuSE Linux version 13.1, took two weeks of hard work. Here are the key items discovered, cutting out most botches and blind alleys.
Music is played by the Grivet Web Player. It accepts a MRL (URI of a track or playlist) on
its web form and invokes the Meow player script with this MRL as an argument.
Grivet is executed as a CGI by the webserver and by its privilege separation
user, which on SuSE is called wwwrun
. In an alternative design there
could be a special user for Grivet, the CGI would be in its UserDir, and that
user would do the execution via SUExec.
The latest bluetooth-applet-3.6.x won't start unless it can open (read-write) /dev/rfkill . This file is provided by the urfkill package (started by d-bus). Also install the rfkill package; "rfkill list" shows which devices are blocked (in my case, none). Both are not in the main distro; obtain them from the SuSE Build Service.
However, /dev/rfkill has mode 644 root:root. Look for /lib/udev/rules.d/61-gnome-bluetooth-rfkill.rules (which we don't have). For testing, chmod 666 /dev/rfkill . But /etc/polkit-default-privs.standard grants access under auth_admin, and I (as the console user) have a persistent auth_admin authorization, so after a restart an ACL was set on /dev/rfkill giving me (by name) RW access.
The private configuration directory for PulseAudio has changed. I needed to delete ~/.pulse (having copied everything into ~/.config/pulse ), otherwise some programs looked for the PulseAudio cookie in one dir and some in the other, failing.
PulseAudio module-udev-detect tried to configure the TurtleBeach USB audio device and failed, not enough bandwidth. Unplugging and replugging the device allowed it to work.
My default.pa sets the default sink to
alsa_output.usb-Burr-Brown_from_TI_USB_Audio_DAC-00-DAC.analog-stereo
but this device no longer exists. Changed to
alsa_output.usb-C-Media_INC._USB_Sound_Device-00-TurtleBeach.analog-stereo .
(Use pactl list-sinks
to find the names to use.)
You will need to do a lot of work as
the Grivet execution user. Here is a convenient command line. Execute
this as root, and it's assumed that this root user has a working X-Windows
connection, as you would get with slogin -X root@localhost
.
xterm -e su --login --shell /bin/bash wwwrun
wwwrun has a useless home directory /var/lib/wwwrun, which is supposed to be (and stay) empty. In my case the effective home directory for running Grivet needs to be set up with this command:
export HOME=/home/httpd/htdocs/grivet
To play a test sound on PulseAudio:
sound=/usr/share/sounds/alsa/test.wav #Or your favorite wav file, not MP3
paplay $sound
pacmd list-sinks | less #To find out what sinks you have
paplay -d bluez_sink.00_1A_7D_52_12_9A $sound #To use a non-default sink
You need to have these packages installed:
Between bluez-4.x and 5.x there was a major change in the API: 4.x has its own socket, while 5.x uses the system d-bus. bluez-alsa-4.19 uses the socket, and neatly makes Bluetooth devices available in the ALSA paradigm. But it has never been updated to use d-bus, i.e. bluez-5.x. Several distros' wikis recommend that if you want to use ALSA sound and don't want to use PulseAudio, downgrade bluez to 4.x. But of course it is unmaintained, and I do want to use PulseAudio for generic desktop sound. In particular, Skype for Linux (which I use) requires PulseAudio. So that route is not going to fly.
But if I make the web-controlled music player use PulseAudio as its backend, it's another layer of complexity and of features that will need to be relearned and reconfigured after an upgrade. It looks like I'm going to have to live with that extra complexity, though. The ideal would be if someone (else) would upgrade bluez-alsa to the new d-bus API.
Many forum posts say you need to edit /etc/bluetooth/audio.conf with this content:
[General]
Disable=Socket
Enable=Media,Source,Sink,Gateway #After disable!
This is for bluez-4.x though they never say so. In bluez-5.8 (likely since 5.0) this file is not read, and if the parameters are transferred to main.conf they will be rejected.
If you have bluetooth-applet or a GUI alternative, it will have a convenient discovery and pairing dialog. Pairing is not user-specific: you can pair as yourself and it can be used by the music service user.
To pair from the command line, start bluetoothctl and give it these commands (omitting the #comments of course). Anally retentive initialization commands are also shown; these are rarely needed in practice.
Powered: yes.
connectcommand does nothing.
When PulseAudio starts up it reads
daemon.conf and default.pa from ~/.config/pulse/, or if the file is not
found it looks in /etc/pulse/. Frequently you will want
~/.config/pulse/default.pa to .include /etc/pulse/default.pa
.
daemon.conf has command line options, and in /etc/pulse/daemon.conf the
defaults are given (all commented out). default.pa tells which modules to
load, and other policy items like the default sink and source.
/etc/pulse/default.pa includes these commands:
load-module module-bluetooth-policy
load-module module-bluetooth-discover
module-bluetooth-discover will load dependent modules such as module-bluez5-device. The PulseAudio instance that will use the Bluetooth sound sink needs these modules. The PulseAudio instance that is not supposed to be using Bluetooth should not load these modules; if it does, it may actually acquire the Bluetooth device, and the web player will not be able to use it. To make that not happen, in my ~/.config/pulse/default.pa I inserted /etc/pulse/default.pa (rather than including it) and edited out the unwanted load-module lines.
I have not figured out how to make one PA instance always get the remote speakers and a different instance always get another Bluetooth device such as a headphone.
When PulseAudio encounters a
device that is not in its configuration cache, its profile (performance
style) initially will be off
, which is not useful for playing sound.
The profile is specific to each user and is saved in PulseAudio's
configuration cache in ~/.config/pulse/$ID-card-database.tdb (where $ID is
the host identifier from /etc/machine-id). Executing
as the user (wwwrun) that will be using the device, start pavucontrol and
turn to the Configuration tab. Find the new device. It has a drop-down
list of available profiles. Choose A2DP. This setting will persist until
changed again or until the cache is deleted (manually).
From the command line you could set the profile by doing:
pacmd list-cards
pactl set-card-profile bluez_card.00_1A_7D_52_12_9A a2dp
As is common, this headset can switch to the HFP/HSP profile for
telephony (mono sound at 7kHz, with microphone and call control button).
One time I got it into this mode, probably by pressing the call button in a
botched attempt to power it off. The output from pacmd list-sinks
includes the profile and the role being used. This command got it back to
A2DP:
pacmd set-sink-port bluez_sink.00_1A_7D_52_12_9A a2dp_sink
What does the Meow player script need to do, to play the music?
Although PulseAudio complains that it cannot launch a session d-bus without access to the X-Windows display, it functions just fine without the d-bus, so I did not pursue getting d-bus properly launched ahead of time. dbus-run-session (not dbus-launch) would be the correct launcher.
PulseAudio needs to communicate with the Bluetooth daemon, which is systemwide and uses the system d-bus. /etc/dbus-1/system.d/bluetooth.conf (from bluez-5.x package) allows the console user to send messages to org.bluez, i.e. the Bluetooth daemon; several other user classes also have this privilege. We need to hack the file adding a stanza that allows the user executing Grivet to talk to the daemon:
<policy user="wwwrun">
<allow send_destination="org.bluez"/>
</policy>
After changing this file you need to send SIGHUP to the daemon, or
use systemctl reload dbus
.
ConsoleKit manages many access permissions, and I investigated whether I could give Grivet access to the Bluetooth device by adding policy stanzas and registering a session for Grivet, but that was a blind alley.
The Meow script needs to do:
pulseaudio --start --log-target=syslog --exit-idle-time=3600
--start means to check if it is already running, and to start the daemon if not. The daemon exits after 60 seconds of non-use. This makes manual testing difficult, so I lengthened the idle time.
Only one sound source at a time can connect to the Bluetooth device. Therefore in my own ~/.config/pulse/default.pa I copied /etc/pulse/default.pa but removed the commands to load-module module-bluetooth-policy and module-bluetooth-discover. Without this, wwwrun would usually but not always fail to get the connection. I also disabled bluetooth-applet. Likely this was not necessary; it works like bluetoothctl and affects the state of the device(s) but does not take possession of them exclusively.
/etc/pulse/default.pa loads module-suspend-on-idle, so it should disconnect from the Bluetooth sink(s) that it isn't using actively. However, this is not the behavior that I see.
For Bluetooth, connect
has connotations that can easily be missed. Two ends have to be connected:
the remote device (headphone or speakers), and the software (PulseAudio)
that is going to send the payload. When PulseAudio starts up it will
contact the Bluetooth daemon and be aware that the device is there as a
potential sound sink, but an actual connection may be delayed quite a long
time. A key step for Meow is to do pacmd list-sinks | grep $sink
(where $sink is the one we want to use, bluez_sink.88_C6_26_0B_3D_06 ,
identifying the device by its MAC address). If this succeeds, try
bluetoothctl <<<"info $macadr" | grep "Connected: yes"
.
<<< is a Bash-ism meaning to feed the following unit on standard
input, as a command. if there is no Bluetooth connection, give the daemon
a kick like this: bluetoothctl <<<"connect $macadr"
Rather than the obvious sequence of commands joined by && and ||, I emit diagnostic messages, and I give PulseAudio 1 second to start up, then retry this sequence five times with 5 seconds between attempts.
If you test connect $macaddr
before PulseAudio has started,
it will not connect (having no sound source to connect to).
hcitool cc $macadr
will initiate a baseband connection, but without
the player software as a partner it will immediately shut down.
Technically this is not a Bluetooth issue, but here is the absurdly simple heart of the Meow player script. $sink is the PulseAudio sink to play on: for me, bluez_sink.88_C6_26_0B_3D_06 .
gst-launch-1.0 uridecodebin "uri=$1" ! pulsesink device="$sink"
The separator is an exclamation point. Uridecodebin will bring in the track or streaming audio (use file:///path/to/track.mp3 for a local file), adapting to the mime-type, e.g. it can handle Icecast. It will assemble a set of codecs to demux the transport stream (if present), interpret the elementary stream(s), resample it to the sink's clock rate and channelscape (mono, stereo, surround), and pass it to the specified sink. It can even do video including YouTube.
The playbin module can pick a default sink by itself, but I had trouble getting it to use the Bluetooth speaker, so I took the easy way out and specified it explicitly to the pulsesink.
Do gst-inspect-1.0 pulsesink
to see these properties.
Here is a link to
documentation for the PulseAudio sink from GStreamer (which does not
document all of the properties returned by gst-inspect).
These properties look useful:
In particular, when I play streaming media the transmitter clock is not exactly the same as the clock in the sound card, so I hear brief pauses in the sound when the sound card performs packets quicker than the source sends them out, or I hear occasional packets being dropped. I believe that slave-method is intended for differences in the announced rates of upstream and downstream elements, like 48000 vs. 44100 bits/sec, but it deserves investigation for also avoiding small differences between the announced and actual rate.
While investigating the various problems I downloaded and compiled bluez-5.30.tar.xz. Build dependencies: dbus-1-devel udev libudev-devel libical-devel (why??) readline-devel (+ ncurses-devel tack) systemd-devel
It looks like A2DP now has multiple codecs: SBC MPEG12 MPEG14 ATRAC. SBC is the original sub-band codec; people including jimc complain that its sound quality is noticeably worse than wired playback on the same headphones. MPEG12 I believe includes what is commonly known as MP3, so in theory and if your headphone or speaker supports it, you could send MP3 files as-is and they would be decompressed inside the headphone. MPEG14 I believe refers to the audio format used in MPEG-4 video. ATRAC is a proprietary family of codecs from Sony.
It would be very interesting to learn how to activate the MPEG12 codec, but that's off topic for this document.