yousufjoyian

remote-desktop-audio-config

Diagnose and fix audio device detection and routing on NixOS xRDP remote desktop sessions. Handles PulseAudio hardware detection, xRDP audio redirection, and USB audio devices.

yousufjoyian 0 Updated 3mo ago
GitHub

Install

npx skillscat add yousufjoyian/claude-skills/remote-desktop-audio-config

Install via the SkillsCat registry.

SKILL.md

Remote Desktop Audio Configuration

Diagnose and resolve audio issues when connecting to a NixOS workstation via xRDP (Windows Remote Desktop).

When to Use This Skill

  • Audio devices not showing up in remote desktop session
  • No sound through RDP audio redirection
  • USB audio devices (headphones, DACs) not detected
  • Need to switch between local hardware output and RDP audio passthrough
  • PulseAudio only shows xrdp virtual devices, no hardware

Architecture Overview

Audio Flow (RDP passthrough):
  App on workstation → PulseAudio → xrdp-sink → RDP channel → Client laptop → Headphones/Speakers

Audio Flow (direct hardware):
  App on workstation → PulseAudio → ALSA card → Physical speakers/headphones on workstation

Key Components

Component Role
PulseAudio Audio server managing sinks/sources
module-udev-detect Auto-detects hardware audio cards
module-xrdp-sink Virtual sink that sends audio over RDP
module-xrdp-source Virtual source for RDP microphone input
xrdp-chansrv Channel server handling rdpsnd protocol
ALSA Kernel-level audio driver layer

Common Problem: Hardware Audio Not Detected

Root Cause

xRDP uses its own minimal PulseAudio config at /etc/xrdp/pulse/default.pa that only loads xrdp virtual devices. It does not load module-udev-detect, so hardware ALSA cards are invisible to PulseAudio.

The NixOS system config (/etc/pulse/default.pa) may have hardware detection configured, but xRDP bypasses it entirely by using /etc/xrdp/pulse/default.pa.

Diagnosis

# Step 1: Check what PulseAudio sees
pactl list sinks short
pactl list sources short
pactl list cards short

# If only xrdp-sink and xrdp-source appear, hardware detection is missing

# Step 2: Check what ALSA sees (kernel level)
cat /proc/asound/cards
cat /proc/asound/pcm

# Step 3: Check loaded PulseAudio modules
pactl list modules short

# If module-udev-detect is NOT listed, that's the problem

# Step 4: Check which default.pa is being used
cat /etc/xrdp/pulse/default.pa
cat /etc/pulse/default.pa

Quick Fix (Runtime)

# Load hardware detection module manually (survives until PA restart)
pactl load-module module-udev-detect

# Verify hardware appeared
pactl list sinks short
pactl list cards short

Permanent Fix (NixOS)

Add module-udev-detect to the xRDP PulseAudio config. Edit /etc/xrdp/pulse/default.pa:

.nofail
.fail
load-module module-augment-properties
load-module module-always-sink

# Hardware audio detection (THIS IS THE KEY LINE)
load-module module-udev-detect

.ifexists module-xrdp-sink.so
load-module module-xrdp-sink
.endif
.ifexists module-xrdp-source.so
load-module module-xrdp-source
.endif
load-module module-native-protocol-unix

Also fix whisper-dictation.nix or equivalent NixOS audio config:

  • Remove load-module module-alsa-card-detect (does not exist as a PA module)
  • Remove duplicate load-module module-udev-detect (already loaded by included stock config)
  • The stock PulseAudio default.pa already loads module-udev-detect via .include

Common Problem: RDP Audio Not Working

Symptoms

  • xrdp-sink is default but no sound reaches the client
  • PulseAudio logs show data_send: send failed

Diagnosis

# Check PA logs for send failures
journalctl --user -u pulseaudio --since "5 minutes ago"

# Check chansrv log for audio format negotiation
# Find the current display number
echo $DISPLAY

# Read the matching chansrv log
cat ~/.local/share/xrdp/xrdp-chansrv.${DISPLAY#:}.log | tail -40

# Look for: sound_process_output_format entries
# If MISSING after a reconnect, rdpsnd channel didn't re-initialize

What to Look For in chansrv Log

Working connection has these entries after socket accepted:

sound_process_output_format:    (format negotiation)
sound_process_training:          (latency test)

Broken connection only has:

num_silent_frames_aac: 4
Detected remote smartcard
Detected remote printer

(No sound_process_output_format = rdpsnd channel not active)

Fixes

  1. Full disconnect and reconnect (not just close window):

    • On Windows: Start → Disconnect, then reconnect
    • This forces rdpsnd channel renegotiation
  2. Restart PulseAudio (if disconnect doesn't help):

    systemctl --user restart pulseaudio

    Then disconnect and reconnect RDP.

  3. Restart chansrv (nuclear option):

    # Find and kill chansrv (xrdp will restart it on next connect)
    pkill xrdp-chansrv

    Then reconnect RDP.

Test Audio

# Generate and play a test tone through xrdp-sink
python3 -c "
import struct, math, sys
sample_rate = 44100
for i in range(sample_rate * 2):
    sample = int(32767 * 0.5 * math.sin(2 * math.pi * 440 * i / sample_rate))
    sys.stdout.buffer.write(struct.pack('<hh', sample, sample))
" | paplay --device=xrdp-sink --format=s16le --channels=2 --rate=44100 --raw

# Check if it sent without errors
journalctl --user -u pulseaudio --since "30 seconds ago"
# Good: RUNNING → IDLE → close_send (no "send failed")
# Bad:  RUNNING → "data_send: send failed" → close_send

Common Problem: USB Audio Not Detected

Diagnosis

# List all USB devices
for dev in /sys/bus/usb/devices/*/; do
  if [ -f "$dev/product" ]; then
    echo "$(cat $dev/product) - $(cat $dev/idVendor 2>/dev/null):$(cat $dev/idProduct 2>/dev/null) - $(cat $dev/manufacturer 2>/dev/null)"
  fi
done

# Check kernel log for USB events
journalctl -k --since "5 minutes ago" | grep -i -E "usb|audio|sound"

# Check if new ALSA card appeared
cat /proc/asound/cards

USB Switcher Notes

If headphones are connected via a USB switcher (e.g., UGreen):

  • The switcher must be actively routed to the workstation (press the switch button)
  • If headphones are routed to a laptop instead, use RDP audio passthrough:
    • Set workstation default sink to xrdp-sink
    • Set laptop audio output to the USB headphones
    • Audio flow: workstation → RDP → laptop → headphones

Quick Reference: Switching Audio Output

# Route audio through RDP to client (laptop headphones)
pactl set-default-sink xrdp-sink

# Route audio to workstation's physical speakers (use name from 'pactl list sinks short')
pactl set-default-sink <alsa_output_sink_name>

# List available sinks to find the right name
pactl list sinks short

# Check current default
pactl get-default-sink

Full Diagnostic Script

#!/usr/bin/env bash
echo "=== Audio Diagnostic ==="
echo ""

echo "--- PulseAudio Server ---"
pactl info 2>&1 | grep -E "Server|Default Sink|Default Source"
echo ""

echo "--- Loaded Modules ---"
pactl list modules short
echo ""

echo "--- Sinks (Outputs) ---"
pactl list sinks short
echo ""

echo "--- Sources (Inputs) ---"
pactl list sources short
echo ""

echo "--- Cards ---"
pactl list cards short
echo ""

echo "--- ALSA Cards (Kernel) ---"
cat /proc/asound/cards
echo ""

echo "--- ALSA PCM Devices ---"
cat /proc/asound/pcm
echo ""

echo "--- USB Audio Devices ---"
for dev in /sys/bus/usb/devices/*/; do
  if [ -f "$dev/product" ]; then
    product=$(cat "$dev/product")
    vid=$(cat "$dev/idVendor" 2>/dev/null)
    pid=$(cat "$dev/idProduct" 2>/dev/null)
    echo "$product ($vid:$pid)"
  fi
done
echo ""

echo "--- xRDP PulseAudio Config ---"
cat /etc/xrdp/pulse/default.pa 2>/dev/null || echo "Not found"
echo ""

echo "--- xRDP chansrv Log (last 20 lines) ---"
DISPLAY_NUM="${DISPLAY#:}"
DISPLAY_NUM="${DISPLAY_NUM%%.*}"
cat ~/.local/share/xrdp/xrdp-chansrv.${DISPLAY_NUM}.log 2>/dev/null | tail -20
echo ""

echo "--- Recent PulseAudio Errors ---"
journalctl --user -u pulseaudio --since "10 minutes ago" 2>&1 | grep -i -E "fail|error|warn" | tail -10
echo ""

echo "--- module-udev-detect Status ---"
if pactl list modules short | grep -q module-udev-detect; then
  echo "LOADED (hardware detection active)"
else
  echo "NOT LOADED (hardware will be invisible!)"
  echo "Fix: pactl load-module module-udev-detect"
fi

Trigger Words

Phrase Action
"fix audio", "no sound" Run full diagnostic
"detect audio devices", "show audio" Check sinks/sources/cards
"audio not working rdp" Diagnose RDP audio chain
"switch audio output" Change default sink
"test audio", "play test tone" Generate and play test tone

Windows RDP Client Checklist

When RDP audio isn't working, verify on the Windows client side:

  1. Connection settings → Local Resources → Remote audio → "Play on this computer"
  2. Volume not muted on the client machine
  3. Audio output on client set to correct device (e.g., USB headphones)
  4. Full disconnect (not just close window) and reconnect if audio channel is stale