pub struct Encoder { /* private fields */ }Expand description
A Constrained Baseline H.264 encoder.
Implementations§
Source§impl Encoder
impl Encoder
Sourcepub fn new(cfg: EncoderConfig) -> Result<Self, EncodeError>
pub fn new(cfg: EncoderConfig) -> Result<Self, EncodeError>
Creates an encoder, validating that the configuration is within the implemented subset.
Examples found in repository?
57fn encode(frames: &[YuvFrame], w: usize, h: usize, rd_skip: bool) -> (f64, usize) {
58 let mut cfg = EncoderConfig::new(w, h);
59 cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
60 cfg.gop_size = 30;
61 cfg.tune_rd_skip = rd_skip;
62 cfg.tune_rd_skip_fast_t = std::env::var("RS_GATE").ok().and_then(|v| v.parse().ok());
63 let mut enc = Encoder::new(cfg).expect("encoder");
64 let t = std::time::Instant::now();
65 let mut bytes = 0usize;
66 for fr in frames {
67 bytes += enc.encode(fr).len();
68 }
69 (t.elapsed().as_secs_f64(), bytes)
70}More examples
44fn main() {
45 let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
46 let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
47 let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
48 let (w, h, frames) = read_y4m(&path, n);
49 let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
50 for (pname, preset) in [("quality", Preset::Quality)] {
51 let mut cfg = EncoderConfig::new(w, h);
52 cfg.qp = qp;
53 cfg.gop_size = 60;
54 cfg.preset = preset;
55 rusty_h264_encoder::bitacct::reset();
56 rusty_h264_encoder::bitacct::set_enabled(true);
57 let mut enc = Encoder::new(cfg).unwrap();
58 let mut total = 0usize;
59 for f in &frames {
60 total += enc.encode(f).len();
61 }
62 rusty_h264_encoder::bitacct::add_actual_bytes(total);
63 rusty_h264_encoder::bitacct::set_enabled(false);
64 let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
65 rusty_h264_encoder::bitacct::dump(
66 &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
67 mbs,
68 );
69 if std::env::var_os("BA_MVDTAB").is_some() {
70 rusty_h264_encoder::bitacct::dump_mvd_table();
71 }
72 }
73}31fn main() {
32 let path = std::env::args().nth(1).unwrap();
33 let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
34 let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
35 let (w, h, frames) = read_y4m(&path, n);
36 for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
37 let mut cfg = EncoderConfig::new(w, h);
38 cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
39 // best-of-3 each arm
40 let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
41 for _ in 0..3 {
42 let mut enc = Encoder::new(cfg.clone()).unwrap();
43 let t = std::time::Instant::now();
44 let mut o = Vec::new();
45 for f in &frames { o.extend_from_slice(&enc.encode(f)); }
46 seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
47 seq_out = o;
48 }
49 let mut par_ms = f64::MAX; let mut par_out = Vec::new();
50 for _ in 0..3 {
51 let enc = Encoder::new(cfg.clone()).unwrap();
52 let t = std::time::Instant::now();
53 let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
54 par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
55 par_out = o;
56 }
57 assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
58 println!("{pn:<9} x{} gop{gop}: seq {seq_ms:.0} ms parallel {par_ms:.0} ms speedup {:.2}x (byte-identical ✓)", frames.len(), seq_ms / par_ms);
59 }
60}24fn main() {
25 let a: Vec<String> = std::env::args().skip(1).collect();
26 let (w, h) = a[1].split_once('x').unwrap();
27 let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
28 let frames = load(&a[0], w, h, std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(60));
29 let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
30 "fast" => Preset::Fast, "quality" => Preset::Quality, _ => Preset::Balanced,
31 };
32 let arm_off: u32 = std::env::var("RS_ARM_OFF").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
33 let arm_on: u32 = std::env::var("RS_ARM_ON").ok().and_then(|v| v.parse().ok()).unwrap_or(3);
34 let run = |on: bool| {
35 let m = if on { arm_on } else { arm_off };
36 let mut cfg = EncoderConfig::new(w, h);
37 cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
38 cfg.gop_size = 30;
39 cfg.preset = preset;
40 cfg.tune_me_snap = m & 1 != 0;
41 cfg.tune_me_subpel_iter = m & 2 != 0;
42 let mut enc = Encoder::new(cfg).expect("enc");
43 let t = std::time::Instant::now();
44 let mut b = 0usize;
45 for f in &frames { b += enc.encode(f).len(); }
46 (t.elapsed().as_secs_f64(), b)
47 };
48 let mut best = [f64::MAX; 2];
49 let mut bytes = [0usize; 2];
50 for pass in 0..10 {
51 let arm = pass % 2;
52 let (t, b) = run(arm == 1);
53 if t < best[arm] { best[arm] = t; }
54 bytes[arm] = b;
55 }
56 let px = (w * h * frames.len()) as f64;
57 println!("arm {arm_off} -> {arm_on} — {} {w}x{h} {} frames {preset:?}", a[0], frames.len());
58 println!(" off : {:>7.1} ms {:>6.2} Mpx/s {:>9} bytes", best[0]*1e3, px/best[0]/1e6, bytes[0]);
59 println!(" on : {:>7.1} ms {:>6.2} Mpx/s {:>9} bytes", best[1]*1e3, px/best[1]/1e6, bytes[1]);
60 println!(" speed {:>6.3}x size {:>+6.2}%", best[0]/best[1],
61 100.0*(bytes[1] as f64/bytes[0] as f64 - 1.0));
62}57fn main() {
58 let path = std::env::args().nth(1).expect("usage: mbtree_bench <clip.y4m>");
59 let env = |k: &str, d: usize| -> usize {
60 std::env::var(k).ok().and_then(|v| v.parse().ok()).unwrap_or(d)
61 };
62 let (n, qp, gop, reps) = (env("MB_FRAMES", 48), env("MB_QP", 27), env("MB_GOP", 30), env("MB_REPS", 3));
63 if let Ok(la) = std::env::var("MB_LA") {
64 std::env::set_var("RFF_MBTREE_LA", la);
65 }
66 let (w, h, frames) = read_y4m(&path, n);
67 println!("mbtree_bench {}x{} x{} qp{qp} gop{gop} (best-of-{reps})", w, h, frames.len());
68
69 let mut off_ms = f64::MAX;
70 let mut on_ms = f64::MAX;
71 let (mut off_h, mut on_h, mut off_b, mut on_b) = (0u64, 0u64, 0usize, 0usize);
72 let mut calls = 0u64;
73 // Alternate the arms so thermal drift hits both equally.
74 for _ in 0..reps {
75 for on in [false, true] {
76 let mut cfg = EncoderConfig::new(w, h);
77 cfg.qp = qp as u8;
78 cfg.gop_size = gop as u32;
79 cfg.preset = Preset::Quality;
80 cfg.mbtree = on;
81 let enc = Encoder::new(cfg).expect("cfg");
82 rusty_h264_encoder::mbtree_satd_reset();
83 let t = std::time::Instant::now();
84 let out: Vec<u8> = enc.encode_all(&frames).expect("encode").concat();
85 let ms = t.elapsed().as_secs_f64() * 1e3;
86 if on {
87 on_ms = on_ms.min(ms);
88 on_h = fnv1a(&out);
89 on_b = out.len();
90 calls = rusty_h264_encoder::mbtree_satd_calls();
91 } else {
92 off_ms = off_ms.min(ms);
93 off_h = fnv1a(&out);
94 off_b = out.len();
95 }
96 }
97 }
98 println!(" mbtree OFF: {off_ms:8.1} ms {off_b:>8} bytes hash {off_h:016x}");
99 println!(" mbtree ON : {on_ms:8.1} ms {on_b:>8} bytes hash {on_h:016x}");
100 println!(
101 " lookahead work: {calls} candidate evals ({:.0}/MB/frame, DETERMINISTIC)",
102 calls as f64 / ((w / 16 * (h / 16)) as f64 * frames.len() as f64)
103 );
104 println!(
105 " lookahead overhead: {:+.1}% size {:+.2}%",
106 100.0 * (on_ms / off_ms - 1.0),
107 100.0 * (on_b as f64 / off_b as f64 - 1.0)
108 );
109}31fn main() {
32 let a: Vec<String> = std::env::args().skip(1).collect();
33 let (w, h) = a[1].split_once('x').unwrap();
34 let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
35 let nf: usize = std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(20);
36 let frames = load(&a[0], w, h, nf);
37
38 let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
39 "fast" => Preset::Fast,
40 "quality" => Preset::Quality,
41 _ => Preset::Balanced,
42 };
43 let mut cfg = EncoderConfig::new(w, h);
44 cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
45 cfg.gop_size = 30;
46 cfg.preset = preset;
47 let mut enc = Encoder::new(cfg).expect("encoder");
48 for f in &frames {
49 let _ = enc.encode(f);
50 }
51
52 let p: Vec<u64> = rusty_h264_encoder::ME_PROBE
53 .iter()
54 .map(|c| c.load(std::sync::atomic::Ordering::Relaxed))
55 .collect();
56 let (n, ours, oracle, worse, evals) = (p[0].max(1), p[1], p[2], p[3], p[4]);
57 let (oracle_sp, worse_sp) = (p[5], p[6]);
58 println!("ME oracle — {} ({w}x{h}, {} frames, {preset:?})\n", a[0], frames.len());
59 println!(" searches {n}");
60 println!(" mean cost ours {:>10.1}", ours as f64 / n as f64);
61 println!(" mean cost exhaustive {:>10.1}", oracle as f64 / n as f64);
62 println!(" ---> we are {:>6.2}% above the achievable minimum",
63 100.0 * (ours as f64 - oracle as f64) / oracle as f64);
64 println!(" searches the oracle beat {:>9} ({:.1}%)", worse, 100.0 * worse as f64 / n as f64);
65 // the oracle's own 49x49 grid + 8 sub-pel probes are included in `evals`
66 println!("
67 + exhaustive SUB-PEL (all quarter-pel in +-3):");
68 println!(" mean cost exhaustive {:>10.1}", oracle_sp as f64 / n as f64);
69 println!(" ---> we are {:>6.2}% above the achievable minimum",
70 100.0 * (ours as f64 - oracle_sp as f64) / oracle_sp as f64);
71 println!(" searches it beat {:>9} ({:.1}%)", worse_sp, 100.0 * worse_sp as f64 / n as f64);
72 let oracle_evals = 0;
73 println!("\n cost() evals/search {:>8.1} (ours, oracle's {oracle_evals} excluded)",
74 evals as f64 / n as f64 - oracle_evals as f64);
75}Sourcepub fn config(&self) -> &EncoderConfig
pub fn config(&self) -> &EncoderConfig
The active configuration.
Sourcepub fn encode(&mut self, frame: &YuvFrame) -> Vec<u8> ⓘ
pub fn encode(&mut self, frame: &YuvFrame) -> Vec<u8> ⓘ
Encodes one frame, returning the Annex-B access unit. Every gop_size
frames (and always the first) is coded as an IDR, prefixed with SPS/PPS.
Generation 1 codes every picture as an IDR (all-intra); inter frames arrive with motion compensation later.
Examples found in repository?
57fn encode(frames: &[YuvFrame], w: usize, h: usize, rd_skip: bool) -> (f64, usize) {
58 let mut cfg = EncoderConfig::new(w, h);
59 cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
60 cfg.gop_size = 30;
61 cfg.tune_rd_skip = rd_skip;
62 cfg.tune_rd_skip_fast_t = std::env::var("RS_GATE").ok().and_then(|v| v.parse().ok());
63 let mut enc = Encoder::new(cfg).expect("encoder");
64 let t = std::time::Instant::now();
65 let mut bytes = 0usize;
66 for fr in frames {
67 bytes += enc.encode(fr).len();
68 }
69 (t.elapsed().as_secs_f64(), bytes)
70}More examples
44fn main() {
45 let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
46 let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
47 let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
48 let (w, h, frames) = read_y4m(&path, n);
49 let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
50 for (pname, preset) in [("quality", Preset::Quality)] {
51 let mut cfg = EncoderConfig::new(w, h);
52 cfg.qp = qp;
53 cfg.gop_size = 60;
54 cfg.preset = preset;
55 rusty_h264_encoder::bitacct::reset();
56 rusty_h264_encoder::bitacct::set_enabled(true);
57 let mut enc = Encoder::new(cfg).unwrap();
58 let mut total = 0usize;
59 for f in &frames {
60 total += enc.encode(f).len();
61 }
62 rusty_h264_encoder::bitacct::add_actual_bytes(total);
63 rusty_h264_encoder::bitacct::set_enabled(false);
64 let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
65 rusty_h264_encoder::bitacct::dump(
66 &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
67 mbs,
68 );
69 if std::env::var_os("BA_MVDTAB").is_some() {
70 rusty_h264_encoder::bitacct::dump_mvd_table();
71 }
72 }
73}31fn main() {
32 let path = std::env::args().nth(1).unwrap();
33 let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
34 let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
35 let (w, h, frames) = read_y4m(&path, n);
36 for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
37 let mut cfg = EncoderConfig::new(w, h);
38 cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
39 // best-of-3 each arm
40 let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
41 for _ in 0..3 {
42 let mut enc = Encoder::new(cfg.clone()).unwrap();
43 let t = std::time::Instant::now();
44 let mut o = Vec::new();
45 for f in &frames { o.extend_from_slice(&enc.encode(f)); }
46 seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
47 seq_out = o;
48 }
49 let mut par_ms = f64::MAX; let mut par_out = Vec::new();
50 for _ in 0..3 {
51 let enc = Encoder::new(cfg.clone()).unwrap();
52 let t = std::time::Instant::now();
53 let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
54 par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
55 par_out = o;
56 }
57 assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
58 println!("{pn:<9} x{} gop{gop}: seq {seq_ms:.0} ms parallel {par_ms:.0} ms speedup {:.2}x (byte-identical ✓)", frames.len(), seq_ms / par_ms);
59 }
60}24fn main() {
25 let a: Vec<String> = std::env::args().skip(1).collect();
26 let (w, h) = a[1].split_once('x').unwrap();
27 let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
28 let frames = load(&a[0], w, h, std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(60));
29 let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
30 "fast" => Preset::Fast, "quality" => Preset::Quality, _ => Preset::Balanced,
31 };
32 let arm_off: u32 = std::env::var("RS_ARM_OFF").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
33 let arm_on: u32 = std::env::var("RS_ARM_ON").ok().and_then(|v| v.parse().ok()).unwrap_or(3);
34 let run = |on: bool| {
35 let m = if on { arm_on } else { arm_off };
36 let mut cfg = EncoderConfig::new(w, h);
37 cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
38 cfg.gop_size = 30;
39 cfg.preset = preset;
40 cfg.tune_me_snap = m & 1 != 0;
41 cfg.tune_me_subpel_iter = m & 2 != 0;
42 let mut enc = Encoder::new(cfg).expect("enc");
43 let t = std::time::Instant::now();
44 let mut b = 0usize;
45 for f in &frames { b += enc.encode(f).len(); }
46 (t.elapsed().as_secs_f64(), b)
47 };
48 let mut best = [f64::MAX; 2];
49 let mut bytes = [0usize; 2];
50 for pass in 0..10 {
51 let arm = pass % 2;
52 let (t, b) = run(arm == 1);
53 if t < best[arm] { best[arm] = t; }
54 bytes[arm] = b;
55 }
56 let px = (w * h * frames.len()) as f64;
57 println!("arm {arm_off} -> {arm_on} — {} {w}x{h} {} frames {preset:?}", a[0], frames.len());
58 println!(" off : {:>7.1} ms {:>6.2} Mpx/s {:>9} bytes", best[0]*1e3, px/best[0]/1e6, bytes[0]);
59 println!(" on : {:>7.1} ms {:>6.2} Mpx/s {:>9} bytes", best[1]*1e3, px/best[1]/1e6, bytes[1]);
60 println!(" speed {:>6.3}x size {:>+6.2}%", best[0]/best[1],
61 100.0*(bytes[1] as f64/bytes[0] as f64 - 1.0));
62}31fn main() {
32 let a: Vec<String> = std::env::args().skip(1).collect();
33 let (w, h) = a[1].split_once('x').unwrap();
34 let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
35 let nf: usize = std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(20);
36 let frames = load(&a[0], w, h, nf);
37
38 let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
39 "fast" => Preset::Fast,
40 "quality" => Preset::Quality,
41 _ => Preset::Balanced,
42 };
43 let mut cfg = EncoderConfig::new(w, h);
44 cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
45 cfg.gop_size = 30;
46 cfg.preset = preset;
47 let mut enc = Encoder::new(cfg).expect("encoder");
48 for f in &frames {
49 let _ = enc.encode(f);
50 }
51
52 let p: Vec<u64> = rusty_h264_encoder::ME_PROBE
53 .iter()
54 .map(|c| c.load(std::sync::atomic::Ordering::Relaxed))
55 .collect();
56 let (n, ours, oracle, worse, evals) = (p[0].max(1), p[1], p[2], p[3], p[4]);
57 let (oracle_sp, worse_sp) = (p[5], p[6]);
58 println!("ME oracle — {} ({w}x{h}, {} frames, {preset:?})\n", a[0], frames.len());
59 println!(" searches {n}");
60 println!(" mean cost ours {:>10.1}", ours as f64 / n as f64);
61 println!(" mean cost exhaustive {:>10.1}", oracle as f64 / n as f64);
62 println!(" ---> we are {:>6.2}% above the achievable minimum",
63 100.0 * (ours as f64 - oracle as f64) / oracle as f64);
64 println!(" searches the oracle beat {:>9} ({:.1}%)", worse, 100.0 * worse as f64 / n as f64);
65 // the oracle's own 49x49 grid + 8 sub-pel probes are included in `evals`
66 println!("
67 + exhaustive SUB-PEL (all quarter-pel in +-3):");
68 println!(" mean cost exhaustive {:>10.1}", oracle_sp as f64 / n as f64);
69 println!(" ---> we are {:>6.2}% above the achievable minimum",
70 100.0 * (ours as f64 - oracle_sp as f64) / oracle_sp as f64);
71 println!(" searches it beat {:>9} ({:.1}%)", worse_sp, 100.0 * worse_sp as f64 / n as f64);
72 let oracle_evals = 0;
73 println!("\n cost() evals/search {:>8.1} (ours, oracle's {oracle_evals} excluded)",
74 evals as f64 / n as f64 - oracle_evals as f64);
75}Sourcepub fn try_encode(&mut self, frame: &YuvFrame) -> Result<Vec<u8>, EncodeError>
pub fn try_encode(&mut self, frame: &YuvFrame) -> Result<Vec<u8>, EncodeError>
Fallible encode.
With a lookahead feature active (currently mb-tree, on by default in the
constant-QP path) this BUFFERS: mb-tree needs a whole GOP of future frames
before it can assign any of their QPs, so the returned Vec is empty while
the GOP fills and then carries that entire GOP’s access units at once. The
concatenation of every return value plus flush is exactly
what encode_all produces — byte for byte.
You must call flush at end of stream or the final
partial GOP is never emitted. (A debug build asserts if the encoder is
dropped with frames still buffered.) For zero added latency set
cfg.mbtree = false, which restores one-AU-per-call behaviour.
Sourcepub fn flush(&mut self) -> Vec<u8> ⓘ
pub fn flush(&mut self) -> Vec<u8> ⓘ
Emits any frames still held by the lookahead queue (end of stream).
Returns the trailing access units, or empty when nothing is buffered — so it is always safe to call, including when no lookahead feature is active.
Sourcepub fn encode_all(
&self,
frames: &[YuvFrame],
) -> Result<Vec<Vec<u8>>, EncodeError>
pub fn encode_all( &self, frames: &[YuvFrame], ) -> Result<Vec<Vec<u8>>, EncodeError>
Batch-encodes every frame, returning one Annex-B access unit per frame.
At constant QP the GOPs are independent — each begins with an IDR that
resets the DPB, frame_num and POC, and SPS/PPS precede every IDR — so they
are encoded in parallel across CPU cores and the result is
byte-identical to calling encode frame-by-frame. With
rate control enabled the per-frame QP depends on history, so this falls back
to sequential encoding. Within a GOP, P-frames are inherently sequential
(each predicts from the previous reconstruction); the parallelism is across
GOPs, so it scales with the number of GOPs in the clip.
Examples found in repository?
31fn main() {
32 let path = std::env::args().nth(1).unwrap();
33 let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
34 let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
35 let (w, h, frames) = read_y4m(&path, n);
36 for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
37 let mut cfg = EncoderConfig::new(w, h);
38 cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
39 // best-of-3 each arm
40 let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
41 for _ in 0..3 {
42 let mut enc = Encoder::new(cfg.clone()).unwrap();
43 let t = std::time::Instant::now();
44 let mut o = Vec::new();
45 for f in &frames { o.extend_from_slice(&enc.encode(f)); }
46 seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
47 seq_out = o;
48 }
49 let mut par_ms = f64::MAX; let mut par_out = Vec::new();
50 for _ in 0..3 {
51 let enc = Encoder::new(cfg.clone()).unwrap();
52 let t = std::time::Instant::now();
53 let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
54 par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
55 par_out = o;
56 }
57 assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
58 println!("{pn:<9} x{} gop{gop}: seq {seq_ms:.0} ms parallel {par_ms:.0} ms speedup {:.2}x (byte-identical ✓)", frames.len(), seq_ms / par_ms);
59 }
60}More examples
57fn main() {
58 let path = std::env::args().nth(1).expect("usage: mbtree_bench <clip.y4m>");
59 let env = |k: &str, d: usize| -> usize {
60 std::env::var(k).ok().and_then(|v| v.parse().ok()).unwrap_or(d)
61 };
62 let (n, qp, gop, reps) = (env("MB_FRAMES", 48), env("MB_QP", 27), env("MB_GOP", 30), env("MB_REPS", 3));
63 if let Ok(la) = std::env::var("MB_LA") {
64 std::env::set_var("RFF_MBTREE_LA", la);
65 }
66 let (w, h, frames) = read_y4m(&path, n);
67 println!("mbtree_bench {}x{} x{} qp{qp} gop{gop} (best-of-{reps})", w, h, frames.len());
68
69 let mut off_ms = f64::MAX;
70 let mut on_ms = f64::MAX;
71 let (mut off_h, mut on_h, mut off_b, mut on_b) = (0u64, 0u64, 0usize, 0usize);
72 let mut calls = 0u64;
73 // Alternate the arms so thermal drift hits both equally.
74 for _ in 0..reps {
75 for on in [false, true] {
76 let mut cfg = EncoderConfig::new(w, h);
77 cfg.qp = qp as u8;
78 cfg.gop_size = gop as u32;
79 cfg.preset = Preset::Quality;
80 cfg.mbtree = on;
81 let enc = Encoder::new(cfg).expect("cfg");
82 rusty_h264_encoder::mbtree_satd_reset();
83 let t = std::time::Instant::now();
84 let out: Vec<u8> = enc.encode_all(&frames).expect("encode").concat();
85 let ms = t.elapsed().as_secs_f64() * 1e3;
86 if on {
87 on_ms = on_ms.min(ms);
88 on_h = fnv1a(&out);
89 on_b = out.len();
90 calls = rusty_h264_encoder::mbtree_satd_calls();
91 } else {
92 off_ms = off_ms.min(ms);
93 off_h = fnv1a(&out);
94 off_b = out.len();
95 }
96 }
97 }
98 println!(" mbtree OFF: {off_ms:8.1} ms {off_b:>8} bytes hash {off_h:016x}");
99 println!(" mbtree ON : {on_ms:8.1} ms {on_b:>8} bytes hash {on_h:016x}");
100 println!(
101 " lookahead work: {calls} candidate evals ({:.0}/MB/frame, DETERMINISTIC)",
102 calls as f64 / ((w / 16 * (h / 16)) as f64 * frames.len() as f64)
103 );
104 println!(
105 " lookahead overhead: {:+.1}% size {:+.2}%",
106 100.0 * (on_ms / off_ms - 1.0),
107 100.0 * (on_b as f64 / off_b as f64 - 1.0)
108 );
109}