Skip to main content

pixelflux/encoders/
vpx.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5 */
6
7//! Software VP8 and VP9 through libvpx, with every frame's references under the session's
8//! control.
9//!
10//! One session type serves both codecs. libvpx runs at the lowest latency it offers in real
11//! time -- one frame in, one packet out, no lag, no alternate-reference lookahead -- with screen
12//! tuning on VP9, and its rate control is either constant-rate at the session's bitrate and VBV
13//! or a quantizer pinned between an equal minimum and maximum, which is how a quality change
14//! reaches a running session without a key frame. A capped constant quality
15//! (`RateSettings::capped`) runs pinned as an uncapped one does until its output overflows the
16//! peak (`PeakBucket`), and from there the same rate control with the pinned quantizer as its
17//! floor, the quantizer free above it, and the peak as its target in a buffer of the constant
18//! rate's kind, until the rate control codes at the floor again; a new peak reaches it live as a
19//! new target does. Free above the floor while the peak does not bind, VP9's rate control walks
20//! to a new floor over frames rather than take it (a still screen's cleanup from 25 to 18 coded
21//! at 21 first). A VP9 session runs in libvpx's flexible mode,
22//! where the codec's eight buffers are addressed by slot, so a frame a client lost leaves the
23//! predictions the way a decoded picture buffer allows; a VP8 session steers its three buffers
24//! with the per-frame reference and refresh flags. What a lost frame would leave behind besides
25//! its picture is kept out too: VP8's entropy probabilities persist across frames unless a
26//! frame declines to update them, so no frame does, and a VP9 decoder takes the previous
27//! frame's motion vectors and probability contexts whatever the references say, which only the
28//! codec's error-resilient mode switches off. VP8 runs error-resilient too, since its
29//! constant-rate control otherwise refreshes GOLDEN on a schedule of its own, whatever a frame's
30//! flags say.
31//!
32//! The blocks the block ledger marks still and clean (`constant_blocks`) reach libvpx as its
33//! active map, on a frame of the rate control's that predicts from the frame before: VP8 takes
34//! an inactive macroblock as a zero-motion skip from its first mode and searches nothing, VP9
35//! codes a superblock whose every block is inactive as one skip, and both copy only the active
36//! blocks, and those changed since, into the buffer the frame is coded from (the patches under
37//! `patches/libvpx`). A still 1080p screen took VP8 0.9 ms a frame on eight threads where it
38//! took 1.6 without the map, and VP9 0.6 where it took 1.9; a 256x256 change beside a clean
39//! screen at 2560x1600 took VP8 3.5 ms where it took 4.5, and VP9 4.1 where it took 6.4.
40
41use 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
54/// The rate target, in kbit/s, a pinned-quantizer session names and never reaches.
55const RATE_CEILING_KBPS: u32 = 100_000;
56
57/// The temporal layers a VP9 session declares. Every frame is coded in the base layer; the
58/// second exists because libvpx sets up and re-targets its per-layer rate control, which the
59/// flexible reference mode runs on, only for a session with more than one layer.
60const VP9_LAYERS: u32 = 2;
61/// The libvpx quantizer levels a session leaves to the library's own bounds.
62const VP8_DEFAULT_MIN_LEVEL: u32 = 4;
63/// libvpx's `aq_mode` for the cyclic refresh.
64const CYCLIC_REFRESH_AQ: c_int = 3;
65/// The squared error under which libvpx's VP8 skips a macroblock as unchanged (its static
66/// threshold), so a still region is left as it is rather than coded again every frame: at 0.25
67/// Mbit/s a 1080p texture under a moving box took 3.0 MB for its 3 s of motion against 5.8, at
68/// 42.0 dB against 41.4, and 304 kB still against 1185. Scrolling text codes the same.
69const VP8_STATIC_THRESHOLD: c_int = 100;
70/// VP8's token partitions, eight, the most it has: a client decoding in software spreads a frame
71/// across them, as across H.264's slices, where one leaves it on one thread. They cost nothing
72/// measurable: at 1080p text, a game frame and dense text code within 0.1% of the bytes and
73/// 0.02 dB of one partition while scrolling, and within 2% still.
74const VP8_TOKEN_PARTITIONS: c_int = VP8_EIGHT_TOKENPARTITION as c_int;
75/// The frame budget a macroblock, in bits, from which VP8 sweeps its refresh in bands
76/// (`VpxEncoder::band_size`): three times what a frame that skips every macroblock costs.
77const BAND_BITS_PER_MB: f64 = 1.0;
78/// The finest VP8 quantizer indices, at and under which a held band is coded with no loop filter
79/// (`band_roi`), as libvpx itself filters a frame there at level 1 at most. libvpx starts a
80/// frame's level from the last frame's and keeps it while a strip across the middle of the
81/// picture shows no gain, which a band elsewhere leaves as it was: the bands above the middle of
82/// a sweep after motion kept the level its coarse frames left (46), and 1080p text swept at
83/// index 2 ended at 42.7 dB at 8 fps 2 Mbit/s against 50.6 unfiltered, at 15 fps 43.6 against
84/// 50.6, at 30 and 60 fps level. At coarser indices libvpx's level stands, unfiltered coming out
85/// 0.2 to 0.4 dB short at 24 and 73. A refresh held whole shows the strip, and libvpx's level
86/// came out 0.3 to 0.8 dB finer than none at index 3.
87const VP8_UNFILTERED_QINDEX: u32 = 16;
88
89/// Quality index steps finer than the paint-over one VP8 codes a refined block at
90/// (`FrameEncoder::refines`): none, since a VP8 session's paint-over quantizer already sits at
91/// the floor its bounds keep (`VP8_DEFAULT_MIN_LEVEL`), and 1080p text beside a video came out
92/// the same at 0, 3, and 6 steps finer (39.7 dB at 2 Mbit/s and 42.6 at 8 against 29.6 and 33.5
93/// unrefined, where VP8's cleanup of the still screen reaches 42.0). VP9 refines no block: in
94/// its screen mode a block whose source has not changed reads as static and is skipped
95/// whatever its segment's quantizer, and refined, the same text came to 25.9 and 35.4 dB
96/// against 25.6 and 34.9, where VP9's cleanup of the still screen reaches 37.7 and 51.1.
97pub const REFINE_FINER: u32 = 0;
98
99/// Whether the loaded libvpx codes VP9 4:4:4 in the flexible mode the sessions run: before 1.13
100/// its layer machinery re-sizes every frame at 4:2:0, so a profile 1 session writes headers
101/// over pictures no decoder takes.
102pub fn encodes_444() -> bool {
103    (unsafe { vpx_codec_version() }) >= (1 << 16) | (13 << 8)
104}
105
106/// Whether the loaded libvpx was built with VP9's high bit depth, which its 10-bit profiles
107/// need.
108pub 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
114/// One libvpx session for one capture.
115pub 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    /// The slot plan of the frame being encoded, recorded once its packet returns.
128    plan: SlotPlan,
129    /// The quality index the next frame is held at whatever the rate control (`hold_quantizer`).
130    held: Option<u32>,
131    /// The share of the picture, from and to in raster order, the held quantizer covers, the
132    /// rest left as it is (`hold_quantizer`, VP8 only); `None` for the whole picture.
133    held_band: Option<(f64, f64)>,
134    /// The bytes of the last held frame, 0 before one (`band_size`).
135    held_bytes: usize,
136    /// One segment per macroblock for a held band (`band_roi`): 1 inside it, 0 outside.
137    band_map: Vec<u8>,
138    /// The blocks the next frame of the rate control's refines (`refine_blocks`).
139    refinement: Option<crate::ledger::Refinement>,
140    /// libvpx's active map for the next frame, one byte a macroblock in raster order, 0 where
141    /// the block is constant (`constant_blocks`); empty where no block is.
142    active_map: Vec<u8>,
143    /// Whether the session's encoder holds an active map, to be taken back before a frame that
144    /// takes none.
145    map_armed: bool,
146    /// The buffers the last coded frame refreshed: a constant block is a skip from the frame
147    /// before only where the next frame predicts from one of them.
148    last_refresh: SlotRefresh,
149    /// The quality index the rate control last coded a frame at, held frames aside.
150    last_quality: Option<u32>,
151    /// A capped constant quality's peak (`PeakBucket`), and whether the last frame was coded
152    /// bound to it (`coded_capped`).
153    peak: Option<PeakBucket>,
154    last_capped: bool,
155    /// Whether libvpx has refused a peak's switch (`switch_peak`), said once.
156    switch_refused: bool,
157    /// The buffer the session opened with, `(size, initial, optimal)` in ms, which a pinned
158    /// quantizer keeps.
159    open_buf: (u32, u32, u32),
160    /// The bytes of the last frame the rate control coded, held frames aside.
161    last_bytes: Option<usize>,
162    /// The size and quality index of the last held key frame of a constant-rate session
163    /// (`super::held_key_start`).
164    held_key: Option<(usize, u32)>,
165    /// The bytes the next frame, a still screen's cleanup, may come to (`cap`, VP9).
166    cap: Option<usize>,
167    /// Whether the rate control runs at a cleanup frame's rate, to be put back.
168    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
179/// The session's last error, with libvpx's detail where it has one. The context is handed
180/// over mutable because libvpx before 1.14 declares the parameter so, though it only reads it.
181/// The peak of a capped constant quality in bits per second, as its bucket counts it.
182fn 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    /// Open a VP8 or VP9 session on host frames in the byte order `rgba` names.
203    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            // Column threading is per tile and VP9's narrowest tile is 256 pixels, so the
325            // width sets how many columns the encode can spread across.
326            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    /// A constant-rate VP9 session refines a still screen through libvpx's cyclic refresh, a
349    /// share of the blocks a frame within the budget. Without it the rate control codes the
350    /// whole screen finer in single frames: at 2 Mbit/s at 1080p up to 44 budgets each, a
351    /// third over the rate for 30 s, for a picture 4.6 dB coarser. A pinned quantizer takes
352    /// none, since the refresh codes its blocks at another.
353    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    /// The layers of a VP9 session, whose quantizer bounds libvpx reads from here rather
362    /// than from the configuration once the flexible mode is on.
363    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    /// Write `rate` and the quantizer `q` (the codec's own domain) into the configuration:
387    /// a constant rate holds the target and VBV with the quantizer bounds the settings name,
388    /// a pinned quantizer names a rate it never reaches with both bounds at the quantizer, and a
389    /// capped one targets its peak in the constant rate's buffer with the quantizer its floor.
390    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    /// Apply the configuration to the running session.
437    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    /// The library behind the session, as the logs name it.
452    pub fn library(&self) -> &'static str {
453        "libvpx"
454    }
455
456    /// Whether the session carries 4:4:4 (VP9 profile 1, or 3 at 10 bits).
457    pub fn is_fullcolor(&self) -> bool {
458        self.planes.i444
459    }
460
461    /// The bits per sample the session codes: 10 in VP9 profiles 2 and 3.
462    pub fn bit_depth(&self) -> u32 {
463        self.planes.bit_depth
464    }
465
466    /// VP9 keeps the limited range of its 4:2:0 in profile 1, so the decoder hint a client
467    /// sends for it stays true; VP8 has no other.
468    pub fn is_full_range(&self) -> bool {
469        false
470    }
471
472    /// The frame the last encoded frame predicted from.
473    pub fn last_reference(&self) -> Reference {
474        self.last_reference
475    }
476
477    /// Leave frame `frame_id` and every frame after it out of the predictions; the next frame
478    /// predicts from an earlier buffer, or is a key frame when none is left.
479    pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
480        self.references.invalidate(frame_id);
481        true
482    }
483
484    /// Every consumer holds frame `frame_id`, or where not `held` was sent it
485    /// (`ReferenceSlots::acknowledge`).
486    pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
487        self.references.acknowledge(frame_id, held);
488    }
489
490    /// The quality index the rate control last coded a frame at, held frames aside.
491    pub fn last_quality(&self) -> Option<u32> {
492        self.last_quality
493    }
494
495    /// Whether the constant quality runs under a peak (`RateSettings::capped`).
496    pub fn capped(&self) -> bool {
497        self.rate.capped
498    }
499
500    /// Whether that peak binds the session, its quantizer free above the pinned one.
501    pub fn peak_bound(&self) -> bool {
502        self.peak.is_some_and(|p| p.binding)
503    }
504
505    /// Whether the last frame was coded bound to the peak.
506    pub fn coded_capped(&self) -> bool {
507        self.last_capped
508    }
509
510    /// A tick that codes no frame (`FrameEncoder::idle`): the peak's bucket drains, and a session
511    /// bound to it counts the tick at its floor, nothing being coded coarser.
512    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    /// The rate settings the session runs: a capped constant quality's as an uncapped one's
524    /// while its peak does not bind.
525    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    /// Whether a still screen's cleanup frame is coded at the rate its cap runs at (`cap`): VP9
534    /// at a constant rate, whose cleanup runs through its rate control, which takes a new target
535    /// live.
536    pub fn follows_cap(&self) -> bool {
537        self.codec == Codec::Vp9 && self.rate.cbr
538    }
539
540    /// Code the next frame, a still screen's cleanup, at the rate that spends `bytes` in
541    /// `pipeline::CLEANUP_FRAME_S`, its buffer as long in time as the session's; the frame after
542    /// goes back to the session's rate. At 60 fps 0.5 Mbit/s VP9 refined 1080p text by 1 dB a
543    /// second at its own rate (32.5 dB 8 s after the stop), and to 55 dB in 8 s at a 2 Mbit/s
544    /// path's.
545    pub fn cap(&mut self, bytes: usize) {
546        if self.follows_cap() {
547            self.cap = Some(bytes);
548        }
549    }
550
551    /// The bytes of the last frame the rate control coded, held frames aside, where a
552    /// constant-rate cleanup runs through that rate control (`holds_quantizer`).
553    pub fn last_size(&self) -> Option<usize> {
554        self.last_bytes.filter(|_| !self.holds_quantizer())
555    }
556
557    /// Whether the cleanup of a still screen holds a frame at its quantizer: at a constant
558    /// quantizer, and at a constant rate for VP8, whose rate control leaves a still screen as
559    /// it is (a screen of text at 2 Mbit/s at 1080p stayed at 33.6 dB, where the held
560    /// refresh reached 42.8). VP9's refines one, so its constant-rate cleanup runs through
561    /// the rate control instead: 43.6 dB in 2.5 s with no frame over 75 kB, where the held
562    /// refresh was one of 347 kB.
563    pub fn holds_quantizer(&self) -> bool {
564        !self.rate.cbr || self.codec == Codec::Vp8
565    }
566
567    /// Encode the next frame at the quantizer the quality index `crf` selects, whatever the rate
568    /// control, and put the session's own rate control back for the frame after: the cleanup of a
569    /// still screen. A held key frame of a constant-rate session starts at the quantizer the
570    /// last one says fits `HELD_KEY_BUDGET_S` of the target (`held_key_start`), and one that
571    /// comes out past it is coded again, as a key frame, at the coarser quantizer
572    /// `held_key_retry` picks.
573    /// `band`, the share of the picture from and to in raster order, confines the quantizer to
574    /// the macroblocks it covers where the session holds a band (`band_size`), and the rest of
575    /// the frame is left as the reference has it.
576    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    /// The bytes of the last held frame (0 before one), where the session holds a band of a
582    /// frame at a quantizer: VP8, through libvpx's region-of-interest map (`band_roi`), at a rate
583    /// that leaves a band room in a frame. A VP8 frame codes every macroblock's mode even where
584    /// it skips them all, about a third of a bit each at 720p and 1080p, so below
585    /// `BAND_BITS_PER_MB` of frame budget a band a frame is mostly that and the sweep cannot keep
586    /// the rate: 1080p text at 0.25 Mbit/s (half a bit) ended at 29.5 dB in bands against 42.0
587    /// held whole, where 720p at 0.25 (1.2 bits) ended at 41.5 against 42.0 with the worst wait
588    /// at the target 5.0 s against 18.5. The refresh is held whole below it.
589    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    /// Hand libvpx the region map of a held band (`band`, from and to in raster order of the
598    /// macroblocks, as NVENC's delta map counts them), or clear it with `None`. The band is
599    /// coded at the frame's own quantizer, the one held, with the static threshold the session
600    /// keeps (`VP8_STATIC_THRESHOLD`) and, where `unfiltered`, no loop filter
601    /// (`VP8_UNFILTERED_QINDEX`); every other macroblock is skipped, a static threshold
602    /// no prediction error reaches making it a copy of the reference, so the frame carries the
603    /// band and nothing else. VP8's segments take a quantizer delta of 63 at most, so the band
604    /// is not held finer than a coarse rest the way NVENC's map holds it: the rest is skipped
605    /// outright, which no quantizer of its own would do better.
606    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            // No loop filter either, which would still touch a skipped block's edges.
622            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    /// Code the blocks `refinement` names in the next frame of the rate control's at its target
643    /// quality (`ledger`), through libvpx's region-of-interest map (`refine_roi`).
644    pub fn refine_blocks(&mut self, refinement: &crate::ledger::Refinement) {
645        self.refinement = Some(refinement.clone());
646    }
647
648    /// Hand libvpx the region map of `refinement`, or clear it with `None`: segment 1 the blocks
649    /// it refines, coded the steps of libvpx's quantizer between the rate control's last and the
650    /// target finer than the frame (which libvpx translates to its internal index), segment 0
651    /// the rest at the frame's own. VP8 maps its 16x16 macroblocks and skips a refined block
652    /// with no static threshold, since an unchanged block under the session's own
653    /// (`VP8_STATIC_THRESHOLD`) would be copied rather than coded finer; VP9 maps its 8x8
654    /// blocks, and runs the map in place of its cyclic refresh for the frame.
655    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    /// Pin the configuration's quantizer bounds to the one the quality index `crf` selects.
699    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    /// Re-program the rate control when a rate or frame-rate setting changed, live.
707    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    /// Encode one packed host frame at the quality index `crf`, as a key frame when
725    /// `force_idr` or when no reference is left to predict from.
726    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        // A key frame skips nothing, so a band is held on a predicted one alone, and a key frame
760        // asked for where a band was due goes at the rate control's quality rather than whole at
761        // the band's quantizer.
762        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    /// Program the rate control the peak's new state asks for (`programmed`): the constant rate
837    /// at the peak once it binds, the pinned quantizer once it lets go. A configuration libvpx
838    /// refuses leaves the session as it ran, the peak's state put back with it, said once; the
839    /// frame that asked for the switch is kept either way.
840    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    /// Mark the macroblocks of the next frame that held still and are clean (`constant`, in
855    /// raster order, empty for none), which libvpx codes as skips without analysis through its
856    /// active map: VP8 picks ZEROMV from the frame before at once, VP9 codes the superblock
857    /// whose every block is marked as one skip. The map goes to the encoder with the next frame
858    /// alone (`arm_active_map`), and only where that frame predicts from the one before; a frame
859    /// coded without a map of its own takes none.
860    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    /// Hand the encoder the frame's active map where `takes` it and the session holds one, and
870    /// take back the map it holds otherwise: libvpx keeps a map until it is replaced, and an
871    /// all-active map still costs VP9 a segmentation map a frame.
872    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    /// Encode one packed host frame at the quantizer the configuration holds.
900    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    /// A frame whose luma spells its index in blocks, so a decoded picture names the frame it is.
1113    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    /// The blocks marked constant code as skips: a still screen's frame comes out smaller with
1152    /// the map than without, and decodes to the same picture; a frame predicting from an older
1153    /// reference, or a key frame, takes no map.
1154    #[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    /// A box that changes every frame beside blocks marked constant, at a size no block divides:
1196    /// the partial copy fills the active blocks and the partial edge blocks from the new picture,
1197    /// the map leaves the box coded, and both codecs decode the box and the still screen as
1198    /// close to the source as without the map.
1199    #[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    /// A frame a client lost is left out of the predictions: the next frame names the newest
1229    /// frame before it, and a decoder that never saw the lost frames decodes it exactly as one
1230    /// that saw everything. VP9 reaches back through its recent frames, VP8 through its anchors.
1231    /// Both codecs count time in frames of the capture's rate, as the fraction it names, a live
1232    /// change of rate included.
1233    #[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            // Frame 5 is reported lost once 6 and 7 have gone out: VP9 predicts from 4 through
1293            // its slots, VP8 from its golden anchor, which still holds the key frame.
1294            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    /// A loss ten frames deep, past the last eight frames, is predicted past from an anchor, and
1338    /// a decoder that never saw the lost frames decodes the frames after it exactly.
1339    #[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    /// A loss no buffer reaches past costs a key frame, and the count restarts there.
1395    #[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    /// The quantizer index read back from a VP9 frame's header is the one libvpx says it coded
1421    /// the frame at, for a key frame and a predicted one of every profile the build codes.
1422    #[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    /// A quality change reaches a running session without a key frame, and a rate change too.
1446    /// The decoded picture's luma, `W` x `H`.
1447    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    /// The mean luma difference of `a` and `b` over `rows`.
1456    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    /// A VP8 refresh held in a band codes the band's macroblocks alone at the held quantizer and
1467    /// leaves every other one as the reference has it: a quarter of the picture costs a fraction
1468    /// of the whole refresh, the band decodes as the whole refresh's does, and the rest exactly
1469    /// as the frame before.
1470    #[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            // The frame after goes back to the rate control, with no band left on it.
1493            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        // 300 macroblocks of 20 a row: the first quarter is rows 0 to 2 and part of row 3.
1499        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        // Half a bit of budget a macroblock (5 kbps at 300 macroblocks and 30 frames a second)
1521        // is mostly the frame's own modes, so no band is offered and the refresh is held whole.
1522        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    /// A VP8 band held at a fine quantizer after coarse motion decodes as close to the picture as
1528    /// the quantizer codes it: it is not loop-filtered at the level the motion's frames left.
1529    #[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        // The picture scrolled a few rows a frame.
1535        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        // Its luma as VP8 codes it: BT.601, limited range.
1543        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        // The top quarter, away from the middle of the picture.
1559        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        // A key frame of the picture at the same quantizer, from a session with no motion behind it.
1566        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    /// A VP9 cleanup frame capped at `bytes` is coded at the rate that spends them in the
1581    /// cleanup's frame time, the frame after at the session's again; VP8, which holds its cleanup
1582    /// at a quantizer, keeps its rate.
1583    #[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    /// A capped constant quality under a peak its picture never reaches codes every frame at the
1608    /// pinned quantizer, as the uncapped session does: a key frame asked for mid-stream and the
1609    /// frames after it, and the cleanup of a still screen at the paint-over quality.
1610    #[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                // A still screen from frame 30, cleaned up at the paint-over quality.
1625                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    /// A switch to the peak libvpx refuses keeps the frame that asked for it and leaves the
1656    /// session as it ran, the peak's state with it.
1657    #[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            // libvpx refuses a configuration that raises the frames it lags by.
1680            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    /// A capped constant quality takes each new peak live, as congestion control steers it: no
1707    /// key frame, and a scroll after a cut to 60% of what it ran at under the high peak codes
1708    /// within the lower one over the five seconds after it, binding once it overflows the peak.
1709    #[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}