Check out my first novel, midnight's simulacra!
KEF LS60
I acquired a pair of KEF LS60 active wireless speakers in January 2024. The speakers are awesome, but getting them working well with Linux was not completely trivial, and is not quite complete.
At the time, I had the following versions of crap:
- Linux 6.7
- Pipewire 1.0.1
- Pulseaudio 16.1
- KEF firmware 2.0
As a first observation, setting up an EQ profile for my small office seemed to substantially improve listening experience. I used Normal Mode (as opposed to expert).
Requirements
I was using MPD 0.23.14 for my music, but I needed a solution at the audio device level, so that video, system sounds, and everything else would use the speakers. So I was looking at pulseaudio or pipewire. I wanted to create a virtual device into which I could dump digital data (probably PWM). The speakers have a line in, but I did not want to do the digital-to-analog conversion on my machine, but rather to use the DAC of the $7,000 speakers. They also have an HDMI input, which could happily accept stereo PCM at up to 24 bits at 192 kHz, but I wanted wireless.
I might end up using the HDMI if I can't eliminate the latency of my wireless solution.
An ideal solution would involve:
- no cables between computer and speakers
- lossless transfer to speakers (either as unpacked PCM or packed FLAC/AAC/etc.)
- DAC at speakers
- minimal latency
- output automatically waking the speakers from standby mode
Wireless
Using the app, set the input to "Wi-Fi". The specs list the following "wireless streaming features":
- AirPlay 2
- Google Chromecast
- Roon Ready
- UPnP Compatible
- Bluetooth 4.2
As far as I'm aware, Bluetooth audio is always going to involve a lossy compression, so I won't be using it. I did set up Bluetooth just to see that it worked, which it did. You'll have to pair the devices, and connect them.
Running avahi-browse -a -t showed numerous zeroconf services:
- ax200 IPv6 ls60w-eb17fe57-5688-4f03-b2a9-4ef78be95abf _spotify-connect._tcp local
- ax200 IPv6 8417151A1273@lilypad speaker AirTunes Remote Audio local
- ax200 IPv6 lilypad speaker AirPlay Remote Video local
- ax200 IPv6 ls60w-eb17fe57-5688-4f03-b2a9-4ef78be95abf _suegrouping._tcp local
- ax200 IPv6 LS60 Wireless-490b3a50ff76a5f16a56b25fa9ca18fe _tidalconnect._tcp local
- ax200 IPv6 8417151A1273@LS60W _kef-info._tcp local
- ax200 IPv6 ls60w-eb17fe57-5688-4f03-b2a9-4ef78be95abf _sues800device._tcp local
- ax200 IPv6 lilypad speaker Web Site local
adding -r for resolution generated more details:
= ax200 IPv6 ls60w-xxx _spotify-connect._tcp local hostname = [ls60w-xxx.local] address = [192.168.88.77] port = [80] txt = ["VERSION=2.9.0" "CPath=/api/stream/spotify:zeroconf"] = ax200 IPv6 8417151A1273@lilypad speaker AirTunes Remote Audio local hostname = [ls60w-xxxx.local] address = [192.168.88.77] port = [7000] txt = ["pk=xxx" "vs=366.0" "vn=65537" "tp=UDP" "sf=0x4" "am=LS60 Wireless" "md=0,1,2" "fv=p20.2.0.66.0x187653b" "ft=0x445F8A00,0x1C340" "et=0,4" "da=true" "cn=0,1"] = ax200 IPv6 lilypad speaker AirPlay Remote Video local hostname = [ls60w-xxx.local] address = [192.168.88.77] port = [7000] txt = ["pk=xxx" "gcgl=0" "gid=xxx" "pi=xxx" "srcvers=366.0" "protovers=1.1" "serialNumber=xxx" "manufacturer=KEF" "model=LS60 Wireless" "flags=0x4" "fv=p20.2.0.66.0x187653b" "rsf=0x0" "features=0x445F8A00,0x1C340" "deviceid=xxx" "acl=0"] = ax200 IPv6 ls60w-xxx _suegrouping._tcp local hostname = [ls60w-xxx.local] address = [192.168.88.77] port = [80] txt = ["ip=192.168.88.77" "groupTimestamp=0" "groupName=" "groupMembers=0" "spotify=true" "transcoder=true" "manufacturer=KEF" "uuid=ls60w-xxx" "name=lilypad speaker"] = ax200 IPv6 LS60 Wireless-xxx _tidalconnect._tcp local hostname = [ls60w-xxx.local] address = [192.168.88.77] port = [2019] txt = ["fn=lilypad speaker" "ve=1" "id=xxx" "ca=2" "mn=LS60 Wireless"] = ax200 IPv6 xxx@LS60W _kef-info._tcp local hostname = [ls60w-xxx.local] address = [192.168.88.77] port = [80] txt = ["groupRole=N/A" "groupName=N/A" "groupId=N/A" "kefId=xxx" "version=2.0.66.0x187653b" "serialNumberSlave=" "serialNumber=xxx" "manufacturer=KEF" "modelName=LS60 Wireless" "model=SP4017" "mac=xxx" "name=lilypad speaker"] = ax200 IPv6 ls60w-xxx _sues800device._tcp local hostname = [ls60w-xxx.local] address = [192.168.88.77] port = [80] txt = ["ip=192.168.88.77" "manufacturer=KEF" "uuid=ls60w-xxx" "serial=xxx" "name=lilypad speaker"] = ax200 IPv6 lilypad speaker Web Site local hostname = [ls60w-xxx.local] address = [192.168.88.77] port = [80] txt = []
I first tried pipewire+AirPlay using the raop-discover module. This was able to find the speakers, and created a device visible in pavucontrol, but I was unable to produce any audible output using it. The monitor showed signal going to the sink, but I heard nothing.
I disabled pipewire and pipewire-pulse, and brought up true pulseaudio. Using pulseaudio-dlna, I got a working sink that generated sound:
Sink #2 State: RUNNING Name: lilypadspeaker_dlna Description: lilypad speaker (DLNA) Driver: module-null-sink.c Sample Specification: s16le 2ch 44100Hz Channel Map: front-left,front-right Owner Module: 25 Mute: no Volume: front-left: 65536 / 100% / 0.00 dB, front-right: 65536 / 100% / 0.00 dB balance 0.00 Base Volume: 65536 / 100% / 0.00 dB Monitor Source: lilypadspeaker_dlna.monitor Latency: 26367 usec, configured 40000 usec Flags: DECIBEL_VOLUME LATENCY SET_FORMATS Properties: device.description = "lilypad speaker (DLNA)" device.class = "abstract" device.icon_name = "audio-card" Formats: pcm
The first number reported for Latency: changed with each run.