Skip to main content

Encoder

Struct Encoder 

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

A Constrained Baseline H.264 encoder.

Implementations§

Source§

impl Encoder

Source

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

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

Examples found in repository?
examples/rd_skip_speed.rs (line 63)
57fn encode(frames: &[YuvFrame], w: usize, h: usize, rd_skip: bool) -> (f64, usize) {
58    let mut cfg = EncoderConfig::new(w, h);
59    cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
60    cfg.gop_size = 30;
61    cfg.tune_rd_skip = rd_skip;
62    cfg.tune_rd_skip_fast_t = std::env::var("RS_GATE").ok().and_then(|v| v.parse().ok());
63    let mut enc = Encoder::new(cfg).expect("encoder");
64    let t = std::time::Instant::now();
65    let mut bytes = 0usize;
66    for fr in frames {
67        bytes += enc.encode(fr).len();
68    }
69    (t.elapsed().as_secs_f64(), bytes)
70}
More examples
Hide additional examples
examples/bit_accountant.rs (line 57)
44fn main() {
45    let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
46    let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
47    let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
48    let (w, h, frames) = read_y4m(&path, n);
49    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
50    for (pname, preset) in [("quality", Preset::Quality)] {
51        let mut cfg = EncoderConfig::new(w, h);
52        cfg.qp = qp;
53        cfg.gop_size = 60;
54        cfg.preset = preset;
55        rusty_h264_encoder::bitacct::reset();
56        rusty_h264_encoder::bitacct::set_enabled(true);
57        let mut enc = Encoder::new(cfg).unwrap();
58        let mut total = 0usize;
59        for f in &frames {
60            total += enc.encode(f).len();
61        }
62        rusty_h264_encoder::bitacct::add_actual_bytes(total);
63        rusty_h264_encoder::bitacct::set_enabled(false);
64        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
65        rusty_h264_encoder::bitacct::dump(
66            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
67            mbs,
68        );
69        if std::env::var_os("BA_MVDTAB").is_some() {
70            rusty_h264_encoder::bitacct::dump_mvd_table();
71        }
72    }
73}
examples/thread_bench.rs (line 42)
31fn main() {
32    let path = std::env::args().nth(1).unwrap();
33    let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
34    let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
35    let (w, h, frames) = read_y4m(&path, n);
36    for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
37        let mut cfg = EncoderConfig::new(w, h);
38        cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
39        // best-of-3 each arm
40        let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
41        for _ in 0..3 {
42            let mut enc = Encoder::new(cfg.clone()).unwrap();
43            let t = std::time::Instant::now();
44            let mut o = Vec::new();
45            for f in &frames { o.extend_from_slice(&enc.encode(f)); }
46            seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
47            seq_out = o;
48        }
49        let mut par_ms = f64::MAX; let mut par_out = Vec::new();
50        for _ in 0..3 {
51            let enc = Encoder::new(cfg.clone()).unwrap();
52            let t = std::time::Instant::now();
53            let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
54            par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
55            par_out = o;
56        }
57        assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
58        println!("{pn:<9} x{} gop{gop}: seq {seq_ms:.0} ms  parallel {par_ms:.0} ms  speedup {:.2}x  (byte-identical ✓)", frames.len(), seq_ms / par_ms);
59    }
60}
examples/me_snap_ab.rs (line 42)
24fn main() {
25    let a: Vec<String> = std::env::args().skip(1).collect();
26    let (w, h) = a[1].split_once('x').unwrap();
27    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
28    let frames = load(&a[0], w, h, std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(60));
29    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
30        "fast" => Preset::Fast, "quality" => Preset::Quality, _ => Preset::Balanced,
31    };
32    let arm_off: u32 = std::env::var("RS_ARM_OFF").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
33    let arm_on: u32 = std::env::var("RS_ARM_ON").ok().and_then(|v| v.parse().ok()).unwrap_or(3);
34    let run = |on: bool| {
35        let m = if on { arm_on } else { arm_off };
36        let mut cfg = EncoderConfig::new(w, h);
37        cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
38        cfg.gop_size = 30;
39        cfg.preset = preset;
40        cfg.tune_me_snap = m & 1 != 0;
41        cfg.tune_me_subpel_iter = m & 2 != 0;
42        let mut enc = Encoder::new(cfg).expect("enc");
43        let t = std::time::Instant::now();
44        let mut b = 0usize;
45        for f in &frames { b += enc.encode(f).len(); }
46        (t.elapsed().as_secs_f64(), b)
47    };
48    let mut best = [f64::MAX; 2];
49    let mut bytes = [0usize; 2];
50    for pass in 0..10 {
51        let arm = pass % 2;
52        let (t, b) = run(arm == 1);
53        if t < best[arm] { best[arm] = t; }
54        bytes[arm] = b;
55    }
56    let px = (w * h * frames.len()) as f64;
57    println!("arm {arm_off} -> {arm_on} — {} {w}x{h} {} frames {preset:?}", a[0], frames.len());
58    println!("  off : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[0]*1e3, px/best[0]/1e6, bytes[0]);
59    println!("  on  : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[1]*1e3, px/best[1]/1e6, bytes[1]);
60    println!("  speed {:>6.3}x   size {:>+6.2}%", best[0]/best[1],
61             100.0*(bytes[1] as f64/bytes[0] as f64 - 1.0));
62}
examples/me_oracle.rs (line 47)
31fn main() {
32    let a: Vec<String> = std::env::args().skip(1).collect();
33    let (w, h) = a[1].split_once('x').unwrap();
34    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
35    let nf: usize = std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(20);
36    let frames = load(&a[0], w, h, nf);
37
38    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
39        "fast" => Preset::Fast,
40        "quality" => Preset::Quality,
41        _ => Preset::Balanced,
42    };
43    let mut cfg = EncoderConfig::new(w, h);
44    cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
45    cfg.gop_size = 30;
46    cfg.preset = preset;
47    let mut enc = Encoder::new(cfg).expect("encoder");
48    for f in &frames {
49        let _ = enc.encode(f);
50    }
51
52    let p: Vec<u64> = rusty_h264_encoder::ME_PROBE
53        .iter()
54        .map(|c| c.load(std::sync::atomic::Ordering::Relaxed))
55        .collect();
56    let (n, ours, oracle, worse, evals) = (p[0].max(1), p[1], p[2], p[3], p[4]);
57    let (oracle_sp, worse_sp) = (p[5], p[6]);
58    println!("ME oracle — {} ({w}x{h}, {} frames, {preset:?})\n", a[0], frames.len());
59    println!("  searches                {n}");
60    println!("  mean cost   ours        {:>10.1}", ours as f64 / n as f64);
61    println!("  mean cost   exhaustive  {:>10.1}", oracle as f64 / n as f64);
62    println!("  ---> we are {:>6.2}% above the achievable minimum",
63             100.0 * (ours as f64 - oracle as f64) / oracle as f64);
64    println!("  searches the oracle beat {:>9} ({:.1}%)", worse, 100.0 * worse as f64 / n as f64);
65    // the oracle's own 49x49 grid + 8 sub-pel probes are included in `evals`
66    println!("
67  + exhaustive SUB-PEL (all quarter-pel in +-3):");
68    println!("  mean cost   exhaustive  {:>10.1}", oracle_sp as f64 / n as f64);
69    println!("  ---> we are {:>6.2}% above the achievable minimum",
70             100.0 * (ours as f64 - oracle_sp as f64) / oracle_sp as f64);
71    println!("  searches it beat        {:>9} ({:.1}%)", worse_sp, 100.0 * worse_sp as f64 / n as f64);
72    let oracle_evals = 0;
73    println!("\n  cost() evals/search     {:>8.1}  (ours, oracle's {oracle_evals} excluded)",
74             evals as f64 / n as f64 - oracle_evals as f64);
75}
Source

pub fn config(&self) -> &EncoderConfig

The active configuration.

Source

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

Encodes one frame, returning the Annex-B access unit. Every gop_size frames (and always the first) is coded as an IDR, prefixed with SPS/PPS.

Generation 1 codes every picture as an IDR (all-intra); inter frames arrive with motion compensation later.

Examples found in repository?
examples/rd_skip_speed.rs (line 67)
57fn encode(frames: &[YuvFrame], w: usize, h: usize, rd_skip: bool) -> (f64, usize) {
58    let mut cfg = EncoderConfig::new(w, h);
59    cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
60    cfg.gop_size = 30;
61    cfg.tune_rd_skip = rd_skip;
62    cfg.tune_rd_skip_fast_t = std::env::var("RS_GATE").ok().and_then(|v| v.parse().ok());
63    let mut enc = Encoder::new(cfg).expect("encoder");
64    let t = std::time::Instant::now();
65    let mut bytes = 0usize;
66    for fr in frames {
67        bytes += enc.encode(fr).len();
68    }
69    (t.elapsed().as_secs_f64(), bytes)
70}
More examples
Hide additional examples
examples/bit_accountant.rs (line 60)
44fn main() {
45    let path = std::env::args().nth(1).unwrap_or_else(|| "video-tests/clips/foreman_cif.y4m".into());
46    let n: usize = std::env::var("BA_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(24);
47    let qp: u8 = std::env::var("BA_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
48    let (w, h, frames) = read_y4m(&path, n);
49    let name = std::path::Path::new(&path).file_stem().unwrap().to_string_lossy().to_string();
50    for (pname, preset) in [("quality", Preset::Quality)] {
51        let mut cfg = EncoderConfig::new(w, h);
52        cfg.qp = qp;
53        cfg.gop_size = 60;
54        cfg.preset = preset;
55        rusty_h264_encoder::bitacct::reset();
56        rusty_h264_encoder::bitacct::set_enabled(true);
57        let mut enc = Encoder::new(cfg).unwrap();
58        let mut total = 0usize;
59        for f in &frames {
60            total += enc.encode(f).len();
61        }
62        rusty_h264_encoder::bitacct::add_actual_bytes(total);
63        rusty_h264_encoder::bitacct::set_enabled(false);
64        let mbs = (w.div_ceil(16) * h.div_ceil(16) * frames.len()) as u64;
65        rusty_h264_encoder::bitacct::dump(
66            &format!("{name} {pname} qp{qp} x{} ({} bytes)", frames.len(), total),
67            mbs,
68        );
69        if std::env::var_os("BA_MVDTAB").is_some() {
70            rusty_h264_encoder::bitacct::dump_mvd_table();
71        }
72    }
73}
examples/thread_bench.rs (line 45)
31fn main() {
32    let path = std::env::args().nth(1).unwrap();
33    let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
34    let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
35    let (w, h, frames) = read_y4m(&path, n);
36    for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
37        let mut cfg = EncoderConfig::new(w, h);
38        cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
39        // best-of-3 each arm
40        let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
41        for _ in 0..3 {
42            let mut enc = Encoder::new(cfg.clone()).unwrap();
43            let t = std::time::Instant::now();
44            let mut o = Vec::new();
45            for f in &frames { o.extend_from_slice(&enc.encode(f)); }
46            seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
47            seq_out = o;
48        }
49        let mut par_ms = f64::MAX; let mut par_out = Vec::new();
50        for _ in 0..3 {
51            let enc = Encoder::new(cfg.clone()).unwrap();
52            let t = std::time::Instant::now();
53            let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
54            par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
55            par_out = o;
56        }
57        assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
58        println!("{pn:<9} x{} gop{gop}: seq {seq_ms:.0} ms  parallel {par_ms:.0} ms  speedup {:.2}x  (byte-identical ✓)", frames.len(), seq_ms / par_ms);
59    }
60}
examples/me_snap_ab.rs (line 45)
24fn main() {
25    let a: Vec<String> = std::env::args().skip(1).collect();
26    let (w, h) = a[1].split_once('x').unwrap();
27    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
28    let frames = load(&a[0], w, h, std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(60));
29    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
30        "fast" => Preset::Fast, "quality" => Preset::Quality, _ => Preset::Balanced,
31    };
32    let arm_off: u32 = std::env::var("RS_ARM_OFF").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
33    let arm_on: u32 = std::env::var("RS_ARM_ON").ok().and_then(|v| v.parse().ok()).unwrap_or(3);
34    let run = |on: bool| {
35        let m = if on { arm_on } else { arm_off };
36        let mut cfg = EncoderConfig::new(w, h);
37        cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
38        cfg.gop_size = 30;
39        cfg.preset = preset;
40        cfg.tune_me_snap = m & 1 != 0;
41        cfg.tune_me_subpel_iter = m & 2 != 0;
42        let mut enc = Encoder::new(cfg).expect("enc");
43        let t = std::time::Instant::now();
44        let mut b = 0usize;
45        for f in &frames { b += enc.encode(f).len(); }
46        (t.elapsed().as_secs_f64(), b)
47    };
48    let mut best = [f64::MAX; 2];
49    let mut bytes = [0usize; 2];
50    for pass in 0..10 {
51        let arm = pass % 2;
52        let (t, b) = run(arm == 1);
53        if t < best[arm] { best[arm] = t; }
54        bytes[arm] = b;
55    }
56    let px = (w * h * frames.len()) as f64;
57    println!("arm {arm_off} -> {arm_on} — {} {w}x{h} {} frames {preset:?}", a[0], frames.len());
58    println!("  off : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[0]*1e3, px/best[0]/1e6, bytes[0]);
59    println!("  on  : {:>7.1} ms  {:>6.2} Mpx/s  {:>9} bytes", best[1]*1e3, px/best[1]/1e6, bytes[1]);
60    println!("  speed {:>6.3}x   size {:>+6.2}%", best[0]/best[1],
61             100.0*(bytes[1] as f64/bytes[0] as f64 - 1.0));
62}
examples/me_oracle.rs (line 49)
31fn main() {
32    let a: Vec<String> = std::env::args().skip(1).collect();
33    let (w, h) = a[1].split_once('x').unwrap();
34    let (w, h): (usize, usize) = (w.parse().unwrap(), h.parse().unwrap());
35    let nf: usize = std::env::var("RS_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(20);
36    let frames = load(&a[0], w, h, nf);
37
38    let preset = match std::env::var("RS_PRESET").unwrap_or_default().as_str() {
39        "fast" => Preset::Fast,
40        "quality" => Preset::Quality,
41        _ => Preset::Balanced,
42    };
43    let mut cfg = EncoderConfig::new(w, h);
44    cfg.qp = std::env::var("RS_QP").ok().and_then(|v| v.parse().ok()).unwrap_or(27);
45    cfg.gop_size = 30;
46    cfg.preset = preset;
47    let mut enc = Encoder::new(cfg).expect("encoder");
48    for f in &frames {
49        let _ = enc.encode(f);
50    }
51
52    let p: Vec<u64> = rusty_h264_encoder::ME_PROBE
53        .iter()
54        .map(|c| c.load(std::sync::atomic::Ordering::Relaxed))
55        .collect();
56    let (n, ours, oracle, worse, evals) = (p[0].max(1), p[1], p[2], p[3], p[4]);
57    let (oracle_sp, worse_sp) = (p[5], p[6]);
58    println!("ME oracle — {} ({w}x{h}, {} frames, {preset:?})\n", a[0], frames.len());
59    println!("  searches                {n}");
60    println!("  mean cost   ours        {:>10.1}", ours as f64 / n as f64);
61    println!("  mean cost   exhaustive  {:>10.1}", oracle as f64 / n as f64);
62    println!("  ---> we are {:>6.2}% above the achievable minimum",
63             100.0 * (ours as f64 - oracle as f64) / oracle as f64);
64    println!("  searches the oracle beat {:>9} ({:.1}%)", worse, 100.0 * worse as f64 / n as f64);
65    // the oracle's own 49x49 grid + 8 sub-pel probes are included in `evals`
66    println!("
67  + exhaustive SUB-PEL (all quarter-pel in +-3):");
68    println!("  mean cost   exhaustive  {:>10.1}", oracle_sp as f64 / n as f64);
69    println!("  ---> we are {:>6.2}% above the achievable minimum",
70             100.0 * (ours as f64 - oracle_sp as f64) / oracle_sp as f64);
71    println!("  searches it beat        {:>9} ({:.1}%)", worse_sp, 100.0 * worse_sp as f64 / n as f64);
72    let oracle_evals = 0;
73    println!("\n  cost() evals/search     {:>8.1}  (ours, oracle's {oracle_evals} excluded)",
74             evals as f64 / n as f64 - oracle_evals as f64);
75}
Source

pub fn try_encode(&mut self, frame: &YuvFrame) -> Result<Vec<u8>, EncodeError>

Fallible encode: validates the frame against the config.

Source

pub fn encode_all( &self, frames: &[YuvFrame], ) -> Result<Vec<Vec<u8>>, EncodeError>

Batch-encodes every frame, returning one Annex-B access unit per frame.

At constant QP the GOPs are independent — each begins with an IDR that resets the DPB, frame_num and POC, and SPS/PPS precede every IDR — so they are encoded in parallel across CPU cores and the result is byte-identical to calling encode frame-by-frame. With rate control enabled the per-frame QP depends on history, so this falls back to sequential encoding. Within a GOP, P-frames are inherently sequential (each predicts from the previous reconstruction); the parallelism is across GOPs, so it scales with the number of GOPs in the clip.

Examples found in repository?
examples/thread_bench.rs (line 53)
31fn main() {
32    let path = std::env::args().nth(1).unwrap();
33    let n: usize = std::env::var("TB_FRAMES").ok().and_then(|v| v.parse().ok()).unwrap_or(240);
34    let gop: u32 = std::env::var("TB_GOP").ok().and_then(|v| v.parse().ok()).unwrap_or(30);
35    let (w, h, frames) = read_y4m(&path, n);
36    for (pn, preset) in [("balanced", Preset::Balanced), ("quality", Preset::Quality)] {
37        let mut cfg = EncoderConfig::new(w, h);
38        cfg.qp = 27; cfg.gop_size = gop; cfg.preset = preset;
39        // best-of-3 each arm
40        let mut seq_ms = f64::MAX; let mut seq_out = Vec::new();
41        for _ in 0..3 {
42            let mut enc = Encoder::new(cfg.clone()).unwrap();
43            let t = std::time::Instant::now();
44            let mut o = Vec::new();
45            for f in &frames { o.extend_from_slice(&enc.encode(f)); }
46            seq_ms = seq_ms.min(t.elapsed().as_secs_f64() * 1e3);
47            seq_out = o;
48        }
49        let mut par_ms = f64::MAX; let mut par_out = Vec::new();
50        for _ in 0..3 {
51            let enc = Encoder::new(cfg.clone()).unwrap();
52            let t = std::time::Instant::now();
53            let o: Vec<u8> = enc.encode_all(&frames).unwrap().concat();
54            par_ms = par_ms.min(t.elapsed().as_secs_f64() * 1e3);
55            par_out = o;
56        }
57        assert_eq!(seq_out, par_out, "seq != parallel — compression WOULD be compromised");
58        println!("{pn:<9} x{} gop{gop}: seq {seq_ms:.0} ms  parallel {par_ms:.0} ms  speedup {:.2}x  (byte-identical ✓)", frames.len(), seq_ms / par_ms);
59    }
60}

Trait Implementations§

Source§

impl Debug for Encoder

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

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

Source§

fn into(self) -> U

Calls U::from(self).

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

Source§

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

Source§

type Error = Infallible

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

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

Performs the conversion.
Source§

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

Source§

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

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

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

Performs the conversion.