Similar to JackTrip, Jamulus is an open source project that uses the Jack Audio Connection Kit. Clients stream their audio to a server, and the server mixes all the audio together and streams the result back to each of the clients. I built Jamulus on my Raspberry Pi by following these instructions, and tested it as a potential alternative to JackTrip.
JackTrip also supports a serverless peer-to-peer (P2P) model, where every client streams their audio directly to every other client. This is used by JamKazam and other online jamming solutions. If you have to open a port in your firewall, you are likely using P2P. While P2P doesn’t require a server, it pushes a lot more work to the clients. The compute processing and bandwidth (both upload and download) required for each client increases proportionally to the number of participants. Given today’s common laptop/desktop processing capabilities, and home Internet connection speeds, the P2P model does not scale well beyond 6 or so participants.
Jamulus has many advantages over JackTrip. It’s been around longer and seems to have a more robust community. I was surprised in particular to see how many public Jamulus servers are already running and available. I even found one close to me in San Francisco with a 10ms round-trip ping time. Perhaps most significantly, it seems far more stable. JackTrip crashed numerous times during the testing I’ve done so far. I’ve had no trouble with Jamulus, at least so far (knock on wood).
Jack streams lossless audio, even at high definition bitrates. Jamulus streams lossy audio, encoded using the Opus codec. This isn’t so much a drawback as a tradeoff. Opus is designed specifically to maximize quality while minimizing latency. Jamulus audio may be lossy, but at least to me, it still sounds very good.
Using “normal” quality, Jamulus consumes about 220 Kbps bandwidth (per client, both ways). Even using “high” quality, it only consumes about 300 Kbps. JackTrip consumes about 890 Kbps bandwidth for 48k, or 1.75 Mbps at 96k. My clients had a CPU load of about 0.4 (40% of 1 CPU core), and my server had a very low load of about 0.05 (5% of 1 CPU core). Compared to JackTrip, that puts a little more load on the clients (where we have a lot of excess) for a lot less load on the server. This suggests it could scale to handle many more concurrent participants than JackTrip, given the same server resources.
Of course, it would have to distribute work evenly out across threads for this to be true. My top analysis for 2 concurrent clients didn’t give a strong indication either way. Jamulus uses multiple threads, but it’s not clear to me how well it’s distributing the work per client:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 11197 root 20 0 78632 33092 17212 S 7.3 1.7 0:27.15 Jamulus 11224 root 20 0 78632 33092 17212 R 1.7 1.7 0:06.01 QThread 11229 root 20 0 78632 33092 17212 S 0.7 1.7 0:02.84 CHighPrecisionT
I tested Jamulus latency using jack_delay with the same EC2 virtual machine and Raspberry Pis (with HiFiBerry DAC+ ADC) as my previous tests. I found configuring a loopback client for this test to be extremely challenging. On my first attempt, all the audio was being excessively garbled. After a lot of troubleshooting, I discovered this was caused by Jamulus’ local mixer. By default, Jamulus plays back all audio coming from yourself. You can use the local mixer in the user interface (UI) to mute your own audio, but it’s not obvious or consistent who “you” are. Sometimes “you” are the left-most channel, and other times the opposite. I had to use the UI to manually mute local capture on each client to clear up feedback from the loopback audio.
Unlike JackTrip, Jamulus provides a very nice UI. It does have a “–nogui” command line option that enables it to run headless. There is also a “–inifile” option that allows you to point to a configuration file. One can use the UI to create a configuration file with the desired settings, and load these back in when running headless. Unfortunately, I haven’t been able to find a “don’t monitor my own audio” setting. Its default behavior of enabling local monitoring, combined with not having a way to disable this other than via the UI, may unfortunately make it unusable for headless applications. Monitoring your own audio is widely known to give a bad experience. I found it difficult to even carry on a conversation over Jamulus while this was happening. Even the Jamulus documentation warns against doing this, so it’s quite surprising to me that the default behavior is setup this way.
Jamulus only seems to work if Jack is configured with a frequency of 48k. I tried using a frames per second of 64, but this caused excessive overruns. All my testing of Jamulus therefore uses a frequency of 48k with frames per second of 128.
Jamulus lets you configure the size of server-side and client-side jitter buffer for each participant, and has a feature to automatically adjust it for you to minimize dropouts. I used jack_delay to test the latency when using different jitter buffer sizes, with identical values set for both clients). Here are the results:
|Client Jitter Buffer||Server Jitter Buffer||Latency|
|Auto||Auto||2206 frames (46ms)|
|2||2||1950 frames (41ms) *dropouts|
|4||4||2462 frames (51ms)|
|8||8||2718 frames (57ms)|
All in all, these results are very similar to JackTrip latency with the same frequency & frames per second. But Jamulus uses far less bandwidth, has far fewer dropouts, and seems to be a lot more stable. JackTrip offers lossless audio at lower latencies when using higher frequency rates (96k), but this comes at a cost of 6 times higher bandwidth utilization and frequent dropouts. If I can figure out a way to disable the local audio monitoring, it would be a great option for headless Raspberry Pis.