Transitioning hardware between operating systems is usually a smooth process these days. Plug in a display, and it just works. But when you start dealing with embedded enterprise hardware, like an OPS PC module tucked inside an interactive flat panel, the generic drivers that ship with Linux often fall short. What works perfectly out-of-the-box on Windows can quickly turn into a debugging marathon on Linux.
What do we have here?
A couple of months ago, I found myself in possession of a few 65″ interactive displays (re-branded versions of these)1. These displays can be outfitted with an Intel OPS-compatible PC module; power, display, audio, and touch are all routed via standardized connectors to the PC. While Windows handles all of this automatically, Linux did not do very well (at all) with sending audio to the display’s internal speakers.
Linux could use the analog “Line Out” 3.5mm jack just fine. It also saw the “HDMI / DisplayPort” connection, but no amount of configuration produced any sort of sound out of the display’s speakers.
While Windows has specific OEM drivers that know exactly which internal audio pipeline to send the signal down, Linux relies on generic snd_hda_intel drivers. I figured it was just a routing issue, but it turned out to be much deeper.
Let’s start with the basics
Running aplay -l showed that the ALSA subsystem at least recognized the display:
card 0: PCH [HDA Intel PCH], device 3: HDMI 0 [PC Monitor]
Subdevices: 0/1
Subdevice #0: subdevice #0
The EDID handshake was successful; Linux knew a monitor was there. The 0/1 subdevices meant PipeWire had a lock on it. Jumping into alsamixer showed no muted S/PDIF channels, sof-firmware was installed, and PipeWire had the output set as the default sink at 100% volume.
Nothing seemed abnormal, the entire audio stack seemed to be doing exactly what it was supposed to do. Yet… silence.
Popping the hood (and the speakers)
If the software routing was correct, the breakdown had to be happening at the very last millimeter: the handshake between the Intel hardware and the display’s internal DAC (Digital-to-Analog Converter).
I tried playing a test tone just to rule out any userspace application issues:speaker-test -D pipewire -c 2 -r 48000 -F S16_LE
…and, not nothing! Not the expected pink noise, but not silence, either. When speaker-test indicated that it was playing audio through the left channel, I heard an infrequent crackle/pop out of the right speaker. Curious, I dropped the sample rate from 48000 Hz to 44100 Hz. This made the popping slightly worse, but otherwise not particularly different.
According to everyone’s favorite predictive text engine, silence punctuated by occasional pops meant that I was dealing with a clock sync failure. Apparently, every pop was the display’s internal DAC waking up, trying to lock onto the audio timing being sent by the Intel video driver, failing to understand the data, and immediately shutting back down.
How low can I go?
What if I cut the audio server out entirely and talked directly to the bare-metal ALSA driver?
I killed PipeWire’s hold on the card and ran speaker-test on the ALSA device:speaker-test -D plughw:0,3 -c 2 -r 48000 -F S16_LE
Huzzah! Crystal clear static (lol), no pops or crackles.
Don’t be like me and leave the volume at 100% while running audio tests. Your ears will thank you later.
The hardware was fine. The OPS physical connection was fine. The Linux kernel and the snd_hda_intel driver were working perfectly. The issue was looking to be somewhere in PipeWire.
The smoking gun
I restarted PipeWire, ran pw-top, and played some audio:
S ID QUANT RATE WAIT BUSY W/Q B/Q ERR FORMAT NAME
R 85 1024 48000 119.4us 13.4us 0.01 0.00 0 S32LE 2 48000 alsa_output.pci-0000_00_1f.3.hdmi-stereo.2
FORMAT: S32LE.
PipeWire, as a modern audio server, was automatically negotiating a 32-bit integer format with the ALSA hardware driver. The Intel audio chip happily accepted the stream (“Sure, I support 32-bit audio!”). But when it blindly passed that 32-bit data over the physical OPS connector to the DAC inside the interactive display, the display had absolutely no idea how to decode the extra data, panicked, and dropped the audio entirely.
By chance, I had explicitly passed the -F S16_LE flag when running speaker-test, forcing standard 16-bit audio. That was the only format the display could understand.
The solution
The following will force PipeWire to only send 16-bit audio:
In ~/.config/wireplumber/main.lua.d/51-force-16bit.lua :
rule = {
matches = {
{
{ "node.name", "matches", "alsa_output.pci-0000_00_1f.3.*" },
},
},
apply_properties = {
["audio.format"] = "S16LE",
},
}
table.insert(alsa_monitor.rules, rule)
You can find your node name with pw-cli ls Node .
Then, restart PipeWire to apply the config change:systemctl --user restart pipewire pipewire-pulse wireplumber
The final touches
Remember how the crackles came out of the opposite speaker when running speaker-test? It seems the audio channels are actually wired backward internally. Flipping PipeWire to match is as simple as adding ["audio.position"] = "FR,FL" as an extra line in apply_properties under the audio.format line.
Final results: perfectly operational audio, a better understanding of the underlying technology, and one less wire to manage. Success. ∎
Footnotes
- These displays are commonly found in schools and other educational institutions; my examples were the result of warranty replacements for a nearby school district. ↩︎