improve: make desktop picker startup faster and quieter #298
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: A/V Sync Tests | |
| # Verifies audio/video sync correctness on every platform: | |
| # 1. Unit + property tests for the timestamp pipeline (encoders, drift | |
| # trackers, muxers). | |
| # 2. The synthetic device matrix: fake cameras/screens/microphones across | |
| # frame rates, sample rates, channel counts and delivery pathologies | |
| # (jitter, drops, static-screen gaps), driven through the real recording | |
| # pipeline and verified at the container level. No capture hardware or | |
| # GPU required, so results are deterministic on hosted runners. | |
| # | |
| # Findings are published as a job-summary table and a JSON artifact per OS. | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "0 5 * * *" | |
| pull_request: | |
| paths: | |
| - "crates/recording/**" | |
| - "crates/enc-ffmpeg/**" | |
| - "crates/enc-avfoundation/**" | |
| - "crates/enc-mediafoundation/**" | |
| - "crates/timestamp/**" | |
| - "crates/rendering/**" | |
| - "crates/editor/**" | |
| - "crates/export/**" | |
| - "crates/audio/**" | |
| - "crates/media-info/**" | |
| - "crates/project/**" | |
| - "scripts/recording-reliability.py" | |
| - "scripts/recording-reliability-owned-process.cs" | |
| - "scripts/test-recording-reliability.py" | |
| - ".github/workflows/sync-tests.yml" | |
| concurrency: | |
| group: sync-tests-${{ github.head_ref || github.ref_name }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| sync-tests: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| runner: | |
| - macos-latest | |
| - windows-2022 | |
| - ubuntu-24.04 | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Rust setup | |
| uses: dtolnay/rust-toolchain@1.88.0 | |
| - name: Rust cache | |
| uses: ./.github/actions/setup-rust-cache | |
| with: | |
| target: ${{ runner.os == 'Windows' && 'x86_64-pc-windows-msvc' || runner.os == 'macOS' && 'aarch64-apple-darwin' || 'x86_64-unknown-linux-gnu' }} | |
| - name: Install desktop dependencies | |
| uses: ./.github/actions/install-desktop-deps | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Setup Python | |
| uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | |
| with: | |
| python-version: "3.13" | |
| - name: Native dependencies | |
| env: | |
| RUST_TARGET_TRIPLE: ${{ runner.os == 'Linux' && 'x86_64-unknown-linux-gnu' || runner.os == 'Windows' && 'x86_64-pc-windows-msvc' || 'aarch64-apple-darwin' }} | |
| run: node scripts/setup.js | |
| - name: Add FFmpeg DLLs to PATH | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: Add-Content -Path $env:GITHUB_PATH -Value "${{ github.workspace }}\\target\\ffmpeg\\bin" | |
| - name: Recording metadata and harness regression tests | |
| shell: bash | |
| run: | | |
| cargo test --locked -p cap-project --lib | |
| python -B scripts/test-recording-reliability.py | |
| - name: Compile recording process supervisor (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: Add-Type -Path scripts/recording-reliability-owned-process.cs | |
| - name: Install software Vulkan driver (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y mesa-vulkan-drivers libvulkan1 | |
| # Hosted Windows runners have no audio endpoint, so WASAPI loopback | |
| # capture (system audio) cannot run at all without a virtual sound | |
| # card. Scream provides a signed null render device; with it installed, | |
| # the loopback keepalive, AUDCLNT_BUFFERFLAGS_SILENT zeroing and QPC | |
| # capture timestamps are exercised against a real audio engine. | |
| # | |
| # Pinned to 3.6: later releases (3.8/4.0) were signed after the 2021 | |
| # kernel cross-signing deprecation and PnP blocks their install on an | |
| # interactive consent prompt, which hangs headless runners. The step | |
| # timeout turns any such hang into a fast failure. | |
| - name: Install virtual audio device (Windows) | |
| if: runner.os == 'Windows' | |
| timeout-minutes: 10 | |
| shell: pwsh | |
| run: | | |
| Invoke-WebRequest -Uri https://github.com/duncanthrax/scream/releases/download/3.6/Scream3.6.zip -OutFile Scream3.6.zip | |
| $expected = "25ea5e778b4e6995a98d448b9b5f6d321f681663f1aeeec69d8e63183d008b19" | |
| $actual = (Get-FileHash -Algorithm SHA256 Scream3.6.zip).Hash.ToLowerInvariant() | |
| if ($actual -ne $expected) { throw "Scream3.6.zip SHA-256 mismatch: got $actual, expected $expected" } | |
| Expand-Archive -Path Scream3.6.zip -DestinationPath Scream | |
| $cert = (Get-AuthenticodeSignature Scream\Install\driver\Scream.sys).SignerCertificate | |
| $store = [System.Security.Cryptography.X509Certificates.X509Store]::new("TrustedPublisher", "LocalMachine") | |
| $store.Open("ReadWrite") | |
| $store.Add($cert) | |
| $store.Close() | |
| Scream\Install\helpers\devcon.exe install Scream\Install\driver\Scream.inf *Scream | |
| if ($LASTEXITCODE -gt 1) { throw "devcon install failed with exit code $LASTEXITCODE" } | |
| Start-Service -Name Audiosrv -ErrorAction SilentlyContinue | |
| - name: Timestamp pipeline unit + property tests | |
| shell: bash | |
| run: | | |
| cargo test --locked -p cap-timestamp -p cap-enc-ffmpeg | |
| cargo test --locked -p cap-camera-effects --lib | |
| cargo test --locked -p cap-recording --lib -- --test-threads=1 | |
| # --nocapture so a WARP-adapter notch skip prints instead of | |
| # looking identical to a pass in the CI log. | |
| cargo test --locked -p cap-rendering -- --nocapture --skip zoom_spring::tests::precompute_cost_is_bounded_for_long_projects | |
| cargo test --locked -p cap-rendering --lib zoom_spring::tests::precompute_cost_is_bounded_for_long_projects -- --exact --nocapture --test-threads=1 | |
| - name: Editor audio playback and export regressions | |
| shell: bash | |
| run: | | |
| cargo test --locked -p cap-audio --lib | |
| cargo test --locked -p cap-export --lib | |
| cargo test --locked -p cap-editor --lib audio::tests:: | |
| cargo test --locked -p cap-editor --lib audio_output::tests:: | |
| cargo test --locked -p cap-editor --lib playback::tests:: | |
| # Real encoders + DASH muxer + remux/validation over full instant-mode | |
| # scenarios: pause/resume excision, stall-recovery bursts with | |
| # same-microsecond timestamps, segment assembly and A/V alignment. | |
| - name: Instant mode scenario harness | |
| shell: bash | |
| run: | | |
| cargo test --locked -p cap-recording --test instant_mode_scenarios | |
| # The hardware harnesses (real screen/mic recordings) only run on | |
| # developer machines, but they must keep compiling: nothing else | |
| # builds the full test target set, and hardware_instant_recording | |
| # once rotted into breaking every non-macOS test build via ungated | |
| # macOS-only imports. | |
| - name: Compile recording test harnesses | |
| shell: bash | |
| run: | | |
| cargo check --locked -p cap-recording --tests | |
| # The AVFoundation encoder (studio camera/display on macOS) has its own | |
| # pts handling; its fps-matrix duration tests guard against re-timing | |
| # sources that deliver at a different rate than configured. | |
| - name: AVFoundation encoder tests (macOS) | |
| if: runner.os == 'macOS' | |
| shell: bash | |
| run: | | |
| cargo test --locked -p cap-enc-avfoundation | |
| # Runs against the Scream endpoint installed above; the env var turns | |
| # a missing endpoint (driver install regression) into a hard failure | |
| # instead of a silent skip. | |
| - name: WASAPI loopback endpoint tests (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| env: | |
| CAP_REQUIRE_AUDIO_ENDPOINT: "1" | |
| run: | | |
| cargo test --locked -p cap-recording --test windows_loopback -- --nocapture | |
| - name: Synthetic device matrix | |
| id: matrix | |
| continue-on-error: true | |
| shell: bash | |
| env: | |
| CAP_SYNC_MATRIX_REPORT: ${{ github.workspace }}/sync-matrix-${{ matrix.runner }}.json | |
| CAP_SYNC_MATRIX_RANDOM_CASES: ${{ github.event_name == 'schedule' && '40' || '6' }} | |
| run: | | |
| cargo test --locked -p cap-recording --test sync_matrix -- --nocapture | |
| # Verifies the editor's playback machinery (decoders, frame scheduling, | |
| # audio pipeline) preserves sync. The fixture recording is generated | |
| # through the real recording pipeline, so no capture hardware is needed; | |
| # rendering uses the platform's software adapter where no GPU exists. | |
| # 30s of pattern stabilizes the drift slope against frame-quantization | |
| # noise. Playback runs at the default 30 fps: lower rates trip the audio | |
| # sync policy's drift-correction threshold every few frames and accrue | |
| # real (policy-induced) drift that fails the gate. | |
| # | |
| # Linux-only: the Windows WARP adapter composites blank frames and the | |
| # macOS runners' paravirtualized Metal collapses to ~2 fps presentation | |
| # regardless of decoder, so neither can sustain a wall-clock playback | |
| # measurement. The decoder logic under test (FFmpeg gap holds) is fully | |
| # exercised here; the macOS AVAssetReader path is covered by running | |
| # `cap selftest playback` locally on real hardware. | |
| - name: Editor playback sync harness | |
| if: runner.os == 'Linux' | |
| shell: bash | |
| run: | | |
| cargo run --locked -p cap -- --log-level info selftest playback --duration 30 --json | |
| - name: Report findings | |
| if: always() | |
| shell: bash | |
| run: | | |
| REPORT="${{ github.workspace }}/sync-matrix-${{ matrix.runner }}.json" | |
| { | |
| echo "## A/V sync matrix — ${{ matrix.runner }}" | |
| echo "" | |
| if [ -f "$REPORT" ]; then | |
| PYTHONIOENCODING=utf-8 python3 - "$REPORT" << 'PYEOF' | |
| import json, sys | |
| report = json.load(open(sys.argv[1])) | |
| print(f"Randomized seed: `{report.get('seed')}` (rerun with CAP_SYNC_MATRIX_SEED)") | |
| print() | |
| print("| Case | Result | Detail |") | |
| print("| --- | --- | --- |") | |
| for case in report.get("cases", []): | |
| if not case["pass"]: | |
| verdict = "FAIL" | |
| elif "skipped:" in case["detail"]: | |
| verdict = "SKIP" | |
| else: | |
| verdict = "PASS" | |
| detail = case["detail"].replace("|", "\\|") | |
| print(f"| {case['name']} | {verdict} | {detail} |") | |
| PYEOF | |
| else | |
| echo "No report produced — the matrix crashed before writing results." | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload findings | |
| if: always() | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: sync-matrix-${{ matrix.runner }} | |
| path: ${{ github.workspace }}/sync-matrix-${{ matrix.runner }}.json | |
| if-no-files-found: ignore | |
| - name: Fail on matrix failures | |
| if: steps.matrix.outcome == 'failure' | |
| shell: bash | |
| run: | | |
| echo "Synthetic sync matrix reported failures; see the job summary." >&2 | |
| exit 1 |