Skip to main content

rd_skip_speed/
rd_skip_speed.rs

1// Dev/test target, not shipped: a panic here IS the diagnostic (that is what an
2// assertion is). The workspace's unwrap/expect/panic denials exist to keep them
3// off the decoder's untrusted-input path, so they are relaxed for this file.
4#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
5// Dev tools also accumulate fields and helpers kept for the NEXT investigation;
6// dead code here is a scratchpad, not a defect.
7#![allow(dead_code, unused)]
8#![allow(clippy::unnecessary_unwrap, clippy::zombie_processes)]
9//! What does the adaptive RD P_Skip decision COST in encode time?
10//!
11//! RD skip trial-encodes the inter candidate, snapshots/restores macroblock state,
12//! and reconstructs a trial skip — real work, paid on every P macroblock the gate
13//! lets through. The BD-rate win is settled; this prices it.
14//!
15//! Both arms run in ONE process, alternating pass by pass, best-of-N: whole-encode
16//! timing on this machine drifts ~20% run to run, far more than the effect, so
17//! separate builds cannot resolve it.
18//!
19//! ```text
20//! cargo run --release -p rusty_h264-encoder --example rd_skip_speed -- <clip.yuv> <w>x<h>
21//! ```
22//! With no arguments it uses a synthetic high-free-skip clip (gate fires).
23
24use rusty_h264_common::YuvFrame;
25use rusty_h264_encoder::{Encoder, EncoderConfig};
26
27fn synth(w: usize, h: usize, f: u64) -> YuvFrame {
28    let mut fr = YuvFrame::black(w, h);
29    for y in 0..h {
30        for x in 0..w {
31            fr.y[y * w + x] = ((x as u64 / 8 * 9 + y as u64 / 8 * 5) & 0xff) as u8;
32        }
33    }
34    for y in h / 2..h {
35        for x in 0..w {
36            let j = ((x as u64 + y as u64 * 3 + f * 7) % 3) as u8;
37            fr.y[y * w + x] = fr.y[y * w + x].saturating_add(j);
38        }
39    }
40    let (cw, ch) = (w / 2, h / 2);
41    for y in 0..ch {
42        for x in 0..cw {
43            fr.u[y * cw + x] = 110;
44            fr.v[y * cw + x] = 140;
45        }
46    }
47    fr
48}
49
50fn load(path: &str, w: usize, h: usize) -> Vec<YuvFrame> {
51    let raw = std::fs::read(path).expect("clip");
52    let fsz = w * h * 3 / 2;
53    raw.chunks_exact(fsz)
54        .take(60)
55        .map(|c| {
56            let mut fr = YuvFrame::black(w, h);
57            fr.y.copy_from_slice(&c[..w * h]);
58            fr.u.copy_from_slice(&c[w * h..w * h + w * h / 4]);
59            fr.v.copy_from_slice(&c[w * h + w * h / 4..]);
60            fr
61        })
62        .collect()
63}
64
65fn encode(frames: &[YuvFrame], w: usize, h: usize, rd_skip: bool) -> (f64, usize) {
66    let mut cfg = EncoderConfig::new(w, h);
67    cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
68    cfg.gop_size = 30;
69    cfg.tune_rd_skip = rd_skip;
70    cfg.tune_rd_skip_fast_t = std::env::var("RS_GATE").ok().and_then(|v| v.parse().ok());
71    let mut enc = Encoder::new(cfg).expect("encoder");
72    let t = std::time::Instant::now();
73    let mut bytes = 0usize;
74    for fr in frames {
75        bytes += enc.encode(fr).len();
76    }
77    (t.elapsed().as_secs_f64(), bytes)
78}
79
80fn main() {
81    let args: Vec<String> = std::env::args().skip(1).collect();
82    let (frames, w, h, label) = if args.len() >= 2 {
83        let (w, h) = args[1].split_once('x').expect("WxH");
84        let (w, h) = (w.parse().unwrap(), h.parse().unwrap());
85        (load(&args[0], w, h), w, h, args[0].clone())
86    } else {
87        let (w, h) = (352usize, 288usize);
88        ((0..60).map(|f| synth(w, h, f)).collect(), w, h, "synthetic".into())
89    };
90
91    let mut best = [f64::MAX; 2];
92    let mut bytes = [0usize; 2];
93    for pass in 0..10 {
94        let arm = pass % 2;
95        let (t, b) = encode(&frames, w, h, arm == 1);
96        if t < best[arm] {
97            best[arm] = t;
98        }
99        bytes[arm] = b;
100    }
101
102    let px = (w * h * frames.len()) as f64;
103    println!("adaptive RD skip — encode cost ({label}, {w}x{h}, {} frames)\n", frames.len());
104    println!("  off : {:>7.1} ms   {:>6.2} Mpx/s   {:>8} bytes", best[0] * 1e3, px / best[0] / 1e6, bytes[0]);
105    println!("  on  : {:>7.1} ms   {:>6.2} Mpx/s   {:>8} bytes", best[1] * 1e3, px / best[1] / 1e6, bytes[1]);
106    println!("\n  speed : {:>6.3}x", best[0] / best[1]);
107    println!("  size  : {:>6.2}%", 100.0 * (bytes[1] as f64 / bytes[0] as f64 - 1.0));
108}