2.9 KiB
Fix Audit Bugs - Design Spec
Date: 2026-04-06 Scope: Critical + High severity bugs from code audit Approach: Minimal fix principle - change only what is necessary
Bugs to Fix
| # | Severity | Bug | File |
|---|---|---|---|
| 1 | CRITICAL | PTS timestamps in microseconds but time_base is 1/fps - output unplayable | state.rs |
| 2 | HIGH | WlOutput dispatch attributes events to wrong output (multi-monitor) | state.rs |
| 3 | HIGH | First captured frame always silently dropped | state.rs |
| 4 | HIGH | DRM render node minor number extraction wrong | state.rs |
Bugs #5-11 (Medium/Low) deferred to future work.
Bug 1: PTS Timestamp Unit Mismatch
Root Cause
on_copy_complete sets PTS in microseconds. But the encoder time_base is 1/fps (frame-number units). Filter graph passes PTS through unchanged. At 30fps, frame 2 has PTS=33333, which the muxer reads as 33333/30 = 1111 seconds.
Fix
Convert PTS from microseconds to frame numbers at the capture boundary.
In state.rs on_copy_complete (line 373):
// Before:
let pts = (tv_sec as i64) * 1_000_000 + (tv_usec as i64);
// After:
let fps = self.args.fps as i64;
let pts = (tv_sec as i64) * fps + (tv_usec as i64) * fps / 1_000_000;
surface.set_pts(Some(pts));
PTS becomes an integer frame counter. No other files change.
Bug 2: WlOutput Multi-Monitor Index Error
Root Cause
Dispatch for WlOutput uses () user data. The handler uses outputs.len().saturating_sub(1) as index - always the last output. With multiple outputs, events for output 0 are written to output N-1.
Fix
Change WlOutput dispatch user data from () to OutputId(global_name).
Changes in state.rs:
- Registry dispatch - bind with OutputId(name) instead of ()
- Change impl Dispatch for WlOutput from user data () to OutputId
- Use output_names lookup to find correct index from data.0
Bug 3: First Frame Silently Dropped
Root Cause
FpsLimit::on_new_frame returns None on first call (buffers it). In on_copy_complete, should_encode is false for the first captured frame, so the surface is dropped without encoding.
Fix
Add first_frame: bool field to State (initial true). On first call to on_copy_complete, force should_encode = true and set first_frame = false.
Bug 4: DRM Minor Number Extraction Wrong
Root Cause
(dev_t as u32) and 0xFFFFF extracts raw bits instead of the Linux minor number. For renderD128 (major=226, minor=128): dev_t=57984, code produces /dev/dri/renderD57984.
Fix
Use Linux standard minor extraction:
let dev = u64::from_ne_bytes(dev_bytes);
let minor = ((dev & 0xFF) | ((dev >> 12) & 0xFFFFFF00)) as u32;
Impact Summary
All changes in state.rs only. ~24 lines changed total.
Out of Scope (Deferred)
- Bug 5: Silent hang without xdg_output_manager
- Bug 6: Intermediate state on encoder failure
- Bug 7: Fragile capture-start in EverythingButFmt
- Bugs 8-11: Low severity
- Tail frame preservation