1use std::ffi::{CStr, c_int, c_long, c_uint, c_void};
42use std::ptr;
43
44use codec_sys::vpx::*;
45
46use super::codec::{
47 Codec, FRAME_ANCHOR, VIDEO_HEADER_LEN, frame_type_from_key, push_video_header, vp8_is_key,
48 vp9_is_key, vpx_level,
49};
50use super::reference::{Reference, ReferenceSlots, SlotPlan, SlotRefresh};
51use super::session::{PeakBucket, Pending, Planes, Quality, RateSettings, encode_threads};
52use crate::CaptureSettings;
53
54const RATE_CEILING_KBPS: u32 = 100_000;
56
57const VP9_LAYERS: u32 = 2;
61const VP8_DEFAULT_MIN_LEVEL: u32 = 4;
63const CYCLIC_REFRESH_AQ: c_int = 3;
65const VP8_STATIC_THRESHOLD: c_int = 100;
70const VP8_TOKEN_PARTITIONS: c_int = VP8_EIGHT_TOKENPARTITION as c_int;
75const BAND_BITS_PER_MB: f64 = 1.0;
78const VP8_UNFILTERED_QINDEX: u32 = 16;
88
89pub const REFINE_FINER: u32 = 0;
98
99pub fn encodes_444() -> bool {
103 (unsafe { vpx_codec_version() }) >= (1 << 16) | (13 << 8)
104}
105
106pub fn encodes_ten_bit() -> bool {
109 (unsafe { vpx_codec_get_caps(vpx_codec_vp9_cx()) })
110 & vpx_codec_caps_t::from(VPX_CODEC_CAP_HIGHBITDEPTH)
111 != 0
112}
113
114pub struct VpxEncoder {
116 codec: Codec,
117 ctx: vpx_codec_ctx_t,
118 cfg: vpx_codec_enc_cfg_t,
119 planes: Planes,
120 threads: u32,
121 quality: Quality,
122 rate: RateSettings,
123 omit_headers: bool,
124 references: ReferenceSlots,
125 last_reference: Reference,
126 pending: Pending,
127 plan: SlotPlan,
129 held: Option<u32>,
131 held_band: Option<(f64, f64)>,
134 held_bytes: usize,
136 band_map: Vec<u8>,
138 refinement: Option<crate::ledger::Refinement>,
140 active_map: Vec<u8>,
143 map_armed: bool,
146 last_refresh: SlotRefresh,
149 last_quality: Option<u32>,
151 peak: Option<PeakBucket>,
154 last_capped: bool,
155 switch_refused: bool,
157 open_buf: (u32, u32, u32),
160 last_bytes: Option<usize>,
162 held_key: Option<(usize, u32)>,
165 cap: Option<usize>,
167 raised: bool,
169}
170
171unsafe impl Send for VpxEncoder {}
172
173impl Drop for VpxEncoder {
174 fn drop(&mut self) {
175 unsafe { vpx_codec_destroy(&mut self.ctx) };
176 }
177}
178
179fn peak_bps(rate: RateSettings) -> u32 {
183 rate.bps().min(u64::from(u32::MAX)) as u32
184}
185
186fn error(ctx: &vpx_codec_ctx_t, what: &str) -> String {
187 unsafe {
188 let detail = vpx_codec_error_detail(ptr::from_ref(ctx).cast_mut());
189 let text = CStr::from_ptr(vpx_codec_err_to_string(ctx.err)).to_string_lossy();
190 if detail.is_null() {
191 format!("{what}: {text}")
192 } else {
193 format!(
194 "{what}: {text} ({})",
195 CStr::from_ptr(detail).to_string_lossy()
196 )
197 }
198 }
199}
200
201impl VpxEncoder {
202 pub fn new(settings: &CaptureSettings, codec: Codec, rgba: bool) -> Result<Self, String> {
204 if !matches!(codec, Codec::Vp8 | Codec::Vp9) {
205 return Err(format!("libvpx encodes no {}", codec.display()));
206 }
207 let _ = rgba;
208 let fullcolor = codec == Codec::Vp9 && settings.video_fullcolor && encodes_444();
209 let ten_bit = codec == Codec::Vp9 && settings.video_bit_depth >= 10 && encodes_ten_bit();
210 let threads = encode_threads(settings) as u32;
211 let rate = RateSettings::new(settings);
212 let quality = Quality::new(codec.quantizer(settings.video_crf));
213 let iface = unsafe {
214 if codec == Codec::Vp8 {
215 vpx_codec_vp8_cx()
216 } else {
217 vpx_codec_vp9_cx()
218 }
219 };
220 let mut cfg: vpx_codec_enc_cfg_t = unsafe { std::mem::zeroed() };
221 if unsafe { vpx_codec_enc_config_default(iface, &mut cfg, 0) } != VPX_CODEC_OK {
222 return Err("libvpx refused its default configuration".into());
223 }
224 cfg.g_w = settings.width.max(1) as u32;
225 cfg.g_h = settings.height.max(1) as u32;
226 cfg.g_timebase = vpx_rational {
227 num: rate.fps.den as i32,
228 den: rate.fps.num as i32,
229 };
230 cfg.g_threads = threads.min(64);
231 cfg.g_lag_in_frames = 0;
232 cfg.g_pass = VPX_RC_ONE_PASS;
233 cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
234 cfg.g_profile = u32::from(fullcolor) + 2 * u32::from(ten_bit);
235 cfg.g_bit_depth = if ten_bit { VPX_BITS_10 } else { VPX_BITS_8 };
236 cfg.g_input_bit_depth = if ten_bit { 10 } else { 8 };
237 cfg.rc_dropframe_thresh = 0;
238 cfg.rc_2pass_vbr_bias_pct = 50;
239 cfg.rc_2pass_vbr_minsection_pct = 100;
240 cfg.rc_2pass_vbr_maxsection_pct = 100;
241 cfg.kf_max_dist = c_int::MAX as u32;
242 if codec == Codec::Vp9 {
243 cfg.ss_number_layers = 1;
244 cfg.ts_number_layers = VP9_LAYERS;
245 cfg.ts_rate_decimator[..VP9_LAYERS as usize].fill(1);
246 cfg.ts_periodicity = 1;
247 cfg.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_BYPASS as c_int;
248 }
249 let mut me = Self {
250 codec,
251 ctx: unsafe { std::mem::zeroed() },
252 cfg,
253 planes: Planes::new(
254 settings.width.max(1) as usize,
255 settings.height.max(1) as usize,
256 fullcolor,
257 if ten_bit { 10 } else { 8 },
258 ),
259 threads,
260 quality,
261 rate,
262 omit_headers: settings.omit_stripe_headers,
263 references: if codec == Codec::Vp9 {
264 ReferenceSlots::vp9()
265 } else {
266 ReferenceSlots::new()
267 },
268 last_reference: Reference::Untracked,
269 pending: Pending::default(),
270 plan: SlotPlan::KEY,
271 held: None,
272 held_band: None,
273 held_bytes: 0,
274 band_map: Vec::new(),
275 refinement: None,
276 active_map: Vec::new(),
277 map_armed: false,
278 last_refresh: SlotRefresh(0),
279 last_quality: None,
280 peak: rate
281 .capped
282 .then(|| PeakBucket::new(peak_bps(rate), rate.vbv(), rate.fps.fps())),
283 open_buf: (cfg.rc_buf_sz, cfg.rc_buf_initial_sz, cfg.rc_buf_optimal_sz),
284 last_capped: false,
285 switch_refused: false,
286 last_bytes: None,
287 cap: None,
288 raised: false,
289 held_key: None,
290 };
291 let q = me.quality.current;
292 me.program_rate(me.programmed(), q);
293 let res = unsafe {
294 vpx_codec_enc_init_ver(
295 &mut me.ctx,
296 iface,
297 &me.cfg,
298 if ten_bit {
299 vpx_codec_flags_t::from(VPX_CODEC_USE_HIGHBITDEPTH)
300 } else {
301 0
302 },
303 VPX_ENCODER_ABI_VERSION as c_int,
304 )
305 };
306 if res != VPX_CODEC_OK {
307 return Err(error(&me.ctx, "libvpx refused the session"));
308 }
309 me.control(VP8E_SET_CPUUSED, if codec == Codec::Vp9 { 8 } else { 16 })?;
310 me.control(VP8E_SET_ENABLEAUTOALTREF, 0)?;
311 me.control(
312 VP8E_SET_STATIC_THRESHOLD,
313 if codec == Codec::Vp8 {
314 VP8_STATIC_THRESHOLD
315 } else {
316 0
317 },
318 )?;
319 me.control(VP8E_SET_MAX_INTRA_BITRATE_PCT, 0)?;
320 if codec == Codec::Vp8 {
321 me.control(VP8E_SET_NOISE_SENSITIVITY, 0)?;
322 me.control(VP8E_SET_TOKEN_PARTITIONS, VP8_TOKEN_PARTITIONS)?;
323 } else {
324 let tile_columns = (settings.width / 256).max(1).ilog2().min(6);
327 me.control(VP9E_SET_TILE_COLUMNS, tile_columns as c_int)?;
328 me.control(VP9E_SET_FRAME_PARALLEL_DECODING, 0)?;
329 me.control(VP9E_SET_COLOR_SPACE, VPX_CS_BT_709 as c_int)?;
330 me.control(VP9E_SET_COLOR_RANGE, VPX_CR_STUDIO_RANGE as c_int)?;
331 me.control(VP9E_SET_TARGET_LEVEL, 255)?;
332 me.control(VP9E_SET_ROW_MT, 1)?;
333 me.control(VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN as c_int)?;
334 me.control(VP9E_SET_SVC, 1)?;
335 me.program_layer()?;
336 me.program_refresh()?;
337 }
338 Ok(me)
339 }
340
341 fn control(&mut self, id: vp8e_enc_control_id, value: c_int) -> Result<(), String> {
342 if unsafe { vpx_codec_control_(&mut self.ctx, id as c_int, value) } != VPX_CODEC_OK {
343 return Err(error(&self.ctx, &format!("libvpx refused control {id}")));
344 }
345 Ok(())
346 }
347
348 fn program_refresh(&mut self) -> Result<(), String> {
354 if self.codec != Codec::Vp9 {
355 return Ok(());
356 }
357 let mode = if self.rate.cbr { CYCLIC_REFRESH_AQ } else { 0 };
358 self.control(VP9E_SET_AQ_MODE, mode)
359 }
360
361 fn program_layer(&mut self) -> Result<(), String> {
364 let mut layer: vpx_svc_parameters = unsafe { std::mem::zeroed() };
365 for i in 0..VP9_LAYERS as usize {
366 layer.max_quantizers[i] = self.cfg.rc_max_quantizer as c_int;
367 layer.min_quantizers[i] = self.cfg.rc_min_quantizer as c_int;
368 layer.speed_per_layer[i] = 8;
369 }
370 layer.scaling_factor_num[0] = 1;
371 layer.scaling_factor_den[0] = 1;
372 layer.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_BYPASS as c_int;
373 let res = unsafe {
374 vpx_codec_control_(
375 &mut self.ctx,
376 VP9E_SET_SVC_PARAMETERS as c_int,
377 &mut layer as *mut vpx_svc_parameters as *mut c_void,
378 )
379 };
380 if res != VPX_CODEC_OK {
381 return Err(error(&self.ctx, "libvpx refused the layer parameters"));
382 }
383 Ok(())
384 }
385
386 fn program_rate(&mut self, rate: RateSettings, q: u32) {
391 let cfg = &mut self.cfg;
392 cfg.g_timebase = vpx_rational {
393 num: rate.fps.den as i32,
394 den: rate.fps.num as i32,
395 };
396 cfg.rc_end_usage = VPX_CBR;
397 if rate.cbr || rate.capped {
398 let kbps = (rate.bps() / 1000).clamp(1, u64::from(u32::MAX)) as u32;
399 let ms = (u64::from(rate.vbv()) * 1000 / rate.bps().max(1)) as u32;
400 cfg.rc_target_bitrate = kbps;
401 cfg.rc_buf_sz = ms;
402 cfg.rc_buf_initial_sz = ms;
403 cfg.rc_buf_optimal_sz = ms * 5 / 6;
404 }
405 if rate.capped {
406 cfg.rc_min_quantizer = vpx_level(self.codec, q);
407 cfg.rc_max_quantizer = 63;
408 } else if rate.cbr {
409 let (lo, hi) = (
410 self.codec.quantizer_bound(rate.min_qp),
411 self.codec.quantizer_bound(rate.max_qp),
412 );
413 cfg.rc_min_quantizer = if lo > 0 {
414 vpx_level(self.codec, lo)
415 } else if self.codec == Codec::Vp8 {
416 VP8_DEFAULT_MIN_LEVEL
417 } else {
418 0
419 };
420 cfg.rc_max_quantizer = if hi > 0 {
421 vpx_level(self.codec, hi)
422 } else {
423 63
424 };
425 } else {
426 cfg.rc_target_bitrate = RATE_CEILING_KBPS;
427 (cfg.rc_buf_sz, cfg.rc_buf_initial_sz, cfg.rc_buf_optimal_sz) = self.open_buf;
428 cfg.rc_min_quantizer = vpx_level(self.codec, q);
429 cfg.rc_max_quantizer = vpx_level(self.codec, q);
430 }
431 cfg.ss_target_bitrate[0] = cfg.rc_target_bitrate;
432 cfg.ts_target_bitrate[..VP9_LAYERS as usize].fill(cfg.rc_target_bitrate);
433 cfg.layer_target_bitrate[..VP9_LAYERS as usize].fill(cfg.rc_target_bitrate);
434 }
435
436 fn reconfigure(&mut self) -> Result<(), String> {
438 if unsafe { vpx_codec_enc_config_set(&mut self.ctx, &self.cfg) } != VPX_CODEC_OK {
439 return Err(error(&self.ctx, "libvpx refused the new configuration"));
440 }
441 if self.codec == Codec::Vp9 {
442 self.program_layer()?;
443 }
444 Ok(())
445 }
446
447 pub fn codec(&self) -> Codec {
448 self.codec
449 }
450
451 pub fn library(&self) -> &'static str {
453 "libvpx"
454 }
455
456 pub fn is_fullcolor(&self) -> bool {
458 self.planes.i444
459 }
460
461 pub fn bit_depth(&self) -> u32 {
463 self.planes.bit_depth
464 }
465
466 pub fn is_full_range(&self) -> bool {
469 false
470 }
471
472 pub fn last_reference(&self) -> Reference {
474 self.last_reference
475 }
476
477 pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
480 self.references.invalidate(frame_id);
481 true
482 }
483
484 pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
487 self.references.acknowledge(frame_id, held);
488 }
489
490 pub fn last_quality(&self) -> Option<u32> {
492 self.last_quality
493 }
494
495 pub fn capped(&self) -> bool {
497 self.rate.capped
498 }
499
500 pub fn peak_bound(&self) -> bool {
502 self.peak.is_some_and(|p| p.binding)
503 }
504
505 pub fn coded_capped(&self) -> bool {
507 self.last_capped
508 }
509
510 pub fn idle(&mut self) {
513 if self
514 .peak
515 .as_mut()
516 .and_then(|p| p.note(0.0, Some(true)))
517 .is_some()
518 {
519 self.switch_peak();
520 }
521 }
522
523 fn programmed(&self) -> RateSettings {
526 if self.peak.is_some_and(|p| !p.binding) {
527 self.rate.uncapped()
528 } else {
529 self.rate
530 }
531 }
532
533 pub fn follows_cap(&self) -> bool {
537 self.codec == Codec::Vp9 && self.rate.cbr
538 }
539
540 pub fn cap(&mut self, bytes: usize) {
546 if self.follows_cap() {
547 self.cap = Some(bytes);
548 }
549 }
550
551 pub fn last_size(&self) -> Option<usize> {
554 self.last_bytes.filter(|_| !self.holds_quantizer())
555 }
556
557 pub fn holds_quantizer(&self) -> bool {
564 !self.rate.cbr || self.codec == Codec::Vp8
565 }
566
567 pub fn hold_quantizer(&mut self, crf: u32, band: Option<(f64, f64)>) {
577 self.held = Some(crf);
578 self.held_band = band.filter(|_| self.band_size().is_some());
579 }
580
581 pub fn band_size(&self) -> Option<usize> {
590 let blocks = f64::from(self.cfg.g_w.div_ceil(16) * self.cfg.g_h.div_ceil(16));
591 let fps = f64::from(self.rate.fps.num) / f64::from(self.rate.fps.den.max(1));
592 let budget_bits = self.rate.bps() as f64 / fps.max(1.0);
593 (self.codec == Codec::Vp8 && budget_bits >= BAND_BITS_PER_MB * blocks)
594 .then_some(self.held_bytes)
595 }
596
597 fn band_roi(&mut self, band: Option<(f64, f64)>, unfiltered: bool) -> Result<(), String> {
607 let cols = self.cfg.g_w.div_ceil(16);
608 let rows = self.cfg.g_h.div_ceil(16);
609 let mut roi: vpx_roi_map_t = unsafe { std::mem::zeroed() };
610 roi.rows = rows;
611 roi.cols = cols;
612 if let Some((from, to)) = band {
613 let blocks = (rows * cols) as usize;
614 let first = ((from.clamp(0.0, 1.0) * blocks as f64).floor() as usize).min(blocks);
615 let last = ((to.clamp(0.0, 1.0) * blocks as f64).ceil() as usize).clamp(first, blocks);
616 self.band_map.clear();
617 self.band_map.resize(blocks, 0);
618 self.band_map[first..last].fill(1);
619 roi.roi_map = self.band_map.as_mut_ptr();
620 roi.delta_q[0] = 63;
621 roi.delta_lf[0] = -63;
623 if unfiltered {
624 roi.delta_lf[1] = -63;
625 }
626 roi.static_threshold[0] = u32::MAX >> 1;
627 roi.static_threshold[1] = VP8_STATIC_THRESHOLD as u32;
628 }
629 let res = unsafe {
630 vpx_codec_control_(
631 &mut self.ctx,
632 VP8E_SET_ROI_MAP as c_int,
633 &mut roi as *mut vpx_roi_map_t as *mut c_void,
634 )
635 };
636 if res != VPX_CODEC_OK {
637 return Err(error(&self.ctx, "libvpx refused the band's region map"));
638 }
639 Ok(())
640 }
641
642 pub fn refine_blocks(&mut self, refinement: &crate::ledger::Refinement) {
645 self.refinement = Some(refinement.clone());
646 }
647
648 fn refine_roi(&mut self, refinement: Option<&crate::ledger::Refinement>) -> Result<(), String> {
656 let unit = if self.codec == Codec::Vp8 { 16 } else { 8 };
657 let cols = self.cfg.g_w.div_ceil(unit);
658 let rows = self.cfg.g_h.div_ceil(unit);
659 let mut roi: vpx_roi_map_t = unsafe { std::mem::zeroed() };
660 roi.rows = rows;
661 roi.cols = cols;
662 roi.ref_frame = [-1; 8];
663 if let Some(r) = refinement {
664 let step = |index: i32| self.codec.quantizer(index) as i32;
665 let delta = (step(r.target) - step(r.from)).clamp(-63, 0);
666 let u = unit as i32;
667 self.band_map.clear();
668 self.band_map.extend((0..rows).flat_map(|row| {
669 (0..cols).map(move |col| {
670 u8::from(r.plan.refines_area(col as i32 * u, row as i32 * u, u, u))
671 })
672 }));
673 roi.roi_map = self.band_map.as_mut_ptr();
674 roi.delta_q[1] = delta;
675 roi.static_threshold[0] = VP8_STATIC_THRESHOLD as u32;
676 }
677 let control = if self.codec == Codec::Vp8 {
678 VP8E_SET_ROI_MAP
679 } else {
680 VP9E_SET_ROI_MAP
681 };
682 let res = unsafe {
683 vpx_codec_control_(
684 &mut self.ctx,
685 control as c_int,
686 &mut roi as *mut vpx_roi_map_t as *mut c_void,
687 )
688 };
689 if res != VPX_CODEC_OK {
690 return Err(error(
691 &self.ctx,
692 "libvpx refused the refinement's region map",
693 ));
694 }
695 Ok(())
696 }
697
698 fn pin_quantizer(&mut self, crf: u32) -> Result<(), String> {
700 let level = vpx_level(self.codec, self.codec.quantizer(crf as i32));
701 self.cfg.rc_min_quantizer = level;
702 self.cfg.rc_max_quantizer = level;
703 self.reconfigure()
704 }
705
706 pub fn reconfigure_rate(&mut self, settings: &CaptureSettings) -> Result<(), String> {
708 let Some(rate) = self.rate.changed(settings) else {
709 return Ok(());
710 };
711 let fps_moved = rate.fps != self.rate.fps;
712 self.rate = rate;
713 if let Some(peak) = self.peak.as_mut() {
714 peak.follow(peak_bps(rate), rate.vbv(), rate.fps.fps());
715 if !peak.binding && !fps_moved {
716 return Ok(());
717 }
718 }
719 self.program_rate(self.programmed(), self.quality.current);
720 self.reconfigure()?;
721 self.program_refresh()
722 }
723
724 pub fn encode_host(
727 &mut self,
728 pixels: &[u8],
729 stride: usize,
730 rgba: bool,
731 frame_number: u64,
732 crf: u32,
733 force_idr: bool,
734 ) -> Result<Vec<u8>, String> {
735 if !self.rate.cbr
736 && let Some(q) = self.quality.update(self.codec.quantizer(crf as i32))
737 {
738 self.program_rate(self.programmed(), q);
739 self.reconfigure()?;
740 }
741 if let Some(bytes) = self.cap.take() {
742 let kbps = ((bytes as f64 * 8.0 / crate::pipeline::CLEANUP_FRAME_S / 1000.0) as u32)
743 .max((self.rate.bps() / 1000) as u32);
744 if kbps != self.cfg.rc_target_bitrate {
745 self.cfg.rc_target_bitrate = kbps;
746 self.cfg.ss_target_bitrate[0] = kbps;
747 self.cfg.ts_target_bitrate[..VP9_LAYERS as usize].fill(kbps);
748 self.cfg.layer_target_bitrate[..VP9_LAYERS as usize].fill(kbps);
749 self.reconfigure()?;
750 }
751 self.raised = true;
752 } else if self.raised {
753 self.program_rate(self.programmed(), self.quality.current);
754 self.reconfigure()?;
755 self.raised = false;
756 }
757 let capped = force_idr && self.rate.cbr;
758 let cap = (self.rate.bps() as f64 / 8.0 * super::HELD_KEY_BUDGET_S) as usize;
759 if force_idr && self.held_band.is_some() {
763 self.held = None;
764 }
765 let held = self.held.take().map(|crf| {
766 if capped {
767 super::held_key_start(crf, self.held_key, cap)
768 } else {
769 crf
770 }
771 });
772 if let Some(crf) = held {
773 self.pin_quantizer(crf)?;
774 }
775 let band = self
776 .held_band
777 .take()
778 .filter(|_| held.is_some() && !force_idr);
779 if band.is_some() {
780 let unfiltered =
781 held.is_some_and(|crf| self.codec.quantizer(crf as i32) <= VP8_UNFILTERED_QINDEX);
782 self.band_roi(band, unfiltered)?;
783 }
784 let refinement = self
785 .refinement
786 .take()
787 .filter(|_| held.is_none() && !force_idr);
788 if let Some(r) = &refinement {
789 self.refine_roi(Some(r))?;
790 }
791 let quality = self.last_quality;
792 let mut result = self.encode_frame(pixels, stride, rgba, frame_number, force_idr);
793 if band.is_some() {
794 self.band_roi(None, false)?;
795 }
796 if refinement.is_some() {
797 self.refine_roi(None)?;
798 }
799 let mut coded_at = held;
800 let retry = match (held, &result) {
801 (Some(crf), Ok(coded)) if capped => super::held_key_retry(crf, coded.len(), cap),
802 _ => None,
803 };
804 if let Some(coarser) = retry {
805 self.pin_quantizer(coarser)?;
806 result = self.encode_frame(pixels, stride, rgba, frame_number, true);
807 coded_at = Some(coarser);
808 }
809 if let (true, Some(crf), Ok(coded)) = (capped, coded_at, &result) {
810 self.held_key = Some((coded.len(), crf));
811 }
812 if held.is_some() {
813 if let Ok(coded) = &result {
814 self.held_bytes = coded.len();
815 }
816 self.last_quality = quality;
817 self.program_rate(self.programmed(), self.quality.current);
818 self.reconfigure()?;
819 } else if let Ok(coded) = &result {
820 self.last_bytes = Some(coded.len());
821 }
822 self.last_capped = self.peak_bound();
823 let at_floor = held
824 .is_none()
825 .then(|| self.last_quality.is_some_and(|q| q <= crf));
826 let switch = match (self.peak.as_mut(), &result) {
827 (Some(peak), Ok(coded)) => peak.note(coded.len() as f64 * 8.0, at_floor),
828 _ => None,
829 };
830 if switch.is_some() {
831 self.switch_peak();
832 }
833 result
834 }
835
836 fn switch_peak(&mut self) {
841 self.program_rate(self.programmed(), self.quality.current);
842 let Err(e) = self.reconfigure() else {
843 return;
844 };
845 if let Some(peak) = self.peak.as_mut() {
846 peak.binding = !peak.binding;
847 }
848 self.program_rate(self.programmed(), self.quality.current);
849 if !std::mem::replace(&mut self.switch_refused, true) {
850 eprintln!("[libvpx] Switching the peak of a capped constant quality refused: {e}");
851 }
852 }
853
854 pub fn constant_blocks(&mut self, constant: &[bool]) {
861 let blocks = self.planes.width.div_ceil(16) * self.planes.height.div_ceil(16);
862 self.active_map.clear();
863 if constant.len() == blocks && constant.contains(&true) {
864 self.active_map
865 .extend(constant.iter().map(|&c| u8::from(!c)));
866 }
867 }
868
869 fn arm_active_map(&mut self, takes: bool) -> Result<(), String> {
873 let arm = takes && !self.active_map.is_empty();
874 if !arm && !self.map_armed {
875 return Ok(());
876 }
877 let mut map: vpx_active_map_t = unsafe { std::mem::zeroed() };
878 map.rows = self.planes.height.div_ceil(16) as c_uint;
879 map.cols = self.planes.width.div_ceil(16) as c_uint;
880 map.active_map = if arm {
881 self.active_map.as_mut_ptr()
882 } else {
883 ptr::null_mut()
884 };
885 let res = unsafe {
886 vpx_codec_control_(
887 &mut self.ctx,
888 VP8E_SET_ACTIVEMAP as c_int,
889 &mut map as *mut vpx_active_map_t as *mut c_void,
890 )
891 };
892 if res != VPX_CODEC_OK {
893 return Err(error(&self.ctx, "libvpx refused the active map"));
894 }
895 self.map_armed = arm;
896 Ok(())
897 }
898
899 fn encode_frame(
901 &mut self,
902 pixels: &[u8],
903 stride: usize,
904 rgba: bool,
905 frame_number: u64,
906 force_idr: bool,
907 ) -> Result<Vec<u8>, String> {
908 let bt601 = self.codec == Codec::Vp8;
909 self.planes
910 .convert(pixels, stride, rgba, false, bt601, self.threads as usize)?;
911
912 let key = force_idr || !self.references.has_reference();
913 let pts = self.references.next_pts();
914 let mut flags: c_long = if key {
915 c_long::from(VPX_EFLAG_FORCE_KF)
916 } else {
917 0
918 };
919 self.plan = self.references.plan(key);
920 self.arm_active_map(!key && self.last_refresh.refreshes(self.plan.predict_from))?;
921 self.active_map.clear();
922 match self.codec {
923 Codec::Vp9 => {
924 let mut refs: vpx_svc_ref_frame_config = unsafe { std::mem::zeroed() };
925 refs.update_buffer_slot[0] = c_int::from(self.plan.refresh.0);
926 if !key {
927 let slot = self.plan.predict_from.trailing_zeros() as c_int;
928 refs.lst_fb_idx[0] = slot;
929 refs.gld_fb_idx[0] = slot;
930 refs.alt_fb_idx[0] = slot;
931 refs.reference_last[0] = 1;
932 }
933 let res = unsafe {
934 vpx_codec_control_(
935 &mut self.ctx,
936 VP9E_SET_SVC_REF_FRAME_CONFIG as c_int,
937 &mut refs as *mut vpx_svc_ref_frame_config as *mut c_void,
938 )
939 };
940 if res != VPX_CODEC_OK {
941 return Err(error(
942 &self.ctx,
943 "libvpx refused the reference configuration",
944 ));
945 }
946 }
947 _ => {
948 flags |= c_long::from(VP8_EFLAG_NO_UPD_ENTROPY);
949 if !key {
950 for (buffer, no_ref, no_upd, force) in [
951 (
952 SlotRefresh::LAST,
953 VP8_EFLAG_NO_REF_LAST,
954 VP8_EFLAG_NO_UPD_LAST,
955 0,
956 ),
957 (
958 SlotRefresh::GOLDEN,
959 VP8_EFLAG_NO_REF_GF,
960 VP8_EFLAG_NO_UPD_GF,
961 VP8_EFLAG_FORCE_GF,
962 ),
963 (
964 SlotRefresh::ALTREF,
965 VP8_EFLAG_NO_REF_ARF,
966 VP8_EFLAG_NO_UPD_ARF,
967 VP8_EFLAG_FORCE_ARF,
968 ),
969 ] {
970 if self.plan.predict_from != buffer {
971 flags |= c_long::from(no_ref);
972 }
973 if self.plan.refresh.refreshes(buffer) {
974 flags |= c_long::from(force);
975 } else {
976 flags |= c_long::from(no_upd);
977 }
978 }
979 }
980 }
981 }
982
983 let mut img: vpx_image = unsafe { std::mem::zeroed() };
984 let fmt = match (self.planes.i444, self.planes.bit_depth > 8) {
985 (true, true) => VPX_IMG_FMT_I44416,
986 (true, false) => VPX_IMG_FMT_I444,
987 (false, true) => VPX_IMG_FMT_I42016,
988 (false, false) => VPX_IMG_FMT_I420,
989 };
990 let [y, u, v] = self.planes.pointers();
991 unsafe {
992 vpx_img_wrap(
993 &mut img,
994 fmt,
995 self.planes.width as u32,
996 self.planes.height as u32,
997 1,
998 y,
999 );
1000 }
1001 let bytes = self.planes.sample_bytes();
1002 img.planes[0] = y;
1003 img.planes[1] = u;
1004 img.planes[2] = v;
1005 img.stride[0] = (self.planes.width * bytes) as c_int;
1006 img.stride[1] = (self.planes.chroma_width() * bytes) as c_int;
1007 img.stride[2] = (self.planes.chroma_width() * bytes) as c_int;
1008 img.bit_depth = self.planes.bit_depth;
1009 img.range = VPX_CR_STUDIO_RANGE;
1010 self.pending.push(pts, frame_number as u16);
1011 let res = unsafe {
1012 vpx_codec_encode(
1013 &mut self.ctx,
1014 &img,
1015 pts as i64,
1016 1,
1017 flags,
1018 u64::from(VPX_DL_REALTIME),
1019 )
1020 };
1021 if res != VPX_CODEC_OK {
1022 self.pending.undo();
1023 return Err(error(&self.ctx, "libvpx refused the frame"));
1024 }
1025
1026 let mut output = Vec::new();
1027 let mut iter: vpx_codec_iter_t = ptr::null_mut();
1028 loop {
1029 let pkt = unsafe { vpx_codec_get_cx_data(&mut self.ctx, &mut iter) };
1030 if pkt.is_null() {
1031 break;
1032 }
1033 let pkt = unsafe { &*pkt };
1034 if pkt.kind != VPX_CODEC_CX_FRAME_PKT {
1035 continue;
1036 }
1037 let frame = unsafe { pkt.data.frame };
1038 let bytes = unsafe { std::slice::from_raw_parts(frame.buf as *const u8, frame.sz) };
1039 let is_key = if self.codec == Codec::Vp8 {
1040 vp8_is_key(bytes)
1041 } else {
1042 vp9_is_key(bytes)
1043 };
1044 let id = self
1045 .pending
1046 .take(frame.pts as u64)
1047 .unwrap_or(frame_number as u16);
1048 let plan = if is_key {
1049 self.references.plan(true)
1050 } else {
1051 self.plan
1052 };
1053 let anchor = if self.references.shared_anchor(plan) {
1054 FRAME_ANCHOR
1055 } else {
1056 0
1057 };
1058 self.last_reference = self.references.record(id, plan);
1059 self.last_refresh = plan.refresh;
1060 if !self.omit_headers {
1061 output.reserve(VIDEO_HEADER_LEN + bytes.len());
1062 push_video_header(
1063 &mut output,
1064 self.codec,
1065 frame_type_from_key(is_key) | anchor,
1066 id,
1067 0,
1068 self.planes.width as u16,
1069 self.planes.height as u16,
1070 self.last_reference,
1071 );
1072 }
1073 output.extend_from_slice(bytes);
1074 }
1075 if !output.is_empty() {
1076 let mut q: c_int = 0;
1077 let res = unsafe {
1078 vpx_codec_control_(
1079 &mut self.ctx,
1080 VP8E_GET_LAST_QUANTIZER as c_int,
1081 &mut q as *mut c_int,
1082 )
1083 };
1084 self.last_quality =
1085 (res == VPX_CODEC_OK).then(|| self.codec.quality_index(q.max(0) as u32));
1086 }
1087 Ok(output)
1088 }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093 use super::*;
1094 use crate::encoders::codec::{FRAME_DELTA, FRAME_KEY, parse_video_type};
1095 use crate::webcam::decode::{Decoder as _, VideoDecoder};
1096
1097 const W: usize = 320;
1098 const H: usize = 240;
1099
1100 fn settings(codec: Codec) -> CaptureSettings {
1101 CaptureSettings {
1102 width: W as i32,
1103 height: H as i32,
1104 target_fps: 30.0,
1105 codec,
1106 video_crf: 25,
1107 use_cpu: true,
1108 ..Default::default()
1109 }
1110 }
1111
1112 fn frame(t: usize) -> Vec<u8> {
1114 let mut f = vec![0u8; W * H * 4];
1115 let (bx, by) = ((t * 9) % (W - 40), (t * 5) % (H - 30));
1116 for y in 0..H {
1117 for x in 0..W {
1118 let i = (y * W + x) * 4;
1119 let g = ((x * 255) / W) as u8;
1120 let cell = (x / 8 + y / 12) % 3 == 0 && x % 8 < 6 && y % 12 < 9;
1121 let (b, gr, r) = if x >= bx && x < bx + 40 && y >= by && y < by + 30 {
1122 (40, 220, 250)
1123 } else if cell {
1124 (30, 30, 30)
1125 } else {
1126 (g, 200 - g / 2, 120)
1127 };
1128 f[i..i + 4].copy_from_slice(&[b, gr, r, 255]);
1129 }
1130 }
1131 f
1132 }
1133
1134 fn luma_distance(
1135 a: &crate::webcam::convert::I420View<'_>,
1136 b: &crate::webcam::convert::I420View<'_>,
1137 ) -> f64 {
1138 a.y.chunks(a.y_stride)
1139 .zip(b.y.chunks(b.y_stride))
1140 .take(a.height)
1141 .flat_map(|(ra, rb)| {
1142 ra[..a.width]
1143 .iter()
1144 .zip(&rb[..a.width])
1145 .map(|(&x, &y)| (f64::from(x) - f64::from(y)).abs())
1146 })
1147 .sum::<f64>()
1148 / (a.width * a.height) as f64
1149 }
1150
1151 #[test]
1155 fn constant_blocks_code_a_still_frame_as_skips() {
1156 for codec in [Codec::Vp8, Codec::Vp9] {
1157 let still = frame(3);
1158 let mut plain = VpxEncoder::new(&settings(codec), codec, false).unwrap();
1159 let mut mapped = VpxEncoder::new(&settings(codec), codec, false).unwrap();
1160 let blocks = W.div_ceil(16) * H.div_ceil(16);
1161 let mut plain_bytes = 0;
1162 let mut mapped_bytes = 0;
1163 for n in 0..6u64 {
1164 plain_bytes = plain
1165 .encode_host(&still, W * 4, false, n, 25, n == 0)
1166 .unwrap()
1167 .len();
1168 mapped.constant_blocks(&vec![true; blocks]);
1169 mapped_bytes = mapped
1170 .encode_host(&still, W * 4, false, n, 25, n == 0)
1171 .unwrap()
1172 .len();
1173 if n == 0 {
1174 assert!(!mapped.map_armed, "{codec:?}: a key frame takes no map");
1175 } else {
1176 assert!(mapped.map_armed, "{codec:?}: frame {n} takes the map");
1177 }
1178 }
1179 assert!(
1180 mapped_bytes < plain_bytes,
1181 "{codec:?}: {mapped_bytes} bytes with the map, {plain_bytes} without"
1182 );
1183 mapped.invalidate_reference(5);
1184 mapped.constant_blocks(&vec![true; blocks]);
1185 mapped
1186 .encode_host(&still, W * 4, false, 6, 25, false)
1187 .unwrap();
1188 assert!(
1189 !mapped.map_armed,
1190 "{codec:?}: a frame predicting past a loss takes no map"
1191 );
1192 }
1193 }
1194
1195 #[test]
1200 fn constant_blocks_leave_a_change_beside_them_coded() {
1201 use super::super::mixed_screen;
1202 for codec in [Codec::Vp8, Codec::Vp9] {
1203 let s = CaptureSettings {
1204 width: mixed_screen::W as i32,
1205 height: mixed_screen::H as i32,
1206 ..settings(codec)
1207 };
1208 let mut plain = VpxEncoder::new(&s, codec, false).unwrap();
1209 let mut mapped = VpxEncoder::new(&s, codec, false).unwrap();
1210 mixed_screen::assert_decodes_alike(
1211 codec,
1212 &format!("{codec:?}"),
1213 |src, n| {
1214 plain
1215 .encode_host(src, mixed_screen::W * 4, false, n, 25, n == 0)
1216 .unwrap()
1217 },
1218 |src, n, map| {
1219 mapped.constant_blocks(map);
1220 mapped
1221 .encode_host(src, mixed_screen::W * 4, false, n, 25, n == 0)
1222 .unwrap()
1223 },
1224 );
1225 }
1226 }
1227
1228 #[test]
1234 fn the_time_base_is_one_frame_of_the_rate() {
1235 for codec in [Codec::Vp8, Codec::Vp9] {
1236 for (num, den) in [(60000i32, 1001i32), (120000, 1001), (144000, 1001), (60, 1)] {
1237 let fps = f64::from(num) / f64::from(den);
1238 let mut enc = VpxEncoder::new(
1239 &CaptureSettings {
1240 target_fps: fps,
1241 ..settings(codec)
1242 },
1243 codec,
1244 false,
1245 )
1246 .expect("session");
1247 assert_eq!(
1248 (enc.cfg.g_timebase.num, enc.cfg.g_timebase.den),
1249 (den, num),
1250 "{codec:?}"
1251 );
1252 enc.reconfigure_rate(&CaptureSettings {
1253 target_fps: 30.0,
1254 ..settings(codec)
1255 })
1256 .expect("30 fps");
1257 assert_eq!(
1258 (enc.cfg.g_timebase.num, enc.cfg.g_timebase.den),
1259 (1, 30),
1260 "{codec:?}"
1261 );
1262 }
1263 }
1264 }
1265
1266 #[test]
1267 fn a_lost_frame_is_predicted_past() {
1268 for codec in [Codec::Vp8, Codec::Vp9] {
1269 let s = settings(codec);
1270 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1271 let mut frames = Vec::new();
1272 for t in 0..8u64 {
1273 let out = enc
1274 .encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
1275 .expect("encode");
1276 assert_eq!(
1277 parse_video_type(out[1]).map(|(_, k)| k),
1278 Some(if t == 0 { FRAME_KEY } else { FRAME_DELTA }),
1279 "{codec:?} {t}"
1280 );
1281 assert_eq!(
1282 enc.last_reference(),
1283 if t == 0 {
1284 Reference::None
1285 } else {
1286 Reference::Frame(t as u16 - 1)
1287 },
1288 "{codec:?} {t}"
1289 );
1290 frames.push(out);
1291 }
1292 assert!(enc.invalidate_reference(5));
1295 let out = enc
1296 .encode_host(&frame(8), W * 4, false, 8, 25, false)
1297 .expect("encode");
1298 let anchor = if codec == Codec::Vp9 { 4 } else { 0 };
1299 assert_eq!(enc.last_reference(), Reference::Frame(anchor), "{codec:?}");
1300 assert_eq!(
1301 parse_video_type(out[1]).map(|(_, k)| k),
1302 Some(FRAME_DELTA),
1303 "{codec:?}"
1304 );
1305 frames.push(out);
1306 frames.push(
1307 enc.encode_host(&frame(9), W * 4, false, 9, 25, false)
1308 .expect("encode"),
1309 );
1310 assert_eq!(enc.last_reference(), Reference::Frame(8), "{codec:?}");
1311 let (mut whole, mut lossy) = (
1312 VideoDecoder::new(codec).unwrap(),
1313 VideoDecoder::new(codec).unwrap(),
1314 );
1315 for (i, f) in frames.iter().enumerate() {
1316 assert!(
1317 whole.decode(&f[VIDEO_HEADER_LEN..]).expect("decode"),
1318 "{codec:?} frame {i}"
1319 );
1320 if !(5..8).contains(&i) {
1321 assert!(
1322 lossy
1323 .decode(&f[VIDEO_HEADER_LEN..])
1324 .expect("decode without 5-7"),
1325 "{codec:?} frame {i}"
1326 );
1327 }
1328 }
1329 let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
1330 assert!(
1331 apart == 0.0,
1332 "{codec:?}: the decoder that lost frames 5-7 shows frame 9 {apart:.2} off the one that saw them"
1333 );
1334 }
1335 }
1336
1337 #[test]
1340 fn a_loss_ten_frames_deep_is_predicted_past() {
1341 for codec in [Codec::Vp8, Codec::Vp9] {
1342 let s = settings(codec);
1343 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1344 let mut frames = Vec::new();
1345 for t in 0..30u64 {
1346 frames.push(
1347 enc.encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
1348 .expect("encode"),
1349 );
1350 }
1351 assert!(enc.invalidate_reference(20));
1352 let out = enc
1353 .encode_host(&frame(30), W * 4, false, 30, 25, false)
1354 .expect("encode");
1355 assert_eq!(
1356 parse_video_type(out[1]).map(|(_, k)| k),
1357 Some(FRAME_DELTA),
1358 "{codec:?}"
1359 );
1360 let anchor = if codec == Codec::Vp9 { 16 } else { 12 };
1361 assert_eq!(enc.last_reference(), Reference::Frame(anchor), "{codec:?}");
1362 frames.push(out);
1363 frames.push(
1364 enc.encode_host(&frame(31), W * 4, false, 31, 25, false)
1365 .expect("encode"),
1366 );
1367 assert_eq!(enc.last_reference(), Reference::Frame(30), "{codec:?}");
1368 let (mut whole, mut lossy) = (
1369 VideoDecoder::new(codec).unwrap(),
1370 VideoDecoder::new(codec).unwrap(),
1371 );
1372 for (i, f) in frames.iter().enumerate() {
1373 assert!(
1374 whole.decode(&f[VIDEO_HEADER_LEN..]).expect("decode"),
1375 "{codec:?} frame {i}"
1376 );
1377 if !(20..30).contains(&i) {
1378 assert!(
1379 lossy
1380 .decode(&f[VIDEO_HEADER_LEN..])
1381 .expect("decode without 20-29"),
1382 "{codec:?} frame {i}"
1383 );
1384 }
1385 }
1386 let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
1387 assert!(
1388 apart == 0.0,
1389 "{codec:?}: the decoder that lost frames 20-29 shows frame 31 {apart:.2} off the one that saw them"
1390 );
1391 }
1392 }
1393
1394 #[test]
1396 fn a_loss_past_the_window_costs_a_key_frame() {
1397 for codec in [Codec::Vp8, Codec::Vp9] {
1398 let s = settings(codec);
1399 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1400 for t in 0..40u64 {
1401 enc.encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
1402 .expect("encode");
1403 }
1404 assert!(enc.invalidate_reference(1));
1405 let out = enc
1406 .encode_host(&frame(40), W * 4, false, 40, 25, false)
1407 .expect("encode");
1408 assert_eq!(
1409 parse_video_type(out[1]).map(|(_, k)| k),
1410 Some(FRAME_KEY),
1411 "{codec:?}"
1412 );
1413 assert_eq!(enc.last_reference(), Reference::None);
1414 enc.encode_host(&frame(41), W * 4, false, 41, 25, false)
1415 .expect("encode");
1416 assert_eq!(enc.last_reference(), Reference::Frame(40), "{codec:?}");
1417 }
1418 }
1419
1420 #[test]
1423 fn a_vp9_header_names_its_quantizer_index() {
1424 use crate::encoders::codec::vp9_base_q_idx;
1425 let depths: &[i32] = if encodes_ten_bit() { &[8, 10] } else { &[8] };
1426 for (&depth, fullcolor) in depths.iter().flat_map(|d| [(d, false), (d, true)]) {
1427 let mut s = settings(Codec::Vp9);
1428 s.video_bit_depth = depth;
1429 s.video_fullcolor = fullcolor;
1430 let mut enc = VpxEncoder::new(&s, Codec::Vp9, fullcolor).expect("session");
1431 for (n, crf, key) in [(0, 40, true), (1, 15, false), (2, 28, false)] {
1432 let coded = enc
1433 .encode_host(&frame(n), W * 4, false, n as u64, crf, key)
1434 .unwrap();
1435 let read = vp9_base_q_idx(&coded[VIDEO_HEADER_LEN..]);
1436 assert_eq!(
1437 read.map(|q| Codec::Vp9.quality_index(q)),
1438 enc.last_quality(),
1439 "{depth}-bit fullcolor {fullcolor} frame {n}: {read:?}"
1440 );
1441 }
1442 }
1443 }
1444
1445 fn luma(dec: &VideoDecoder) -> Vec<u8> {
1448 let f = dec.frame().expect("a decoded picture");
1449 f.y.chunks(f.y_stride)
1450 .take(H)
1451 .flat_map(|row| row[..W].iter().copied())
1452 .collect()
1453 }
1454
1455 fn rows_distance(a: &[u8], b: &[u8], rows: std::ops::Range<usize>) -> f64 {
1457 let n = rows.len() * W;
1458 a[rows.start * W..rows.end * W]
1459 .iter()
1460 .zip(&b[rows.start * W..rows.end * W])
1461 .map(|(&x, &y)| (f64::from(x) - f64::from(y)).abs())
1462 .sum::<f64>()
1463 / n as f64
1464 }
1465
1466 #[test]
1471 fn a_vp8_band_codes_its_macroblocks_alone() {
1472 let mut s = settings(Codec::Vp8);
1473 s.video_cbr_mode = true;
1474 s.video_bitrate_kbps = 30;
1475 let still = frame(0);
1476 let run = |band: Option<(f64, f64)>| {
1477 let mut enc = VpxEncoder::new(&s, Codec::Vp8, false).expect("session");
1478 let mut dec = VideoDecoder::new(Codec::Vp8).unwrap();
1479 for t in 0..6u64 {
1480 let f = enc
1481 .encode_host(&still, W * 4, false, t, 25, t == 0)
1482 .unwrap();
1483 assert!(dec.decode(&f[VIDEO_HEADER_LEN..]).unwrap());
1484 }
1485 let before = luma(&dec);
1486 assert_eq!(enc.band_size(), Some(0), "no frame held yet");
1487 enc.hold_quantizer(5, band);
1488 let held = enc.encode_host(&still, W * 4, false, 6, 25, false).unwrap();
1489 assert_eq!(parse_video_type(held[1]).map(|(_, k)| k), Some(FRAME_DELTA));
1490 assert!(dec.decode(&held[VIDEO_HEADER_LEN..]).unwrap());
1491 assert_eq!(enc.band_size(), Some(held.len()));
1492 let next = enc.encode_host(&still, W * 4, false, 7, 25, false).unwrap();
1494 assert!(dec.decode(&next[VIDEO_HEADER_LEN..]).unwrap());
1495 (held.len(), before, luma(&dec))
1496 };
1497 let (whole, before, refreshed) = run(None);
1498 let (banded, band_before, band_after) = run(Some((0.0, 0.25)));
1500 assert!(
1501 banded * 2 < whole,
1502 "a quarter of the picture cost {banded} bytes against {whole} for the whole"
1503 );
1504 assert_eq!(
1505 before, band_before,
1506 "both runs reach the same picture before the hold"
1507 );
1508 let changed = rows_distance(&before, &refreshed, 0..48);
1509 assert!(
1510 changed > 0.5,
1511 "the held quantizer refines the picture ({changed:.2})"
1512 );
1513 let band = rows_distance(&band_after, &refreshed, 0..48);
1514 assert!(
1515 band < changed / 4.0,
1516 "the band decodes as the whole refresh's ({band:.2})"
1517 );
1518 let rest = rows_distance(&band_after, &band_before, 64..H);
1519 assert!(rest < 0.01, "the rest is left as it was ({rest:.3})");
1520 s.video_bitrate_kbps = 5;
1523 let starved = VpxEncoder::new(&s, Codec::Vp8, false).expect("session");
1524 assert_eq!(starved.band_size(), None);
1525 }
1526
1527 #[test]
1530 fn a_vp8_band_after_coarse_motion_is_as_sharp_as_its_quantizer() {
1531 let mut s = settings(Codec::Vp8);
1532 s.video_cbr_mode = true;
1533 s.video_bitrate_kbps = 30;
1534 let scrolled = |t: usize| {
1536 let f = frame(0);
1537 let rows = W * 4;
1538 let shift = (t * 5) % H;
1539 [&f[shift * rows..], &f[..shift * rows]].concat()
1540 };
1541 let still = scrolled(12);
1542 let source: Vec<u8> = still
1544 .chunks(4)
1545 .map(|p| {
1546 let (b, g, r) = (f64::from(p[0]), f64::from(p[1]), f64::from(p[2]));
1547 (16.0 + (65.481 * r + 128.553 * g + 24.966 * b) / 255.0).round() as u8
1548 })
1549 .collect();
1550 let mut enc = VpxEncoder::new(&s, Codec::Vp8, false).expect("session");
1551 let mut dec = VideoDecoder::new(Codec::Vp8).unwrap();
1552 for t in 0..12u64 {
1553 let f = enc
1554 .encode_host(&scrolled(t as usize), W * 4, false, t, 25, t == 0)
1555 .unwrap();
1556 assert!(dec.decode(&f[VIDEO_HEADER_LEN..]).unwrap());
1557 }
1558 enc.hold_quantizer(5, Some((0.0, 0.25)));
1560 let held = enc
1561 .encode_host(&still, W * 4, false, 12, 25, false)
1562 .unwrap();
1563 assert!(dec.decode(&held[VIDEO_HEADER_LEN..]).unwrap());
1564 let off = rows_distance(&luma(&dec), &source, 0..48);
1565 let mut fresh = VpxEncoder::new(&settings(Codec::Vp8), Codec::Vp8, false).expect("session");
1567 let mut fresh_dec = VideoDecoder::new(Codec::Vp8).unwrap();
1568 fresh.hold_quantizer(5, None);
1569 let key = fresh
1570 .encode_host(&still, W * 4, false, 0, 25, true)
1571 .unwrap();
1572 assert!(fresh_dec.decode(&key[VIDEO_HEADER_LEN..]).unwrap());
1573 let floor = rows_distance(&luma(&fresh_dec), &source, 0..48);
1574 assert!(
1575 off < floor + 0.2,
1576 "the band decodes {off:.2} off the picture, a key frame at its quantizer {floor:.2}"
1577 );
1578 }
1579
1580 #[test]
1584 fn a_vp9_cleanup_frame_runs_at_its_caps_rate_and_the_next_at_the_sessions() {
1585 for codec in [Codec::Vp8, Codec::Vp9] {
1586 let mut s = settings(codec);
1587 s.video_cbr_mode = true;
1588 s.video_bitrate_kbps = 500;
1589 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1590 enc.encode_host(&frame(0), W * 4, false, 0, 25, true)
1591 .unwrap();
1592 enc.cap(400_000);
1593 enc.encode_host(&frame(0), W * 4, false, 1, 25, false)
1594 .unwrap();
1595 let raised = enc.cfg.rc_target_bitrate;
1596 enc.encode_host(&frame(0), W * 4, false, 2, 25, false)
1597 .unwrap();
1598 let after = enc.cfg.rc_target_bitrate;
1599 if codec == Codec::Vp9 {
1600 assert_eq!((raised, after), (16_000, 500));
1601 } else {
1602 assert_eq!((raised, after), (500, 500), "{codec:?}");
1603 }
1604 }
1605 }
1606
1607 #[test]
1611 fn a_capped_quality_codes_as_pinned_under_a_peak_it_does_not_reach() {
1612 for codec in [Codec::Vp8, Codec::Vp9] {
1613 let pinned = settings(codec);
1614 let capped = CaptureSettings {
1615 video_bitrate_kbps: 100_000,
1616 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
1617 ..pinned.clone()
1618 };
1619 let mut a = VpxEncoder::new(&pinned, codec, false).expect("session");
1620 let mut b = VpxEncoder::new(&capped, codec, false).expect("session");
1621 assert!(b.rate.capped && !a.rate.capped);
1622 for t in 0..40 {
1623 let key = t == 0 || t == 15;
1624 let (at, crf) = if t < 30 { (t, 25) } else { (30, 18) };
1626 let plain = a
1627 .encode_host(
1628 &crate::encoders::scrolled_text(W, H, at),
1629 W * 4,
1630 false,
1631 t as u64,
1632 crf,
1633 key,
1634 )
1635 .unwrap();
1636 let held = b
1637 .encode_host(
1638 &crate::encoders::scrolled_text(W, H, at),
1639 W * 4,
1640 false,
1641 t as u64,
1642 crf,
1643 key,
1644 )
1645 .unwrap();
1646 assert_eq!(
1647 (plain.len(), a.last_quality()),
1648 (held.len(), b.last_quality()),
1649 "{codec:?} frame {t}"
1650 );
1651 }
1652 }
1653 }
1654
1655 #[test]
1658 fn a_refused_switch_to_the_peak_keeps_the_frame_and_the_state() {
1659 for codec in [Codec::Vp8, Codec::Vp9] {
1660 let mut s = CaptureSettings {
1661 video_bitrate_kbps: 100_000,
1662 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
1663 ..settings(codec)
1664 };
1665 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1666 for t in 0..5 {
1667 enc.encode_host(
1668 &crate::encoders::scrolled_text(W, H, t),
1669 W * 4,
1670 false,
1671 t as u64,
1672 25,
1673 t == 0,
1674 )
1675 .unwrap();
1676 }
1677 s.video_bitrate_kbps = 10;
1678 enc.reconfigure_rate(&s).expect("rate");
1679 enc.cfg.g_lag_in_frames = 25;
1681 for t in 5..20 {
1682 let out = enc.encode_host(
1683 &crate::encoders::scrolled_text(W, H, t),
1684 W * 4,
1685 false,
1686 t as u64,
1687 25,
1688 false,
1689 );
1690 assert!(
1691 out.is_ok_and(|f| !f.is_empty()),
1692 "{codec:?} frame {t} was dropped"
1693 );
1694 }
1695 assert!(
1696 enc.switch_refused,
1697 "{codec:?}: the switch was never refused"
1698 );
1699 assert!(
1700 !enc.peak_bound(),
1701 "{codec:?}: bound to a peak libvpx never took"
1702 );
1703 }
1704 }
1705
1706 #[test]
1710 fn a_capped_quality_moves_its_peak_without_a_key_frame() {
1711 const CUT: usize = 20;
1712 const AFTER: usize = 150;
1713 for codec in [Codec::Vp8, Codec::Vp9] {
1714 let mut s = CaptureSettings {
1715 video_bitrate_kbps: 100_000,
1716 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
1717 ..settings(codec)
1718 };
1719 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1720 let kbps =
1721 |f: &[usize]| f.iter().sum::<usize>() as f64 * 30.0 / f.len() as f64 / 1000.0;
1722 let mut sizes = Vec::new();
1723 for t in 0..CUT + AFTER {
1724 if t == CUT {
1725 s.video_bitrate_kbps = (kbps(&sizes[1..]) * 0.6) as i32;
1726 }
1727 enc.reconfigure_rate(&s).expect("rate");
1728 let out = enc
1729 .encode_host(
1730 &crate::encoders::scrolled_text(W, H, t),
1731 W * 4,
1732 false,
1733 t as u64,
1734 25,
1735 t == 0,
1736 )
1737 .unwrap();
1738 assert!(
1739 t == 0 || parse_video_type(out[1]).map(|(_, k)| k) == Some(FRAME_DELTA),
1740 "{codec:?} frame {t} is a key frame"
1741 );
1742 sizes.push((out.len() - VIDEO_HEADER_LEN) * 8);
1743 }
1744 let after = kbps(&sizes[CUT + 10..]);
1745 assert!(
1746 after <= 1.1 * f64::from(s.video_bitrate_kbps),
1747 "{codec:?}: the scroll ran at {after:.0} kbps under a {} kbps peak",
1748 s.video_bitrate_kbps
1749 );
1750 }
1751 }
1752
1753 #[test]
1754 fn quality_and_rate_move_without_a_key_frame() {
1755 for codec in [Codec::Vp8, Codec::Vp9] {
1756 let mut s = settings(codec);
1757 s.video_crf = 40;
1758 let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1759 let coarse = enc
1760 .encode_host(&frame(0), W * 4, false, 0, 40, true)
1761 .unwrap();
1762 let fine = enc
1763 .encode_host(&frame(1), W * 4, false, 1, 15, false)
1764 .unwrap();
1765 assert_eq!(enc.quality.current, codec.quantizer(15));
1766 assert_eq!(
1767 parse_video_type(fine[1]).map(|(_, k)| k),
1768 Some(FRAME_DELTA),
1769 "{codec:?}"
1770 );
1771 let again = enc
1772 .encode_host(&frame(2), W * 4, false, 2, 15, false)
1773 .unwrap();
1774 assert!(
1775 again.len() + fine.len() > coarse.len() / 2,
1776 "{codec:?}: the finer quantizer spends more"
1777 );
1778 s.target_fps = 15.0;
1779 enc.reconfigure_rate(&s).expect("rate");
1780 let after = enc
1781 .encode_host(&frame(3), W * 4, false, 3, 15, false)
1782 .unwrap();
1783 assert_eq!(
1784 parse_video_type(after[1]).map(|(_, k)| k),
1785 Some(FRAME_DELTA),
1786 "{codec:?}"
1787 );
1788 let mut dec = VideoDecoder::new(codec).unwrap();
1789 for f in [&coarse, &fine, &again, &after] {
1790 assert!(dec.decode(&f[VIDEO_HEADER_LEN..]).unwrap(), "{codec:?}");
1791 }
1792 }
1793 }
1794}