Skip to content

perf(linux): eliminate unnecessary PipeWire resampling by matching stream clock - #40

Closed
Horuse wants to merge 1 commit into
mainfrom
perf/linux-pipewire-stream-matching
Closed

perf(linux): eliminate unnecessary PipeWire resampling by matching stream clock#40
Horuse wants to merge 1 commit into
mainfrom
perf/linux-pipewire-stream-matching

Conversation

@Horuse

@Horuse Horuse commented Sep 9, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Replaces the Linux virtual-device configuration-file workflow with native
PipeWire runtime objects. Splitwave creates support.null-audio-sink adapters
through core.create_object, removes owned nodes through
registry.destroy_global, and restores the saved virtual-device list when the
app starts. The legacy 50-splitwave-sinks.conf file is removed without
restarting PipeWire.

The Linux audio path now uses the sample rate and channel count reported by
PipeWire or stored in the Splitwave virtual-device configuration. Playback,
cue playback, microphone and sink-monitor capture propagate these values into
PipeWire format negotiation. System and app capture use the pipeline sample
rate. Capture streams advertise a rate range with the requested rate as the
default, observe the negotiated format through PipeWire's param_changed
event, and atomically notify the normalizer when that rate changes. The
normalizer then drops the transition backlog and rebuilds its resampler without
restarting the capture stream. The playback callback writes directly into the
mapped PipeWire buffer, so it performs no allocation.

Why is this the right approach?

PipeWire provides the complete lifecycle API needed here: server objects can
be created through the core and removed through the registry. Runtime objects
avoid restarting the user's audio server and interrupting unrelated apps.
Splitwave's existing virtual-devices.json store provides persistence, while
the in-process cache supplies the configured format to device_info.

External PipeWire nodes are resolved from registry properties. The parser
handles audio.rate, fractional node.rate values such as 1/44100,
audio.channels, and channel counts derived from audio.position. Missing
format information returns an explicit device error instead of silently using
48 kHz stereo.

No new dependency was added.

Checklist

  • Diff is limited to the change — no unrelated edits
  • bun run check passes — frontend code was not changed; command was not run
  • cargo check --manifest-path src-tauri/Cargo.toml passes on macOS, Linux, Windows
  • bun run format leaves the tree clean — cargo fmt passes
  • Generated TS types are committed with the Rust change (not applicable; no #[derive(TS)] changes)
  • No new dependency without a reason in the PR description
  • I read the RT audio path section of docs/CONCEPT.md and confirmed this
    change adds no allocations, locks, or syscalls to cpal / SCK callbacks
    or DspWorker::run

Platform coverage

  • Developed on: macOS, Apple Silicon
  • Tested on: macOS, Linux
  • What I did to test:
    • Ran cargo check --manifest-path src-tauri/Cargo.toml on macOS, Linux successfully.
    • Ran Linux cargo check --manifest-path src-tauri/Cargo.toml successfully with PipeWire, PulseAudio, GTK and WebKit development libraries installed.
    • Ran cargo test --manifest-path src-tauri/Cargo.toml --lib on Linux

@Horuse Horuse closed this Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant