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 71)
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}
More examples
Hide additional examples
examples/thread_bench.rs (line 50)
39fn main() {
40    let path = std::env::args().nth(1).unwrap();
41    let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
42    let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
43    let (w, h, frames) = read_y4m(&path, n);
44    for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
45        let mut cfg = EncoderConfig::new(w, h);
46        cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
47        // best-of-3 each arm
48        let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
49        for _ in 0..3 {
50            let mut enc = Encoder::new(cfg.clone()).unwrap();
51            let t = std::time::Instant::now();
52            let mut o = Vec::new();
53            for f in &frames { o.extend_from_slice(&enc.encode(f)); }
54            seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
55            seq_out = o;
56        }
57        let mut par_ms = f64::MAX; let mut par_out = Vec::new();
58        for _ in 0..3 {
59            let enc = Encoder::new(cfg.clone()).unwrap();
60            let t = std::time::Instant::now();
61            let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
62            par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
63            par_out = o;
64        }
65        assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
66        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);
67    }
68}
examples/me_snap_ab.rs (line 50)
32fn main() {
33    let a: Vec<String> = std::env::args().skip(1).collect();
34    let (w, h) = a[1].split_once('x').unwrap();
35    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
36    let frames = load(&a[0], w, h, std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(60));
37    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
38        "fast" => Preset::Fast, "quality" => Preset::Quality, _ => Preset::Balanced,
39    };
40    let arm_off: u32 = std::env::var("RS_ARM_OFF").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
41    let arm_on: u32 = std::env::var("RS_ARM_ON").ok().and_then(|v| v.parse().ok()).unwrap_or(3);
42    let run = |on: bool| {
43        let m = if on { arm_on } else { arm_off };
44        let mut cfg = EncoderConfig::new(w, h);
45        cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
46        cfg.gop_size = 30;
47        cfg.preset = preset;
48        cfg.tune_me_snap = m & 1 != 0;
49        cfg.tune_me_subpel_iter = m & 2 != 0;
50        let mut enc = Encoder::new(cfg).expect("enc");
51        let t = std::time::Instant::now();
52        let mut b = 0usize;
53        for f in &frames { b += enc.encode(f).len(); }
54        (t.elapsed().as_secs_f64(), b)
55    };
56    let mut best = [f64::MAX; 2];
57    let mut bytes = [0usize; 2];
58    for pass in 0..10 {
59        let arm = pass % 2;
60        let (t, b) = run(arm == 1);
61        if t < best[arm] { best[arm] = t; }
62        bytes[arm] = b;
63    }
64    let px = (w * h * frames.len()) as f64;
65    println!("arm {arm_off} -> {arm_on} — {} {w}x{h} {} frames {preset:?}", a[0], frames.len());
66    println!("  off : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[0]*1e3, px/best[0]/1e6, bytes[0]);
67    println!("  on  : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[1]*1e3, px/best[1]/1e6, bytes[1]);
68    println!("  speed {:>6.3}x   size {:>+6.2}%", best[0]/best[1],
69             100.0*(bytes[1] as f64/bytes[0] as f64 - 1.0));
70}
examples/me_oracle.rs (line 55)
39fn main() {
40    let a: Vec<String> = std::env::args().skip(1).collect();
41    let (w, h) = a[1].split_once('x').unwrap();
42    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
43    let nf: usize = std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(20);
44    let frames = load(&a[0], w, h, nf);
45
46    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
47        "fast" => Preset::Fast,
48        "quality" => Preset::Quality,
49        _ => Preset::Balanced,
50    };
51    let mut cfg = EncoderConfig::new(w, h);
52    cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
53    cfg.gop_size = 30;
54    cfg.preset = preset;
55    let mut enc = Encoder::new(cfg).expect("encoder");
56    for f in &frames {
57        let _ = enc.encode(f);
58    }
59
60    let p: Vec<u64> = rusty_h264_encoder::ME_PROBE
61        .iter()
62        .map(|c| c.load(std::sync::atomic::Ordering::Relaxed))
63        .collect();
64    let (n, ours, oracle, worse, evals) = (p[0].max(1), p[1], p[2], p[3], p[4]);
65    let (oracle_sp, worse_sp) = (p[5], p[6]);
66    println!("ME oracle — {} ({w}x{h}, {} frames, {preset:?})\n", a[0], frames.len());
67    println!("  searches                {n}");
68    println!("  mean cost   ours        {:>10.1}", ours as f64 / n as f64);
69    println!("  mean cost   exhaustive  {:>10.1}", oracle as f64 / n as f64);
70    println!("  ---> we are {:>6.2}% above the achievable minimum",
71             100.0 * (ours as f64 - oracle as f64) / oracle as f64);
72    println!("  searches the oracle beat {:>9} ({:.1}%)", worse, 100.0 * worse as f64 / n as f64);
73    // the oracle's own 49x49 grid + 8 sub-pel probes are included in `evals`
74    println!("
75  + exhaustive SUB-PEL (all quarter-pel in +-3):");
76    println!("  mean cost   exhaustive  {:>10.1}", oracle_sp as f64 / n as f64);
77    println!("  ---> we are {:>6.2}% above the achievable minimum",
78             100.0 * (ours as f64 - oracle_sp as f64) / oracle_sp as f64);
79    println!("  searches it beat        {:>9} ({:.1}%)", worse_sp, 100.0 * worse_sp as f64 / n as f64);
80    let oracle_evals = 0;
81    println!("\n  cost() evals/search     {:>8.1}  (ours, oracle's {oracle_evals} excluded)",
82             evals as f64 / n as f64 - oracle_evals as f64);
83}
examples/bit_accountant.rs (line 80)
52fn main() {
53    let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
54    let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
55    let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
56    let (w, h, frames) = read_y4m(&path, n);
57    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
58    for (pname, preset) in [("quality", Preset::Quality)] {
59        let mut cfg = EncoderConfig::new(w, h);
60        cfg.qp = qp;
61        cfg.gop_size = 60;
62        cfg.preset = preset;
63        // The accountant must be runnable in the SAME configuration whose gap is
64        // under investigation, or its split describes a different encoder than the
65        // one being measured. BA_BFRAMES / BA_REFS mirror the all-tools arm.
66        if let Ok(v) = std::env::var("BA_BFRAMES") {
67            if let Ok(b) = v.parse::<u32>() {
68                cfg.bframes = b;
69                cfg.bframes_adaptive = false;
70            }
71        }
72        if let Ok(v) = std::env::var("BA_REFS") {
73            if let Ok(r) = v.parse::<u32>() {
74                cfg.num_ref_frames = r;
75            }
76        }
77        let cfg_bframes = cfg.bframes;
78        rusty_h264_encoder::bitacct::reset();
79        rusty_h264_encoder::bitacct::set_enabled(true);
80        let mut enc = Encoder::new(cfg).unwrap();
81        // B-frames require the lookahead path (`encode_all`); the per-frame
82        // `encode` loop cannot reorder. Use whichever the config demands so the
83        // accountant can profile the B-carrying arm at all.
84        let total: usize = if cfg_bframes > 0 {
85            enc.encode_all(&frames).unwrap().iter().map(|n| n.len()).sum()
86        } else {
87            let mut t = 0usize;
88            for f in &frames {
89                t += enc.encode(f).len();
90            }
91            t
92        };
93        rusty_h264_encoder::bitacct::add_actual_bytes(total);
94        rusty_h264_encoder::bitacct::set_enabled(false);
95        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
96        rusty_h264_encoder::bitacct::dump(
97            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
98            mbs,
99        );
100        if std::env::var_os("RFF_BSTATS").is_some() {
101            rusty_h264_encoder::mb16::bstats::dump();
102        }
103        if std::env::var_os("BA_MVDTAB").is_some() {
104            rusty_h264_encoder::bitacct::dump_mvd_table();
105        }
106    }
107}
examples/mbtree_bench.rs (line 98)
65fn main() {
66    let path = std::env::args().nth(1).expect("usage: mbtree_bench <clip.y4m>");
67    let env = |k: &str, d: usize| -> usize {
68        std::env::var(k).ok().and_then(|v| v.parse().ok()).unwrap_or(d)
69    };
70    let (n, qp, gop, reps) = (env("MB_FRAMES", 48), env("MB_QP", 27), env("MB_GOP", 30), env("MB_REPS", 3));
71    if let Ok(la) = std::env::var("MB_LA") {
72        std::env::set_var("RFF_MBTREE_LA", la);
73    }
74    let (w, h, frames) = read_y4m(&path, n);
75    println!("mbtree_bench {}x{} x{} qp{qp} gop{gop} (best-of-{reps})", w, h, frames.len());
76
77    let mut off_ms = f64::MAX;
78    let mut on_ms = f64::MAX;
79    let (mut off_h, mut on_h, mut off_b, mut on_b) = (0u64, 0u64, 0usize, 0usize);
80    let mut calls = 0u64;
81    // PAIRED sampling (H-41/H-43). Alternating the arms is not enough: taking
82    // `min` over each arm INDEPENDENTLY draws the two minima from different
83    // moments, so drift never cancels and the overhead figure swings by more
84    // than the effect. Keep a per-round RATIO instead — both arms of a ratio
85    // are adjacent in time, so the pairing survives whatever the box is doing.
86    let mut ratios: Vec<f64> = Vec::with_capacity(reps);
87    for r in 0..reps {
88        let (mut r_off, mut r_on) = (0.0f64, 0.0f64);
89        // Swap which arm leads each round so a "second one is warmer" effect
90        // cancels across rounds rather than accumulating into the ratio.
91        let order: [bool; 2] = if r % 2 == 0 { [false, true] } else { [true, false] };
92        for on in order {
93            let mut cfg = EncoderConfig::new(w, h);
94            cfg.qp = qp as u8;
95            cfg.gop_size = gop as u32;
96            cfg.preset = Preset::Quality;
97            cfg.mbtree = on;
98            let enc = Encoder::new(cfg).expect("cfg");
99            rusty_h264_encoder::mbtree_satd_reset();
100            let t = std::time::Instant::now();
101            let out: Vec<u8> = enc.encode_all(&frames).expect("encode").concat();
102            let ms = t.elapsed().as_secs_f64() * 1e3;
103            if on {
104                on_ms = on_ms.min(ms);
105                on_h = fnv1a(&out);
106                on_b = out.len();
107                calls = rusty_h264_encoder::mbtree_satd_calls();
108                r_on = ms;
109            } else {
110                off_ms = off_ms.min(ms);
111                off_h = fnv1a(&out);
112                off_b = out.len();
113                r_off = ms;
114            }
115        }
116        ratios.push(r_on / r_off);
117    }
118    println!("  mbtree OFF: {off_ms:8.1} ms  {off_b:>8} bytes  hash {off_h:016x}");
119    println!("  mbtree ON : {on_ms:8.1} ms  {on_b:>8} bytes  hash {on_h:016x}");
120    println!(
121        "  lookahead work: {calls} candidate evals ({:.0}/MB/frame, DETERMINISTIC)",
122        calls as f64 / ((w / 16 * (h / 16)) as f64 * frames.len() as f64)
123    );
124    // The unpaired figure, kept only so old logs stay comparable — do not quote it.
125    println!(
126        "  lookahead overhead (UNPAIRED, min-of-N per arm — noisy, historical): {:+.1}%",
127        100.0 * (on_ms / off_ms - 1.0)
128    );
129    let mut sorted = ratios.clone();
130    sorted.sort_by(|a, b| a.partial_cmp(b).expect("finite"));
131    let med = sorted[sorted.len() / 2];
132    let wins = ratios.iter().filter(|&&r| r < 1.0).count();
133    let n = ratios.len();
134    let z = (wins as f64 - n as f64 / 2.0) / (0.5 * (n as f64).sqrt());
135    print!("  per-round ON/OFF ratios:");
136    for r in &ratios {
137        print!(" {r:.3}");
138    }
139    println!();
140    println!(
141        "  lookahead overhead (PAIRED median): {:+.1}%   [ON faster in {wins}/{n}, z={z:+.2} {}]",
142        100.0 * (med - 1.0),
143        if z.abs() > 2.0 { "VERDICT" } else { "no directional verdict" }
144    );
145    println!("  size {:+.2}%  (deterministic)", 100.0 * (on_b as f64 / off_b as f64 - 1.0));
146}
Source

pub fn config(&self) -> &EncoderConfig

The active configuration.

Source

pub fn encode(&mut self, frame: &YuvFrame) -> Vec<u8> ⓘ

Encodes one frame, returning zero or more Annex-B access units. IDR placement follows the keyint model (gop_size ceiling, min_keyint, scene cuts), each IDR prefixed with SPS/PPS.

With a lookahead feature active (mb-tree, on by default) frames buffer until a window fills, so a call may return EMPTY bytes — call flush at end of stream to drain the tail.

Examples found in repository?
examples/rd_skip_speed.rs (line 75)
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}
More examples
Hide additional examples
examples/thread_bench.rs (line 53)
39fn main() {
40    let path = std::env::args().nth(1).unwrap();
41    let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
42    let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
43    let (w, h, frames) = read_y4m(&path, n);
44    for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
45        let mut cfg = EncoderConfig::new(w, h);
46        cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
47        // best-of-3 each arm
48        let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
49        for _ in 0..3 {
50            let mut enc = Encoder::new(cfg.clone()).unwrap();
51            let t = std::time::Instant::now();
52            let mut o = Vec::new();
53            for f in &frames { o.extend_from_slice(&enc.encode(f)); }
54            seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
55            seq_out = o;
56        }
57        let mut par_ms = f64::MAX; let mut par_out = Vec::new();
58        for _ in 0..3 {
59            let enc = Encoder::new(cfg.clone()).unwrap();
60            let t = std::time::Instant::now();
61            let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
62            par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
63            par_out = o;
64        }
65        assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
66        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);
67    }
68}
examples/me_snap_ab.rs (line 53)
32fn main() {
33    let a: Vec<String> = std::env::args().skip(1).collect();
34    let (w, h) = a[1].split_once('x').unwrap();
35    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
36    let frames = load(&a[0], w, h, std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(60));
37    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
38        "fast" => Preset::Fast, "quality" => Preset::Quality, _ => Preset::Balanced,
39    };
40    let arm_off: u32 = std::env::var("RS_ARM_OFF").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
41    let arm_on: u32 = std::env::var("RS_ARM_ON").ok().and_then(|v| v.parse().ok()).unwrap_or(3);
42    let run = |on: bool| {
43        let m = if on { arm_on } else { arm_off };
44        let mut cfg = EncoderConfig::new(w, h);
45        cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
46        cfg.gop_size = 30;
47        cfg.preset = preset;
48        cfg.tune_me_snap = m & 1 != 0;
49        cfg.tune_me_subpel_iter = m & 2 != 0;
50        let mut enc = Encoder::new(cfg).expect("enc");
51        let t = std::time::Instant::now();
52        let mut b = 0usize;
53        for f in &frames { b += enc.encode(f).len(); }
54        (t.elapsed().as_secs_f64(), b)
55    };
56    let mut best = [f64::MAX; 2];
57    let mut bytes = [0usize; 2];
58    for pass in 0..10 {
59        let arm = pass % 2;
60        let (t, b) = run(arm == 1);
61        if t < best[arm] { best[arm] = t; }
62        bytes[arm] = b;
63    }
64    let px = (w * h * frames.len()) as f64;
65    println!("arm {arm_off} -> {arm_on} — {} {w}x{h} {} frames {preset:?}", a[0], frames.len());
66    println!("  off : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[0]*1e3, px/best[0]/1e6, bytes[0]);
67    println!("  on  : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[1]*1e3, px/best[1]/1e6, bytes[1]);
68    println!("  speed {:>6.3}x   size {:>+6.2}%", best[0]/best[1],
69             100.0*(bytes[1] as f64/bytes[0] as f64 - 1.0));
70}
examples/me_oracle.rs (line 57)
39fn main() {
40    let a: Vec<String> = std::env::args().skip(1).collect();
41    let (w, h) = a[1].split_once('x').unwrap();
42    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
43    let nf: usize = std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(20);
44    let frames = load(&a[0], w, h, nf);
45
46    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
47        "fast" => Preset::Fast,
48        "quality" => Preset::Quality,
49        _ => Preset::Balanced,
50    };
51    let mut cfg = EncoderConfig::new(w, h);
52    cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
53    cfg.gop_size = 30;
54    cfg.preset = preset;
55    let mut enc = Encoder::new(cfg).expect("encoder");
56    for f in &frames {
57        let _ = enc.encode(f);
58    }
59
60    let p: Vec<u64> = rusty_h264_encoder::ME_PROBE
61        .iter()
62        .map(|c| c.load(std::sync::atomic::Ordering::Relaxed))
63        .collect();
64    let (n, ours, oracle, worse, evals) = (p[0].max(1), p[1], p[2], p[3], p[4]);
65    let (oracle_sp, worse_sp) = (p[5], p[6]);
66    println!("ME oracle — {} ({w}x{h}, {} frames, {preset:?})\n", a[0], frames.len());
67    println!("  searches                {n}");
68    println!("  mean cost   ours        {:>10.1}", ours as f64 / n as f64);
69    println!("  mean cost   exhaustive  {:>10.1}", oracle as f64 / n as f64);
70    println!("  ---> we are {:>6.2}% above the achievable minimum",
71             100.0 * (ours as f64 - oracle as f64) / oracle as f64);
72    println!("  searches the oracle beat {:>9} ({:.1}%)", worse, 100.0 * worse as f64 / n as f64);
73    // the oracle's own 49x49 grid + 8 sub-pel probes are included in `evals`
74    println!("
75  + exhaustive SUB-PEL (all quarter-pel in +-3):");
76    println!("  mean cost   exhaustive  {:>10.1}", oracle_sp as f64 / n as f64);
77    println!("  ---> we are {:>6.2}% above the achievable minimum",
78             100.0 * (ours as f64 - oracle_sp as f64) / oracle_sp as f64);
79    println!("  searches it beat        {:>9} ({:.1}%)", worse_sp, 100.0 * worse_sp as f64 / n as f64);
80    let oracle_evals = 0;
81    println!("\n  cost() evals/search     {:>8.1}  (ours, oracle's {oracle_evals} excluded)",
82             evals as f64 / n as f64 - oracle_evals as f64);
83}
examples/bit_accountant.rs (line 89)
52fn main() {
53    let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
54    let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
55    let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
56    let (w, h, frames) = read_y4m(&path, n);
57    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
58    for (pname, preset) in [("quality", Preset::Quality)] {
59        let mut cfg = EncoderConfig::new(w, h);
60        cfg.qp = qp;
61        cfg.gop_size = 60;
62        cfg.preset = preset;
63        // The accountant must be runnable in the SAME configuration whose gap is
64        // under investigation, or its split describes a different encoder than the
65        // one being measured. BA_BFRAMES / BA_REFS mirror the all-tools arm.
66        if let Ok(v) = std::env::var("BA_BFRAMES") {
67            if let Ok(b) = v.parse::<u32>() {
68                cfg.bframes = b;
69                cfg.bframes_adaptive = false;
70            }
71        }
72        if let Ok(v) = std::env::var("BA_REFS") {
73            if let Ok(r) = v.parse::<u32>() {
74                cfg.num_ref_frames = r;
75            }
76        }
77        let cfg_bframes = cfg.bframes;
78        rusty_h264_encoder::bitacct::reset();
79        rusty_h264_encoder::bitacct::set_enabled(true);
80        let mut enc = Encoder::new(cfg).unwrap();
81        // B-frames require the lookahead path (`encode_all`); the per-frame
82        // `encode` loop cannot reorder. Use whichever the config demands so the
83        // accountant can profile the B-carrying arm at all.
84        let total: usize = if cfg_bframes > 0 {
85            enc.encode_all(&frames).unwrap().iter().map(|n| n.len()).sum()
86        } else {
87            let mut t = 0usize;
88            for f in &frames {
89                t += enc.encode(f).len();
90            }
91            t
92        };
93        rusty_h264_encoder::bitacct::add_actual_bytes(total);
94        rusty_h264_encoder::bitacct::set_enabled(false);
95        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
96        rusty_h264_encoder::bitacct::dump(
97            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
98            mbs,
99        );
100        if std::env::var_os("RFF_BSTATS").is_some() {
101            rusty_h264_encoder::mb16::bstats::dump();
102        }
103        if std::env::var_os("BA_MVDTAB").is_some() {
104            rusty_h264_encoder::bitacct::dump_mvd_table();
105        }
106    }
107}
Source

pub fn encode_planes( &mut self, frame: &YuvPlanes<'_>, ) -> Result<Vec<u8>, EncodeError>

encode over a borrowed frame — the camera’s DMA buffer, not a copy. A tight view feeds the coder directly; a padded one is gathered into a reused scratch frame first. Byte-identical to encode(&view.to_frame()).

Source

pub fn encode_into( &mut self, frame: &YuvPlanes<'_>, out: &mut [u8], ) -> Result<usize, EncodeError>

encode_planes into a caller-owned buffer — the packetizer’s, on a chip — returning the access unit’s length.

An access unit that does not fit is EncodeError::BufferTooSmall with the size it needed; the picture is then lost (the encoder has already coded it and moved on) and the next call still works, so size the buffer for the worst case: an IDR at low QP can approach width * height * 3 / 2. Bytes are identical to [encode_planes].

Source

pub fn flush_into(&mut self, out: &mut [u8]) -> Result<usize, EncodeError>

flush into a caller-owned buffer; see encode_into for the buffer contract.

Source

pub fn request_keyframe(&mut self)

Make the next picture an IDR (a late joiner on the mesh needs one now, not at the next GOP boundary). Rate-control state, the frame counter and the scene-cut history survive; only the DPB restarts, as at any IDR. With a lookahead active the buffered frames are coded first and the IDR lands on the next frame submitted, in coding order.

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

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 = !

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

fn try_from(value: U) -> Result<T, !>

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.