Skip to main content

EncoderConfig

Struct EncoderConfig 

Source
pub struct EncoderConfig {
Show 40 fields pub width: usize, pub height: usize, pub profile: Profile, pub chroma: ChromaFormat, pub level_idc: u8, pub qp: u8, pub gop_size: u32, pub bitrate: u32, pub framerate: f32, pub num_ref_frames: u32, pub preset: Preset, pub tune_me_subpel_iter: bool, pub tune_greedy_skip: bool, pub tune_greedy_skip_min_free: Option<u32>, pub tune_bskip_rd: Option<f64>, pub tune_bskip_busy_pct: Option<usize>, pub tune_bskip_dirwin_pct: Option<usize>, pub tune_b_split: bool, pub tune_rd_skip: bool, pub tune_rd_skip_min_free: Option<u32>, pub tune_rd_skip_fast_t: Option<f64>, pub bframes: u32, pub bframe_qp_offset: i32, pub aq_strength: f64, pub bframes_adaptive: bool, pub i_qp_offset: i32, pub cabac: bool, pub cabac_init_idc: u32, pub cabac_lambda_scale: f64, pub tune_lme_hi: Option<f64>, pub tune_lme_tex_thresh: Option<i64>, pub tune_lme_motion_thresh: Option<f64>, pub cabac_dz_div: i64, pub cabac_rdoq: f64, pub transform_8x8: bool, pub sub_8x8: Option<bool>, pub me_wide: Option<bool>, pub mbtree: bool, pub mbtree_strength: f64, pub mbtree_lookahead: LookaheadMode, /* private fields */
}
Expand description

Configuration for an crate::Encoder.

Fields§

§width: usize

Picture width in luma samples. Arbitrary (not restricted to /16).

§height: usize

Picture height in luma samples.

§profile: Profile

Target profile. Only Profile::ConstrainedBaseline is implemented.

§chroma: ChromaFormat

Chroma format. Only ChromaFormat::Yuv420 is implemented.

§level_idc: u8

level_idc (e.g. 30 = level 3.0). Caller is responsible for choosing a level that admits the resolution/bitrate; not yet validated.

§qp: u8

Quantization parameter (0..=51). With rate control off this is the fixed QP for every frame; with it on, the base/fallback QP and pic_init_qp.

§gop_size: u32

Frames between IDR pictures. 1 = all-intra (every frame an IDR).

§bitrate: u32

Target bitrate in bits per second. 0 disables rate control (constant QP); any positive value enables average-bitrate control, which varies the per-frame QP around qp to converge on this rate.

§framerate: f32

Frame rate (frames per second), used by rate control to turn the bitrate target into a per-frame bit budget.

§num_ref_frames: u32

Number of reference frames the encoder may use for P-pictures (1..=16). 1 keeps the single-reference bitstream; higher values let P-macroblocks pick an older reference (ref_idx), helping occlusion/periodic motion.

§preset: Preset

Speed/quality trade-off. Defaults to Preset::Balanced.

§tune_me_subpel_iter: bool

Walk the sub-pel refinement until it stops improving, instead of a single 8-point pass per step. Independent of Self::tune_me_snap — measured separately, because the two were first built coupled and the attribution was ambiguous.

§tune_greedy_skip: bool§tune_greedy_skip_min_free: Option<u32>

Minimum online FREE-skip percentage for Self::tune_greedy_skip to engage, dispatched on exactly the signal that gates RD skip (Self::tune_rd_skip_min_free). The greedy skip wins on temporally redundant content and LOSES on detailed content (BD-SSIM: akiyo -0.59, FourPeople -0.32 vs foreman +1.23) — the same sign-flip, separated by the same signal. None resolves to 85 — the calibrated default, at which the corpus regression on foreman (+1.23% BD-SSIM, previously shipping) becomes 0.00 and nothing else regresses. Some(0) restores the old ungated behaviour; Some(101) disables the greedy skip entirely.

§tune_bskip_rd: Option<f64>

RD B_Skip strength, in units of lambda. DEFAULT-ON at 48.0; None/<=0 restores the previous exactly-free-only rule byte-identically.

A B macroblock is skipped when direct WON the mode decision and its prediction distortion is under T*lambda — i.e. the residual is not worth its bits. Our previous rule demanded the residual quantize to EXACTLY zero, which reaches 93.5% of B macroblocks on akiyo and 34.5% on foreman (at or ABOVE x264) but collapses to 7.8% on mobile where x264 still finds 27.4%. The deficit is BUSY-CONTENT-ONLY, so this is DISPATCHED on the online free-skip rate of the frame (Self::tune_bskip_busy_pct) rather than applied as a flat constant — on content where we already out-skip x264 it stays a byte-identical no-op.

4-QP per-clip BD at T=48 (worst clip 0.00): mobile -0.51 PSNR / -0.96 SSIM, foreman -0.30 / -0.34, bus -0.10 / -0.50, akiyo byte-identical.

§tune_bskip_busy_pct: Option<usize>

Engage Self::tune_bskip_rd only while the frame’s online FREE-skip rate is below this percentage — the busy-content dispatch. Default 60.

§tune_bskip_dirwin_pct: Option<usize>

Minimum online DIRECT-WIN rate (percent of not-free B macroblocks where direct won the mode decision) for Self::tune_bskip_rd to engage. Default 10 — calibrated on the one corpus clip that regressed (football, 7.0%) against the lowest-rate winner (foreman, 14.1%).

§tune_b_split: bool

Search B 16x8 / 8x16 partitions. x264 spends 13.5% of its B macroblocks there; we had none, which is why the B bucket kept reading as a CODING gap after every constant in it had been swept flat. DEFAULT ON: the 4-QP per-clip table wins on all seven clips and both metrics with no sign flip (BD-SSIM akiyo -0.17%, FourPeople -0.80%, tempete -1.66%, mobile -3.36%, foreman -3.49%, bus -4.56%, football -7.09%), so there is nothing to dispatch on – the win simply concentrates on busy/high-motion content. CABAC B path only; the CAVLC B path still emits 16x16 modes.

§tune_rd_skip: bool§tune_rd_skip_min_free: Option<u32>

Minimum FREE-skip percentage, measured online over the frame so far, for Self::tune_rd_skip to engage on the rest of that frame.

None resolves per preset, because the signal’s SCALE is preset-dependent: sub-pel refinement predicts better, so it lifts the free-skip rate on ALL content and the same absolute bar starts admitting content that loses. Fast (no sub-pel) calibrates to 60; the sub-pel presets need 90. Each is the smallest bar at which no corpus clip regresses on BD-PSNR or BD-SSIM. Some(0) forces RD skip on everywhere (which LOSES badly on detailed content); Some(101) disables it.

§tune_rd_skip_fast_t: Option<f64>

Skip-gate on the null arm’s cost, in units of lambda: when SSD(skip) <= lambda * T the skip is taken WITHOUT trial-encoding the coded arm at all.

The RD skip decision has to encode the coded arm to price it, and 55-80% of the time it then throws that encode away — the null arm wins. This is the standard search-skip gate over that: it trades a small number of decisions (the RD comparison would occasionally have coded) for not encoding at all. None/<= 0.0 disables it (every candidate is priced exactly). Unlike the rest of the decision this is NOT byte-identical, so it is BD-rate gated.

§bframes: u32

Number of B-frames between reference (I/P) anchors. 0 = no B-frames (Constrained Baseline, byte-identical). >0 requires Main profile (B is illegal in Baseline) and activates the reorder pipeline: anchors are coded ahead of the B-frames that reference them (L0 past + L1 future), and B- frames are non-reference. WORK IN PROGRESS — see the B-frame build plan.

§bframe_qp_offset: i32

QP offset applied to B-frames (added to qp). B-frames are non-reference, so their coding error never propagates — quantizing them harder (a positive offset) spends the saved bits on the reference anchors. Only used when bframes > 0. Default 2.

§aq_strength: f64

Adaptive Quantization strength. Modulates the QP per macroblock by content: flat/low-variance MBs (where blocking & banding are visible) get a FINER QP, busy/high-variance MBs (where the eye masks error) a COARSER one — moving bits to where they’re seen, a perceptual (SSIM) win at ~neutral PSNR. The QP shift is relative to the FRAME’s mean log-variance (content-invariant), rate- compensated, and its EFFECTIVE strength backs off automatically where the log-variance spread is extreme (pathological synthetic content), so it never regresses. Default 1.0 (on); 0.0 = off (uniform QP, byte-identical).

§bframes_adaptive: bool

Content-adaptive B-frame ENABLE. When set (with bframes > 0), the encoder measures the clip’s temporal predictability (a cheap global-motion bi- prediction residual) and codes B-frames ONLY when they’ll help — smooth / predictable motion, where bi-pred + spatial-direct are cheap. On busy content it falls back to P-only, so B-frames never regress. Default false.

§i_qp_offset: i32

Per-GOP I-frame QP cascade — the BASE offset for the classic ip_ratio (added to qp on each GOP’s I-frame; it’s the root reference for its whole GOP, so coding it finer propagates quality GOP-wide). Default -3. In the B-capable batch path this is CONTENT-ADAPTIVE per GOP: predictable GOPs — where the I-frame dominates the GOP’s bits — deepen it up to 2 further QP steps (calibrated: busy ≈ base, compressible ≈ base−2). 0 disables the cascade entirely (byte-identical escape hatch). Constant-QP only.

§cabac: bool

CABAC entropy coding (PPS entropy_coding_mode_flag = 1, Main profile). Codes ~5–17% smaller than CAVLC at matched quality (I- and P-slices; B-slice CABAC pending). Default false (CAVLC — Constrained Baseline, unchanged).

§cabac_init_idc: u32

cabac_init_idc (0..2) — selects one of 3 context-initialization tables for P/B slices (I-slices always use the I preset). The best table is content-dependent; 0 is the default. Signalled in the P/B slice header.

§cabac_lambda_scale: f64

Multiplier on the mode-decision Lagrangian (√λ) in the CABAC P/B path only. CABAC codes ~9% fewer bits than the CAVLC-flavoured rate estimate the mode decision uses, so the rate term is slightly over-weighted; this retunes it. Default 1.0 (unchanged). CAVLC path is never affected.

§tune_lme_hi: Option<f64>

ME lambda scale used on NORMAL-texture content, dispatched by the frame’s median source macroblock variance. None = no dispatch (always Self::cabac_lambda_scale). DEFAULT None — the dispatch is OFF.

The texture gate works for what it was built for: it holds mobile (median MB variance 1554) at the conservative value, byte-identically, where a flat 1.8 costs +0.42% BD-SSIM. But it does NOT clear the monotone bar, because bus regresses at EVERY high value tried (1.4 +0.35, 1.6 +0.09, 1.8 +0.29 BD-PSNR vs 1.25) and bus’s texture (454) sits BELOW football’s (583), which WANTS the high value — so no threshold on this signal separates them. Kept as an opt-in knob with the machinery intact; needs a second, motion-flavoured term before it can be default-on.

§tune_lme_tex_thresh: Option<i64>

Median source MB variance at or above which the conservative Self::cabac_lambda_scale is used instead of Self::tune_lme_hi. Default 650. Measured medians: akiyo 61, foreman 219, city 300, bus 454, football 583, tempete 746, mobile 1554. 650 sits between football (583, which WANTS the high lambda) and tempete (746, which regresses on BD-SSIM at it) — the third loser, found only after the first two were gated. An earlier 800 let tempete through by 54 points.

§tune_lme_motion_thresh: Option<f64>

Global-MC residual at or above which the conservative Self::cabac_lambda_scale is used. Default 26.0 — measured residuals are akiyo 1.5, foreman 9.6, city 12.4, mobile 19.5, football 24.8, bus 27.5; Default 20.0. Measured residuals: akiyo 1.5, foreman 9.6, city 12.4, mobile 19.5, football 24.8, bus 27.5. At 20 bus is clean (0.00 BD-PSNR / -0.03 BD-SSIM); a looser 24-26 leaves bus slightly positive because the per-frame residual straddles it. The cost of 20 is football’s win (-0.57 -> -0.01): its per-frame residual also crosses 20, so it is held conservative. Deliberate — protecting a regressor outranks capturing a win.

§cabac_dz_div: i64

Quantizer dead-zone divisor override for the CABAC path (F = 2^qbits/dz). A smaller divisor (bigger F) keeps more near-threshold coefficients — cheaper under CABAC’s context-coded residual than under CAVLC. 0 = use the standard content-derived dead-zone (default, unchanged).

§cabac_rdoq: f64

CABAC trellis-quantization (RDOQ) strength. Each 4×4 residual coefficient is RD-optimized (level vs level−1 minimizing SSD + λ·R_cabac, λ scaled by this; ~8 calibrated). 0.0 = off. DEFAULT-ON for CABAC I-slices (frame-type adaptive — P/B off, sparse residual gains ~0); CAVLC path always off.

§transform_8x8: bool

High-profile 8×8 transform (transform_8x8_mode_flag). When set, an intra macroblock may use one 8×8 integer DCT per 8×8 block (I_8x8) instead of four 4×4s — a per-MB RD choice that wins on smooth / large-structure content. Requires High profile (profile_idc 100). CAVLC only (our decoder has no CABAC 8×8). Default false.

§sub_8x8: Option<bool>

P_8x8 sub-partition motion: allow a P macroblock to split into four 8×8 partitions, each with its own motion vector (finer motion granularity on complex / boundary motion). A per-MB RD choice vs 16×16/16×8/8×16, gated on the heavy-16×16 motion-boundary signal. A NET WIN on real content (12-clip Derf corpus: −0.23% mean BD, big wins on bus/mobile/flower; a rigorous 6-channel discovery harvest proved no cheap gate beats default-on, oracle headroom only 0.18%), so it is DEFAULT-ON for the Quality preset. Quality-only. None = follow the preset (ON for Quality); Some(b) forces it either way. (8×4/4×8/4×4 sub-shapes within an 8×8 are a further split, not yet built.)

§me_wide: Option<bool>

Adaptive WIDE motion search: on flat source blocks (where the gradient-descent diamond stalls at a plateau and misses the true MV) cover the ±16 neighbourhood with a grid search instead; busy blocks keep the fast diamond. A big win on smooth/low-motion content (the diamond’s flat-surface failure), free on busy content — content-adaptive (a per-frame coherence gate keeps it from regressing even on pure pans), so it is DEFAULT-ON for the Quality preset. Quality-only. None = follow the preset (ON for Quality); Some(b) forces it either way.

§mbtree: bool

Macroblock-tree lookahead adaptive QP (TEMPORAL AQ). A cheap forward pass over each GOP’s source frames propagates future-reference importance backward along motion vectors and lowers the QP of heavily-referenced macroblocks — investing bits where they pay off across many later frames. The complement to the spatial aq_strength. Per-GOP-centered (rate-preserving). Applies only in the batch (encode_all) constant-QP path, where the GOP’s future frames are available (a bframes > 0 encode uses the reorder pipeline and ignores it).

Default true since 0.5.0 (H-37) — the gate cleared and the architectural blocker is gone: the streaming path now carries a one-GOP lookahead queue, so encode() + flush is byte-identical to encode_all(). Set false for zero added latency (one AU per encode call) or for the pre-0.5.0 bytes. Evidence:

  • BD: the 4-QP per-clip gate CLEARS with room to spare (akiyo −4.82%, foreman −3.13%, football −0.53%, bus −0.29%, mobile −0.24%, city_4cif +0.01% neutral) — the monotone non-regression bar, not a mean.
  • Cost: content-INDEPENDENT at 16-21 candidate evaluations per macroblock per frame across that corpus (1.3× spread) ≈ 1-2% of a busy-clip encode. The per-clip “blowups” (+251%, +34%) were wall-clock artifacts of a drifting box.

COST OF THE DEFAULT: encode() now returns a whole GOP’s access units at once (empty while the GOP fills), so end-to-end latency is up to gop_size frames and flush() is required at end of stream. Batch callers (encode_all) are unaffected — they already had the whole GOP.

§mbtree_strength: f64

mb-tree QP-offset strength: qp_offset = -strength · log2((intra+propagate)/intra). Larger = more aggressive bit redistribution toward referenced MBs. Default 0.9.

§mbtree_lookahead: LookaheadMode

Resolution the mb-tree lookahead motion search runs at (see LookaheadMode). Default HalfRes — fastest (~4× the lookahead), a small BD-rate cost on fine detail; use Hybrid to recover full-res quality at ~1.7×. Only relevant when mbtree is on.

Implementations§

Source§

impl EncoderConfig

Source

pub fn new(width: usize, height: usize) -> Self

A minimal all-intra Constrained Baseline configuration at the given size.

Examples found in repository?
examples/rd_skip_speed.rs (line 58)
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 37)
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 36)
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 43)
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 51)
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 85)
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 mb_width(&self) -> usize

Picture width rounded up to whole macroblocks.

Source

pub fn mb_height(&self) -> usize

Picture height rounded up to whole macroblocks.

Trait Implementations§

Source§

impl Clone for EncoderConfig

Source§

fn clone(&self) -> EncoderConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for EncoderConfig

Source§

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

Formats the value using the given formatter. 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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.