Check out my first novel, midnight's simulacra!

KEF LS60

From dankwiki

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.