Audio Transcoding with a Raspberry Pi

Nowhere Man
4 min readJan 4, 2017

--

A few months back I bought myself a radio scanner. I’ve been listening to scanner streams near and far through Broadcastify for years. While I was able to hear some local stuff that way, they did not offer a stream of my local Police Department. And I was curious to see what else was flying through the air around me, so I finally decided to go ahead and get the equipment to pull in those broadcasts myself.

One of the features of this particular scanner that seemed a bit quirky to me at first was that it came with a wifi dongle. Using that connectivity in concert with an iOS app you could listen to and control the scanner from your iOS device. A cool idea poorly executed. The app was frustrating to use and there was no Android option. And regardless of any of that, what I really wanted to do was cast the scanner’s audio to my Google Home and Chromecast Audio. With a little help from Wireshark, I was able to hunt down the raw stream coming from the scanner in hopes of plugging it into one of the many Android apps that allow you to cast arbitrary streams. Sadly it turned out that the scanner was serving its audio stream via RTSP. Not compatible with Chromecast.

The next option was to introduce a third device into the mix and transcode the RTSP stream into something that could be played by the Chromecast. I chose a Raspberry Pi for the task. There are two tools involved in my solution: GStreamer to ingest and manipulate the audio stream from the scanner and Icecast to serve a compatible stream back out to the Chromecast.

Icecast was the easy piece. All it took was a single command and the installer walked me through a minimal setup process that had things up and running in less than 5 minutes.

sudo apt-get install icecast2

Then came the hard part. Installing GStreamer is easy enough…

sudo apt-get install gstreamer-1.0 gstreamer-1.0-tools

…but configuring it is a little overwhelming. It’s a kitchen sink sort of tool that can do all sorts of audio and video tricks. And it uses a pipeline syntax, which makes a lot of sense, but it can be more than a little frustrating. If you don’t get exactly the right combination of things in exactly the right order, it won’t work.

There was a lot of trial and error involved in figuring out how to get it to do what I wanted to do: ingest an audio stream from an RTSP server, convert it to mp3, and pass the result off to Icecast. I found that it was a bit easier to take things one piece at a time. First, ingest the RTSP stream and play it natively on the Pi through the headphone jack. That was actually not very difficult…

gst-launch-1.0 playbin uri=rtsp://xxx.xxx.xxx.xxx/au:scanner.au

…but it was a little quiet. Could I adjust the volume? Yes, but not using the playbin command. If you want to do any manipulation of the audio signal you have to use rtspsrc instead. That little nugget would come in handy later on. So to play it a little louder…

gst-launch-1.0 rtspsrc location=rtsp://xxx.xxx.xxx.xxx/au:scanner.au ! decodebin ! volume volume=5 ! autoaudiosink

…and that’s where things started to get a little weird. It turns out that you can’t just swap in the Icecast command for the audio sink on the end. The headphone jack and the Icecast server expect different types of inputs. That makes sense when you think about it, but it made debugging the whole thing that much more exasperating. After experimenting for the better part of a day with every combination imaginable I pasted this onto the command line of my Pi…

gst-launch-1.0 rtspsrc location=rtsp://xxx.xxx.xxx.xxx/au:scanner.au ! decodebin ! audioconvert ! audioresample ! lamemp3enc bitrate=64 mono=true target=bitrate cbr=true ! shout2send ip=xxx.xxx.xxx.xxx username=source password=xxxx public=true streamname=uniden mount=/uniden

…for the first time in hours I did not see any errors in the result. Was it working? I grabbed my Android phone, launched my casting app of choice, and typed in the URL where I expected the stream to be hosted on the Icecast server. Seconds passed as things buffered and I waited for the scanner to find someone talking. I jumped in my seat when I finally heard the voice of a light rail driver emanating from every connected speaker in the house. It was working!

But I wasn’t done. This was just a checkpoint from which I could experiment with some more fine tuning of the audio quality. Things were back to being on the quiet side and the bass was too heavy, voices were muffled and difficult to understand. Was there an equalizer I could stick into the pipeline to jack up the treble? There was. There was also a data queue pipeline element, a sort of buffer to guard against inconsistencies in the stream or the network or the mp3 encoder. This is one of those things you can keep tweaking forever as you find new problems or discover new things you can make it do, but as of right now, my pipeline looks like this…

gst-launch-1.0 rtspsrc location=rtsp://xxx.xxx.xxx.xxx/au:scanner.au ! decodebin ! queue2 ! audioconvert ! audioresample ! volume volume=3 ! equalizer-3bands band0=-6 band2=6 ! queue2 ! lamemp3enc bitrate=64 mono=true target=bitrate cbr=true ! queue2 ! shout2send ip=xxx.xxx.xxx.xxx username=source password=xxxx public=true streamname=uniden mount=/uniden

A few hours in it seems to be pretty stable on the Pi and is using less than 5% of the CPU.

--

--