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 VP9 session runs in libvpx's flexible mode,
15//! where the codec's eight buffers are addressed by slot, so a frame a client lost leaves the
16//! predictions the way a decoded picture buffer allows; a VP8 session steers its three buffers
17//! with the per-frame reference and refresh flags. What a lost frame would leave behind besides
18//! its picture is kept out too: VP8's entropy probabilities persist across frames unless a
19//! frame declines to update them, so no frame does, and a VP9 decoder takes the previous
20//! frame's motion vectors and probability contexts whatever the references say, which only the
21//! codec's error-resilient mode switches off. VP8 runs error-resilient too, since its
22//! constant-rate control otherwise refreshes GOLDEN on a schedule of its own, whatever a frame's
23//! flags say.
24
25use std::ffi::{CStr, c_int, c_long, c_void};
26use std::ptr;
27
28use codec_sys::vpx::*;
29
30use super::codec::{
31    Codec, FRAME_ANCHOR, VIDEO_HEADER_LEN, frame_type_from_key, push_video_header, vp8_is_key,
32    vp9_is_key, vpx_level,
33};
34use super::reference::{Reference, ReferenceSlots, SlotPlan, SlotRefresh};
35use super::session::{Pending, Planes, Quality, RateSettings, encode_threads};
36use crate::RustCaptureSettings;
37
38/// The rate target, in kbit/s, a pinned-quantizer session names and never reaches.
39const RATE_CEILING_KBPS: u32 = 100_000;
40
41/// The temporal layers a VP9 session declares. Every frame is coded in the base layer; the
42/// second exists because libvpx sets up and re-targets its per-layer rate control, which the
43/// flexible reference mode runs on, only for a session with more than one layer.
44const VP9_LAYERS: u32 = 2;
45/// The libvpx quantizer levels a session leaves to the library's own bounds.
46const VP8_DEFAULT_MIN_LEVEL: u32 = 4;
47/// libvpx's `aq_mode` for the cyclic refresh.
48const CYCLIC_REFRESH_AQ: c_int = 3;
49/// The squared error under which libvpx's VP8 skips a macroblock as unchanged (its static
50/// threshold), so a still region is left as it is rather than coded again every frame: at 0.25
51/// Mbit/s a 1080p texture under a moving box took 3.0 MB for its 3 s of motion against 5.8, at
52/// 42.0 dB against 41.4, and 304 kB still against 1185. Scrolling text codes the same.
53const VP8_STATIC_THRESHOLD: c_int = 100;
54/// VP8's token partitions, eight, the most it has: a client decoding in software spreads a frame
55/// across them, as across H.264's slices, where one leaves it on one thread. They cost nothing
56/// measurable: at 1080p text, a game frame and dense text code within 0.1% of the bytes and
57/// 0.02 dB of one partition while scrolling, and within 2% still.
58const VP8_TOKEN_PARTITIONS: c_int = VP8_EIGHT_TOKENPARTITION as c_int;
59/// The frame budget a macroblock, in bits, from which VP8 sweeps its refresh in bands
60/// (`VpxEncoder::band_size`): three times what a frame that skips every macroblock costs.
61const BAND_BITS_PER_MB: f64 = 1.0;
62
63/// Whether the loaded libvpx codes VP9 4:4:4 in the flexible mode the sessions run: before 1.13
64/// its layer machinery re-sizes every frame at 4:2:0, so a profile 1 session writes headers
65/// over pictures no decoder takes.
66pub fn encodes_444() -> bool {
67    (unsafe { vpx_codec_version() }) >= (1 << 16) | (13 << 8)
68}
69
70/// Whether the loaded libvpx was built with VP9's high bit depth, which its 10-bit profiles
71/// need.
72pub fn encodes_ten_bit() -> bool {
73    (unsafe { vpx_codec_get_caps(vpx_codec_vp9_cx()) })
74        & VPX_CODEC_CAP_HIGHBITDEPTH as vpx_codec_caps_t
75        != 0
76}
77
78/// One libvpx session for one capture.
79pub struct VpxEncoder {
80    codec: Codec,
81    ctx: vpx_codec_ctx_t,
82    cfg: vpx_codec_enc_cfg_t,
83    planes: Planes,
84    threads: u32,
85    quality: Quality,
86    rate: RateSettings,
87    omit_headers: bool,
88    references: ReferenceSlots,
89    last_reference: Reference,
90    pending: Pending,
91    /// The slot plan of the frame being encoded, recorded once its packet returns.
92    plan: SlotPlan,
93    /// The quality index the next frame is held at whatever the rate control (`hold_quantizer`).
94    held: Option<u32>,
95    /// The share of the picture, from and to in raster order, the held quantizer covers, the
96    /// rest left as it is (`hold_quantizer`, VP8 only); `None` for the whole picture.
97    held_band: Option<(f64, f64)>,
98    /// The bytes of the last held frame, 0 before one (`band_size`).
99    held_bytes: usize,
100    /// One segment per macroblock for a held band (`band_roi`): 1 inside it, 0 outside.
101    band_map: Vec<u8>,
102    /// The quality index the rate control last coded a frame at, held frames aside.
103    last_quality: Option<u32>,
104    /// The bytes of the last frame the rate control coded, held frames aside.
105    last_bytes: Option<usize>,
106    /// The size and quality index of the last held key frame of a constant-rate session
107    /// (`super::held_key_start`).
108    held_key: Option<(usize, u32)>,
109}
110
111unsafe impl Send for VpxEncoder {}
112
113impl Drop for VpxEncoder {
114    fn drop(&mut self) {
115        unsafe { vpx_codec_destroy(&mut self.ctx) };
116    }
117}
118
119/// The session's last error, with libvpx's detail where it has one. The context is handed
120/// over mutable because libvpx before 1.14 declares the parameter so, though it only reads it.
121fn error(ctx: &vpx_codec_ctx_t, what: &str) -> String {
122    unsafe {
123        let detail = vpx_codec_error_detail(ptr::from_ref(ctx).cast_mut());
124        let text = CStr::from_ptr(vpx_codec_err_to_string(ctx.err)).to_string_lossy();
125        if detail.is_null() {
126            format!("{what}: {text}")
127        } else {
128            format!(
129                "{what}: {text} ({})",
130                CStr::from_ptr(detail).to_string_lossy()
131            )
132        }
133    }
134}
135
136impl VpxEncoder {
137    /// Open a VP8 or VP9 session on host frames in the byte order `rgba` names.
138    pub fn new(settings: &RustCaptureSettings, codec: Codec, rgba: bool) -> Result<Self, String> {
139        if !matches!(codec, Codec::Vp8 | Codec::Vp9) {
140            return Err(format!("libvpx encodes no {}", codec.display()));
141        }
142        let _ = rgba;
143        let fullcolor = codec == Codec::Vp9 && settings.video_fullcolor && encodes_444();
144        let ten_bit = codec == Codec::Vp9 && settings.video_bit_depth >= 10 && encodes_ten_bit();
145        let threads = encode_threads() as u32;
146        let rate = RateSettings::new(settings);
147        let quality = Quality::new(codec.quantizer(settings.video_crf));
148        let iface = unsafe {
149            if codec == Codec::Vp8 {
150                vpx_codec_vp8_cx()
151            } else {
152                vpx_codec_vp9_cx()
153            }
154        };
155        let mut cfg: vpx_codec_enc_cfg_t = unsafe { std::mem::zeroed() };
156        if unsafe { vpx_codec_enc_config_default(iface, &mut cfg, 0) } != VPX_CODEC_OK {
157            return Err("libvpx refused its default configuration".into());
158        }
159        cfg.g_w = settings.width.max(1) as u32;
160        cfg.g_h = settings.height.max(1) as u32;
161        cfg.g_timebase = vpx_rational {
162            num: rate.fps.den as i32,
163            den: rate.fps.num as i32,
164        };
165        cfg.g_threads = threads.min(64);
166        cfg.g_lag_in_frames = 0;
167        cfg.g_pass = VPX_RC_ONE_PASS;
168        cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
169        cfg.g_profile = fullcolor as u32 + 2 * ten_bit as u32;
170        cfg.g_bit_depth = if ten_bit { VPX_BITS_10 } else { VPX_BITS_8 };
171        cfg.g_input_bit_depth = if ten_bit { 10 } else { 8 };
172        cfg.rc_dropframe_thresh = 0;
173        cfg.rc_2pass_vbr_bias_pct = 50;
174        cfg.rc_2pass_vbr_minsection_pct = 100;
175        cfg.rc_2pass_vbr_maxsection_pct = 100;
176        cfg.kf_max_dist = c_int::MAX as u32;
177        if codec == Codec::Vp9 {
178            cfg.ss_number_layers = 1;
179            cfg.ts_number_layers = VP9_LAYERS;
180            cfg.ts_rate_decimator[..VP9_LAYERS as usize].fill(1);
181            cfg.ts_periodicity = 1;
182            cfg.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_BYPASS as c_int;
183        }
184        let mut me = Self {
185            codec,
186            ctx: unsafe { std::mem::zeroed() },
187            cfg,
188            planes: Planes::new(
189                settings.width.max(1) as usize,
190                settings.height.max(1) as usize,
191                fullcolor,
192                if ten_bit { 10 } else { 8 },
193            ),
194            threads,
195            quality,
196            rate,
197            omit_headers: settings.omit_stripe_headers,
198            references: if codec == Codec::Vp9 {
199                ReferenceSlots::vp9()
200            } else {
201                ReferenceSlots::new()
202            },
203            last_reference: Reference::Untracked,
204            pending: Pending::default(),
205            plan: SlotPlan::KEY,
206            held: None,
207            held_band: None,
208            held_bytes: 0,
209            band_map: Vec::new(),
210            last_quality: None,
211            last_bytes: None,
212            held_key: None,
213        };
214        let q = me.quality.current;
215        me.program_rate(rate, q);
216        let res = unsafe {
217            vpx_codec_enc_init_ver(
218                &mut me.ctx,
219                iface,
220                &me.cfg,
221                if ten_bit {
222                    VPX_CODEC_USE_HIGHBITDEPTH as vpx_codec_flags_t
223                } else {
224                    0
225                },
226                VPX_ENCODER_ABI_VERSION as c_int,
227            )
228        };
229        if res != VPX_CODEC_OK {
230            return Err(error(&me.ctx, "libvpx refused the session"));
231        }
232        me.control(VP8E_SET_CPUUSED, if codec == Codec::Vp9 { 8 } else { 16 })?;
233        me.control(VP8E_SET_ENABLEAUTOALTREF, 0)?;
234        me.control(
235            VP8E_SET_STATIC_THRESHOLD,
236            if codec == Codec::Vp8 {
237                VP8_STATIC_THRESHOLD
238            } else {
239                0
240            },
241        )?;
242        me.control(VP8E_SET_MAX_INTRA_BITRATE_PCT, 0)?;
243        if codec == Codec::Vp8 {
244            me.control(VP8E_SET_NOISE_SENSITIVITY, 0)?;
245            me.control(VP8E_SET_TOKEN_PARTITIONS, VP8_TOKEN_PARTITIONS)?;
246        } else {
247            // Column threading is per tile and VP9's narrowest tile is 256 pixels, so the
248            // width sets how many columns the encode can spread across.
249            let tile_columns = (settings.width / 256).max(1).ilog2().min(6);
250            me.control(VP9E_SET_TILE_COLUMNS, tile_columns as c_int)?;
251            me.control(VP9E_SET_FRAME_PARALLEL_DECODING, 0)?;
252            me.control(VP9E_SET_COLOR_SPACE, VPX_CS_BT_709 as c_int)?;
253            me.control(VP9E_SET_COLOR_RANGE, VPX_CR_STUDIO_RANGE as c_int)?;
254            me.control(VP9E_SET_TARGET_LEVEL, 255)?;
255            me.control(VP9E_SET_ROW_MT, 1)?;
256            me.control(VP9E_SET_TUNE_CONTENT, VP9E_CONTENT_SCREEN as c_int)?;
257            me.control(VP9E_SET_SVC, 1)?;
258            me.program_layer()?;
259            me.program_refresh()?;
260        }
261        Ok(me)
262    }
263
264    fn control(&mut self, id: vp8e_enc_control_id, value: c_int) -> Result<(), String> {
265        if unsafe { vpx_codec_control_(&mut self.ctx, id as c_int, value) } != VPX_CODEC_OK {
266            return Err(error(&self.ctx, &format!("libvpx refused control {id}")));
267        }
268        Ok(())
269    }
270
271    /// A constant-rate VP9 session refines a still screen through libvpx's cyclic refresh, a
272    /// share of the blocks a frame within the budget. Without it the rate control codes the
273    /// whole screen finer in single frames: at 2 Mbit/s at 1080p up to 44 budgets each, a
274    /// third over the rate for 30 s, for a picture 4.6 dB coarser. A pinned quantizer takes
275    /// none, since the refresh codes its blocks at another.
276    fn program_refresh(&mut self) -> Result<(), String> {
277        if self.codec != Codec::Vp9 {
278            return Ok(());
279        }
280        let mode = if self.rate.cbr { CYCLIC_REFRESH_AQ } else { 0 };
281        self.control(VP9E_SET_AQ_MODE, mode)
282    }
283
284    /// The layers of a VP9 session, whose quantizer bounds libvpx reads from here rather
285    /// than from the configuration once the flexible mode is on.
286    fn program_layer(&mut self) -> Result<(), String> {
287        let mut layer: vpx_svc_parameters = unsafe { std::mem::zeroed() };
288        for i in 0..VP9_LAYERS as usize {
289            layer.max_quantizers[i] = self.cfg.rc_max_quantizer as c_int;
290            layer.min_quantizers[i] = self.cfg.rc_min_quantizer as c_int;
291            layer.speed_per_layer[i] = 8;
292        }
293        layer.scaling_factor_num[0] = 1;
294        layer.scaling_factor_den[0] = 1;
295        layer.temporal_layering_mode = VP9E_TEMPORAL_LAYERING_MODE_BYPASS as c_int;
296        let res = unsafe {
297            vpx_codec_control_(
298                &mut self.ctx,
299                VP9E_SET_SVC_PARAMETERS as c_int,
300                &mut layer as *mut vpx_svc_parameters as *mut c_void,
301            )
302        };
303        if res != VPX_CODEC_OK {
304            return Err(error(&self.ctx, "libvpx refused the layer parameters"));
305        }
306        Ok(())
307    }
308
309    /// Write `rate` and the quantizer `q` (the codec's own domain) into the configuration:
310    /// a constant rate holds the target and VBV with the quantizer bounds the settings name,
311    /// a pinned quantizer names a rate it never reaches with both bounds at the quantizer.
312    fn program_rate(&mut self, rate: RateSettings, q: u32) {
313        let cfg = &mut self.cfg;
314        cfg.g_timebase = vpx_rational {
315            num: rate.fps.den as i32,
316            den: rate.fps.num as i32,
317        };
318        cfg.rc_end_usage = VPX_CBR;
319        if rate.cbr {
320            let kbps = (rate.bps() / 1000).clamp(1, u32::MAX as u64) as u32;
321            cfg.rc_target_bitrate = kbps;
322            let (lo, hi) = (
323                self.codec.quantizer_bound(rate.min_qp),
324                self.codec.quantizer_bound(rate.max_qp),
325            );
326            cfg.rc_min_quantizer = if lo > 0 {
327                vpx_level(self.codec, lo)
328            } else if self.codec == Codec::Vp8 {
329                VP8_DEFAULT_MIN_LEVEL
330            } else {
331                0
332            };
333            cfg.rc_max_quantizer = if hi > 0 {
334                vpx_level(self.codec, hi)
335            } else {
336                63
337            };
338            let ms = (rate.vbv() as u64 * 1000 / rate.bps().max(1)) as u32;
339            cfg.rc_buf_sz = ms;
340            cfg.rc_buf_initial_sz = ms;
341            cfg.rc_buf_optimal_sz = ms * 5 / 6;
342        } else {
343            cfg.rc_target_bitrate = RATE_CEILING_KBPS;
344            cfg.rc_min_quantizer = vpx_level(self.codec, q);
345            cfg.rc_max_quantizer = vpx_level(self.codec, q);
346        }
347        cfg.ss_target_bitrate[0] = cfg.rc_target_bitrate;
348        cfg.ts_target_bitrate[..VP9_LAYERS as usize].fill(cfg.rc_target_bitrate);
349        cfg.layer_target_bitrate[..VP9_LAYERS as usize].fill(cfg.rc_target_bitrate);
350    }
351
352    /// Apply the configuration to the running session.
353    fn reconfigure(&mut self) -> Result<(), String> {
354        if unsafe { vpx_codec_enc_config_set(&mut self.ctx, &self.cfg) } != VPX_CODEC_OK {
355            return Err(error(&self.ctx, "libvpx refused the new configuration"));
356        }
357        if self.codec == Codec::Vp9 {
358            self.program_layer()?;
359        }
360        Ok(())
361    }
362
363    pub fn codec(&self) -> Codec {
364        self.codec
365    }
366
367    /// The library behind the session, as the logs name it.
368    pub fn library(&self) -> &'static str {
369        "libvpx"
370    }
371
372    /// Whether the session carries 4:4:4 (VP9 profile 1, or 3 at 10 bits).
373    pub fn is_fullcolor(&self) -> bool {
374        self.planes.i444
375    }
376
377    /// The bits per sample the session codes: 10 in VP9 profiles 2 and 3.
378    pub fn bit_depth(&self) -> u32 {
379        self.planes.bit_depth
380    }
381
382    /// VP9 keeps the limited range of its 4:2:0 in profile 1, so the decoder hint a client
383    /// sends for it stays true; VP8 has no other.
384    pub fn is_full_range(&self) -> bool {
385        false
386    }
387
388    /// The frame the last encoded frame predicted from.
389    pub fn last_reference(&self) -> Reference {
390        self.last_reference
391    }
392
393    /// Leave frame `frame_id` and every frame after it out of the predictions; the next frame
394    /// predicts from an earlier buffer, or is a key frame when none is left.
395    pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
396        self.references.invalidate(frame_id);
397        true
398    }
399
400    /// Every consumer holds frame `frame_id`, or where not `held` was sent it
401    /// (`ReferenceSlots::acknowledge`).
402    pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
403        self.references.acknowledge(frame_id, held);
404    }
405
406    /// The quality index the rate control last coded a frame at, held frames aside.
407    pub fn last_quality(&self) -> Option<u32> {
408        self.last_quality
409    }
410
411    /// The bytes of the last frame the rate control coded, held frames aside, where a
412    /// constant-rate cleanup runs through that rate control (`holds_quantizer`).
413    pub fn last_size(&self) -> Option<usize> {
414        self.last_bytes.filter(|_| !self.holds_quantizer())
415    }
416
417    /// Whether the cleanup of a still screen holds a frame at its quantizer: at a constant
418    /// quantizer, and at a constant rate for VP8, whose rate control leaves a still screen as
419    /// it is (a screen of text at 2 Mbit/s at 1080p stayed at 33.6 dB, where the held
420    /// refresh reached 42.8). VP9's refines one, so its constant-rate cleanup runs through
421    /// the rate control instead: 43.6 dB in 2.5 s with no frame over 75 kB, where the held
422    /// refresh was one of 347 kB.
423    pub fn holds_quantizer(&self) -> bool {
424        !self.rate.cbr || self.codec == Codec::Vp8
425    }
426
427    /// Encode the next frame at the quantizer the quality index `crf` selects, whatever the rate
428    /// control, and put the session's own rate control back for the frame after: the cleanup of a
429    /// still screen. A held key frame of a constant-rate session starts at the quantizer the
430    /// last one says fits `HELD_KEY_BUDGET_S` of the target (`held_key_start`), and one that
431    /// comes out past it is coded again, as a key frame, at the coarser quantizer
432    /// `held_key_retry` picks.
433    /// `band`, the share of the picture from and to in raster order, confines the quantizer to
434    /// the macroblocks it covers where the session holds a band (`band_size`), and the rest of
435    /// the frame is left as the reference has it.
436    pub fn hold_quantizer(&mut self, crf: u32, band: Option<(f64, f64)>) {
437        self.held = Some(crf);
438        self.held_band = band.filter(|_| self.band_size().is_some());
439    }
440
441    /// The bytes of the last held frame (0 before one), where the session holds a band of a
442    /// frame at a quantizer: VP8, through libvpx's region-of-interest map (`band_roi`), at a rate
443    /// that leaves a band room in a frame. A VP8 frame codes every macroblock's mode even where
444    /// it skips them all, about a third of a bit each at 720p and 1080p, so below
445    /// `BAND_BITS_PER_MB` of frame budget a band a frame is mostly that and the sweep cannot keep
446    /// the rate: 1080p text at 0.25 Mbit/s (half a bit) ended at 29.5 dB in bands against 42.0
447    /// held whole, where 720p at 0.25 (1.2 bits) ended at 41.5 against 42.0 with the worst wait
448    /// at the target 5.0 s against 18.5. The refresh is held whole below it.
449    pub fn band_size(&self) -> Option<usize> {
450        let blocks = f64::from(self.cfg.g_w.div_ceil(16) * self.cfg.g_h.div_ceil(16));
451        let fps = f64::from(self.rate.fps.num) / f64::from(self.rate.fps.den.max(1));
452        let budget_bits = self.rate.bps() as f64 / fps.max(1.0);
453        (self.codec == Codec::Vp8 && budget_bits >= BAND_BITS_PER_MB * blocks)
454            .then_some(self.held_bytes)
455    }
456
457    /// Hand libvpx the region map of a held band (`band`, from and to in raster order of the
458    /// macroblocks, as NVENC's delta map counts them), or clear it with `None`. The band is
459    /// coded at the frame's own quantizer, the one held, with the static threshold the session
460    /// keeps (`VP8_STATIC_THRESHOLD`); every other macroblock is skipped, a static threshold
461    /// no prediction error reaches making it a copy of the reference, so the frame carries the
462    /// band and nothing else. VP8's segments take a quantizer delta of 63 at most, so the band
463    /// is not held finer than a coarse rest the way NVENC's map holds it: the rest is skipped
464    /// outright, which no quantizer of its own would do better.
465    fn band_roi(&mut self, band: Option<(f64, f64)>) -> Result<(), String> {
466        let cols = self.cfg.g_w.div_ceil(16);
467        let rows = self.cfg.g_h.div_ceil(16);
468        let mut roi: vpx_roi_map_t = unsafe { std::mem::zeroed() };
469        roi.rows = rows;
470        roi.cols = cols;
471        if let Some((from, to)) = band {
472            let blocks = (rows * cols) as usize;
473            let first = ((from.clamp(0.0, 1.0) * blocks as f64).floor() as usize).min(blocks);
474            let last = ((to.clamp(0.0, 1.0) * blocks as f64).ceil() as usize).clamp(first, blocks);
475            self.band_map.clear();
476            self.band_map.resize(blocks, 0);
477            self.band_map[first..last].fill(1);
478            roi.roi_map = self.band_map.as_mut_ptr();
479            roi.delta_q[0] = 63;
480            // No loop filter either, which would still touch a skipped block's edges.
481            roi.delta_lf[0] = -63;
482            roi.static_threshold[0] = u32::MAX >> 1;
483            roi.static_threshold[1] = VP8_STATIC_THRESHOLD as u32;
484        }
485        let res = unsafe {
486            vpx_codec_control_(
487                &mut self.ctx,
488                VP8E_SET_ROI_MAP as c_int,
489                &mut roi as *mut vpx_roi_map_t as *mut c_void,
490            )
491        };
492        if res != VPX_CODEC_OK {
493            return Err(error(&self.ctx, "libvpx refused the band's region map"));
494        }
495        Ok(())
496    }
497
498    /// Pin the configuration's quantizer bounds to the one the quality index `crf` selects.
499    fn pin_quantizer(&mut self, crf: u32) -> Result<(), String> {
500        let level = vpx_level(self.codec, self.codec.quantizer(crf as i32));
501        self.cfg.rc_min_quantizer = level;
502        self.cfg.rc_max_quantizer = level;
503        self.reconfigure()
504    }
505
506    /// Re-program the rate control when a rate or frame-rate setting changed, live.
507    pub fn reconfigure_rate(&mut self, settings: &RustCaptureSettings) -> Result<(), String> {
508        let Some(rate) = self.rate.changed(settings) else {
509            return Ok(());
510        };
511        self.rate = rate;
512        self.program_rate(rate, self.quality.current);
513        self.reconfigure()?;
514        self.program_refresh()
515    }
516
517    /// Encode one packed host frame at the quality index `crf`, as a key frame when
518    /// `force_idr` or when no reference is left to predict from.
519    pub fn encode_host(
520        &mut self,
521        pixels: &[u8],
522        stride: usize,
523        rgba: bool,
524        frame_number: u64,
525        crf: u32,
526        force_idr: bool,
527    ) -> Result<Vec<u8>, String> {
528        if !self.rate.cbr
529            && let Some(q) = self.quality.update(self.codec.quantizer(crf as i32))
530        {
531            self.program_rate(self.rate, q);
532            self.reconfigure()?;
533        }
534        let capped = force_idr && self.rate.cbr;
535        let cap = (self.rate.bps() as f64 / 8.0 * super::HELD_KEY_BUDGET_S) as usize;
536        let held = self.held.take().map(|crf| {
537            if capped {
538                super::held_key_start(crf, self.held_key, cap)
539            } else {
540                crf
541            }
542        });
543        if let Some(crf) = held {
544            self.pin_quantizer(crf)?;
545        }
546        // A key frame skips nothing, so a band is held on a predicted one alone.
547        let band = self
548            .held_band
549            .take()
550            .filter(|_| held.is_some() && !force_idr);
551        if band.is_some() {
552            self.band_roi(band)?;
553        }
554        let quality = self.last_quality;
555        let mut result = self.encode_frame(pixels, stride, rgba, frame_number, force_idr);
556        if band.is_some() {
557            self.band_roi(None)?;
558        }
559        let mut coded_at = held;
560        let retry = match (held, &result) {
561            (Some(crf), Ok(coded)) if capped => super::held_key_retry(crf, coded.len(), cap),
562            _ => None,
563        };
564        if let Some(coarser) = retry {
565            self.pin_quantizer(coarser)?;
566            result = self.encode_frame(pixels, stride, rgba, frame_number, true);
567            coded_at = Some(coarser);
568        }
569        if let (true, Some(crf), Ok(coded)) = (capped, coded_at, &result) {
570            self.held_key = Some((coded.len(), crf));
571        }
572        if held.is_some() {
573            if let Ok(coded) = &result {
574                self.held_bytes = coded.len();
575            }
576            self.last_quality = quality;
577            self.program_rate(self.rate, self.quality.current);
578            self.reconfigure()?;
579        } else if let Ok(coded) = &result {
580            self.last_bytes = Some(coded.len());
581        }
582        result
583    }
584
585    /// Encode one packed host frame at the quantizer the configuration holds.
586    fn encode_frame(
587        &mut self,
588        pixels: &[u8],
589        stride: usize,
590        rgba: bool,
591        frame_number: u64,
592        force_idr: bool,
593    ) -> Result<Vec<u8>, String> {
594        let bt601 = self.codec == Codec::Vp8;
595        self.planes
596            .convert(pixels, stride, rgba, false, bt601, self.threads as usize)?;
597
598        let key = force_idr || !self.references.has_reference();
599        let pts = self.references.next_pts();
600        let mut flags: c_long = if key { VPX_EFLAG_FORCE_KF as c_long } else { 0 };
601        self.plan = self.references.plan(key);
602        match self.codec {
603            Codec::Vp9 => {
604                let mut refs: vpx_svc_ref_frame_config = unsafe { std::mem::zeroed() };
605                refs.update_buffer_slot[0] = self.plan.refresh.0 as c_int;
606                if !key {
607                    let slot = self.plan.predict_from.trailing_zeros() as c_int;
608                    refs.lst_fb_idx[0] = slot;
609                    refs.gld_fb_idx[0] = slot;
610                    refs.alt_fb_idx[0] = slot;
611                    refs.reference_last[0] = 1;
612                }
613                let res = unsafe {
614                    vpx_codec_control_(
615                        &mut self.ctx,
616                        VP9E_SET_SVC_REF_FRAME_CONFIG as c_int,
617                        &mut refs as *mut vpx_svc_ref_frame_config as *mut c_void,
618                    )
619                };
620                if res != VPX_CODEC_OK {
621                    return Err(error(
622                        &self.ctx,
623                        "libvpx refused the reference configuration",
624                    ));
625                }
626            }
627            _ => {
628                flags |= VP8_EFLAG_NO_UPD_ENTROPY as c_long;
629                if !key {
630                    for (buffer, no_ref, no_upd, force) in [
631                        (
632                            SlotRefresh::LAST,
633                            VP8_EFLAG_NO_REF_LAST,
634                            VP8_EFLAG_NO_UPD_LAST,
635                            0,
636                        ),
637                        (
638                            SlotRefresh::GOLDEN,
639                            VP8_EFLAG_NO_REF_GF,
640                            VP8_EFLAG_NO_UPD_GF,
641                            VP8_EFLAG_FORCE_GF,
642                        ),
643                        (
644                            SlotRefresh::ALTREF,
645                            VP8_EFLAG_NO_REF_ARF,
646                            VP8_EFLAG_NO_UPD_ARF,
647                            VP8_EFLAG_FORCE_ARF,
648                        ),
649                    ] {
650                        if self.plan.predict_from != buffer {
651                            flags |= no_ref as c_long;
652                        }
653                        if self.plan.refresh.refreshes(buffer) {
654                            flags |= force as c_long;
655                        } else {
656                            flags |= no_upd as c_long;
657                        }
658                    }
659                }
660            }
661        }
662
663        let mut img: vpx_image = unsafe { std::mem::zeroed() };
664        let fmt = match (self.planes.i444, self.planes.bit_depth > 8) {
665            (true, true) => VPX_IMG_FMT_I44416,
666            (true, false) => VPX_IMG_FMT_I444,
667            (false, true) => VPX_IMG_FMT_I42016,
668            (false, false) => VPX_IMG_FMT_I420,
669        };
670        let [y, u, v] = self.planes.pointers();
671        unsafe {
672            vpx_img_wrap(
673                &mut img,
674                fmt,
675                self.planes.width as u32,
676                self.planes.height as u32,
677                1,
678                y,
679            );
680        }
681        let bytes = self.planes.sample_bytes();
682        img.planes[0] = y;
683        img.planes[1] = u;
684        img.planes[2] = v;
685        img.stride[0] = (self.planes.width * bytes) as c_int;
686        img.stride[1] = (self.planes.chroma_width() * bytes) as c_int;
687        img.stride[2] = (self.planes.chroma_width() * bytes) as c_int;
688        img.bit_depth = self.planes.bit_depth;
689        img.range = VPX_CR_STUDIO_RANGE;
690        self.pending.push(pts, frame_number as u16);
691        let res = unsafe {
692            vpx_codec_encode(
693                &mut self.ctx,
694                &img,
695                pts as i64,
696                1,
697                flags,
698                VPX_DL_REALTIME as u64,
699            )
700        };
701        if res != VPX_CODEC_OK {
702            self.pending.undo();
703            return Err(error(&self.ctx, "libvpx refused the frame"));
704        }
705
706        let mut output = Vec::new();
707        let mut iter: vpx_codec_iter_t = ptr::null_mut();
708        loop {
709            let pkt = unsafe { vpx_codec_get_cx_data(&mut self.ctx, &mut iter) };
710            if pkt.is_null() {
711                break;
712            }
713            let pkt = unsafe { &*pkt };
714            if pkt.kind != VPX_CODEC_CX_FRAME_PKT {
715                continue;
716            }
717            let frame = unsafe { pkt.data.frame };
718            let bytes = unsafe { std::slice::from_raw_parts(frame.buf as *const u8, frame.sz) };
719            let is_key = if self.codec == Codec::Vp8 {
720                vp8_is_key(bytes)
721            } else {
722                vp9_is_key(bytes)
723            };
724            let id = self
725                .pending
726                .take(frame.pts as u64)
727                .unwrap_or(frame_number as u16);
728            let plan = if is_key {
729                self.references.plan(true)
730            } else {
731                self.plan
732            };
733            let anchor = if self.references.shared_anchor(plan) {
734                FRAME_ANCHOR
735            } else {
736                0
737            };
738            self.last_reference = self.references.record(id, plan);
739            if !self.omit_headers {
740                output.reserve(VIDEO_HEADER_LEN + bytes.len());
741                push_video_header(
742                    &mut output,
743                    self.codec,
744                    frame_type_from_key(is_key) | anchor,
745                    id,
746                    0,
747                    self.planes.width as u16,
748                    self.planes.height as u16,
749                    self.last_reference,
750                );
751            }
752            output.extend_from_slice(bytes);
753        }
754        if !output.is_empty() {
755            let mut q: c_int = 0;
756            let res = unsafe {
757                vpx_codec_control_(
758                    &mut self.ctx,
759                    VP8E_GET_LAST_QUANTIZER as c_int,
760                    &mut q as *mut c_int,
761                )
762            };
763            self.last_quality =
764                (res == VPX_CODEC_OK).then(|| self.codec.quality_index(q.max(0) as u32));
765        }
766        Ok(output)
767    }
768}
769
770#[cfg(test)]
771mod tests {
772    use super::*;
773    use crate::encoders::codec::{FRAME_DELTA, FRAME_KEY, parse_video_type};
774    use crate::webcam::decode::{Decoder as _, VideoDecoder};
775
776    const W: usize = 320;
777    const H: usize = 240;
778
779    fn settings(codec: Codec) -> RustCaptureSettings {
780        RustCaptureSettings {
781            width: W as i32,
782            height: H as i32,
783            target_fps: 30.0,
784            codec,
785            video_crf: 25,
786            use_cpu: true,
787            ..Default::default()
788        }
789    }
790
791    /// A frame whose luma spells its index in blocks, so a decoded picture names the frame it is.
792    fn frame(t: usize) -> Vec<u8> {
793        let mut f = vec![0u8; W * H * 4];
794        let (bx, by) = ((t * 9) % (W - 40), (t * 5) % (H - 30));
795        for y in 0..H {
796            for x in 0..W {
797                let i = (y * W + x) * 4;
798                let g = ((x * 255) / W) as u8;
799                let cell = (x / 8 + y / 12) % 3 == 0 && x % 8 < 6 && y % 12 < 9;
800                let (b, gr, r) = if x >= bx && x < bx + 40 && y >= by && y < by + 30 {
801                    (40, 220, 250)
802                } else if cell {
803                    (30, 30, 30)
804                } else {
805                    (g, 200 - g / 2, 120)
806                };
807                f[i..i + 4].copy_from_slice(&[b, gr, r, 255]);
808            }
809        }
810        f
811    }
812
813    fn luma_distance(
814        a: &crate::webcam::convert::I420View<'_>,
815        b: &crate::webcam::convert::I420View<'_>,
816    ) -> f64 {
817        a.y.chunks(a.y_stride)
818            .zip(b.y.chunks(b.y_stride))
819            .take(a.height)
820            .flat_map(|(ra, rb)| {
821                ra[..a.width]
822                    .iter()
823                    .zip(&rb[..a.width])
824                    .map(|(&x, &y)| (x as f64 - y as f64).abs())
825            })
826            .sum::<f64>()
827            / (a.width * a.height) as f64
828    }
829
830    /// A frame a client lost is left out of the predictions: the next frame names the newest
831    /// frame before it, and a decoder that never saw the lost frames decodes it exactly as one
832    /// that saw everything. VP9 reaches back through its recent frames, VP8 through its anchors.
833    /// Both codecs count time in frames of the capture's rate, as the fraction it names, a live
834    /// change of rate included.
835    #[test]
836    fn the_time_base_is_one_frame_of_the_rate() {
837        for codec in [Codec::Vp8, Codec::Vp9] {
838            for (num, den) in [(60000i32, 1001i32), (120000, 1001), (144000, 1001), (60, 1)] {
839                let fps = num as f64 / den as f64;
840                let mut enc = VpxEncoder::new(
841                    &RustCaptureSettings {
842                        target_fps: fps,
843                        ..settings(codec)
844                    },
845                    codec,
846                    false,
847                )
848                .expect("session");
849                assert_eq!(
850                    (enc.cfg.g_timebase.num, enc.cfg.g_timebase.den),
851                    (den, num),
852                    "{codec:?}"
853                );
854                enc.reconfigure_rate(&RustCaptureSettings {
855                    target_fps: 30.0,
856                    ..settings(codec)
857                })
858                .expect("30 fps");
859                assert_eq!(
860                    (enc.cfg.g_timebase.num, enc.cfg.g_timebase.den),
861                    (1, 30),
862                    "{codec:?}"
863                );
864            }
865        }
866    }
867
868    #[test]
869    fn a_lost_frame_is_predicted_past() {
870        for codec in [Codec::Vp8, Codec::Vp9] {
871            let s = settings(codec);
872            let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
873            let mut frames = Vec::new();
874            for t in 0..8u64 {
875                let out = enc
876                    .encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
877                    .expect("encode");
878                assert_eq!(
879                    parse_video_type(out[1]).map(|(_, k)| k),
880                    Some(if t == 0 { FRAME_KEY } else { FRAME_DELTA }),
881                    "{codec:?} {t}"
882                );
883                assert_eq!(
884                    enc.last_reference(),
885                    if t == 0 {
886                        Reference::None
887                    } else {
888                        Reference::Frame(t as u16 - 1)
889                    },
890                    "{codec:?} {t}"
891                );
892                frames.push(out);
893            }
894            // Frame 5 is reported lost once 6 and 7 have gone out: VP9 predicts from 4 through
895            // its slots, VP8 from its golden anchor, which still holds the key frame.
896            assert!(enc.invalidate_reference(5));
897            let out = enc
898                .encode_host(&frame(8), W * 4, false, 8, 25, false)
899                .expect("encode");
900            let anchor = if codec == Codec::Vp9 { 4 } else { 0 };
901            assert_eq!(enc.last_reference(), Reference::Frame(anchor), "{codec:?}");
902            assert_eq!(
903                parse_video_type(out[1]).map(|(_, k)| k),
904                Some(FRAME_DELTA),
905                "{codec:?}"
906            );
907            frames.push(out);
908            frames.push(
909                enc.encode_host(&frame(9), W * 4, false, 9, 25, false)
910                    .expect("encode"),
911            );
912            assert_eq!(enc.last_reference(), Reference::Frame(8), "{codec:?}");
913            let (mut whole, mut lossy) = (
914                VideoDecoder::new(codec).unwrap(),
915                VideoDecoder::new(codec).unwrap(),
916            );
917            for (i, f) in frames.iter().enumerate() {
918                assert!(
919                    whole.decode(&f[VIDEO_HEADER_LEN..]).expect("decode"),
920                    "{codec:?} frame {i}"
921                );
922                if !(5..8).contains(&i) {
923                    assert!(
924                        lossy
925                            .decode(&f[VIDEO_HEADER_LEN..])
926                            .expect("decode without 5-7"),
927                        "{codec:?} frame {i}"
928                    );
929                }
930            }
931            let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
932            assert!(
933                apart == 0.0,
934                "{codec:?}: the decoder that lost frames 5-7 shows frame 9 {apart:.2} off the one that saw them"
935            );
936        }
937    }
938
939    /// A loss ten frames deep, past the last eight frames, is predicted past from an anchor, and
940    /// a decoder that never saw the lost frames decodes the frames after it exactly.
941    #[test]
942    fn a_loss_ten_frames_deep_is_predicted_past() {
943        for codec in [Codec::Vp8, Codec::Vp9] {
944            let s = settings(codec);
945            let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
946            let mut frames = Vec::new();
947            for t in 0..30u64 {
948                frames.push(
949                    enc.encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
950                        .expect("encode"),
951                );
952            }
953            assert!(enc.invalidate_reference(20));
954            let out = enc
955                .encode_host(&frame(30), W * 4, false, 30, 25, false)
956                .expect("encode");
957            assert_eq!(
958                parse_video_type(out[1]).map(|(_, k)| k),
959                Some(FRAME_DELTA),
960                "{codec:?}"
961            );
962            let anchor = if codec == Codec::Vp9 { 16 } else { 12 };
963            assert_eq!(enc.last_reference(), Reference::Frame(anchor), "{codec:?}");
964            frames.push(out);
965            frames.push(
966                enc.encode_host(&frame(31), W * 4, false, 31, 25, false)
967                    .expect("encode"),
968            );
969            assert_eq!(enc.last_reference(), Reference::Frame(30), "{codec:?}");
970            let (mut whole, mut lossy) = (
971                VideoDecoder::new(codec).unwrap(),
972                VideoDecoder::new(codec).unwrap(),
973            );
974            for (i, f) in frames.iter().enumerate() {
975                assert!(
976                    whole.decode(&f[VIDEO_HEADER_LEN..]).expect("decode"),
977                    "{codec:?} frame {i}"
978                );
979                if !(20..30).contains(&i) {
980                    assert!(
981                        lossy
982                            .decode(&f[VIDEO_HEADER_LEN..])
983                            .expect("decode without 20-29"),
984                        "{codec:?} frame {i}"
985                    );
986                }
987            }
988            let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
989            assert!(
990                apart == 0.0,
991                "{codec:?}: the decoder that lost frames 20-29 shows frame 31 {apart:.2} off the one that saw them"
992            );
993        }
994    }
995
996    /// A loss no buffer reaches past costs a key frame, and the count restarts there.
997    #[test]
998    fn a_loss_past_the_window_costs_a_key_frame() {
999        for codec in [Codec::Vp8, Codec::Vp9] {
1000            let s = settings(codec);
1001            let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1002            for t in 0..40u64 {
1003                enc.encode_host(&frame(t as usize), W * 4, false, t, 25, t == 0)
1004                    .expect("encode");
1005            }
1006            assert!(enc.invalidate_reference(1));
1007            let out = enc
1008                .encode_host(&frame(40), W * 4, false, 40, 25, false)
1009                .expect("encode");
1010            assert_eq!(
1011                parse_video_type(out[1]).map(|(_, k)| k),
1012                Some(FRAME_KEY),
1013                "{codec:?}"
1014            );
1015            assert_eq!(enc.last_reference(), Reference::None);
1016            enc.encode_host(&frame(41), W * 4, false, 41, 25, false)
1017                .expect("encode");
1018            assert_eq!(enc.last_reference(), Reference::Frame(40), "{codec:?}");
1019        }
1020    }
1021
1022    /// The quantizer index read back from a VP9 frame's header is the one libvpx says it coded
1023    /// the frame at, for a key frame and a predicted one of every profile the build codes.
1024    #[test]
1025    fn a_vp9_header_names_its_quantizer_index() {
1026        use crate::encoders::codec::vp9_base_q_idx;
1027        let depths: &[i32] = if encodes_ten_bit() { &[8, 10] } else { &[8] };
1028        for (&depth, fullcolor) in depths.iter().flat_map(|d| [(d, false), (d, true)]) {
1029            let mut s = settings(Codec::Vp9);
1030            s.video_bit_depth = depth;
1031            s.video_fullcolor = fullcolor;
1032            let mut enc = VpxEncoder::new(&s, Codec::Vp9, fullcolor).expect("session");
1033            for (n, crf, key) in [(0, 40, true), (1, 15, false), (2, 28, false)] {
1034                let coded = enc
1035                    .encode_host(&frame(n), W * 4, false, n as u64, crf, key)
1036                    .unwrap();
1037                let read = vp9_base_q_idx(&coded[VIDEO_HEADER_LEN..]);
1038                assert_eq!(
1039                    read.map(|q| Codec::Vp9.quality_index(q)),
1040                    enc.last_quality(),
1041                    "{depth}-bit fullcolor {fullcolor} frame {n}: {read:?}"
1042                );
1043            }
1044        }
1045    }
1046
1047    /// A quality change reaches a running session without a key frame, and a rate change too.
1048    /// The decoded picture's luma, `W` x `H`.
1049    fn luma(dec: &VideoDecoder) -> Vec<u8> {
1050        let f = dec.frame().expect("a decoded picture");
1051        f.y.chunks(f.y_stride)
1052            .take(H)
1053            .flat_map(|row| row[..W].iter().copied())
1054            .collect()
1055    }
1056
1057    /// The mean luma difference of `a` and `b` over `rows`.
1058    fn rows_distance(a: &[u8], b: &[u8], rows: std::ops::Range<usize>) -> f64 {
1059        let n = rows.len() * W;
1060        a[rows.start * W..rows.end * W]
1061            .iter()
1062            .zip(&b[rows.start * W..rows.end * W])
1063            .map(|(&x, &y)| (x as f64 - y as f64).abs())
1064            .sum::<f64>()
1065            / n as f64
1066    }
1067
1068    /// A VP8 refresh held in a band codes the band's macroblocks alone at the held quantizer and
1069    /// leaves every other one as the reference has it: a quarter of the picture costs a fraction
1070    /// of the whole refresh, the band decodes as the whole refresh's does, and the rest exactly
1071    /// as the frame before.
1072    #[test]
1073    fn a_vp8_band_codes_its_macroblocks_alone() {
1074        let mut s = settings(Codec::Vp8);
1075        s.video_cbr_mode = true;
1076        s.video_bitrate_kbps = 30;
1077        let still = frame(0);
1078        let run = |band: Option<(f64, f64)>| {
1079            let mut enc = VpxEncoder::new(&s, Codec::Vp8, false).expect("session");
1080            let mut dec = VideoDecoder::new(Codec::Vp8).unwrap();
1081            for t in 0..6u64 {
1082                let f = enc
1083                    .encode_host(&still, W * 4, false, t, 25, t == 0)
1084                    .unwrap();
1085                assert!(dec.decode(&f[VIDEO_HEADER_LEN..]).unwrap());
1086            }
1087            let before = luma(&dec);
1088            assert_eq!(enc.band_size(), Some(0), "no frame held yet");
1089            enc.hold_quantizer(5, band);
1090            let held = enc.encode_host(&still, W * 4, false, 6, 25, false).unwrap();
1091            assert_eq!(parse_video_type(held[1]).map(|(_, k)| k), Some(FRAME_DELTA));
1092            assert!(dec.decode(&held[VIDEO_HEADER_LEN..]).unwrap());
1093            assert_eq!(enc.band_size(), Some(held.len()));
1094            // The frame after goes back to the rate control, with no band left on it.
1095            let next = enc.encode_host(&still, W * 4, false, 7, 25, false).unwrap();
1096            assert!(dec.decode(&next[VIDEO_HEADER_LEN..]).unwrap());
1097            (held.len(), before, luma(&dec))
1098        };
1099        let (whole, before, refreshed) = run(None);
1100        // 300 macroblocks of 20 a row: the first quarter is rows 0 to 2 and part of row 3.
1101        let (banded, band_before, band_after) = run(Some((0.0, 0.25)));
1102        assert!(
1103            banded * 2 < whole,
1104            "a quarter of the picture cost {banded} bytes against {whole} for the whole"
1105        );
1106        assert_eq!(
1107            before, band_before,
1108            "both runs reach the same picture before the hold"
1109        );
1110        let changed = rows_distance(&before, &refreshed, 0..48);
1111        assert!(
1112            changed > 0.5,
1113            "the held quantizer refines the picture ({changed:.2})"
1114        );
1115        let band = rows_distance(&band_after, &refreshed, 0..48);
1116        assert!(
1117            band < changed / 4.0,
1118            "the band decodes as the whole refresh's ({band:.2})"
1119        );
1120        let rest = rows_distance(&band_after, &band_before, 64..H);
1121        assert!(rest < 0.01, "the rest is left as it was ({rest:.3})");
1122        // Half a bit of budget a macroblock (5 kbps at 300 macroblocks and 30 frames a second)
1123        // is mostly the frame's own modes, so no band is offered and the refresh is held whole.
1124        s.video_bitrate_kbps = 5;
1125        let starved = VpxEncoder::new(&s, Codec::Vp8, false).expect("session");
1126        assert_eq!(starved.band_size(), None);
1127    }
1128
1129    #[test]
1130    fn quality_and_rate_move_without_a_key_frame() {
1131        for codec in [Codec::Vp8, Codec::Vp9] {
1132            let mut s = settings(codec);
1133            s.video_crf = 40;
1134            let mut enc = VpxEncoder::new(&s, codec, false).expect("session");
1135            let coarse = enc
1136                .encode_host(&frame(0), W * 4, false, 0, 40, true)
1137                .unwrap();
1138            let fine = enc
1139                .encode_host(&frame(1), W * 4, false, 1, 15, false)
1140                .unwrap();
1141            assert_eq!(enc.quality.current, codec.quantizer(15));
1142            assert_eq!(
1143                parse_video_type(fine[1]).map(|(_, k)| k),
1144                Some(FRAME_DELTA),
1145                "{codec:?}"
1146            );
1147            let again = enc
1148                .encode_host(&frame(2), W * 4, false, 2, 15, false)
1149                .unwrap();
1150            assert!(
1151                again.len() + fine.len() > coarse.len() / 2,
1152                "{codec:?}: the finer quantizer spends more"
1153            );
1154            s.target_fps = 15.0;
1155            enc.reconfigure_rate(&s).expect("rate");
1156            let after = enc
1157                .encode_host(&frame(3), W * 4, false, 3, 15, false)
1158                .unwrap();
1159            assert_eq!(
1160                parse_video_type(after[1]).map(|(_, k)| k),
1161                Some(FRAME_DELTA),
1162                "{codec:?}"
1163            );
1164            let mut dec = VideoDecoder::new(codec).unwrap();
1165            for f in [&coarse, &fine, &again, &after] {
1166                assert!(dec.decode(&f[VIDEO_HEADER_LEN..]).unwrap(), "{codec:?}");
1167            }
1168        }
1169    }
1170}