Main premises of this solution:
1. RTP clients of comparable performance/load
2. no world clock or similar overhead
3. LAN-restricted.
4. one server, numbers of clients limited by the size of the LAN
5. Linux only (could be ported easily, but that's not my concern)
1. Install dependencies
Python and Gstreamer 1.x (1.6 tested) is needed. Details for Debian-like distro can be found at [1]. Those packages need to be installed on the server and clients. I also recommend using/installing screen utility - that will ease managing of persistent terminal sessions. I didn't bothered to daemonize and the like. Alsa-utils also comes in handy.
2. Check system variables
2.1 Identify LAN broadcast address to be used. On the server issue the following:
$ ip r s t 255|grep '^broad.*255'|grep -v '127.255.255.255'|cut -d ' ' -f 2
One of displayed addresses is your broadcast.
2.2 On every client issue the following:
# for i in /proc/sys/net/core/rmem_*; do echo "${i##*/}: $(cat $i)"; done
If values of both are at least 32-64KB, you should be OK. For higher sample rates or more than 2-channel streams these values should be increased. E.g. to change default read buffer size to 96KB issue the following:
# for i in /proc/sys/net/core/rmem_*; do echo 98304 > $i; done
To make changes persistent: man sysctl.conf
2.3 Identify ALSA loopback capture device on the server. Should be something like "hw:x,1" or "hw:x,1,y"
2.4 For every client identify a DAC as seen by ALSA. Should be something like "hw:x,0".
2.5 You may stop NTP daemons. No need for them running.
3 Grab software [2] and customize:
In file mserver-udp.py replace DEFAULT_BROADCAST with 2.1 and DEFAULT_ALSASRC with 2.3
In file mclient-udp.py replace DEFAULT_ALSASINK with 2.4. You may also change sample rate that will be mandated by RTP clients with DEFAULT_RATE. More on this limitation later.
Copy mserver-udp.py to the server and mclient-udp.py to clients.
Ensure client ingress firewall policies permit UDP traffic at DEFAULT_PORT.
4. Launch software
Some digression, first. Launching server side script might be tricky, because of how ALSA loopback works and because this software won't do any resampling explicitly (my design decision). ALSA loopback won't resample either, but what's more important audio stream parameters are defined by the first application that connects to loopback device, remain constant and cannot be changed unless all ends of the loopback substream are closed [3]. E.g. either single loopback substream will handle only tracks with one designated sample rate or player that connects to ALSA loopback will handle resampling, or ALSA will do that implicitly (if you define ALSA device in your player config using plughw plugin), or simply your player returns an error. This also shows that if you plan to use ALSA loopback explicit RTP channel definition as found in mclient-udp.py won't restrict functionality even further. You may overcome this easily by having separate infrastructures, e.g. for 44.1kHz 2-channel stream and 44.1kHz 2-channel stream. To accomplish this you need two substreams of single ALSA loop device (instead of one), two instances of mserver-udp.py (with the same broadcast but different UDP ports and ALSA sources), two instances of mclient-udp.py (with different UDP ports and same ALSA sinks). Instances of mclient-udp.py cannot be run simultaneously on a given host; some form of management is required, not necessarily RTSP, but this is beyond the scope of this lengthy post, so I will stop here.
4.1 Server side
Ensure that ALSA loopback substream will be configured to the same specs as RTP clients are.
Launch a player first or issue something like this (assuming hw:1,0 is playback end of the ALSA loopback):
$ gst-launch-1.0 filesrc location=path.to.some.flac ! flacparse ! flacdec ! audioconvert ! alsasink device=hw:1,0
path.to.some.flac contains standard CD audio format track.
On a new screen terminal session:
$ /path/to/script/mserver-udp.py
Detach screen session. Once Python script running, you may stop/terminate gst-launch-1.0/player.
4.2 Client side
On a new screen terminal session:
$ /path/to/script/mclient-udp.py
Detach screen session. Then issue the following (this optional if client is under load):
# chrt -arp 60 $(ps -eo pid,cmd|grep [m]clie | cut -d ' ' -f 1)
[1]
https://wiki.ubuntu.com/Novacut/GStreamer1.0#Installing_GStreamer_1.0_packages
[2]
https://github.com/fortaa/tmp/archive/master.zip
[3]
Matrix:Module-aloop - AlsaProject