Playing around with DLNA sound

For quite a while, our family has had some Sonos speakers. They're nicely designed devices and have a pleasant and balanced sound - at least to my decidedly un-audiophile ears. However, like so many consumer electronics, they are not very good on the free software front. The 'correct' way to interact with them is through closed-source desktop and mobile applications, which are pretty user-hostile and in any case are not offered for Linux. Nor does the Windows binary work in Wine. However, some time ago, I happened to be booted into Windows for one reason or another, and noticed that the Sonos speakers showed up in Windows Media Player. And sure enough, it was perfectly feasible to cast MP3s from WMP to the speakers without ever having to touch the proprietary client software. Surely, I thought to myself, if the venerable Windows Media Player has this capability, there must be Linux software able to do the same thing. And so, I began digging. I found out that the protocol that was being employed was DLNA, which is in turn a subset of UPnP 'Universal Plug 'n' Play'. Lots of Linux media players boasted of being able to play media over DLNA. However, at this point I began to get bogged down in the terminology, and my head started spinning with 'servers' and 'receivers' and 'control points' and 'media renderers' and various other nebulous overlapping concepts. Never mind, I thought - this is supposed to be easy - I just need to install the right player software, it'll detect the network speakers, and I'll be good to go. Nope. No such luck. I installed, and then uninstalled, a lot of software (thankfully this is something that Linux is very good at). Nothing would even give the slightest indication that it was seeing the speakers. After frittering away several hours in this manner, I resigned myself to the fact that this technology was likely all too grimly proprietary to work nicely on Linux, and bravely gave up. But the idea itched away at the back of my mind, and made itself known every time I had to go and find my phone to put the radio on. So one day I decided to try again. Some concerted web searching uncovered something rather better than all the eclectic music players and media servers that I had tried prior - the rather wonderful utility `pulseaudio-dlna`. This tool, seemingly the work of one Massimo Mund, detects local DLNA speakers, and dyanamically creates corresponding sinks for them in PulseAudio. It then seemingly serves the output streams from the sinks over a local server to the speakers (I'm not entirely sure how it works because I don't really understand either the DLNA protocol or the inner workings of `pulseaudio-dlna`). The consequence of this is a rather magical one - speakers on your local network automatically appearing as first-class citizens in `pavucontrol` or similar utilities, sitting alongside your internal laptop speakers and your headphone output as equals. This really is how computers are supposed to work! And, for a short time, it did work! With much coaxing on my laptop, `pulseaudio-dlna` would detect the Sonos speakers and register them in PulseAudio - and I really was able to cast music to the speakers from within Arch Linux. It felt marvellous. That was, of course, until it broke - and break it did, consistently struggling to hold a connection for more than a couple of minutes. I felt so close, yet ultimately I still hadn't found a workable solution to my problem. Insult to injury was that `pulseaudio-dlna` steadfastly refused to work at all on my desktop PC - I assumed because of multicast not working well between wired and wireless devices. Thus, the project went back onto the back-burner. That was until a couple of days ago. Armed with a recently acquired laptop, I was confident that a combination of experience and magical ThinkPad superpowers would finally allow me to acheive stable audio playback from a Linux machine to the Sonos speakers. I started where I left off - with `pulseaudio-dlna`. Installing it from the AUR was no hassle, but upon running it I was greeting with the familiar outright failure to detect any speakers. But this time, it only took me a few minutes of poking around before the solution hit me. For some reason it had slipped my mind all those times before, but it now seemed very clear that the problem was not with the DLNA software, but rather with that perennial foil of network applications, the firewall! With slight trepidation, I flushed the `nftables` ruleset and restarted the `pulseaudio-dlna` script. Sure enough, up popped the speakers, detected nearly instantly. I felt very silly and rather proud at the same time. Clearly, I had found the root of my problems, but having no firewall is hardly a long-term fix. So today, I set about trying to let the various components of `pulseaudio-dlna` through the otherwise intact firewall. At first I thought it would just be a matter of opening ports 1900 (DLNA) and 8080 (HTTP). That didn't work (I imagine I might have tried this a long time, and discounted the firewall as the problem because this naive fix didn't work - but I'm not certain of that). A little more probing revealed that DLNA 1900 is very much *not* a TCP connection - another facepalm moment - but changing the firewall rule to UDP didn't seem to fix the problem either. At this point `pulseaudio-dlna` was picking up the speakers on occasion, but nothing like the instantaneous detection observed when the firewall was down. The final trick that solved the puzzle was logging to the ring buffer the packets that `nftables` rejected. Here, it was clear that the speakers were barraging a variety of seemingly random ports with UDP connections. I was loathe to simply open up all the high UDP ports in the firewall, so in my desparation I decided to actually read the docs. Here, it was very clearly described that `pulseaudio-dlna` requires connections to be accepted on 8080 (TCP), 1900 (UDP), and a random search port (also UDP), and that those with a firewall may choose a fixed value for the search port. I grimaced slightly at the thought of the several hours I had lost that could've been saved by a cursory scan of the README. No matter - armed with this knowledge I was finally able to get `pulseaudio-dlna` running at full efficiency without needing to take a proverbial fireman's hose to my entire firewall. So, end of story, right? Not quite. I was very happy with how well this solution was working, but there were a few niggles. Firstly, that I had to remember (or trust my shell history to recall) the particular encoder settings that worked with the speakers. Secondly, I had to manually start and stop the `pulseaudio-dlna` script. And thirdly, the required firewall holes were in place all the time, and I had surrendered the ability to have a random search port, which is presumably good for security (at least I'm assuming, since it's the default behaviour!). I set about rectifying these quibbles in one fell swoop by writing `autopadl`, a very small utility that wraps `pulseaudio-dlna` in a systemd service, and dynamically reconfigures the firewall (with a random search port) when the service starts and when it stops. As an added bonus the command line flags for `pulseaudio-dlna` can be permanently configured in an rc file. This solution works really rather nicely and turns the already elegant `pulseaudio-dlna` into something really quite lovely. As an encore, I might try configuring the systemd unit to require a connection to my home wifi as a target (I think this is a thing you can do), so the DLNA service automatically starts and stops itself depending on whether I'm in the house or not, but that's a project for another day. As it stands, I'm chuffed with my newly won ability to listen to music and the radio (the latency is too high for video) on some very nice speakers without having to even glance at a bloated, clumsy, non-free software client. Though perhaps next time I will finally have learnt that the best time to read the manual is at the start of the process and not $n hours in :-) You can find `pulseaudio-dlna` here: You can find `autopadl` here:
This webpage was authored with libre software Site Modified: Thu Oct 29 16:44:55 GMT 2020