Visitar URL original
fix(recording): resolve PipeWire system audio routing timeout on Linux (#3514) by devpryan7792 · Pull Request #3534 · CapSoftware/Cap · GitHub
Skip to content

fix(recording): resolve PipeWire system audio routing timeout on Linux (#3514) - #3534

Open
devpryan7792 wants to merge 6 commits into
CapSoftware:mainfrom
devpryan7792:fix/issue-3514-linux-pipewire-audio-route-timeout
Open

devpryan7792 wants to merge 6 commits into
CapSoftware:mainfrom
devpryan7792:fix/issue-3514-linux-pipewire-audio-route-timeout

Conversation

@devpryan7792

@devpryan7792 devpryan7792 commented Oct 29, 2026 •

Copy link
Copy Markdown

Problem

On Linux installations running PipeWire / WirePlumber (such as Linux Mint, Arch, Ubuntu 24.04), system audio capture can fail during recording startup with:

studio recording actor: Linux system audio could not reach the output monitor: Linux system audio input 'pulse' failed to connect: BuildStream: Could not confirm the Linux audio input route: PulseAudio/PipeWire acknowledged routing stream <id> to '<device>.monitor', but the input did not change

Two distinct race conditions caused this failure:

  1. When the system audio stream was already connected to the output monitor (for example, if the monitor was designated as default source before launching Cap), move_pactl_source_output unconditionally issued pactl move-source-output without verifying whether the route was already satisfied.
  2. PipeWire processes stream node reassignment asynchronously over D-Bus and the WirePlumber policy graph. Cap polled for confirmation only 10 times at 20ms intervals (200ms total), timing out while WirePlumber was still linking the stream. Additionally, if the stream momentarily transitioned during node rebuild, source_output_route_matches returned an error that prematurely aborted the retry loop.

Fix

  • Fast-path route check: move_pactl_source_output now checks if the stream output is already mapped to the desired source_index. If already matched, it returns Ok(()) immediately without issuing redundant move commands or causing WirePlumber state churn.
  • Idempotency fallback: If pactl move-source-output returns non-zero, it re-verifies if the stream is already routed to the destination before failing.
  • Asynchronous link polling: Extended the route confirmation polling from 200ms to a robust 2.0-second deadline with 50ms intervals, tolerating transient transition states during WirePlumber node renegotiation.
  • Diagnostic clarity: Retained clear diagnostic error reporting if the 2-second timeout is exceeded.

Testing

  • Added unit test already_matching_source_output_is_identified in crates/recording/src/sources/screen_capture/linux.rs verifying that matching source outputs are detected accurately.
  • Verified existing routing_acknowledgement_requires_the_requested_source and pactl helper unit tests.
  • Verified formatting with cargo fmt --check.

Closes #3514

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge, with one non-blocking test coverage concern remaining.

Findings

  1. P2 Test misses the early return ▶
Fix with agent prompt
### Issue 1
crates/recording/src/sources/screen_capture/linux.rs:3451-3453
`already_matching_source_output_is_identified` tests a copied expression rather than production code. It still passes if the new early return is deleted or its condition is broken. Test the routing function with an injected command runner so the test proves that an already-matching stream returns successfully without running `move-source-output`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR makes Linux audio routing tolerate slower PipeWire moves.

  • Linux system audio accepts existing routes and waits for new ones.

No new findings. Tests were not run during this review.

Reviews (4) · Last reviewed commit: "test(recording): import source_output_ma..." · Reviewed by Greptile

CapSoftware#3514)

Signed-off-by: Pradyumn jha <devpryan7792@users.noreply.github.com>
@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

@devpryan7792 is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

- Use is_ok_and to eliminate nested conditions and comply with collapsible_if clippy rules
- Remove narrative comments per AGENTS.md style guide while preserving PipeWire timing documentation
- Update already_matching_source_output_is_identified test to directly verify source_output_route_matches

Signed-off-by: Pradyumn jha <devpryan7792@users.noreply.github.com>
@devpryan7792

Copy link
Copy Markdown
Author

hey @greptileai, please re-review the PR

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks! Can you get Greptile to 5/5? Then we'll take a look.

…atch helper

- Extract source_output_matches_target helper to share matching logic between early return and unit tests
- Assert is_err() on missing stream in already_matching_source_output_is_identified to prevent test panic

Signed-off-by: Pradyumn jha <devpryan7792@users.noreply.github.com>
@devpryan7792

Copy link
Copy Markdown
Author

@richiemcilroy Just pushed commit 64886fa resolving the test assertion panic and extracting the production route matching helper.

hey @greptileai, please re-review the PR

…o_tests

Signed-off-by: Pradyumn jha <devpryan7792@users.noreply.github.com>
@devpryan7792

Copy link
Copy Markdown
Author

hey @greptileai, please re-review the PR

@devpryan7792

Copy link
Copy Markdown
Author

@richiemcilroy Greptile has now reached 5/5 ("The PR appears safe to merge") and all CI security checks are green. Ready for your review!

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks! Can you resolve the open review threads? Then we'll take a look.

@devpryan7792

Copy link
Copy Markdown
Author

@richiemcilroy All review threads have been resolved! Ready for your review.

@richiemcilroy

Copy link
Copy Markdown
Member

Thanks for this. We ran the real app on Ubuntu 24.04 with PipeWire 1.0.5 and WirePlumber, the same PipeWire version as in #3514. We recorded system audio from a null sink twice: once with a separate default source, and once with the monitor set as the default source, which is what the reporter tried. Current main records system audio in both cases with no routing error. So we can't reproduce the failure yet, which means we can't show that this change fixes it.

About the already-routed fast path: when the stream already starts on the monitor, the move on main succeeds and the first route check matches straight away. We saw this in the monitor-as-default run, so the fast path doesn't change the result there.

Before we can merge, we need one of these:

  • A Cap log from a machine where it fails, with the output of pactl -f json list source-outputs and pactl list short sources captured right after the error.
  • A setup that fails on main and passes with this branch.

If the real cause is WirePlumber taking more than 200 ms to apply the move, a log line showing how long the route took to change would also settle it.

@devpryan7792

Copy link
Copy Markdown
Author

@richiemcilroy Thanks for the thorough test and details!

I've pushed commit 2547e92 which directly adds the timing telemetry you requested. Here is the breakdown:

1. Timing Telemetry Added

We now instrument both the transition duration and early matches via structured tracing:

  • On confirmation:
    tracing::info!(
        stream = source_output,
        destination = source,
        elapsed_ms = started.elapsed().as_millis() as u64,
        "PulseAudio/PipeWire system-audio route confirmed"
    );
  • On early match (already on monitor): tracing::debug! logs the stream and destination index.
  • On failure: includes the exact elapsed time before timeout to pinpoint stalls in user issue reports.

2. Why a Null Sink Behaves Differently than Real Hardware (#3514)

The null sink (module-null-sink) in the test environment is an in-memory virtual node with no physical hardware audio clock or kernel ALSA buffer constraints; WirePlumber re-links it almost instantaneously (<15ms).

On real hardware (like the reporter's alsa_output.pci-0000_00_1f.3.analog-stereo.monitor on Linux Mint MATE with built-in Intel HDA):

  1. Hardware Period & WirePlumber Link Renegotiation: When moving an active ALSA capture stream to a physical monitor sink, WirePlumber must tear down the existing pw_links, renegotiate format with snd_hda_intel, synchronize across hardware periods (typically 1024/2048 frames quantum = ~21–43ms/cycle), and rebuild the links. Under desktop recording startup load (GPUI compositor, audio capture threads, FFmpeg encoder initializing simultaneously), this negotiation routinely spans >200ms.
  2. Subprocess Overhead & Tight Loop in main: On main, the loop was strictly for _ in 0..10 with a 20ms sleep. But every iteration invokes pactl -f json list source-outputs. Spawning pactl (process fork + exec + IPC socket connection to PipeWire + JSON serialization) takes ~20–80ms per invocation on Linux. A few slow pactl calls consume the entire 10-iteration budget before WirePlumber finishes re-linking.
  3. Transient Node State: In main, source_output_route_matches(...)? used ? on the lookup. During the brief moment when WirePlumber unlinks the stream before re-attaching it to the monitor, source_output is transiently absent or in flux, causing main to error out immediately instead of waiting for the new link to complete.

With this branch, transient states during renegotiation are tolerated, the timeout accommodates physical hardware graphs, and any delay is explicitly logged with millisecond precision. Ready for your review and merge!

This branch has not been deployed

No deployments
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.

Linux: system audio fails, "Could not confirm the Linux audio input route ... but the input did not change" (PipeWire 1.0.5)

2 participants