66 lines
7.6 KiB
Markdown
66 lines
7.6 KiB
Markdown
---
|
|
title: Streaming Music From Subsonic To Retro Radios
|
|
date: 2023-02-10T11:54:00+01:00
|
|
tags:
|
|
- radio
|
|
- music
|
|
- navidrome
|
|
categories:
|
|
- software
|
|
- retro
|
|
---
|
|
|
|
Remember those FM transmitters you used in your car in the early 2000s to connect your iPod to your car radio tuner because it didn't have an audio-in port? I built an intricate version for my music setup. This idea is (again) inspired by William Woodruff's excellent [Modernizing my 1980s Sound System](https://blog.yossarian.net/2022/11/07/Modernizing-my-1980s-sound-system) post, where he combined a Raspberri Pi 3, a USB DAC, and HifiBerryOS to stream to his 1980s speakers from any device on the lAN through AirPlay or DLNA. William wanted to use his old boxes on his new [Navidrome streaming music server](/tags/navidrome) using any Subsonic-compatible client.
|
|
|
|
That sounded (ha!) very cool, and I wanted to expand upon that idea to get it running on our own cheap retro radios. The problem is that our 1980s hardware doesn't even come with a (stereo) audio-in jack: the only way to play music is to plug in a cassette/CD, or tune in on a radio channel using FM or AM. The second challenge was also leveraging existing Subsonic clients, such as [Sonixd](https://github.com/jeffvli/sonixd) and [Substreamer](https://substreamerapp.com/). How to achieve that?
|
|
|
|
## The setup
|
|
|
|
After a week of fiddling, I came up with an ingenious---but ultimately suboptimal---solution. Observe:
|
|
|
|
![](../subfmproxy.jpg "The Subfmproxy setup.")
|
|
|
|
What's going on here? The idea was to be able to select a song on my phone through the Substreamer app I already have installed that interacts with my Navidrome music server on the NAS. To achieve that, I went with a "man in the middle" system: I set up a reverse proxy server on a Radxa Rock Pi 4C+---a Raspberry Pi clone, more on that later. By pointing the app to the Pi (#1 in the photo), it acts as the _genuine_ server, but it's just passing on all HTTP calls, except for the `/rest/stream` [Subsonic API call](http://www.subsonic.org/pages/api.jsp#stream): that one contains our music data!
|
|
|
|
Then, we duplicate that data for the response and the local file system, play that song on the Pi itself since it has an audio chip, feed the audio-out into a [Adafruit Si4713 FM transmitter](https://www.adafruit.com/product/1958) breakout board that's connected via I2C/GPIO (#2), which in turn converts it into FM sound waves on a certain channel (#3), that---finally---our old radio is able to pick up! How about that?
|
|
|
|
The FM transmitter is small and the I2C protocol only requires 2 wires: a clock and an 8-bit (actually 7) data wire, with 2 for 3.3V and ground, and a fifth one to pulse a reset signal. The board works flawlessly with a cheap Arduino UNO, but as I also wanted to host a Go-based HTTP server and required an audio chip, I needed a bit more beef.
|
|
|
|
Speaking of which, the [Rock Pi 4](https://wiki.radxa.com/Rock4/getting_started) was a nightmare to install: after the 8th flashed `.iso`, it finally recognized the audio and video chip, booting the X server. Only the official Debian image seems to work, while I kept trying to install an Armbian one. To further complicate matters, 99% of embedded hardware software libraries are compatible with a Raspberry Pi, but not with a clone. The Rock Pi has a [different GPIO mapping](https://wiki.radxa.com/Rock4/hardware/gpio) that of course was missing in every framework I tried. I eventually fixed it myself in CircuitPython and the pull request is approved, yay! The only reason I bought a Chinese clone was because RaspPi's have been and still are on back-order. In other words, they're unavailable, and it sucks: to the amateur hobbyist such as myself, Linux compatibility is a serious issue.
|
|
|
|
As for the FM transmitter setup, a bit of soldering is required to connect the pins which I had to retry more than a couple of times as it's very _very_ tiny according to my standards and refused to be recognized by `mraa-i2c detect` on I2C bus 0 (which is actually I2C7 or pins 3 and 5). Another few days of fiddling later, I discovered sending a high/low/high signal to the reset pin fixed everything.
|
|
|
|
The proof of concept code is available as the [subfmproxy git repository](https://git.brainbaking.com/wgroeneveld/subfmproxy/). Did you know that in Go it takes a single line to initialize a reverse proxy: `httputil.NewSingleHostReverseProxy(target)`?
|
|
|
|
To summarize, the whole setup involves:
|
|
|
|
- Navidrome running on our local NAS;
|
|
- ... which is connected to Substreamer on my phone;
|
|
- ... but not really since it's forwarded by my reverse proxy on the Rock Pi via Wi-Fi;
|
|
- ... which plays the stream locally via the embedded audio chip before passing it on[^cellp];
|
|
- ... which gets piped into the Adafruit FM transmitter connected to the Rock Pi;
|
|
- ... which converts it to an FM signal on a certain bandwidth;
|
|
- ... which any old radio is able to pick up;
|
|
- ... which _finally_ plays the music I originally selected on the phone.
|
|
|
|
[^cellp]: That's another problem: Substreamer doesn't know it needs to shut up, meaning the music is played via the Adafruit transmitter _and_ directly on my phone. I "fixed" this by muting the phone...
|
|
|
|
## The problem
|
|
|
|
William has also been using a commercial FM transmitter for a few weeks before coming up with the DAC approach, so I was well aware of its shortcomings: potential interference with commercial senders' stronger signals and occasional drop-outs or cracklings. No problem for me, that's part of the retro radio charm.
|
|
|
|
But my man-in-the-middle Subsonic API spoofing approach isn't entirely waterproof either:
|
|
|
|
1. There is no HTTP call when pressing the pause button. That means the radio keeps on playing.
|
|
2. Some clients, like Sonixd, cache previous songs. Play song 1 (a `/stream` call), play song 2 (another call), go back to song 1, and suddenly, the Rock Pi server isn't hit: song 1 has already been downloaded and is simply replayed at client level. The Rock Pi will never know.
|
|
3. Some clients, like Sonixd and Navidrome's more streamlined API, stream in data chunks. The Subsonic API doesn't explain this: a GET to `/stream` should return binary data---the _whole_ re-encoded song---but in reality, depending on the client used, it's not easily playable after scraping off the response. Given enough spare time to reverse-engineer existing clients, I could probably fix this.
|
|
|
|
I guess this project will never be promoted beyond concept mode. Alternative versions I thought about, but ultimately rejected:
|
|
|
|
1. I could simply play random songs we own or songs from a pre-set playlist and broadcast all day long, thereby creating our own FM station. But then, the Subsonic proxy would be pointless: simply scan all songs in a network share. Also, the Rock Pi 4C+ is passively cooled at the moment, but after streaming for a few hours, it gets quite hot. Do I want to have it powered on all day long?
|
|
2. To fix the pause and chunking problem, I could write my own client, but that would require interaction with yet another app, not to mention a lot more effort.
|
|
|
|
At the moment, most of my music is played while working from home through a Pioneer box connected via the DELL U2421E Hub Monitor ([best purchase ever](/post/2021/02/my-retro-desktop-setup/)), meaning I simply play music on my Mac that's connected to the screen via USB-C. I could also connect that stereo jack to the Adafruit transmitter and have an Arduino control it. The retro radio lives downstairs and is only occasionally used; I knew this was just an excuse to experiment and learn, but not to create something that would be used regularly.
|
|
|
|
At least I managed to learn how GPIO and I2C works in Linux, and I [contributed to open source software](/works/oss-contributions/).
|