detfalskested

Streaming audio from your mobile phone to the internet

Yesterday, I finally did something that has been on my todo list for years.

I'm a big fan of the Squeezebox network music players that Logitech tried to kill more than 10 years ago[1]. So much that I wrote cursebox, a CLI tool to control your players.

One (possibly the only?) drawback of the Squeezebox ecosystem is that it's difficult to have friends over that want to play something from their phone. As someone who refuses to use Spotify and similar services with "all" music available, the problem gets even worse.

The number of residents in my apartment recently doubled, and my better half insisted that she need a simple way to play music on the speakers. Otherwise she'd go and buy a Bluetooth speaker. I considered that to be an embarrassing defeat – both for me and the Squeezebox ecosystem – that we would not suffer.

So, what to do then?

The obvious solution is to make the whole Squeezebox system act as Bluetooth speaker: Connect to it and listen to whatever you want from your phone.

I saw a couple of paths to success:

  1. Buy a Bluetooth receiver with an AUX out, connect it to the home server running the Logitech Media Server (LMS) and find a way to pass that incoming audio signal into LMS.
  2. Directly use the Bluetooth radio built into the home server and do sort of the same as the above.

The second solution seemed to be the better: No new hardware. I only needed to set up some software and write a bit of code.

I made a quick proof of concept on my desktop computer and found a way to:

Easy peasy! This was going well. But then I started hitting all the road blocks.

When moving this proof of concept to the server, I wasn't able to make a Bluetooth connection that would allow me to share the audio from the phone. I spent several hours researching. Did a lot of trial and (even more) error. Tried to set the device class for the server Bluetooth radio to something that would accept audio. But nothing worked. Even despite both server and desktop running Ubuntu. But my conclusion was that either a) the Bluetooth support in Ubuntu Server is very basic and I didn't have the skills to find out how to make it work or b) there was a problem with the specific Bluetooth module in this computer.

Alright. Back to the first option: A Bluetooth receiver that I could connect to the minijack in on the server.

A while back I did a quick search for Bluetooth receivers on DBA and found lots of those, even in my area. But yesterday I could only find a few and they were far away. And I wanted to solve this problem immediately!

So: Return to the second option, more research, more failure.

In order to find out whether the missing audio sharing options from phone to server was a software or a hardware issue, I dug out an old Raspberry Pi from a stowed away box. Then I installed Ubuntu Server, only to conclude this had the same issue. In the true spirit of debugging, I then installed the full desktop Raspberry Pi OS – and this time it worked! Alright: It is a software limitation in the server version of Ubuntu then.

Leave the server alone for now: I was on the right path with the Raspberry Pi. If I could get it to work, I could just set it up, hide it somewhere and forget about it.

Continuing the work on my previous proof of concept, I had to install and use pavucontrol to make sure the audio playing on the system was the source for recording.

As a first test, I recorded into Audacity. Just to see that things were working and I was able to record the audio from the phone. Great. Next step was to find a way to stream that audio to the network.

I was pretty sure ffmpeg could do this. And after a bit of searching the internets, I managed to compose a command that did exactly that:

ffmpeg -ac 1 -f alsa -re -i default -acodec libmp3lame -ab 128k \
    -ac 1 -f rtp rtp://${SERVER_IP_ADDRESS}:3140

It took a bit of time for me to realise that the IP address had to be that of the server, not the Raspberry Pi (or 0.0.0.0 or similar), probably because it is a UDP stream (please correct me if I'm wrong). Great success... Until I realised that LMS does not support the RTP protocol.

Also, I found out later, this was only mono. Not stereo. I had to change the -ac 1 arguments to -ac 2 in order to capture, transcode and stream a stereo signal.

No worries. I would just change it to be a HTTP stream instead. This way, it could also be served to multiple clients, if I ever ran into that need.

ffmpeg -ac 2 -f alsa -re -i default -acodec libmp3lame -ab 128k \
    -listen 1 -ac 2 -f mp3 http://0.0.0.0:3140/stream.mp3

This worked in VLC player locally on the Raspberry Pi. It also worked from the browser on my desktop computer. But it seemed like LMS would only buffer the stream for a while and then give up. I also had the problem that when the client disconnected, ffmpeg would throw and error and quit. And I didn't want to SSH into the Rasperry Pi to restart the stream every time someone disconnected from it.

I was sure I was so close to the finishing line.

Then it struck me to try and figure out how the good people at SomaFM do their streaming. I downloaded a playlist file and saw their streaming URLs to be something like: https://ice6.somafm.com/doomed-256-mp3. And ice6 put me on the right track. I figured this was an indication that they are running Icecast as their streaming server. And during my research on how to set up streaming with ffmpeg, I found an example of how to pass on a stream from ffmpeg to Icecast.

sudo apt install icecast2 on the server. The setup was so easy that I was sure I missed something. But it turns out that you don't have to set up your relays in Icecast in advance. You simply just define from your source (which in my case is ffmpeg) that you want your stream to be available on /my-path and then that's the path the stream is available on from Icecast. Neat!

And after a bit of tweaking, my final command looks like this:

ffmpeg -ac 2 -f alsa -re -i default -acodec libmp3lame -ab 320k \
    -listen 1 -ac 2 -metadata title="Bluetooth stream" -f mp3 \
    icecast://source:${ICECAST_SOURCE_PASSWORD}@${SERVER_IP}:3333/bluetooth.mp3

Then, in LMS, add http://${SERVER_IP}:3333/bluetooth.mp3 to the playlist, and the wonderful tones are streaming from my phone, via Bluetooth to the Raspberry Pi, via ffmpeg to Icecast, via LMS and out to my Squeezeboxes – which are all perfectly synced. With a delay from phone to Squeezebox(es) of about 4 seconds, which to me is tolerable.

At this point I had spend around 12 hours on the project. But it was all worth it and a huge quality of life improvement.

As a software developer, I'm well aware that this will give me future headaches and require some maintenance. But for now I'm happy. There's just one thing left to polish: Set up Bluetooth pairing that doesn't require interaction on the computer side of things.

[1] The Squeezebox product line is a prime example of the power of open source. Despite Logitech discontinuing production of the players i 2012, they still live on in all their glory. And a big part of the reason for that is that the server software, Logitech Media Server, is open source (and still being actively developed!), so Squeezebox owners continue using their hardware as if nothing happened. As opposed to Sonos speakers that were not just hit by planned obsolescence, but rather a forced/encouraged obsolescence (which Sonos later had to regret). The Squeezebox players even sell second hand at low prices.