docs: adopt fan-out (Option C) after feasibility spike
Ran the direct-link spike on the live graph (PipeWire 1.6.8, WirePlumber 0.5.15). Fan-out carries full-level audio for paplay, mpv and VLC while the application keeps its existing speaker link; WirePlumber does not reap foreign links across default-sink switch, suspend/resume or 100s steady state; and non-lingering links are destroyed automatically when their owning connection is SIGKILLed. The decisive result is that destroying the capture sink mid-share left the application playing to its speakers undisturbed, so capture-side failure degrades to "not captured" rather than breaking the user's audio. That is the property the move-based design had to work hard to approximate. Records what the spike does not prove: fidelity beyond signal presence, daemon restart, quantum perturbation, and exclusive/passthrough streams. The capture null sink is still pactl-owned, so Stop Share continues to leak a module every time and the graceful-stop work is still owed. Eligibility becomes a broad guarded selector rather than a narrow allowlist, since copying no longer risks disturbing the source. Option A and its attendant cleanup, restore and output-switch machinery are retained for the record but are no longer the plan. A v3 rewrite is owed once the AEC playback-leg identity is settled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -215,7 +215,33 @@ saved-default restoration plus idempotent crash repair.
|
||||
B's genuine advantages, recorded honestly: new apps land in the mix automatically, no
|
||||
registry-add race, and no sweeping up of arbitrary module playback legs.
|
||||
|
||||
## 5.1 Option C — copy instead of move (Codex's proposal; now the leading candidate)
|
||||
## 5.1 Option C — copy instead of move — ✅ **DECIDED: this supersedes Option A**
|
||||
|
||||
> **STATUS 2026-07-20: feasibility spike RUN, Option C ADOPTED by both reviewers.**
|
||||
> Measurements: `~/Documents/handoff-docs/Claude/peerspeak/fanout-spike-results-2026-07-20.md`.
|
||||
> Codex ratification: `…/Codex/peerspeak/review-2026-07-20-fanout-spike-round4-ratification.md`.
|
||||
> Everything below in §5 (Option A/B), §6, §7 and §8.2–8.3 is **retained for the record
|
||||
> but is no longer the plan** — most of it describes problems Option C does not have.
|
||||
> **A v3 rewrite is owed once the AEC gate (§10, OPEN-Q 3) is settled.**
|
||||
>
|
||||
> Measured on this machine (PipeWire 1.6.8 / WirePlumber 0.5.15):
|
||||
> fan-out carries full-level audio (−24.1 dB, matching the speaker monitor) for
|
||||
> **paplay, mpv and VLC**; the app keeps its speaker link; WirePlumber does **not** reap
|
||||
> foreign links across default-sink switch, switch-back, suspend, resume, or 100 s
|
||||
> steady state; and non-lingering links are destroyed automatically when their owning
|
||||
> connection is **SIGKILLed**.
|
||||
> **Strongest result — destroying the capture sink mid-share left the application
|
||||
> playing to its speakers undisturbed.** Capture-side failure degrades to
|
||||
> "not captured," never to "the user's audio is broken."
|
||||
>
|
||||
> **Not yet proven** (do not overclaim these): fidelity beyond signal presence — equal
|
||||
> `mean_volume` on a sine cannot see xruns, drift or dropouts; daemon restart;
|
||||
> quantum/latency perturbation; sample-rate mismatch, surround, `port.exclusive`,
|
||||
> IEC958 passthrough; two concurrent capture sinks.
|
||||
> **Also still true:** the capture null sink is `pactl`-loaded and pipewire-pulse-owned
|
||||
> (`audio.rs:69`), so **Stop Share still leaks one null-sink module every time**.
|
||||
> Option C fixes stranded *desktop audio*, not the sink leak — §6's graceful stop is
|
||||
> still owed, and the capture sink should become connection-owned too.
|
||||
|
||||
Codex's round-2 review made the strongest architectural point either review has
|
||||
produced: **the design is overbuilt because it *moves* audio that pixelpass only needs
|
||||
@@ -256,8 +282,38 @@ open question is whether **WirePlumber tears down foreign links** it did not cre
|
||||
part of its own policy management. That is the one thing that would kill Option C, and
|
||||
it is cheap to test.
|
||||
|
||||
**Recommendation: a direct-link feasibility spike before any further work on Option A.**
|
||||
If fan-out holds on this graph, Option C supersedes Option A outright.
|
||||
### 5.2 Eligibility under Option C — a broad guarded selector
|
||||
|
||||
Because copying is cheap and non-destructive, the narrow allowlist of §4.2 is no longer
|
||||
justified — but "fan out everything and let mis-selection be harmless" is **also wrong**:
|
||||
selecting peerspeak-owned playback recreates self-echo, selecting the AEC leg recreates
|
||||
call echo, and a second link still participates in format/buffer negotiation, so it can
|
||||
perturb the source even though the original link survives.
|
||||
|
||||
Agreed replacement:
|
||||
|
||||
- Start from **all** `Stream/Output/Audio` nodes.
|
||||
- Exclude `peerspeak.owned` (§4.1).
|
||||
- Exclude the proven AEC identity (**OPEN-Q 3 — the remaining gate**).
|
||||
- Exclude pixelpass-owned objects and any graph path with capture ancestry (cycles).
|
||||
- Skip `port.exclusive`, encoded/passthrough, and already-owned links.
|
||||
- Create **per-port** links, **retain the Link proxies** for the session, and count a
|
||||
stream captured only once every required link reaches `ACTIVE`. In the Rust
|
||||
`pipewire` crate, dropping a proxy destroys the object — that is the ownership
|
||||
mechanism, and it is also what makes SIGKILL safe.
|
||||
- On failure: leave the original route untouched, report the stream as unsupported.
|
||||
|
||||
`node.dont-move` drops out of the predicate entirely — fan-out is not a metadata move.
|
||||
|
||||
### 5.3 Next gate
|
||||
|
||||
**Load `module-echo-cancel` on the live graph and establish a stable, pixelpass-
|
||||
observable identity for its actual playback `Stream/Output/Audio` leg** — not merely the
|
||||
virtual sink — that survives unload/reload without PID guesswork. Then fan out an
|
||||
ordinary tone and demonstrate that remote-call audio is absent from the capture monitor.
|
||||
|
||||
If no stable tag or module/group relationship exists, the AEC path must be redesigned
|
||||
before this feature proceeds. Both reviewers agree nothing belongs ahead of this.
|
||||
|
||||
## 6. Graceful stop is a prerequisite, not a test case
|
||||
|
||||
|
||||
Reference in New Issue
Block a user