pub struct Encoder { /* private fields */ }Expand description
A Constrained Baseline H.264 encoder.
Implementations§
Source§impl Encoder
impl Encoder
Sourcepub fn new(cfg: EncoderConfig) -> Result<Self, EncodeError>
pub fn new(cfg: EncoderConfig) -> Result<Self, EncodeError>
Creates an encoder, validating that the configuration is within the implemented subset.
Examples found in repository?
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
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}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}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}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}Sourcepub fn config(&self) -> &EncoderConfig
pub fn config(&self) -> &EncoderConfig
The active configuration.
Sourcepub fn encode(&mut self, frame: &YuvFrame) -> Vec<u8> ⓘ
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?
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
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}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}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}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}Sourcepub fn try_encode(&mut self, frame: &YuvFrame) -> Result<Vec<u8>, EncodeError>
pub fn try_encode(&mut self, frame: &YuvFrame) -> Result<Vec<u8>, EncodeError>
Fallible encode: validates the frame against the config.
Sourcepub fn encode_all(
&self,
frames: &[YuvFrame],
) -> Result<Vec<Vec<u8>>, EncodeError>
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?
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}