1use std::ffi::{CStr, c_int, c_long, c_void};
26use std::ptr;
27
28use codec_sys::vpx::*;
29
30use super::codec::{
31 Codec, FRAME_ANCHOR, VIDEO_HEADER_LEN, frame_type_from_key, push_video_header, vp8_is_key,
32 vp9_is_key, vpx_level,
33};
34use super::reference::{Reference, ReferenceSlots, SlotPlan, SlotRefresh};
35use super::session::{Pending, Planes, Quality, RateSettings, encode_threads};
36use crate::RustCaptureSettings;
37
38const RATE_CEILING_KBPS: u32 = 100_000;
40
41const VP9_LAYERS: u32 = 2;
45const VP8_DEFAULT_MIN_LEVEL: u32 = 4;
47const CYCLIC_REFRESH_AQ: c_int = 3;
49const VP8_STATIC_THRESHOLD: c_int = 100;
54const VP8_TOKEN_PARTITIONS: c_int = VP8_EIGHT_TOKENPARTITION as c_int;
59const BAND_BITS_PER_MB: f64 = 1.0;
62
63pub fn encodes_444() -> bool {
67 (unsafe { vpx_codec_version() }) >= (1 << 16) | (13 << 8)
68}
69
70pub fn encodes_ten_bit() -> bool {
73 (unsafe { vpx_codec_get_caps(vpx_codec_vp9_cx()) })
74 & VPX_CODEC_CAP_HIGHBITDEPTH as vpx_codec_caps_t
75 != 0
76}
77
78pub struct VpxEncoder {
80 codec: Codec,
81 ctx: vpx_codec_ctx_t,
82 cfg: vpx_codec_enc_cfg_t,
83 planes: Planes,
84 threads: u32,
85 quality: Quality,
86 rate: RateSettings,
87 omit_headers: bool,
88 references: ReferenceSlots,
89 last_reference: Reference,
90 pending: Pending,
91 plan: SlotPlan,
93 held: Option<u32>,
95 held_band: Option<(f64, f64)>,
98 held_bytes: usize,
100 band_map: Vec<u8>,
102 last_quality: Option<u32>,
104 last_bytes: Option<usize>,
106 held_key: Option<(usize, u32)>,
109}
110
111unsafe impl Send for VpxEncoder {}
112
113impl Drop for VpxEncoder {
114 fn drop(&mut self) {
115 unsafe { vpx_codec_destroy(&mut self.ctx) };
116 }
117}
118
119fn error(ctx: &vpx_codec_ctx_t, what: &str) -> String {
122 unsafe {
123 let detail = vpx_codec_error_detail(ptr::from_ref(ctx).cast_mut());
124 let text = CStr::from_ptr(vpx_codec_err_to_string(ctx.err)).to_string_lossy();
125 if detail.is_null() {
126 format!("{what}: {text}")
127 } else {
128 format!(
129 "{what}: {text} ({})",
130 CStr::from_ptr(detail).to_string_lossy()
131 )
132 }
133 }
134}
135
136impl VpxEncoder {
137 pub fn new(settings: &RustCaptureSettings, codec: Codec, rgba: bool) -> Result<Self, String> {
139 if !matches!(codec, Codec::Vp8 | Codec::Vp9) {
140 return Err(format!("libvpx encodes no {}", codec.display()));
141 }
142 let _ = rgba;
143 let fullcolor = codec == Codec::Vp9 && settings.video_fullcolor && encodes_444();
144 let ten_bit = codec == Codec::Vp9 && settings.video_bit_depth >= 10 && encodes_ten_bit();
145 let threads = encode_threads() as u32;
146 let rate = RateSettings::new(settings);
147 let quality = Quality::new(codec.quantizer(settings.video_crf));
148 let iface = unsafe {
149 if codec == Codec::Vp8 {
150 vpx_codec_vp8_cx()
151 } else {
152 vpx_codec_vp9_cx()
153 }
154 };
155 let mut cfg: vpx_codec_enc_cfg_t = unsafe { std::mem::zeroed() };
156 if unsafe { vpx_codec_enc_config_default(iface, &mut cfg, 0) } != VPX_CODEC_OK {
157 return Err("libvpx refused its default configuration".into());
158 }
159 cfg.g_w = settings.width.max(1) as u32;
160 cfg.g_h = settings.height.max(1) as u32;
161 cfg.g_timebase = vpx_rational {
162 num: rate.fps.den as i32,
163 den: rate.fps.num as i32,
164 };
165 cfg.g_threads = threads.min(64);
166 cfg.g_lag_in_frames = 0;
167 cfg.g_pass = VPX_RC_ONE_PASS;
168 cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
169 cfg.g_profile = fullcolor as u32 + 2 * ten_bit as u32;
170 cfg.g_bit_depth = if ten_bit { VPX_BITS_10 } else { VPX_BITS_8 };
171 cfg.g_input_bit_depth = if ten_bit { 10 } else { 8 };
172 cfg.rc_dropframe_thresh = 0;
173 cfg.rc_2pass_vbr_bias_pct = 50;
174 cfg.rc_2pass_vbr_minsection_pct = 100;
175 cfg.rc_2pass_vbr_maxsection_pct = 100;
176 cfg.kf_max_dist = c_int::MAX as u32;
177 if codec == Codec::Vp9 {
178 cfg.ss_number_layers = 1;
179 cfg.ts_number_layers = VP9_LAYERS;
180 cfg.ts_rate_decimator[..VP9_LAYERS as usize].fill(1);
181 cfg.ts_periodicity = 1;
182 cfg.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_BYPASS as c_int;
183 }
184 let mut me = Self {
185 codec,
186 ctx: unsafe { std::mem::zeroed() },
187 cfg,
188 planes: Planes::new(
189 settings.width.max(1) as usize,
190 settings.height.max(1) as usize,
191 fullcolor,
192 if ten_bit { 10 } else { 8 },
193 ),
194 threads,
195 quality,
196 rate,
197 omit_headers: settings.omit_stripe_headers,
198 references: if codec == Codec::Vp9 {
199 ReferenceSlots::vp9()
200 } else {
201 ReferenceSlots::new()
202 },
203 last_reference: Reference::Untracked,
204 pending: Pending::default(),
205 plan: SlotPlan::KEY,
206 held: None,
207 held_band: None,
208 held_bytes: 0,
209 band_map: Vec::new(),
210 last_quality: None,
211 last_bytes: None,
212 held_key: None,
213 };
214 let q = me.quality.current;
215 me.program_rate(rate, q);
216 let res = unsafe {
217 vpx_codec_enc_init_ver(
218 &mut me.ctx,
219 iface,
220 &me.cfg,
221 if ten_bit {
222 VPX_CODEC_USE_HIGHBITDEPTH as vpx_codec_flags_t
223 } else {
224 0
225 },
226 VPX_ENCODER_ABI_VERSION as c_int,
227 )
228 };
229 if res != VPX_CODEC_OK {
230 return Err(error(&me.ctx, "libvpx refused the session"));
231 }
232 me.control(VP8E_SET_CPUUSED, if codec == Codec::Vp9 { 8 } else { 16 })?;
233 me.control(VP8E_SET_ENABLEAUTOALTREF, 0)?;
234 me.control(
235 VP8E_SET_STATIC_THRESHOLD,
236 if codec == Codec::Vp8 {
237 VP8_STATIC_THRESHOLD
238 } else {
239 0
240 },
241 )?;
242 me.control(VP8E_SET_MAX_INTRA_BITRATE_PCT, 0)?;
243 if codec == Codec::Vp8 {
244 me.control(VP8E_SET_NOISE_SENSITIVITY, 0)?;
245 me.control(VP8E_SET_TOKEN_PARTITIONS, VP8_TOKEN_PARTITIONS)?;
246 } else {
247 let tile_columns = (settings.width / 256).max(1).ilog2().min(6);
250 me.control(VP9E_SET_TILE_COLUMNS, tile_columns as c_int)?;
251 me.control(VP9E_SET_FRAME_PARALLEL_DECODING, 0)?;
252 me.control(VP9E_SET_COLOR_SPACE, VPX_CS_BT_709 as c_int)?;
253 me.control(VP9E_SET_COLOR_RANGE, VPX_CR_STUDIO_RANGE as c_int)?;
254 me.control(VP9E_SET_TARGET_LEVEL, 255)?;
255 me.control(VP9E_SET_ROW_MT, 1)?;
256 me.control(VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN as c_int)?;
257 me.control(VP9E_SET_SVC, 1)?;
258 me.program_layer()?;
259 me.program_refresh()?;
260 }
261 Ok(me)
262 }
263
264 fn control(&mut self, id: vp8e_enc_control_id, value: c_int) -> Result<(), String> {
265 if unsafe { vpx_codec_control_(&mut self.ctx, id as c_int, value) } != VPX_CODEC_OK {
266 return Err(error(&self.ctx, &format!("libvpx refused control {id}")));
267 }
268 Ok(())
269 }
270
271 fn program_refresh(&mut self) -> Result<(), String> {
277 if self.codec != Codec::Vp9 {
278 return Ok(());
279 }
280 let mode = if self.rate.cbr { CYCLIC_REFRESH_AQ } else { 0 };
281 self.control(VP9E_SET_AQ_MODE, mode)
282 }
283
284 fn program_layer(&mut self) -> Result<(), String> {
287 let mut layer: vpx_svc_parameters = unsafe { std::mem::zeroed() };
288 for i in 0..VP9_LAYERS as usize {
289 layer.max_quantizers[i] = self.cfg.rc_max_quantizer as c_int;
290 layer.min_quantizers[i] = self.cfg.rc_min_quantizer as c_int;
291 layer.speed_per_layer[i] = 8;
292 }
293 layer.scaling_factor_num[0] = 1;
294 layer.scaling_factor_den[0] = 1;
295 layer.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_BYPASS as c_int;
296 let res = unsafe {
297 vpx_codec_control_(
298 &mut self.ctx,
299 VP9E_SET_SVC_PARAMETERS as c_int,
300 &mut layer as *mut vpx_svc_parameters as *mut c_void,
301 )
302 };
303 if res != VPX_CODEC_OK {
304 return Err(error(&self.ctx, "libvpx refused the layer parameters"));
305 }
306 Ok(())
307 }
308
309 fn program_rate(&mut self, rate: RateSettings, q: u32) {
313 let cfg = &mut self.cfg;
314 cfg.g_timebase = vpx_rational {
315 num: rate.fps.den as i32,
316 den: rate.fps.num as i32,
317 };
318 cfg.rc_end_usage = VPX_CBR;
319 if rate.cbr {
320 let kbps = (rate.bps() / 1000).clamp(1, u32::MAX as u64) as u32;
321 cfg.rc_target_bitrate = kbps;
322 let (lo, hi) = (
323 self.codec.quantizer_bound(rate.min_qp),
324 self.codec.quantizer_bound(rate.max_qp),
325 );
326 cfg.rc_min_quantizer = if lo > 0 {
327 vpx_level(self.codec, lo)
328 } else if self.codec == Codec::Vp8 {
329 VP8_DEFAULT_MIN_LEVEL
330 } else {
331 0
332 };
333 cfg.rc_max_quantizer = if hi > 0 {
334 vpx_level(self.codec, hi)
335 } else {
336 63
337 };
338 let ms = (rate.vbv() as u64 * 1000 / rate.bps().max(1)) as u32;
339 cfg.rc_buf_sz = ms;
340 cfg.rc_buf_initial_sz = ms;
341 cfg.rc_buf_optimal_sz = ms * 5 / 6;
342 } else {
343 cfg.rc_target_bitrate = RATE_CEILING_KBPS;
344 cfg.rc_min_quantizer = vpx_level(self.codec, q);
345 cfg.rc_max_quantizer = vpx_level(self.codec, q);
346 }
347 cfg.ss_target_bitrate[0] = cfg.rc_target_bitrate;
348 cfg.ts_target_bitrate[..VP9_LAYERS as usize].fill(cfg.rc_target_bitrate);
349 cfg.layer_target_bitrate[..VP9_LAYERS as usize].fill(cfg.rc_target_bitrate);
350 }
351
352 fn reconfigure(&mut self) -> Result<(), String> {
354 if unsafe { vpx_codec_enc_config_set(&mut self.ctx, &self.cfg) } != VPX_CODEC_OK {
355 return Err(error(&self.ctx, "libvpx refused the new configuration"));
356 }
357 if self.codec == Codec::Vp9 {
358 self.program_layer()?;
359 }
360 Ok(())
361 }
362
363 pub fn codec(&self) -> Codec {
364 self.codec
365 }
366
367 pub fn library(&self) -> &'static str {
369 "libvpx"
370 }
371
372 pub fn is_fullcolor(&self) -> bool {
374 self.planes.i444
375 }
376
377 pub fn bit_depth(&self) -> u32 {
379 self.planes.bit_depth
380 }
381
382 pub fn is_full_range(&self) -> bool {
385 false
386 }
387
388 pub fn last_reference(&self) -> Reference {
390 self.last_reference
391 }
392
393 pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
396 self.references.invalidate(frame_id);
397 true
398 }
399
400 pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
403 self.references.acknowledge(frame_id, held);
404 }
405
406 pub fn last_quality(&self) -> Option<u32> {
408 self.last_quality
409 }
410
411 pub fn last_size(&self) -> Option<usize> {
414 self.last_bytes.filter(|_| !self.holds_quantizer())
415 }
416
417 pub fn holds_quantizer(&self) -> bool {
424 !self.rate.cbr || self.codec == Codec::Vp8
425 }
426
427 pub fn hold_quantizer(&mut self, crf: u32, band: Option<(f64, f64)>) {
437 self.held = Some(crf);
438 self.held_band = band.filter(|_| self.band_size().is_some());
439 }
440
441 pub fn band_size(&self) -> Option<usize> {
450 let blocks = f64::from(self.cfg.g_w.div_ceil(16) * self.cfg.g_h.div_ceil(16));
451 let fps = f64::from(self.rate.fps.num) / f64::from(self.rate.fps.den.max(1));
452 let budget_bits = self.rate.bps() as f64 / fps.max(1.0);
453 (self.codec == Codec::Vp8 && budget_bits >= BAND_BITS_PER_MB * blocks)
454 .then_some(self.held_bytes)
455 }
456
457 fn band_roi(&mut self, band: Option<(f64, f64)>) -> Result<(), String> {
466 let cols = self.cfg.g_w.div_ceil(16);
467 let rows = self.cfg.g_h.div_ceil(16);
468 let mut roi: vpx_roi_map_t = unsafe { std::mem::zeroed() };
469 roi.rows = rows;
470 roi.cols = cols;
471 if let Some((from, to)) = band {
472 let blocks = (rows * cols) as usize;
473 let first = ((from.clamp(0.0, 1.0) * blocks as f64).floor() as usize).min(blocks);
474 let last = ((to.clamp(0.0, 1.0) * blocks as f64).ceil() as usize).clamp(first, blocks);
475 self.band_map.clear();
476 self.band_map.resize(blocks, 0);
477 self.band_map[first..last].fill(1);
478 roi.roi_map = self.band_map.as_mut_ptr();
479 roi.delta_q[0] = 63;
480 roi.delta_lf[0] = -63;
482 roi.static_threshold[0] = u32::MAX >> 1;
483 roi.static_threshold[1] = VP8_STATIC_THRESHOLD as u32;
484 }
485 let res = unsafe {
486 vpx_codec_control_(
487 &mut self.ctx,
488 VP8E_SET_ROI_MAP as c_int,
489 &mut roi as *mut vpx_roi_map_t as *mut c_void,
490 )
491 };
492 if res != VPX_CODEC_OK {
493 return Err(error(&self.ctx, "libvpx refused the band's region map"));
494 }
495 Ok(())
496 }
497
498 fn pin_quantizer(&mut self, crf: u32) -> Result<(), String> {
500 let level = vpx_level(self.codec, self.codec.quantizer(crf as i32));
501 self.cfg.rc_min_quantizer = level;
502 self.cfg.rc_max_quantizer = level;
503 self.reconfigure()
504 }
505
506 pub fn reconfigure_rate(&mut self, settings: &RustCaptureSettings) -> Result<(), String> {
508 let Some(rate) = self.rate.changed(settings) else {
509 return Ok(());
510 };
511 self.rate = rate;
512 self.program_rate(rate, self.quality.current);
513 self.reconfigure()?;
514 self.program_refresh()
515 }
516
517 pub fn encode_host(
520 &mut self,
521 pixels: &[u8],
522 stride: usize,
523 rgba: bool,
524 frame_number: u64,
525 crf: u32,
526 force_idr: bool,
527 ) -> Result<Vec<u8>, String> {
528 if !self.rate.cbr
529 && let Some(q) = self.quality.update(self.codec.quantizer(crf as i32))
530 {
531 self.program_rate(self.rate, q);
532 self.reconfigure()?;
533 }
534 let capped = force_idr && self.rate.cbr;
535 let cap = (self.rate.bps() as f64 / 8.0 * super::HELD_KEY_BUDGET_S) as usize;
536 let held = self.held.take().map(|crf| {
537 if capped {
538 super::held_key_start(crf, self.held_key, cap)
539 } else {
540 crf
541 }
542 });
543 if let Some(crf) = held {
544 self.pin_quantizer(crf)?;
545 }
546 let band = self
548 .held_band
549 .take()
550 .filter(|_| held.is_some() && !force_idr);
551 if band.is_some() {
552 self.band_roi(band)?;
553 }
554 let quality = self.last_quality;
555 let mut result = self.encode_frame(pixels, stride, rgba, frame_number, force_idr);
556 if band.is_some() {
557 self.band_roi(None)?;
558 }
559 let mut coded_at = held;
560 let retry = match (held, &result) {
561 (Some(crf), Ok(coded)) if capped => super::held_key_retry(crf, coded.len(), cap),
562 _ => None,
563 };
564 if let Some(coarser) = retry {
565 self.pin_quantizer(coarser)?;
566 result = self.encode_frame(pixels, stride, rgba, frame_number, true);
567 coded_at = Some(coarser);
568 }
569 if let (true, Some(crf), Ok(coded)) = (capped, coded_at, &result) {
570 self.held_key = Some((coded.len(), crf));
571 }
572 if held.is_some() {
573 if let Ok(coded) = &result {
574 self.held_bytes = coded.len();
575 }
576 self.last_quality = quality;
577 self.program_rate(self.rate, self.quality.current);
578 self.reconfigure()?;
579 } else if let Ok(coded) = &result {
580 self.last_bytes = Some(coded.len());
581 }
582 result
583 }
584
585 fn encode_frame(
587 &mut self,
588 pixels: &[u8],
589 stride: usize,
590 rgba: bool,
591 frame_number: u64,
592 force_idr: bool,
593 ) -> Result<Vec<u8>, String> {
594 let bt601 = self.codec == Codec::Vp8;
595 self.planes
596 .convert(pixels, stride, rgba, false, bt601, self.threads as usize)?;
597
598 let key = force_idr || !self.references.has_reference();
599 let pts = self.references.next_pts();
600 let mut flags: c_long = if key { VPX_EFLAG_FORCE_KF as c_long } else { 0 };
601 self.plan = self.references.plan(key);
602 match self.codec {
603 Codec::Vp9 => {
604 let mut refs: vpx_svc_ref_frame_config = unsafe { std::mem::zeroed() };
605 refs.update_buffer_slot[0] = self.plan.refresh.0 as c_int;
606 if !key {
607 let slot = self.plan.predict_from.trailing_zeros() as c_int;
608 refs.lst_fb_idx[0] = slot;
609 refs.gld_fb_idx[0] = slot;
610 refs.alt_fb_idx[0] = slot;
611 refs.reference_last[0] = 1;
612 }
613 let res = unsafe {
614 vpx_codec_control_(
615 &mut self.ctx,
616 VP9E_SET_SVC_REF_FRAME_CONFIG as c_int,
617 &mut refs as *mut vpx_svc_ref_frame_config as *mut c_void,
618 )
619 };
620 if res != VPX_CODEC_OK {
621 return Err(error(
622 &self.ctx,
623 "libvpx refused the reference configuration",
624 ));
625 }
626 }
627 _ => {
628 flags |= VP8_EFLAG_NO_UPD_ENTROPY as c_long;
629 if !key {
630 for (buffer, no_ref, no_upd, force) in [
631 (
632 SlotRefresh::LAST,
633 VP8_EFLAG_NO_REF_LAST,
634 VP8_EFLAG_NO_UPD_LAST,
635 0,
636 ),
637 (
638 SlotRefresh::GOLDEN,
639 VP8_EFLAG_NO_REF_GF,
640 VP8_EFLAG_NO_UPD_GF,
641 VP8_EFLAG_FORCE_GF,
642 ),
643 (
644 SlotRefresh::ALTREF,
645 VP8_EFLAG_NO_REF_ARF,
646 VP8_EFLAG_NO_UPD_ARF,
647 VP8_EFLAG_FORCE_ARF,
648 ),
649 ] {
650 if self.plan.predict_from != buffer {
651 flags |= no_ref as c_long;
652 }
653 if self.plan.refresh.refreshes(buffer) {
654 flags |= force as c_long;
655 } else {
656 flags |= no_upd as c_long;
657 }
658 }
659 }
660 }
661 }
662
663 let mut img: vpx_image = unsafe { std::mem::zeroed() };
664 let fmt = match (self.planes.i444, self.planes.bit_depth > 8) {
665 (true, true) => VPX_IMG_FMT_I44416,
666 (true, false) => VPX_IMG_FMT_I444,
667 (false, true) => VPX_IMG_FMT_I42016,
668 (false, false) => VPX_IMG_FMT_I420,
669 };
670 let [y, u, v] = self.planes.pointers();
671 unsafe {
672 vpx_img_wrap(
673 &mut img,
674 fmt,
675 self.planes.width as u32,
676 self.planes.height as u32,
677 1,
678 y,
679 );
680 }
681 let bytes = self.planes.sample_bytes();
682 img.planes[0] = y;
683 img.planes[1] = u;
684 img.planes[2] = v;
685 img.stride[0] = (self.planes.width * bytes) as c_int;
686 img.stride[1] = (self.planes.chroma_width() * bytes) as c_int;
687 img.stride[2] = (self.planes.chroma_width() * bytes) as c_int;
688 img.bit_depth = self.planes.bit_depth;
689 img.range = VPX_CR_STUDIO_RANGE;
690 self.pending.push(pts, frame_number as u16);
691 let res = unsafe {
692 vpx_codec_encode(
693 &mut self.ctx,
694 &img,
695 pts as i64,
696 1,
697 flags,
698 VPX_DL_REALTIME as u64,
699 )
700 };
701 if res != VPX_CODEC_OK {
702 self.pending.undo();
703 return Err(error(&self.ctx, "libvpx refused the frame"));
704 }
705
706 let mut output = Vec::new();
707 let mut iter: vpx_codec_iter_t = ptr::null_mut();
708 loop {
709 let pkt = unsafe { vpx_codec_get_cx_data(&mut self.ctx, &mut iter) };
710 if pkt.is_null() {
711 break;
712 }
713 let pkt = unsafe { &*pkt };
714 if pkt.kind != VPX_CODEC_CX_FRAME_PKT {
715 continue;
716 }
717 let frame = unsafe { pkt.data.frame };
718 let bytes = unsafe { std::slice::from_raw_parts(frame.buf as *const u8, frame.sz) };
719 let is_key = if self.codec == Codec::Vp8 {
720 vp8_is_key(bytes)
721 } else {
722 vp9_is_key(bytes)
723 };
724 let id = self
725 .pending
726 .take(frame.pts as u64)
727 .unwrap_or(frame_number as u16);
728 let plan = if is_key {
729 self.references.plan(true)
730 } else {
731 self.plan
732 };
733 let anchor = if self.references.shared_anchor(plan) {
734 FRAME_ANCHOR
735 } else {
736 0
737 };
738 self.last_reference = self.references.record(id, plan);
739 if !self.omit_headers {
740 output.reserve(VIDEO_HEADER_LEN + bytes.len());
741 push_video_header(
742 &mut output,
743 self.codec,
744 frame_type_from_key(is_key) | anchor,
745 id,
746 0,
747 self.planes.width as u16,
748 self.planes.height as u16,
749 self.last_reference,
750 );
751 }
752 output.extend_from_slice(bytes);
753 }
754 if !output.is_empty() {
755 let mut q: c_int = 0;
756 let res = unsafe {
757 vpx_codec_control_(
758 &mut self.ctx,
759 VP8E_GET_LAST_QUANTIZER as c_int,
760 &mut q as *mut c_int,
761 )
762 };
763 self.last_quality =
764 (res == VPX_CODEC_OK).then(|| self.codec.quality_index(q.max(0) as u32));
765 }
766 Ok(output)
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773 use crate::encoders::codec::{FRAME_DELTA, FRAME_KEY, parse_video_type};
774 use crate::webcam::decode::{Decoder as _, VideoDecoder};
775
776 const W: usize = 320;
777 const H: usize = 240;
778
779 fn settings(codec: Codec) -> RustCaptureSettings {
780 RustCaptureSettings {
781 width: W as i32,
782 height: H as i32,
783 target_fps: 30.0,
784 codec,
785 video_crf: 25,
786 use_cpu: true,
787 ..Default::default()
788 }
789 }
790
791 fn frame(t: usize) -> Vec<u8> {
793 let mut f = vec![0u8; W * H * 4];
794 let (bx, by) = ((t * 9) % (W - 40), (t * 5) % (H - 30));
795 for y in 0..H {
796 for x in 0..W {
797 let i = (y * W + x) * 4;
798 let g = ((x * 255) / W) as u8;
799 let cell = (x / 8 + y / 12) % 3 == 0 && x % 8 < 6 && y % 12 < 9;
800 let (b, gr, r) = if x >= bx && x < bx + 40 && y >= by && y < by + 30 {
801 (40, 220, 250)
802 } else if cell {
803 (30, 30, 30)
804 } else {
805 (g, 200 - g / 2, 120)
806 };
807 f[i..i + 4].copy_from_slice(&[b, gr, r, 255]);
808 }
809 }
810 f
811 }
812
813 fn luma_distance(
814 a: &crate::webcam::convert::I420View<'_>,
815 b: &crate::webcam::convert::I420View<'_>,
816 ) -> f64 {
817 a.y.chunks(a.y_stride)
818 .zip(b.y.chunks(b.y_stride))
819 .take(a.height)
820 .flat_map(|(ra, rb)| {
821 ra[..a.width]
822 .iter()
823 .zip(&rb[..a.width])
824 .map(|(&x, &y)| (x as f64 - y as f64).abs())
825 })
826 .sum::<f64>()
827 / (a.width * a.height) as f64
828 }
829
830 #[test]
836 fn the_time_base_is_one_frame_of_the_rate() {
837 for codec in [Codec::Vp8, Codec::Vp9] {
838 for (num, den) in [(60000i32, 1001i32), (120000, 1001), (144000, 1001), (60, 1)] {
839 let fps = num as f64 / den as f64;
840 let mut enc = VpxEncoder::new(
841 &RustCaptureSettings {
842 target_fps: fps,
843 ..settings(codec)
844 },
845 codec,
846 false,
847 )
848 .expect("session");
849 assert_eq!(
850 (enc.cfg.g_timebase.num, enc.cfg.g_timebase.den),
851 (den, num),
852 "{codec:?}"
853 );
854 enc.reconfigure_rate(&RustCaptureSettings {
855 target_fps: 30.0,
856 ..settings(codec)
857 })
858 .expect("30 fps");
859 assert_eq!(
860 (enc.cfg.g_timebase.num, enc.cfg.g_timebase.den),
861 (1, 30),
862 "{codec:?}"
863 );
864 }
865 }
866 }
867
868 #[test]
869 fn a_lost_frame_is_predicted_past() {
870 for codec in [Codec::Vp8, Codec::Vp9] {
871 let s = settings(codec);
872 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
873 let mut frames = Vec::new();
874 for t in 0..8u64 {
875 let out = enc
876 .encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
877 .expect("encode");
878 assert_eq!(
879 parse_video_type(out[1]).map(|(_, k)| k),
880 Some(if t == 0 { FRAME_KEY } else { FRAME_DELTA }),
881 "{codec:?} {t}"
882 );
883 assert_eq!(
884 enc.last_reference(),
885 if t == 0 {
886 Reference::None
887 } else {
888 Reference::Frame(t as u16 - 1)
889 },
890 "{codec:?} {t}"
891 );
892 frames.push(out);
893 }
894 assert!(enc.invalidate_reference(5));
897 let out = enc
898 .encode_host(&frame(8), W * 4, false, 8, 25, false)
899 .expect("encode");
900 let anchor = if codec == Codec::Vp9 { 4 } else { 0 };
901 assert_eq!(enc.last_reference(), Reference::Frame(anchor), "{codec:?}");
902 assert_eq!(
903 parse_video_type(out[1]).map(|(_, k)| k),
904 Some(FRAME_DELTA),
905 "{codec:?}"
906 );
907 frames.push(out);
908 frames.push(
909 enc.encode_host(&frame(9), W * 4, false, 9, 25, false)
910 .expect("encode"),
911 );
912 assert_eq!(enc.last_reference(), Reference::Frame(8), "{codec:?}");
913 let (mut whole, mut lossy) = (
914 VideoDecoder::new(codec).unwrap(),
915 VideoDecoder::new(codec).unwrap(),
916 );
917 for (i, f) in frames.iter().enumerate() {
918 assert!(
919 whole.decode(&f[VIDEO_HEADER_LEN..]).expect("decode"),
920 "{codec:?} frame {i}"
921 );
922 if !(5..8).contains(&i) {
923 assert!(
924 lossy
925 .decode(&f[VIDEO_HEADER_LEN..])
926 .expect("decode without 5-7"),
927 "{codec:?} frame {i}"
928 );
929 }
930 }
931 let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
932 assert!(
933 apart == 0.0,
934 "{codec:?}: the decoder that lost frames 5-7 shows frame 9 {apart:.2} off the one that saw them"
935 );
936 }
937 }
938
939 #[test]
942 fn a_loss_ten_frames_deep_is_predicted_past() {
943 for codec in [Codec::Vp8, Codec::Vp9] {
944 let s = settings(codec);
945 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
946 let mut frames = Vec::new();
947 for t in 0..30u64 {
948 frames.push(
949 enc.encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
950 .expect("encode"),
951 );
952 }
953 assert!(enc.invalidate_reference(20));
954 let out = enc
955 .encode_host(&frame(30), W * 4, false, 30, 25, false)
956 .expect("encode");
957 assert_eq!(
958 parse_video_type(out[1]).map(|(_, k)| k),
959 Some(FRAME_DELTA),
960 "{codec:?}"
961 );
962 let anchor = if codec == Codec::Vp9 { 16 } else { 12 };
963 assert_eq!(enc.last_reference(), Reference::Frame(anchor), "{codec:?}");
964 frames.push(out);
965 frames.push(
966 enc.encode_host(&frame(31), W * 4, false, 31, 25, false)
967 .expect("encode"),
968 );
969 assert_eq!(enc.last_reference(), Reference::Frame(30), "{codec:?}");
970 let (mut whole, mut lossy) = (
971 VideoDecoder::new(codec).unwrap(),
972 VideoDecoder::new(codec).unwrap(),
973 );
974 for (i, f) in frames.iter().enumerate() {
975 assert!(
976 whole.decode(&f[VIDEO_HEADER_LEN..]).expect("decode"),
977 "{codec:?} frame {i}"
978 );
979 if !(20..30).contains(&i) {
980 assert!(
981 lossy
982 .decode(&f[VIDEO_HEADER_LEN..])
983 .expect("decode without 20-29"),
984 "{codec:?} frame {i}"
985 );
986 }
987 }
988 let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
989 assert!(
990 apart == 0.0,
991 "{codec:?}: the decoder that lost frames 20-29 shows frame 31 {apart:.2} off the one that saw them"
992 );
993 }
994 }
995
996 #[test]
998 fn a_loss_past_the_window_costs_a_key_frame() {
999 for codec in [Codec::Vp8, Codec::Vp9] {
1000 let s = settings(codec);
1001 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1002 for t in 0..40u64 {
1003 enc.encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
1004 .expect("encode");
1005 }
1006 assert!(enc.invalidate_reference(1));
1007 let out = enc
1008 .encode_host(&frame(40), W * 4, false, 40, 25, false)
1009 .expect("encode");
1010 assert_eq!(
1011 parse_video_type(out[1]).map(|(_, k)| k),
1012 Some(FRAME_KEY),
1013 "{codec:?}"
1014 );
1015 assert_eq!(enc.last_reference(), Reference::None);
1016 enc.encode_host(&frame(41), W * 4, false, 41, 25, false)
1017 .expect("encode");
1018 assert_eq!(enc.last_reference(), Reference::Frame(40), "{codec:?}");
1019 }
1020 }
1021
1022 #[test]
1025 fn a_vp9_header_names_its_quantizer_index() {
1026 use crate::encoders::codec::vp9_base_q_idx;
1027 let depths: &[i32] = if encodes_ten_bit() { &[8, 10] } else { &[8] };
1028 for (&depth, fullcolor) in depths.iter().flat_map(|d| [(d, false), (d, true)]) {
1029 let mut s = settings(Codec::Vp9);
1030 s.video_bit_depth = depth;
1031 s.video_fullcolor = fullcolor;
1032 let mut enc = VpxEncoder::new(&s, Codec::Vp9, fullcolor).expect("session");
1033 for (n, crf, key) in [(0, 40, true), (1, 15, false), (2, 28, false)] {
1034 let coded = enc
1035 .encode_host(&frame(n), W * 4, false, n as u64, crf, key)
1036 .unwrap();
1037 let read = vp9_base_q_idx(&coded[VIDEO_HEADER_LEN..]);
1038 assert_eq!(
1039 read.map(|q| Codec::Vp9.quality_index(q)),
1040 enc.last_quality(),
1041 "{depth}-bit fullcolor {fullcolor} frame {n}: {read:?}"
1042 );
1043 }
1044 }
1045 }
1046
1047 fn luma(dec: &VideoDecoder) -> Vec<u8> {
1050 let f = dec.frame().expect("a decoded picture");
1051 f.y.chunks(f.y_stride)
1052 .take(H)
1053 .flat_map(|row| row[..W].iter().copied())
1054 .collect()
1055 }
1056
1057 fn rows_distance(a: &[u8], b: &[u8], rows: std::ops::Range<usize>) -> f64 {
1059 let n = rows.len() * W;
1060 a[rows.start * W..rows.end * W]
1061 .iter()
1062 .zip(&b[rows.start * W..rows.end * W])
1063 .map(|(&x, &y)| (x as f64 - y as f64).abs())
1064 .sum::<f64>()
1065 / n as f64
1066 }
1067
1068 #[test]
1073 fn a_vp8_band_codes_its_macroblocks_alone() {
1074 let mut s = settings(Codec::Vp8);
1075 s.video_cbr_mode = true;
1076 s.video_bitrate_kbps = 30;
1077 let still = frame(0);
1078 let run = |band: Option<(f64, f64)>| {
1079 let mut enc = VpxEncoder::new(&s, Codec::Vp8, false).expect("session");
1080 let mut dec = VideoDecoder::new(Codec::Vp8).unwrap();
1081 for t in 0..6u64 {
1082 let f = enc
1083 .encode_host(&still, W * 4, false, t, 25, t == 0)
1084 .unwrap();
1085 assert!(dec.decode(&f[VIDEO_HEADER_LEN..]).unwrap());
1086 }
1087 let before = luma(&dec);
1088 assert_eq!(enc.band_size(), Some(0), "no frame held yet");
1089 enc.hold_quantizer(5, band);
1090 let held = enc.encode_host(&still, W * 4, false, 6, 25, false).unwrap();
1091 assert_eq!(parse_video_type(held[1]).map(|(_, k)| k), Some(FRAME_DELTA));
1092 assert!(dec.decode(&held[VIDEO_HEADER_LEN..]).unwrap());
1093 assert_eq!(enc.band_size(), Some(held.len()));
1094 let next = enc.encode_host(&still, W * 4, false, 7, 25, false).unwrap();
1096 assert!(dec.decode(&next[VIDEO_HEADER_LEN..]).unwrap());
1097 (held.len(), before, luma(&dec))
1098 };
1099 let (whole, before, refreshed) = run(None);
1100 let (banded, band_before, band_after) = run(Some((0.0, 0.25)));
1102 assert!(
1103 banded * 2 < whole,
1104 "a quarter of the picture cost {banded} bytes against {whole} for the whole"
1105 );
1106 assert_eq!(
1107 before, band_before,
1108 "both runs reach the same picture before the hold"
1109 );
1110 let changed = rows_distance(&before, &refreshed, 0..48);
1111 assert!(
1112 changed > 0.5,
1113 "the held quantizer refines the picture ({changed:.2})"
1114 );
1115 let band = rows_distance(&band_after, &refreshed, 0..48);
1116 assert!(
1117 band < changed / 4.0,
1118 "the band decodes as the whole refresh's ({band:.2})"
1119 );
1120 let rest = rows_distance(&band_after, &band_before, 64..H);
1121 assert!(rest < 0.01, "the rest is left as it was ({rest:.3})");
1122 s.video_bitrate_kbps = 5;
1125 let starved = VpxEncoder::new(&s, Codec::Vp8, false).expect("session");
1126 assert_eq!(starved.band_size(), None);
1127 }
1128
1129 #[test]
1130 fn quality_and_rate_move_without_a_key_frame() {
1131 for codec in [Codec::Vp8, Codec::Vp9] {
1132 let mut s = settings(codec);
1133 s.video_crf = 40;
1134 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1135 let coarse = enc
1136 .encode_host(&frame(0), W * 4, false, 0, 40, true)
1137 .unwrap();
1138 let fine = enc
1139 .encode_host(&frame(1), W * 4, false, 1, 15, false)
1140 .unwrap();
1141 assert_eq!(enc.quality.current, codec.quantizer(15));
1142 assert_eq!(
1143 parse_video_type(fine[1]).map(|(_, k)| k),
1144 Some(FRAME_DELTA),
1145 "{codec:?}"
1146 );
1147 let again = enc
1148 .encode_host(&frame(2), W * 4, false, 2, 15, false)
1149 .unwrap();
1150 assert!(
1151 again.len() + fine.len() > coarse.len() / 2,
1152 "{codec:?}: the finer quantizer spends more"
1153 );
1154 s.target_fps = 15.0;
1155 enc.reconfigure_rate(&s).expect("rate");
1156 let after = enc
1157 .encode_host(&frame(3), W * 4, false, 3, 15, false)
1158 .unwrap();
1159 assert_eq!(
1160 parse_video_type(after[1]).map(|(_, k)| k),
1161 Some(FRAME_DELTA),
1162 "{codec:?}"
1163 );
1164 let mut dec = VideoDecoder::new(codec).unwrap();
1165 for f in [&coarse, &fine, &again, &after] {
1166 assert!(dec.decode(&f[VIDEO_HEADER_LEN..]).unwrap(), "{codec:?}");
1167 }
1168 }
1169 }
1170}