1use std::time::Instant;
15
16use oxideav_av1::encoder::still::{encode_still_yuv, StillOptions};
17use oxideav_av1::encoder::yuv_frame::{ChromaFormat, YuvFrame};
18use oxideav_av1::Av1CodecConfig;
19use oxideav_avif::{encode_still_grid, EncodeOptions, StillChroma, StillImage};
20use oxideav_core::{CodecId, CodecParameters, ExecutionContext, Frame, Packet, TimeBase};
21
22fn plane(w: u32, h: u32, seed: u32) -> Vec<u16> {
23 (0..u64::from(w) * u64::from(h))
24 .map(|i| {
25 let x = (i % u64::from(w)) as f64;
26 let y = (i / u64::from(w)) as f64;
27 let s = f64::from(seed);
28 let v = 128.0
29 + 60.0 * ((x * 0.013 + s).sin() * (y * 0.011 - s).cos())
30 + 30.0 * ((x * 0.21 + y * 0.17).sin())
31 + ((i.wrapping_mul(2654435761) >> 29) & 7) as f64;
32 v.clamp(0.0, 255.0) as u16
33 })
34 .collect()
35}
36
37fn decode(bytes: &[u8], threads: usize) -> (Vec<u8>, f64) {
38 let params = CodecParameters::video(CodecId::new(oxideav_avif::CODEC_ID_STR));
39 let mut dec = oxideav_avif::make_decoder(¶ms).expect("decoder");
40 if threads > 1 {
41 dec.set_execution_context(&ExecutionContext::with_threads(threads));
42 }
43 let t = Instant::now();
44 dec.send_packet(&Packet::new(0, TimeBase::new(1, 1), bytes.to_vec()))
45 .expect("decode");
46 let frame = match dec.receive_frame().expect("frame") {
47 Frame::Video(v) => v,
48 other => panic!("unexpected {other:?}"),
49 };
50 let secs = t.elapsed().as_secs_f64();
51 let mut out = Vec::new();
52 for p in &frame.planes[..frame.image_plane_count()] {
53 out.extend_from_slice(&p.data);
54 }
55 (out, secs)
56}
57
58fn main() {
59 let mut args = std::env::args().skip(1);
60 let threads: usize = args.next().and_then(|s| s.parse().ok()).unwrap_or(8);
61 let width: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(4000);
62 let height: u32 = args.next().and_then(|s| s.parse().ok()).unwrap_or(3000);
63
64 let input = YuvFrame {
65 width,
66 height,
67 bit_depth: 8,
68 format: ChromaFormat::Yuv420,
69 y: plane(width, height, 1),
70 u: plane(width / 2, height / 2, 2),
71 v: plane(width / 2, height / 2, 3),
72 };
73 let t = Instant::now();
74 let coded = encode_still_yuv(&input, &StillOptions::new(120).threads(threads)).expect("encode");
75 let av1c = Av1CodecConfig::from_sequence_header(&coded.seq).to_bytes();
76 let file = oxideav_avif::encode_still_av1(
77 width,
78 height,
79 coded.temporal_unit_bytes,
80 av1c,
81 vec![8, 8, 8],
82 )
83 .expect("mux");
84 eprintln!(
85 "{width}x{height} 4:2:0 still: {} bytes, encoded in {:.2} s on {threads} threads",
86 file.len(),
87 t.elapsed().as_secs_f64()
88 );
89
90 let (serial, t1) = decode(&file, 1);
91 let (parallel, tn) = decode(&file, threads);
92 assert_eq!(serial, parallel, "byte-identical at any budget");
93 eprintln!(
94 "decode: serial {t1:.3} s, {threads} workers {tn:.3} s ({:.2}x), identical bytes",
95 t1 / tn
96 );
97
98 let registry_frame = Frame::Video(oxideav_core::frame::VideoFrame {
101 pts: Some(0),
102 planes: [
103 (&input.y, width),
104 (&input.u, width / 2),
105 (&input.v, width / 2),
106 ]
107 .iter()
108 .map(|(p, w)| oxideav_core::frame::VideoPlane {
109 stride: *w as usize,
110 data: p.iter().map(|&v| v as u8).collect(),
111 })
112 .collect(),
113 });
114 let mut p = CodecParameters::video(CodecId::new(oxideav_avif::CODEC_ID_STR));
115 p.width = Some(width);
116 p.height = Some(height);
117 p.pixel_format = Some(oxideav_core::PixelFormat::Yuv420P);
118 p.options = oxideav_core::CodecOptions::new().set("quality", "50");
119 let encode_registry = |budget: usize| -> (Vec<u8>, f64) {
120 let mut enc = oxideav_avif::make_encoder(&p).expect("encoder");
121 if budget > 1 {
122 enc.set_execution_context(&ExecutionContext::with_threads(budget));
123 }
124 let t = Instant::now();
125 enc.send_frame(®istry_frame).expect("send");
126 let pkt = enc.receive_packet().expect("packet");
127 (pkt.data, t.elapsed().as_secs_f64())
128 };
129 let (e1, te1) = encode_registry(1);
130 let (en, ten) = encode_registry(threads);
131 assert_eq!(e1, en, "registry encoder: byte-identical at any budget");
132 eprintln!(
133 "registry encode (quality 50): {} bytes, serial {te1:.2} s, {threads} workers {ten:.2} s ({:.2}x), identical bytes",
134 e1.len(),
135 te1 / ten
136 );
137
138 let t = Instant::now();
140 let still = StillImage::yuv(
141 width,
142 height,
143 8,
144 StillChroma::Yuv420,
145 input.y.clone(),
146 input.u.clone(),
147 input.v.clone(),
148 )
149 .expect("still");
150 let grid = encode_still_grid(&still, &EncodeOptions::new(120, 0, false), 4, 3).expect("grid");
151 eprintln!(
152 "{width}x{height} as a 4x3 grid: {} bytes, encoded in {:.2} s (serial encoder)",
153 grid.len(),
154 t.elapsed().as_secs_f64()
155 );
156 let (serial, t1) = decode(&grid, 1);
157 let (parallel, tn) = decode(&grid, threads);
158 assert_eq!(serial, parallel, "grid: byte-identical at any budget");
159 eprintln!(
160 "grid decode: serial {t1:.3} s, {threads} workers {tn:.3} s ({:.2}x), identical bytes",
161 t1 / tn
162 );
163}