fix(quality): scale after videoconvert at exact even WxH

Live medium-quality stream errored with "negotiation problem" on the
host and rendered a squashed, garbled picture in the viewer. Two causes,
both from inserting videoscale before videoconvert with PAR+range caps:

- videoscale was scaling pipewiresrc's raw output directly. The portal
  source's format/memory (e.g. DMABuf) isn't something software videoscale
  negotiates — the original pipeline always fed pipewiresrc through
  videoconvert first. Move videoscale *after* videoconvert so it operates
  on system-memory NV12/I420.
- `pixel-aspect-ratio=1/1` + a width range over-constrained negotiation
  and risked a non-square-PAR / distorted result. Instead compute an exact
  even WxH from the known source dimensions (Wayland: portal size; X11:
  root/window geometry), preserving aspect, and pin it fully in the caps.
  This is also downscale-only now — a source already at/below the target
  height is left native instead of upscaled. Unknown dims (rare X11
  geometry failure) fall back to the height-only + square-pixel + even
  width-range negotiation.

source_dims threaded through pipeline::spawn from both backends. Smoke
test updated to mirror the new ordering (1920x1080 -> 852x480, videoscale
after videoconvert) and still asserts an even sub-source width.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 15:25:05 -04:00
co-authored by Claude Opus 4.7
parent 7483b9aae8
commit 8044a42f98
4 changed files with 69 additions and 42 deletions
+16 -8
View File
@@ -21,13 +21,21 @@ pub async fn start(opts: &HostOpts, quality: &EffectiveQuality) -> Result<Captur
None
};
// Geometry is informational (mirrors Wayland's portal-handshake log line);
// a failure here shouldn't abort capture — ximagesrc will surface a real
// error if the X connection is genuinely unusable.
match read_geometry(xid) {
Ok((w, h)) => tracing::info!(width = w, height = h, xid = ?xid, "X11 capture geometry"),
Err(e) => tracing::warn!("could not read X11 geometry (capture will still try): {e:#}"),
}
// Geometry mirrors Wayland's portal-handshake log line and feeds the
// downscale presets (so they can compute an exact target size). A failure
// here shouldn't abort capture — ximagesrc will surface a real error if the
// X connection is genuinely unusable, and the scaler falls back to a
// height-only negotiation when dims are unknown.
let source_dims = match read_geometry(xid) {
Ok((w, h)) => {
tracing::info!(width = w, height = h, xid = ?xid, "X11 capture geometry");
Some((w as u32, h as u32))
}
Err(e) => {
tracing::warn!("could not read X11 geometry (capture will still try): {e:#}");
None
}
};
let mut source_args = vec![
"ximagesrc".to_string(),
@@ -42,7 +50,7 @@ pub async fn start(opts: &HostOpts, quality: &EffectiveQuality) -> Result<Captur
}
// X11 has no leaked fd to clean up, so the post-spawn hook is a no-op.
pipeline::spawn(opts, quality, source_args, || {}).await
pipeline::spawn(opts, quality, source_dims, source_args, || {}).await
}
/// Run `xwininfo` and let the user click the window they want to share, then