home / skills / yousufjoyian / claude-skills / remote-desktop-audio-config
This skill diagnoses and fixes PulseAudio and xRDP audio routing on NixOS, ensuring hardware detection, USB devices, and reliable RDP sound.
npx playbooks add skill yousufjoyian/claude-skills --skill remote-desktop-audio-configReview the files below or copy the command above to add this skill to your agents.
---
name: remote-desktop-audio-config
description: 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.
---
# 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
```bash
# 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)
```bash
# 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
```bash
# 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):
```bash
systemctl --user restart pulseaudio
```
Then disconnect and reconnect RDP.
3. **Restart chansrv** (nuclear option):
```bash
# Find and kill chansrv (xrdp will restart it on next connect)
pkill xrdp-chansrv
```
Then reconnect RDP.
### Test Audio
```bash
# 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
```bash
# 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
```bash
# 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
```bash
#!/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
This skill diagnoses and fixes audio device detection and routing for NixOS systems accessed via xRDP (Windows Remote Desktop). It focuses on PulseAudio hardware detection, xrdp audio redirection, and USB audio device handling. The goal is to restore local hardware output or RDP passthrough reliably and permanently where possible.
The skill inspects PulseAudio sinks, sources, modules, and ALSA kernel devices to determine whether hardware cards are visible to the xRDP PulseAudio instance. It examines xrdp-specific PulseAudio config, xrdp-chansrv logs for RDPSND negotiation, and system USB/ALSA state. Fixes include runtime module loading, NixOS config edits to include module-udev-detect in xRDP default.pa, and guidance to restart services or chansrv for channel reinitialization.
Why do I only see xrdp-sink and not my hardware sinks?
xRDP uses its own PulseAudio default.pa that may not load module-udev-detect, so ALSA cards are invisible; load module-udev-detect or add it to /etc/xrdp/pulse/default.pa.
Audio works locally but not over RDP after reconnecting?
The rdpsnd channel may not have reinitialized. Do a full Disconnect → Reconnect, check chansrv logs for format negotiation, or restart pulseaudio and reconnect.