Skip to main content

EncoderConfig

Struct EncoderConfig 

Source
pub struct EncoderConfig {
Show 54 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 min_keyint: u32, pub scenecut: u32, pub lookahead: 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 b_pyramid: bool, pub i_qp_offset: i32, pub cabac: bool, pub weightp: 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 tune_lme_q: Option<f64>, pub cabac_dz_div: i64, pub cabac_rdoq: f64, pub cabac_rdoq_p: f64, pub cabac_rdoq_b: f64, pub tune_sub8x8_split: bool, pub tune_sub8_rd: bool, pub tune_intra_rd: bool, pub tune_shape_rd: bool, pub tune_rd_lambda_mb: bool, pub transform_8x8: bool, pub sub_8x8: Option<bool>, pub me_wide: Option<bool>, pub mbtree: bool, pub mbtree_spread_min: f64, 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

MAXIMUM frames between IDR pictures (x264’s keyint). 1 = all-intra (every frame an IDR). With scenecut active, IDRs land at detected scene changes and this is only the forced-refresh ceiling — exactly x264’s model (its default keyint is 250).

§min_keyint: u32

MINIMUM frames between IDR pictures (x264’s min-keyint, default 25): a scene cut closer than this to the previous IDR does not spend one. Ignored by the forced gop_size refresh and by all-intra.

§scenecut: u32

Scene-cut sensitivity (x264’s scenecut, default 40; 0 = OFF — fixed-cadence IDRs, byte-identical to the pre-scenecut encoder). A cut fires when the frame-pair inter/intra activity ratio reaches 1 - scenecut/100 (the x264 rule) — motion compensation recovering less than scenecut% of the frame’s spatial energy means the content changed, not moved.

§lookahead: u32

Lookahead window in frames (x264’s rc-lookahead, default 40): the buffering bound for streaming mb-tree and the mb-tree window inside long scenecut GOPs — a 250-frame GOP must not mean a 250-frame buffer.

§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.

§b_pyramid: bool

B-pyramid (x264 parity — its default is normal): with 2+ B’s per anchor gap, the MIDDLE B is coded first as a REFERENCE (nal_ref_idc 2, deblocked recon in the DPB, sliding-window marking) and the leaf B’s bracket against it — halving the leaf prediction distance. v1 is CABAC-path (the default); CAVLC B stays leaf-only.

§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).

§weightp: bool

Explicit weighted prediction for P slices (x264 parity — its weightp defaults on). Per-slice, per-reference LUMA (w, offset) at denom 6, estimated by a fade detector (DC-ratio fit, SAD-gated); identity weights everywhere the estimator finds no gain, so non-fade content pays only the table’s few header bits per slice. Chroma is unweighted (flag 0) — matching what x264’s own weightp streams carry.

§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.

§tune_lme_q: Option<f64>

OPT-IN, BD-gate pending (Great Gate P1 machinery — docs/great-gate.md §6 P2): the population-shaped PER-MB form of the Self::tune_lme_tex_thresh texture veto. Some(q) routes each P frame’s top-q fraction of highest-variance MBs to the conservative Self::cabac_lambda_scale individually (per-frame percentile — the routed fraction is content-invariant by construction, where the absolute median test cannot separate bus 454 from football 583, which want opposite values). The motion veto stays frame-level; B slices keep the frame-median form. None (default) = the frame-level veto exactly, byte-identical. Env override for sweep arms: RFF_LME_Q.

§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.

§cabac_rdoq_p: f64

Trellis (RDOQ) strength for CABAC P slices — SHIPPED as a CONTENT DISPATCH (default 32.0), applied only where grain_signature() or is_screen() fires; every other clip stays byte-identical to off. A flat default is REFUTED (sign flips by content — see the default’s comment below). The original structure-adaptive prediction (“a P is a reference, expect wash-or-loss”) held for natural content and is exactly why the gate exists. 0.0 = off everywhere.

§cabac_rdoq_b: f64

Trellis (RDOQ) strength for CABAC B slices — DEFAULT-ON unconditionally (32.0; 6/6 clips win, zero losers — see the default’s comment). Non-reference, so the structure-adaptive law says the trade is clean (nothing depends on a B’s reconstruction). 0.0 = off.

§tune_sub8x8_split: bool

OPT-IN, BD-gate pending (Great Gate P3.3): search 8x4/4x8/4x4 sub-partitions inside P_8x8 (CABAC quality path, single-ref). The decoder has parsed these since bring-up; the encoder emits them only when this is set. false (default) = byte-identical. Env sweep arm: RFF_SUB8X8_SPLIT=1. Default-on decision DEFERRED until the 4-wide MC/cost kernels land (census #8 – the scalar fall-through would double-charge the feature’s speed cost).

§tune_sub8_rd: bool

PROBE (Great Gate P3.3 gate): price the sub-8x8 SPLIT-vs-8x8 decision in the RD currency (SSD_recon + lambda*bits, both arms fully planned) instead of the SATD proxy. Only meaningful with Self::tune_sub8x8_split. Costs two extra macroblock plans per split candidate — a probe, not a shipping speed point.

§tune_intra_rd: bool

PROBE (Great Gate P3 RD-pricing #2): price the INTRA-vs-INTER decision by SSD_recon + lambda*bits (both candidates planned for real) instead of the SATD proxy plus the fitted Self::tune_intra_penalty. That penalty is itself a correction for this proxy’s bias, so if the probe wins the penalty should be re-swept (probably toward 0 — the P2 lambda campaign already found 0 better on all three clips). Costs one extra trial-encode plus one extra MB plan per coded macroblock.

§tune_shape_rd: bool

PROBE (Great Gate P3 RD-pricing #3): price the PARTITION SHAPE decision (16x16 / 16x8 / 8x16 / P_8x8) by SSD_recon + lambda*bits instead of the SATD proxy. The third SATD-priced default-on site. Costs one full macroblock plan per candidate shape.

§tune_rd_lambda_mb: bool

Price the inter RD trials with the macroblock’s ACTUAL quantizer rather than the slice’s frame-level one. AQ (aq_strength, default 1.0) and mb-tree rewrite QP per macroblock; a frame-level lambda misprices rate by 2^((qp_frame-qp_mb)/3) at every RD site, worst on the high-variance macroblocks AQ moves furthest. false restores the frame-lambda form for A/B (the arm must PIN the value, never rely on an absent override).

§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_spread_min: f64

Minimum dispersion of mb-tree’s own propagation offsets for it to apply at all (the DIFFERENTIATION LATCH — see mbtree.rs). Below this the offsets carry no information and are zeroed, which is byte-identical to mb-tree off. 0.0 disables the latch (ungated, pre-gate behaviour).

§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/bframes_signals.rs (line 48)
42fn main() {
43    let mut args = std::env::args().skip(1);
44    let path = args.next().expect("clip");
45    let nframes: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(30);
46    let (w, h, frames) = read_y4m(&path, nframes);
47    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
48    let cfg = EncoderConfig::new(w, h);
49    let (bi, gmc, mg, dc, screen, grain) = rusty_h264_encoder::bframes_gate_signals(&cfg, &frames);
50    println!(
51        "{name:<24} bi={bi:>7.3} gmc={gmc:>8.3} mgain={mg:.3} dcfrac={dc:.3} screen={} grain={}",
52        screen as u8, grain as u8
53    );
54}
examples/scenecut_probe.rs (line 50)
44fn main() {
45    let mut args = std::env::args().skip(1);
46    let path = args.next().expect("clip");
47    let nframes: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(60);
48    let (w, h, frames) = read_y4m(&path, nframes);
49    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
50    let cfg = EncoderConfig::new(w, h); // defaults: scenecut 40 → threshold 0.60
51    let ratios = rusty_h264_encoder::scene_cut_ratios(&cfg, &frames);
52    let max = ratios.iter().cloned().fold(0.0f64, f64::max);
53    let thresh = 1.0 - cfg.scenecut as f64 / 100.0;
54    let flat = ratios.iter().filter(|&&r| r >= thresh).count();
55    // v2 spike rule: high AND a jump over the recent baseline (min of the two
56    // previous pair ratios) — a cut is a discontinuity, chaos is a plateau.
57    let mut spike = 0usize;
58    for i in 0..ratios.len() {
59        let base = match i {
60            0 => 1.0,
61            1 => ratios[0],
62            _ => ratios[i - 1].min(ratios[i - 2]),
63        };
64        if ratios[i] >= thresh && ratios[i] >= base + 0.25 {
65            spike += 1;
66        }
67    }
68    println!(
69        "{name:<24} pairs={} max_ratio={max:.3} flat@{thresh:.2}={flat} spike={spike}",
70        ratios.len()
71    );
72}
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}
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 = !

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.