Files
pixelpass/scripts/smoke-pipeline.sh
T
mollusk 8044a42f98 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>
2026-05-24 15:25:05 -04:00

90 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Pipeline-only smoke test: mirrors host/wayland.rs's gst pipeline but
# substitutes videotestsrc/audiotestsrc for pipewiresrc/pulsesrc and writes
# to a file instead of fdsink, so it runs without the portal/PipeWire and
# without an HTTP/iroh client. Catches AV1 encode + mpegtsmux + AAC mux
# regressions in ~5 seconds without any user interaction.
#
# Usage: scripts/smoke-pipeline.sh
# Exits 0 on pass, non-zero with diagnostic on fail.
set -euo pipefail
OUT="${TMPDIR:-/tmp}/pixelpass-smoke-$$.ts"
trap 'rm -f "$OUT"' EXIT
echo "[smoke] running gst pipeline (videotestsrc + audiotestsrc, ~2s) -> $OUT"
gst-launch-1.0 -q \
mpegtsmux name=mux \
! queue ! filesink location="$OUT" \
videotestsrc num-buffers=60 is-live=false \
! video/x-raw,width=640,height=360,framerate=30/1 \
! videoconvert ! video/x-raw,format=NV12 \
! vah264enc rate-control=cbr bitrate=2000 key-int-max=60 \
! h264parse config-interval=-1 \
! video/x-h264,stream-format=byte-stream,alignment=au \
! mux. \
audiotestsrc num-buffers=94 is-live=false \
! audioconvert ! audioresample \
! audio/x-raw,rate=48000,channels=2 \
! avenc_aac bitrate=128000 ! aacparse ! mux.
[[ -s "$OUT" ]] || { echo "[smoke] FAIL: output file empty or missing"; exit 1; }
echo "[smoke] output size: $(stat -c %s "$OUT") bytes"
echo "[smoke] ffprobe stream listing"
ffprobe -v error -show_entries stream=index,codec_type,codec_name "$OUT" \
| sed 's/^/ /'
# Real gate: mpv must actually decode video. The previous --frames=10 check was
# a false positive — if there's no video stream, --frames silently does nothing
# and mpv exits 0 after playing the audio. We instead grep mpv -v output for
# "video=playing"; "video=eof" means mpv saw no video track.
echo "[smoke] mpv video-decode probe (-v, asserting 'video=playing')"
MPV_LOG=$(mpv --no-config -v --vo=null --ao=null --frames=10 "$OUT" 2>&1)
echo "$MPV_LOG" | grep -E 'video=(playing|eof)' | sed 's/^/ /'
if echo "$MPV_LOG" | grep -q 'video=playing'; then
echo "[smoke] PASS — pipeline produces an mpv-decodable video + audio stream"
else
echo "[smoke] FAIL: mpv did not decode video (saw video=eof or no video state)"
echo "[smoke] last 30 lines of mpv output:"
echo "$MPV_LOG" | tail -30 | sed 's/^/ /'
exit 1
fi
# ── quality-preset downscale check ────────────────────────────────────────
# Mirrors the videoscale step host/pipeline.rs inserts for a non-Source preset:
# AFTER videoconvert (scaling system-memory NV12, not the raw source format) and
# pinned to an exact even WxH computed from the source size. A 16:9 source @
# 480p wants width 853.3 -> 852 even. Asserts the negotiated size matches and
# the encoder accepts it. Guards both the "even-width caveat" and the
# negotiation/placement regression that squashed the picture.
SCALED="${TMPDIR:-/tmp}/pixelpass-smoke-scaled-$$.ts"
trap 'rm -f "$OUT" "$SCALED"' EXIT
echo "[smoke] downscale check: 1920x1080 -> 852x480 (videoscale after videoconvert)"
gst-launch-1.0 -q \
mpegtsmux name=mux ! queue ! filesink location="$SCALED" \
videotestsrc num-buffers=30 is-live=false \
! video/x-raw,width=1920,height=1080,framerate=30/1 \
! videorate ! video/x-raw,framerate=30/1 \
! queue ! videoconvert ! video/x-raw,format=NV12 \
! videoscale ! video/x-raw,format=NV12,width=852,height=480 \
! vah264enc rate-control=cbr bitrate=1000 key-int-max=60 \
! h264parse config-interval=-1 \
! video/x-h264,stream-format=byte-stream,alignment=au ! mux. \
audiotestsrc num-buffers=47 is-live=false \
! audioconvert ! audioresample ! audio/x-raw,rate=48000,channels=2 \
! avenc_aac bitrate=128000 ! aacparse ! mux.
SCALED_DIMS=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height \
-of csv=p=0 "$SCALED" | head -1)
SCALED_W=${SCALED_DIMS%,*}
SCALED_H=${SCALED_DIMS#*,}
echo " negotiated ${SCALED_W}x${SCALED_H}"
if [[ "$SCALED_H" == "480" && $((SCALED_W % 2)) -eq 0 && "$SCALED_W" -lt 1920 ]]; then
echo "[smoke] PASS — downscale produced an even width below source (proportional)"
else
echo "[smoke] FAIL: expected even width < 1920 at height 480, got ${SCALED_W}x${SCALED_H}"
exit 1
fi