Skip to main content

Encoder

Struct Encoder 

Source
pub struct Encoder { /* private fields */ }
Expand description

A Constrained Baseline H.264 encoder.

Implementations§

Source§

impl Encoder

Source

pub fn new(cfg: EncoderConfig) -> Result<Self, EncodeError>

Creates an encoder, validating that the configuration is within the implemented subset.

Examples found in repository?
examples/rd_skip_speed.rs (line 63)
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
Hide additional examples
examples/thread_bench.rs (line 42)
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}
examples/me_snap_ab.rs (line 42)
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}
examples/me_oracle.rs (line 47)
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}
examples/bit_accountant.rs (line 72)
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        // The accountant must be runnable in the SAME configuration whose gap is
56        // under investigation, or its split describes a different encoder than the
57        // one being measured. BA_BFRAMES / BA_REFS mirror the all-tools arm.
58        if let Ok(v) = std::env::var("BA_BFRAMES") {
59            if let Ok(b) = v.parse::<u32>() {
60                cfg.bframes = b;
61                cfg.bframes_adaptive = false;
62            }
63        }
64        if let Ok(v) = std::env::var("BA_REFS") {
65            if let Ok(r) = v.parse::<u32>() {
66                cfg.num_ref_frames = r;
67            }
68        }
69        let cfg_bframes = cfg.bframes;
70        rusty_h264_encoder::bitacct::reset();
71        rusty_h264_encoder::bitacct::set_enabled(true);
72        let mut enc = Encoder::new(cfg).unwrap();
73        // B-frames require the lookahead path (`encode_all`); the per-frame
74        // `encode` loop cannot reorder. Use whichever the config demands so the
75        // accountant can profile the B-carrying arm at all.
76        let total: usize = if cfg_bframes > 0 {
77            enc.encode_all(&frames).unwrap().iter().map(|n| n.len()).sum()
78        } else {
79            let mut t = 0usize;
80            for f in &frames {
81                t += enc.encode(f).len();
82            }
83            t
84        };
85        rusty_h264_encoder::bitacct::add_actual_bytes(total);
86        rusty_h264_encoder::bitacct::set_enabled(false);
87        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
88        rusty_h264_encoder::bitacct::dump(
89            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
90            mbs,
91        );
92        if std::env::var_os("RFF_BSTATS").is_some() {
93            rusty_h264_encoder::mb16::bstats::dump();
94        }
95        if std::env::var_os("BA_MVDTAB").is_some() {
96            rusty_h264_encoder::bitacct::dump_mvd_table();
97        }
98    }
99}
examples/mbtree_bench.rs (line 90)
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    // PAIRED sampling (H-41/H-43). Alternating the arms is not enough: taking
74    // `min` over each arm INDEPENDENTLY draws the two minima from different
75    // moments, so drift never cancels and the overhead figure swings by more
76    // than the effect. Keep a per-round RATIO instead — both arms of a ratio
77    // are adjacent in time, so the pairing survives whatever the box is doing.
78    let mut ratios: Vec<f64> = Vec::with_capacity(reps);
79    for r in 0..reps {
80        let (mut r_off, mut r_on) = (0.0f64, 0.0f64);
81        // Swap which arm leads each round so a "second one is warmer" effect
82        // cancels across rounds rather than accumulating into the ratio.
83        let order: [bool; 2] = if r % 2 == 0 { [false, true] } else { [true, false] };
84        for on in order {
85            let mut cfg = EncoderConfig::new(w, h);
86            cfg.qp = qp as u8;
87            cfg.gop_size = gop as u32;
88            cfg.preset = Preset::Quality;
89            cfg.mbtree = on;
90            let enc = Encoder::new(cfg).expect("cfg");
91            rusty_h264_encoder::mbtree_satd_reset();
92            let t = std::time::Instant::now();
93            let out: Vec<u8> = enc.encode_all(&frames).expect("encode").concat();
94            let ms = t.elapsed().as_secs_f64() * 1e3;
95            if on {
96                on_ms = on_ms.min(ms);
97                on_h = fnv1a(&out);
98                on_b = out.len();
99                calls = rusty_h264_encoder::mbtree_satd_calls();
100                r_on = ms;
101            } else {
102                off_ms = off_ms.min(ms);
103                off_h = fnv1a(&out);
104                off_b = out.len();
105                r_off = ms;
106            }
107        }
108        ratios.push(r_on / r_off);
109    }
110    println!("  mbtree OFF: {off_ms:8.1} ms  {off_b:>8} bytes  hash {off_h:016x}");
111    println!("  mbtree ON : {on_ms:8.1} ms  {on_b:>8} bytes  hash {on_h:016x}");
112    println!(
113        "  lookahead work: {calls} candidate evals ({:.0}/MB/frame, DETERMINISTIC)",
114        calls as f64 / ((w / 16 * (h / 16)) as f64 * frames.len() as f64)
115    );
116    // The unpaired figure, kept only so old logs stay comparable — do not quote it.
117    println!(
118        "  lookahead overhead (UNPAIRED, min-of-N per arm — noisy, historical): {:+.1}%",
119        100.0 * (on_ms / off_ms - 1.0)
120    );
121    let mut sorted = ratios.clone();
122    sorted.sort_by(|a, b| a.partial_cmp(b).expect("finite"));
123    let med = sorted[sorted.len() / 2];
124    let wins = ratios.iter().filter(|&&r| r < 1.0).count();
125    let n = ratios.len();
126    let z = (wins as f64 - n as f64 / 2.0) / (0.5 * (n as f64).sqrt());
127    print!("  per-round ON/OFF ratios:");
128    for r in &ratios {
129        print!(" {r:.3}");
130    }
131    println!();
132    println!(
133        "  lookahead overhead (PAIRED median): {:+.1}%   [ON faster in {wins}/{n}, z={z:+.2} {}]",
134        100.0 * (med - 1.0),
135        if z.abs() > 2.0 { "VERDICT" } else { "no directional verdict" }
136    );
137    println!("  size {:+.2}%  (deterministic)", 100.0 * (on_b as f64 / off_b as f64 - 1.0));
138}
Source

pub fn config(&self) -> &EncoderConfig

The active configuration.

Source

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?
examples/rd_skip_speed.rs (line 67)
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
Hide additional examples
examples/thread_bench.rs (line 45)
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}
examples/me_snap_ab.rs (line 45)
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}
examples/me_oracle.rs (line 49)
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}
examples/bit_accountant.rs (line 81)
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        // The accountant must be runnable in the SAME configuration whose gap is
56        // under investigation, or its split describes a different encoder than the
57        // one being measured. BA_BFRAMES / BA_REFS mirror the all-tools arm.
58        if let Ok(v) = std::env::var("BA_BFRAMES") {
59            if let Ok(b) = v.parse::<u32>() {
60                cfg.bframes = b;
61                cfg.bframes_adaptive = false;
62            }
63        }
64        if let Ok(v) = std::env::var("BA_REFS") {
65            if let Ok(r) = v.parse::<u32>() {
66                cfg.num_ref_frames = r;
67            }
68        }
69        let cfg_bframes = cfg.bframes;
70        rusty_h264_encoder::bitacct::reset();
71        rusty_h264_encoder::bitacct::set_enabled(true);
72        let mut enc = Encoder::new(cfg).unwrap();
73        // B-frames require the lookahead path (`encode_all`); the per-frame
74        // `encode` loop cannot reorder. Use whichever the config demands so the
75        // accountant can profile the B-carrying arm at all.
76        let total: usize = if cfg_bframes > 0 {
77            enc.encode_all(&frames).unwrap().iter().map(|n| n.len()).sum()
78        } else {
79            let mut t = 0usize;
80            for f in &frames {
81                t += enc.encode(f).len();
82            }
83            t
84        };
85        rusty_h264_encoder::bitacct::add_actual_bytes(total);
86        rusty_h264_encoder::bitacct::set_enabled(false);
87        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
88        rusty_h264_encoder::bitacct::dump(
89            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
90            mbs,
91        );
92        if std::env::var_os("RFF_BSTATS").is_some() {
93            rusty_h264_encoder::mb16::bstats::dump();
94        }
95        if std::env::var_os("BA_MVDTAB").is_some() {
96            rusty_h264_encoder::bitacct::dump_mvd_table();
97        }
98    }
99}
Source

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.

Source

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.

Source

pub fn try_flush(&mut self) -> Result<Vec<u8>, EncodeError>

Fallible flush.

Source

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?
examples/thread_bench.rs (line 53)
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
Hide additional examples
examples/bit_accountant.rs (line 77)
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        // The accountant must be runnable in the SAME configuration whose gap is
56        // under investigation, or its split describes a different encoder than the
57        // one being measured. BA_BFRAMES / BA_REFS mirror the all-tools arm.
58        if let Ok(v) = std::env::var("BA_BFRAMES") {
59            if let Ok(b) = v.parse::<u32>() {
60                cfg.bframes = b;
61                cfg.bframes_adaptive = false;
62            }
63        }
64        if let Ok(v) = std::env::var("BA_REFS") {
65            if let Ok(r) = v.parse::<u32>() {
66                cfg.num_ref_frames = r;
67            }
68        }
69        let cfg_bframes = cfg.bframes;
70        rusty_h264_encoder::bitacct::reset();
71        rusty_h264_encoder::bitacct::set_enabled(true);
72        let mut enc = Encoder::new(cfg).unwrap();
73        // B-frames require the lookahead path (`encode_all`); the per-frame
74        // `encode` loop cannot reorder. Use whichever the config demands so the
75        // accountant can profile the B-carrying arm at all.
76        let total: usize = if cfg_bframes > 0 {
77            enc.encode_all(&frames).unwrap().iter().map(|n| n.len()).sum()
78        } else {
79            let mut t = 0usize;
80            for f in &frames {
81                t += enc.encode(f).len();
82            }
83            t
84        };
85        rusty_h264_encoder::bitacct::add_actual_bytes(total);
86        rusty_h264_encoder::bitacct::set_enabled(false);
87        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
88        rusty_h264_encoder::bitacct::dump(
89            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
90            mbs,
91        );
92        if std::env::var_os("RFF_BSTATS").is_some() {
93            rusty_h264_encoder::mb16::bstats::dump();
94        }
95        if std::env::var_os("BA_MVDTAB").is_some() {
96            rusty_h264_encoder::bitacct::dump_mvd_table();
97        }
98    }
99}
examples/mbtree_bench.rs (line 93)
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    // PAIRED sampling (H-41/H-43). Alternating the arms is not enough: taking
74    // `min` over each arm INDEPENDENTLY draws the two minima from different
75    // moments, so drift never cancels and the overhead figure swings by more
76    // than the effect. Keep a per-round RATIO instead — both arms of a ratio
77    // are adjacent in time, so the pairing survives whatever the box is doing.
78    let mut ratios: Vec<f64> = Vec::with_capacity(reps);
79    for r in 0..reps {
80        let (mut r_off, mut r_on) = (0.0f64, 0.0f64);
81        // Swap which arm leads each round so a "second one is warmer" effect
82        // cancels across rounds rather than accumulating into the ratio.
83        let order: [bool; 2] = if r % 2 == 0 { [false, true] } else { [true, false] };
84        for on in order {
85            let mut cfg = EncoderConfig::new(w, h);
86            cfg.qp = qp as u8;
87            cfg.gop_size = gop as u32;
88            cfg.preset = Preset::Quality;
89            cfg.mbtree = on;
90            let enc = Encoder::new(cfg).expect("cfg");
91            rusty_h264_encoder::mbtree_satd_reset();
92            let t = std::time::Instant::now();
93            let out: Vec<u8> = enc.encode_all(&frames).expect("encode").concat();
94            let ms = t.elapsed().as_secs_f64() * 1e3;
95            if on {
96                on_ms = on_ms.min(ms);
97                on_h = fnv1a(&out);
98                on_b = out.len();
99                calls = rusty_h264_encoder::mbtree_satd_calls();
100                r_on = ms;
101            } else {
102                off_ms = off_ms.min(ms);
103                off_h = fnv1a(&out);
104                off_b = out.len();
105                r_off = ms;
106            }
107        }
108        ratios.push(r_on / r_off);
109    }
110    println!("  mbtree OFF: {off_ms:8.1} ms  {off_b:>8} bytes  hash {off_h:016x}");
111    println!("  mbtree ON : {on_ms:8.1} ms  {on_b:>8} bytes  hash {on_h:016x}");
112    println!(
113        "  lookahead work: {calls} candidate evals ({:.0}/MB/frame, DETERMINISTIC)",
114        calls as f64 / ((w / 16 * (h / 16)) as f64 * frames.len() as f64)
115    );
116    // The unpaired figure, kept only so old logs stay comparable — do not quote it.
117    println!(
118        "  lookahead overhead (UNPAIRED, min-of-N per arm — noisy, historical): {:+.1}%",
119        100.0 * (on_ms / off_ms - 1.0)
120    );
121    let mut sorted = ratios.clone();
122    sorted.sort_by(|a, b| a.partial_cmp(b).expect("finite"));
123    let med = sorted[sorted.len() / 2];
124    let wins = ratios.iter().filter(|&&r| r < 1.0).count();
125    let n = ratios.len();
126    let z = (wins as f64 - n as f64 / 2.0) / (0.5 * (n as f64).sqrt());
127    print!("  per-round ON/OFF ratios:");
128    for r in &ratios {
129        print!(" {r:.3}");
130    }
131    println!();
132    println!(
133        "  lookahead overhead (PAIRED median): {:+.1}%   [ON faster in {wins}/{n}, z={z:+.2} {}]",
134        100.0 * (med - 1.0),
135        if z.abs() > 2.0 { "VERDICT" } else { "no directional verdict" }
136    );
137    println!("  size {:+.2}%  (deterministic)", 100.0 * (on_b as f64 / off_b as f64 - 1.0));
138}

Trait Implementations§

Source§

impl Debug for Encoder

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for Encoder

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.