Linux Virtual Audio Cable: Free VAC Alternatives with PipeWire, PulseAudio and JACK

PipeWire official documentation site showing the low-level multimedia framework used as a Linux virtual audio cable

TL;DR: A Linux virtual audio cable is free and built in. Create a PipeWire or PulseAudio null sink, set one app to play to it, set the next app to record from its monitor, and wire it visually with qpwgraph. JACK fits low latency work and ALSA snd-aloop fits headless servers. Full commands, ham radio workflows and a VAC comparison table inside.

What a Linux Virtual Audio Cable Is

A Linux virtual audio cable is a virtual audio device that routes sound from one application to another entirely in software, with no physical cable or sound card involved.

If you come from Windows, you know Virtual Audio Cable (VAC) by Eugene Muzychenko and VB-Cable by VB-Audio. You install a driver, it creates a device called Cable Input and Cable Output, you set your SDR program to play to Cable Input, you set WSJT-X or a recorder to listen to Cable Output, and audio flows digitally with no loss from speaker to microphone. On Linux the same idea exists, but there’s no paid driver to buy. The audio server itself can create these virtual devices on demand, rename them, script them, and delete them when you are done.

This guide shows the four native ways to do it on modern Linux, which one to pick for ham radio, and exact copy-paste commands for PipeWire, PulseAudio, JACK2 and ALSA loopback. It is written for radio amateurs moving an HF or VHF digital station from Windows to Linux. But the same patterns work for podcasting, streaming, transcription and accessibility routing.

Linux virtual audio cable routing diagram showing SDR software feeding a virtual sink monitored by WSJT-X decoder on a Linux desktop
PipeWire null sink acting as a Linux virtual audio cable between an SDR receiver and a digital mode decoder. No physical cable needed.

A Short History: Why Windows Needs VAC and Linux Does Not

Windows audio was built around one assumption: one app owns the sound card at a time for playback, and recording comes from a microphone jack. Routing app-to-app audio was never part of the original Windows Multimedia Extensions, DirectSound or WASAPI design. That gap is why third party drivers appeared. VAC launched in 1999 as a WDM/KS filter driver that exposed virtual endpoints. VB-Cable followed later with a simpler donationware model. Both work well, but both are kernel drivers you must install, license and maintain through every Windows feature update.

Linux took a different path. Audio moved into user space servers early:

  • 1998 to 2003: OSS and then ALSA in the kernel handled hardware, but mixing was primitive. Apps fought over /dev/dsp.
  • 2004: PulseAudio (then called Polypaudio) added per-app volumes, network audio and software mixing. It introduced the null sink module, which is effectively a virtual cable.
  • 2002 onward: JACK Audio Connection Kit served pro-audio users with sample-accurate patching between apps, the model qpwgraph still copies today.
  • 2017 onward: PipeWire by Wim Taymans replaced both roles. It speaks PulseAudio, JACK and ALSA protocols on one daemon, adds video routing for Wayland screen sharing, and uses a graph where any output can connect to any input.

That history explains the culture shock. On Windows you ask which VAC app to buy. On Linux you ask which layer you already run, then you create a sink in one command. Ubuntu 22.10, Fedora 35 and later, Debian 12 and later, Arch, openSUSE Tumbleweed and Raspberry Pi OS Bookworm all ship PipeWire by default. If you run one of those, you already own the VAC equivalent.

How to Check What Audio Server You Run

Open a terminal and run:

pactl info | grep -i "server name"
pw-cli info 0 | grep -i "version"
ps aux | grep -E "pipewire|pulseaudio|jackd" | grep -v grep

Typical results:

  • Server Name: PulseAudio (on PipeWire 1.2.x) means you run PipeWire with PulseAudio compatibility. Use the PipeWire section below.
  • Server Name: PulseAudio 16.x with no PipeWire process means classic PulseAudio. Use the PulseAudio section.
  • jackd -d alsa running means JACK is active. You can still use PipeWire tools if PipeWire owns the hardware.

For ham radio, lock your sample rate early. Most SDR and digital mode software expects 48000 Hz. Check with:

pw-metadata -n settings 2>/dev/null | grep clock
pactl list sinks | grep -i "sample spec"
cat /proc/asound/cards

If your interface runs at 44100 Hz and WSJT-X expects 48000 Hz, the server will resample. PipeWire does this cleanly, but fixing the rate at the source removes one cause of warble and decode loss.

Option 1: PipeWire Null Sink, the Recommended VAC Replacement

PipeWire is the best Linux virtual audio cable for most hams in 2026. It is installed by default, it accepts old PulseAudio commands, it talks native JACK, and its graph tools look like a VAC repeater panel.

Create a virtual cable in one command

pactl load-module module-null-sink sink_name=VAC1 sink_properties=device.description=VAC1 channel_map=stereo rate=48000

That creates:

  • A playback device called VAC1 that shows up in every app
  • A recording source called Monitor of VAC1 that carries whatever plays into VAC1

Route audio like this:

  1. In your source app (SDR++, GQRX, CubicSDR, browser, fldigi TX monitor), set audio output to VAC1.
  2. In your sink app (WSJT-X, JS8Call, FreeDV, Audacity, OBS), set audio input to Monitor of VAC1.
  3. Open pavucontrol Playback and Recording tabs to verify meters move.

No reboot, no driver signing, no license key.

Make it survive reboot

pactl load-module lasts until logout. For a permanent shack cable, add a PipeWire config drop-in:

mkdir -p ~/.config/pipewire/pipewire.conf.d/

Create ~/.config/pipewire/pipewire.conf.d/10-vac.conf with:

context.modules = [
  { name = libpipewire-module-adapter
    args = {
      factory.name = support.null-audio-sink
      node.name = "VAC1"
      node.description = "VAC1"
      media.class = "Audio/Sink"
      audio.channels = 2
      audio.rate = 48000
    }
  }
]

Then reload:

systemctl --user restart pipewire pipewire-pulse wireplumber
pactl list sinks short | grep VAC1

For a club station with two independent paths (for example HF receiver to WSJT-X plus VHF receiver to Dire Wolf), create VAC1 and VAC2 with two blocks. Name them by function such as HF-RX and VHF-APRS so pavucontrol stays readable during a contest.

Wire it visually with qpwgraph or Helvum

Command line sinks are exact, but a graph is faster when you patch three apps live. Two free tools dominate:

  • qpwgraph (Qt, in Debian/Ubuntu as qpwgraph, Fedora as qpwgraph, Arch AUR as qpwgraph): full patchbay with saveable patch snapshots. Save one snapshot per activity such as ft8.json and sat.json.
  • Helvum (GTK, Flatpak org.pipewire.Helvum): simpler canvas, good on GNOME and touch laptops.

Install on Debian/Ubuntu:

sudo apt install qpwgraph pavucontrol pipewire-jack

Install on Fedora:

sudo dnf install qpwgraph pavucontrol helvum

Workflow in qpwgraph:

  • Left side shows outputs such as SDR++ and browser. Right side shows inputs such as WSJT-X and recorder.
  • Drag from source output to VAC1:input or directly to decoder input for temporary tests.
  • Monitor ports start with Monitor_. Connect those to decoders, not the sink input itself.
  • Use the red disconnect button before major repatching to avoid feedback loops to speakers.

Helvum shows the same graph with less clutter and is ideal for field laptops where screen space is tight.

PipeWire loopback for speaker plus decoder monitoring

VAC on Windows often pairs with Audio Repeater to hear what you decode. On PipeWire, load a loopback to copy the cable to your speakers without breaking the digital path:

pactl load-module module-loopback source=VAC1.monitor sink=@DEFAULT_SINK@ latency_msec=50

Set latency to 50 ms for voice monitoring, 10 to 20 ms for CW sidetone work if your CPU allows. Unload with pactl unload-module and the module index shown by pactl list modules short.

Useful PipeWire commands for the shack notebook

pactl list sinks short
pactl list sources short
pactl set-default-sink alsa_output.usb-Device-00.analog-stereo
pw-top
pw-dump | grep -A2 VAC1
pw-loopback -m '[ FL FR ]' --capture-props='media.class=Audio/Sink' --playback-props='media.class=Audio/Source'

pw-top is the PipeWire equivalent of top for audio. It shows quantum, rate and DSP load per node. If DSP load spikes above 80 percent during 192 kHz SDR use, raise the quantum in ~/.config/pipewire/pipewire.conf.d/20-quantum.conf or drop SDR sample rate before decoding.

Option 2: PulseAudio Null Sink on Older Systems

If you still run classic PulseAudio (Ubuntu 20.04 LTS, older Mint, some Pi images), the method is nearly identical because PipeWire kept the same module names.

Create the cable:

pactl load-module module-null-sink sink_name=VAC1 sink_properties=device.description=VAC1 rate=48000 channels=2
pactl load-module module-loopback source=VAC1.monitor sink=alsa_output.pci-0000_00_1b.0.analog-stereo latency_msec=60
pactl list sinks short

Make it permanent by adding to /etc/pulse/default.pa or ~/.config/pulse/default.pa:

load-module module-null-sink sink_name=VAC1 sink_properties=device.description=VAC1 rate=48000 channels=2
load-module module-loopback source=VAC1.monitor latency_msec=60

Then restart PulseAudio (PipeWire users shouldn’t do this):

pulseaudio -k
pulseaudio --start

Control volumes with:

pavucontrol &
pacmd list-sinks | grep -E "name:|description"
pactl set-sink-volume VAC1 100%
pactl set-source-volume VAC1.monitor 100%

Keep virtual cables at 100 percent (0 dB) for digital modes. Attenuation here is digital scaling before the decoder, not analog gain, and lowering it only costs SNR in WSJT-X or fldigi. Adjust speaker volume at the hardware sink instead.

PulseAudio limits each null sink to one format at a time. If you mix a 44100 Hz browser stream and a 48000 Hz SDR into the same cable, the daemon resamples one of them. That is normally fine for voice, but for weak signal work give FT8 its own dedicated cable at 48000 Hz and let YouTube share a second cable at default rate.

Option 3: JACK2 for Low Latency and Pro-Audio Routing

JACK is the original Linux virtual patchbay and still the lowest latency option. It is overkill for FT8 alone, but it shines when you route CW keying, remote audio over netjack, or guitar-style effects chains alongside radio.

Install on Debian/Ubuntu:

sudo apt install jackd2 qjackctl

Install on Fedora:

sudo dnf install jack-audio-connection-kit qjackctl

Basic start for a USB interface at 48000 Hz with 128 frame period:

jackd -d alsa -d hw:1 -r 48000 -p 128 -n 2 &
qjackctl &

In QjackCtl Connect panel, outputs appear on the left, inputs on the right. Connect sdr:Audio Out to wsjtx:in, or insert calfjack filters in between for notch and EQ. Save the patchbay snapshot per band.

PipeWire users get JACK compatibility without running jackd:

sudo apt install pipewire-jack
pw-jack wsjtx &
pw-jack fldigi &

This runs each app with PipeWire pretending to be JACK, so JACK-only meters and recorders connect in qpwgraph alongside PulseAudio apps. Latency is slightly higher than bare metal JACK but far lower than classic PulseAudio, and you keep Bluetooth, USB and browser audio working at the same time, which bare JACK historically broke.

Use JACK when you need sub 10 ms round trip for CW sidetone, when you bridge two machines with jack_netsource, or when you already own a JACK effects workflow. For simple decode routing, PipeWire alone is easier.

Option 4: ALSA Loopback for Headless Servers and Containers

On a headless Raspberry Pi, a VPS, or a Docker container with no desktop audio server, the kernel snd-aloop driver creates hardware-looking loopback cards that any ALSA app can open.

Load it:

sudo modprobe snd-aloop index=1 pcm_substreams=2
aplay -l | grep Loopback

This creates card 1 with two subdevices. App A plays to hw:Loopback,0,0. App B records from hw:Loopback,1,0. Data written to the first appears on the second with no server in between.

Make it load on boot:

echo "snd-aloop index=1 pcm_substreams=2" | sudo tee /etc/modules-load.d/snd-aloop.conf

Typical ham uses:

  • Dire Wolf on a Pi without desktop, fed from an SDR over stdin or udp then out to plughw:Loopback
  • multimon-ng decoding from a loopback while rtl_fm feeds it, all in a systemd service
  • Recording overnight VHF beacons with arecord -D hw:Loopback,1,0 -f S16_LE -r 48000 night.wav while the receiver writes to hw:Loopback,0,0

ALSA loopback has no volume control, no meters and no GUI. It is sample-accurate and immune to desktop crashes, which is why repeater controllers and APRS iGates favor it. Pair it with alsamixer -c Loopback only to verify the card exists, not to set digital mode levels.

Windows VAC Versus Linux Options at a Glance

Item Windows VAC / VB-Cable PipeWire null sink PulseAudio null sink JACK2 ALSA snd-aloop
Cost Paid per cable pack or donationware Free, in distro Free, in distro Free, open source Free, in kernel
Install Kernel driver plus reboot Already installed on modern distros Already installed on older distros jackd2 plus qjackctl package modprobe snd-aloop
Max cables Limited by license, typically 1 to 4 Unlimited, one command each Unlimited, one command each Unlimited ports per client Limited by pcm_substreams param
GUI patchbay Audio Repeater, third party mixer qpwgraph, Helvum, Coppwr pavucontrol, paprefs QjackCtl, Carla, Patchage None, device names only
Latency typical 10 to 30 ms WDM/KS 10 to 25 ms at 48 kHz 30 to 80 ms 3 to 10 ms tuned Near zero kernel copy
Sample rate Fixed per cable in control panel Per sink, resampled on the fly Per sink, daemon resample Single graph rate, strict Fixed by apps opening device
Scriptable Limited command line Full CLI plus WirePlumber Lua Full pactl and pacmd CLI Full jack_* CLI plus Python Shell plus aplay and arecord
Survives reboot Yes, driver persists Yes with config drop-in Yes with default.pa edit Only with session manager Yes with modules-load.d
Best ham fit Windows-only shack Daily SDR to WSJT-X routing Older laptops and Pis CW, remote audio, effects Headless iGate and recorder

If you only remember one row: PipeWire gives you unlimited free cables, a visual patchbay, and compatibility with both PulseAudio and JACK apps at once. That combination is why it replaced the other two for most desktop hams after 2022.

Practical Ham Radio Workflows on Linux

SDR receiver to WSJT-X or JS8Call

This is the number one VAC use case moving to Linux. Example with SDR++ and WSJT-X on PipeWire:

  1. Create VAC1 at 48000 Hz as shown above.
  2. In SDR++, set Audio output to VAC1. Set SDR++ demodulator to USB, filter 3 kHz, volume around 70 percent so the monitor meter peaks near -10 dB.
  3. In WSJT-X, set Audio input to Monitor of VAC1. Set output to your speakers or a second cable for TX monitoring.
  4. In pavucontrol Recording tab, confirm WSJT-X shows Monitor of VAC1. In Playback tab, confirm SDR++ shows VAC1.
  5. Tune to 14.074 MHz FT8, watch WSJT-X waterfall and decode list. If the waterfall is flat, you patched the wrong monitor. If it is red and overloaded, lower SDR++ AF gain, never the sink volume.

Save this wiring in qpwgraph as hf-ft8.qpwgraph so one click restores it after a reboot.

Browser WebSDR to fldigi decode

Many amateurs decode SSTV, HELL or WEFAX from a browser-based KiwiSDR or WebSDR. Browsers on Linux output to PulseAudio/PipeWire by default, so they work with the same cable:

  1. Create VAC2 named WebSDR.
  2. Play the KiwiSDR stream in Firefox or Chrome. In pavucontrol Playback, move that browser stream to WebSDR.
  3. In fldigi or QSSTV, set capture to Monitor of WebSDR.
  4. For Robot 36 SSTV on 14.230 MHz, set fldigi Op Mode to SSTV and enable auto slant correction before relying on levels.

This replaces the Windows trick of setting VAC as default playback device and then losing all system sounds. On Linux only the browser tab you move goes to the cable. Discord, Zoom and system alerts keep playing to speakers.

Dire Wolf APRS iGate with separate RX and TX paths

Packet work benefits from two cables to avoid TX audio leaking into the RX decoder:

  • VAC-APRS-RX carries receiver audio to Dire Wolf input.
  • VAC-APRS-TX carries Dire Wolf output to the transmitter interface or to a dummy recorder for testing.

Example direwolf.conf snippet:

ADEVICE plughw:Loopback,1,0 plughw:Loopback,0,0
CHANNEL 0
MODEM 1200

On desktop PipeWire, substitute ADEVICE VAC-APRS-RX VAC-APRS-TX via the PulseAudio compatibility names shown in pactl list sources short. Test with aplay test-packet.wav -D VAC-APRS-RX and watch Dire Wolf decode without keying hardware.

Recording and streaming without mic bleed

For YouTube demos, club training nights or remote Elmering:

  • Route radio audio to VAC-REC.
  • In OBS Studio, add source Audio Input Capture set to Monitor of VAC-REC.
  • In OBS Advanced Audio, enable Monitor and Output on that source to hear it while viewers hear clean radio audio with no room echo.
  • Record with parec -d VAC-REC.monitor --file-format=wav rec.wav for archival IQ-adjacent audio.

This is cleaner than the Windows stereo mix hack because the recording tap is per cable, not whole-system output.

Latency, Levels and Troubleshooting

Digital modes care about levels and stability more than ultra low latency. Voice and CW care about latency. Use these rules:

  • Keep virtual sinks at 0 dB. Control loudness at the app AF gain or at the hardware sink. A red clipping indicator on a null sink means digital overflow before the decoder, and no amount of RF gain will fix it.
  • Match rates where you can. Run interface, sink and decoder at 48000 Hz for WSJT-X, FreeDV and most SDRs. Use 44100 Hz only if every app in that chain is music-oriented.
  • Watch quantum on PipeWire. Default 1024 frames at 48000 Hz is about 21 ms. Lower to 256 or 128 for CW sidetone, raise to 2048 for weak CPU netbooks decoding while compiling.
  • If audio stutters only when the browser is open, the browser likely opened the sink at a different rate and forced resampling. Give the browser its own cable.
  • If pavucontrol shows no apps, the app may be using raw ALSA and bypassing the server. Install pipewire-alsa or launch with apulse shim, or switch that app to its PulseAudio output option in preferences.
  • If qpwgraph shows duplicate connections after resume from suspend, press Disconnect All on the virtual node and reload your saved patch snapshot.
  • For permission errors in Flatpak apps (Bottles, OBS from Flathub), grant audio with Flatseal or flatpak override --socket=pulseaudio --socket=pipewire-0 <app-id>.

A quick health check script for the shack:

pactl list sinks short | grep -E "VAC|HF|VHF"
pactl list sources short | grep monitor
pw-top -n 1

If sinks exist, monitors exist, and pw-top shows no XRUN counter climbing, the virtual cable layer is healthy. Any remaining decode problem is RF gain, filter width or mode mismatch, not Linux audio.

Which Linux Virtual Audio Cable Should You Choose

  • Daily HF digital on a modern laptop or desktop: PipeWire null sink plus qpwgraph. It’s the closest one-to-one VAC replacement, it’s already installed, and it handles SDR, browser and decoder at once.
  • Older LTS install or Raspberry Pi with PulseAudio only: PulseAudio null sink plus pavucontrol. No need to migrate a stable field Pi just for VAC.
  • Low latency CW, guitar-style processing, or multi-machine audio: JACK2 or pw-jack wrappers. Accept the extra tuning for sub 10 ms performance.
  • Headless iGate, beacon monitor or Docker recorder with no GUI: ALSA snd-aloop. It survives desktop crashes and uses almost no CPU.
  • Mixed shack with Windows still in the rack: keep VAC on Windows, use PipeWire on Linux, bridge machines with roc-toolkit, netjack2 or even plain ffmpeg over LAN. Don’t try to pass VAC drivers through Wine, it is unstable and unsupported.

You don’t need to buy anything. You don’t need to reboot into a signed driver. Create the sink, patch the graph, save the snapshot, and get back on the air.

Sources and Further Reading

73 de 9M2PJU

Post Comment