Streaming desktop audio on Linux without PulseAudio or JACK

Many people prefer the simplicity of Linux ALSA over additional layers like PulseAudio or JACK. This short how-to guide shows how to add the feature of remote audio over the network on a these systems. As developer of the trx software I'm frequently asked if I have instructions on how to use it for desktop audio, and this guide is in answer to that. The requirements are:

I'll explain such a setup here. This is how I have used use my laptop to send a full mix of the desktop audio (from Firefox, mplayer etc.) in realtime to a separate PC connected to a hi-fi system. I can switch or re-route audio without re-starting applications, and this is my favoured way for on-line listening like Soundcloud.

$ scc local                # send audio to local speakers
$ scc box                  # send audio to the host 'box'
$ scc none                 # switch off audio

Prerequisites

We're going to do real-time routing of audio in userland. You'll need the following software:

You'll also need to make sure the tx and alsaloop commands are able to run with "realtime" scheduling. For most PAM-based Linux systems this means the rtprio flag in limits.conf and there are some instructions on the Arch Linux wiki which are a useful template on how to do this. Your distribution may be similar or may vary.

Loopback device; the 'hub'

ALSA provides a 'loopback' device via the snd-aloop kernel module. This is going to act as the hub of audio from our desktop and onward to its destination. When JACK or PulseAudio are used they fulfil this role, whereas we're going to do it using ALSA itself.

First, load the kernel module:

$ su
# modprobe snd-aloop

Now we have a dummy soundcard which we can refer to as hw:Loopback,0. A quick test of it should show audio playing in realtime (but you won't be able to hear it):

$ aplay -vv -D hw:Loopback,0 test.wav 

The loopback device is different to a dummy device in ALSA useland and as a kernel module it is able to provide its own clock to the applications which connect to it.

A mix of our desktop audio

Now we feed our desktop audio into our loopback device. Add the following to your own (not root's) $HOME/.asoundrc file:

pcm.!default {
	type plug
	slave.pcm {
		type dmix
		ipc_key 2867
		slave {
 			pcm "hw:Loopback,0,0"
			rate 48000
			format S16_LE
			channels 2
			period_size 1024
			buffer_size 8192
		}
	}
}

You can adjust some of the parameters to taste, but be aware that the Opus codec (which we'll be using in a moment) doesn't support 44100Hz sample rate.

Until now, this is very similar to almost any ALSA-based desktop audio. The difference is our use of the loopback device instead of real hardware, which we can now relay to a destination of our choosing.

Routing audio

First, test our loopback device is working. Play some audio to the default soundcard using your chosen desktop application, or a simple command:

$ aplay -vv test.wav

You won't hear anything to begin with; the audio is not routed anywhere. Let's tap into our audio and play it out through the soundcard:

$ alsaloop -C hw:Loopback,1,0 -P hw:0

Substitute hw:0 for whatever your hardware audio interface is.

There is one complication we need to solve; the loopback audio device takes its sample rate and format from the first audio to connect to it (whether sending or receiving.) This can quickly become unpredictable and confusing to debug. Imagine the scenario where our dmix device is not operating yet as no application is playing sound, but we run alsaloop; the loopback device will now be set up with one format (prehaps governed by the audio interface itself). This may prevent the dmix device from connecting to it, usually with a confusing and obscure error message, or none at all.

To avoid this we create a plug to read from the loopback device at a fixed format, which is an exact match for the dmix device on the other side. Add the following to $HOME/.asoundrc:

pcm.hubcap {
	type plug
	slave {
		pcm "hw:Loopback,1,0"
		rate 48000
		format S16_LE
	}
}

This new 'hubcap' device is the one we will always use to capture the desktop audio, so the command for routing to the speakers becomes:

$ alsaloop -C hubcap -P hw:0

Sending audio over the network

Finally, the most useful and probably the easiest part. With these components we can use trx to send and receive the audio just like any live source.

In this example I'm going to send audio to a host called 'box'. On that host, get ready to receive audio.

$ ssh box
box$ rx

Now let's send the desktop audio to that host:

$ tx -d hub -m 2 -h box

Note that we're using a small buffer size (the -m flag) here to reduce any unnecessary delay and get audio onto the network as soon as possible.

And there you have it! Remote audio over a wifi or ethernet network, running with a healthy Opus codec. Now all that's left is to tidy this setup into a something easy to configure and switch.

A script for routing audio

Here's a link to a short shell script which implements the information in this guide to optionally routes the audio locally or to another host. Now it's just a case of using:

$ scc box

to send audio to the host 'box' in the background, or

$ scc local

to switch to using the local audio interface or speakers. No re-starting of applications is necessary.

Final words

Thanks for reading. Hopefully this short explaination will get you started with some of the more advanced ALSA routing that is possible. This guide is based on my setup which I've been using for many years now, but there could easily be mistakes or corrections in this explanation so let me know if something isn't correct. Feel free to send any feedback by email.


Mark Hills
December 2015