Skip to main content

pixelflux/encoders/
svtav1.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 AV1 through SVT-AV1 at preset 11 in its real-time mode (`PRESET`), one frame in and one
8//! packet out (two frames deep before 2.3), with rate control either constant-rate at the
9//! session's bitrate and VBV or a constant quantizer. A new bitrate reaches the running
10//! encoder with the next picture where the release takes one (`HAS_EVENTS`); a quality,
11//! frame-rate, or VBV change re-opens it, as a bitrate change does on an earlier release.
12//!
13//! A capped constant quantizer (`RateSettings::capped`) holds its peak beside the library
14//! (`PeakOffset`), as x264 does: the library's own capped CRF (`max_bit_rate`) needs its temporal
15//! dependency model, which the low-delay structure a real-time session codes in leaves off, so
16//! the library drops the cap there (4.3.0: "Maximum bit rate only supported with tpl on"), and
17//! its constant rate cannot be switched to live. The session opens with per-picture quantizers
18//! (`use_qp_file`) and names none while its peak does not bind, which the patched library codes
19//! at the sequence's quantizer (`patches/svtav1`, `svtav1_sequence_qp`), so a session the peak
20//! never binds codes as an uncapped one; while it binds, a picture is forced at the quality
21//! raised by the peak's offset, and from then until the encoder next opens a change of quality
22//! is forced on the pictures it applies to rather than re-opening on a key frame
23//! (`SvtAv1Encoder::forced_level`). A library without the patch runs the constant quantizer
24//! uncapped.
25//!
26//! A constant-rate session on such a release names its references: it predicts every frame
27//! from the one before in a flat structure and has the library store anchors on the schedule
28//! of `ReferenceSlots`, its GOLDEN and ALTREF, so a frame a client lost is predicted past from
29//! an anchor older than it, at `ANCHOR_PRESET`. Any other session leaves the references to the library, and a
30//! frame a client lost costs the key frame the caller codes on the refusal, which SVT-AV1
31//! before 2.0 codes only from a re-open as well.
32//!
33//! The blocks the ledger marks still and clean reach the library as the picture's
34//! constant-block map (`constant_blocks`, `EbBufferHeaderType.constant_blocks`; the patches
35//! under `patches/svtav1`): it copies only the other blocks, and those changed since its pooled
36//! buffer last held the picture, into the buffers it codes from, bit for bit the stream of a
37//! whole copy (the copy of a still 1080p frame took 0.011 ms on the calling thread where it took
38//! 0.22, and at 2560x1600 0.025 where it took 0.52), and at a constant quantizer codes a 64x64
39//! superblock of them as a zero-motion skip with no motion search, partition pass, candidate
40//! search, or transform (a still 1080p frame took 1.08 ms on six logical processors where it
41//! took 1.70, and at 2560x1600 1.88 where it took 2.86). A constant rate takes the copy alone:
42//! its refresh of a still picture at a finer quantizer runs through the blocks a map would hold,
43//! and holding them let its quantizer fall until a frame coded whole came out at many times
44//! the budget. An SVT-AV1 a distribution packages copies every picture whole and analyses every
45//! superblock.
46//!
47//! The session refines no blocks (`FrameEncoder::refines`): the library's region map sets a
48//! segment's quantizer under a constant quantizer alone, its constant rate never setting up
49//! segmentation, and the mode decision keeps the frame's lambda for every segment, so a still
50//! block skips whatever its segment's quantizer. Half a still 720p picture mapped 150 quantizer
51//! index steps finer than its qp 50 key frame came out at the map's 17 bytes a frame more, at
52//! presets 4 to 11 (SVT-AV1 2.3).
53
54use std::ffi::CString;
55use std::ptr;
56use std::sync::{Condvar, Mutex};
57
58use codec_sys::svtav1::*;
59
60use super::codec::{
61    Codec, FRAME_ANCHOR, VIDEO_HEADER_LEN, VPX_QINDEX, av1_is_key, frame_type_from_key,
62    push_video_header, vpx_level,
63};
64use super::reference::{Reference, ReferenceSlots, SlotPlan, SlotRefresh};
65use super::session::{PeakOffset, Pending, Planes, Quality, RateSettings, encode_threads};
66use crate::CaptureSettings;
67
68/// The lowest quantizer level the real-time mode runs at. Below three the library faults, and
69/// below ten it codes every other frame of a scrolling text desktop with the lower third of
70/// the picture wrong, at three times the bytes of level ten.
71const RTC_MIN_LEVEL: u32 = 10;
72
73/// The lowest quantizer level a forced picture finer than the session's quality codes at
74/// (`SvtAv1Encoder::forced_level`): the library's own floor, below which it faults.
75#[cfg(svtav1_sequence_qp)]
76const FORCED_MIN_LEVEL: u32 = 3;
77
78/// The preset frames are coded at: in the real-time mode, a quarter less encode time than preset
79/// 10 for more bytes at a fixed quantizer, which the quantizer table absorbs; the presets above
80/// it are no faster.
81const PRESET: i8 = 11;
82
83/// The preset a frame predicting from an anchor alone is coded at. At preset 11 the library keeps
84/// some blocks the picture changed since the anchor as they stand in the anchor, a stale copy
85/// every client shows (test_slow_page's bar, 14 frames back, left where it was in a 64-pixel
86/// block); preset 10 codes them. A session naming its references opens at it, since the library
87/// takes a preset change only toward a faster one, and codes every other frame at `PRESET`.
88const ANCHOR_PRESET: i8 = 10;
89
90/// The preset a constant-rate session opens at and codes a still screen's held key frame at: the
91/// slowest at which the real-time mode keeps a frame's references to two, which the anchors the
92/// session stores (`MANAGED_REFS`) need; the library refuses them below it. The real-time mode
93/// turns the screen-content tools (palette, intra block copy) off from this preset on. The
94/// session's first key frame and every other frame keep their own presets: the library takes a
95/// picture's preset change within the open one's and faster.
96const KEY_PRESET: i8 = 9;
97
98/// The highest constant-rate target the library takes, at open and live alike.
99const MAX_BITRATE_BPS: u64 = 100_000_000;
100
101/// The anchors a session that names its references has the library hold: GOLDEN and ALTREF, and
102/// the ones they replaced until a picture releases them.
103const MANAGED_REFS: u8 = 4;
104
105/// SVT-AV1 before 2.0 codes a key frame asked for mid-stream only in its random-access quality
106/// mode; a session on one re-opens the encoder for it, and the first frame answers.
107const KEYFRAME_BY_REOPEN: bool = SVT_AV1_VERSION_MAJOR < 2;
108
109/// SVT-AV1 before 1.5 drains an encoder on release even when it took no frame, and waits for a
110/// packet that never comes; a session released before its first frame feeds it one.
111const FEED_BEFORE_RELEASE: bool =
112    SVT_AV1_VERSION_MAJOR < 1 || (SVT_AV1_VERSION_MAJOR == 1 && SVT_AV1_VERSION_MINOR < 5);
113
114/// The handles alive, held while one is created or released and while the probe forks: before
115/// 4.0 the library rebuilds a process-wide processor table in every handle it creates and frees
116/// it in every one it releases, and from 4.1 handle setup takes process-wide locks that a child
117/// forked meanwhile inherits held. The probe forks only while no handle is alive, since a live
118/// session's worker threads hold that state as well.
119pub(crate) static LIFECYCLE: Mutex<usize> = Mutex::new(0);
120
121/// Notified at each handle released, for a probe waiting for none to be alive.
122pub(crate) static RELEASED: Condvar = Condvar::new();
123
124/// One SVT-AV1 session for one capture.
125pub struct SvtAv1Encoder {
126    handle: *mut EbComponentType,
127    config: Box<EbSvtAv1EncConfiguration>,
128    /// The input picture the library reads: the header and the plane pointers it carries.
129    input: Box<(EbBufferHeaderType, EbSvtIOFormat)>,
130    planes: Planes,
131    threads: i32,
132    quality: Quality,
133    rate: RateSettings,
134    omit_headers: bool,
135    fresh: bool,
136    /// The change the next picture carries to the running encoder.
137    events: Events,
138    /// The preset the running encoder codes the next picture at unless told otherwise.
139    preset: i8,
140    /// The buffers of a session that names its references, LAST the library's own.
141    references: Option<ReferenceSlots>,
142    /// The anchors the library holds, by the id each was stored under.
143    anchors: Vec<u32>,
144    last_reference: Reference,
145    next_pts: u64,
146    pending: Pending,
147    /// The quantizer the next frame is held at whatever the rate control (`hold_quantizer`).
148    held: Option<u32>,
149    /// Whether the frame after a held key frame of a constant-rate session restates its target.
150    restate_target: bool,
151    /// The 16x16 blocks of the next picture that held still and are clean, one byte each in
152    /// raster order, nonzero where constant (`constant_blocks`); empty where none is.
153    constant: Vec<u8>,
154    /// The quality index the rate control last coded a frame at, held frames aside.
155    last_quality: Option<u32>,
156    /// A capped constant quantizer's peak (`PeakOffset`), where the release forces a picture's
157    /// quantizer and keeps the sequence's for one that names none (`svtav1_sequence_qp`).
158    peak: Option<PeakOffset>,
159    /// Whether the peak has bound a frame since the encoder last opened, from which a change
160    /// of quality is forced on the pictures it applies to rather than re-opening.
161    bound_since_open: bool,
162    /// Whether the last frame was coded coarser than the session's quality for its peak.
163    last_capped: bool,
164    /// The quality index the last frame was asked for, which bounds the peak's offset.
165    last_crf: u32,
166    /// The quality index the session codes at (`quality`), from which a forced finer quality
167    /// is measured (`forced_level`).
168    quality_index: u32,
169}
170
171/// The id an anchor is stored under: its timestamp, off the zero the library reserves.
172fn anchor_id(pts: u64) -> u32 {
173    (pts % u64::from(u32::MAX)) as u32 + 1
174}
175
176unsafe impl Send for SvtAv1Encoder {}
177
178fn error(what: &str, code: EbErrorType) -> String {
179    format!("{what} (SVT-AV1 error {code:#x})")
180}
181
182impl Drop for SvtAv1Encoder {
183    fn drop(&mut self) {
184        self.close();
185    }
186}
187
188impl SvtAv1Encoder {
189    /// Open a session on host frames; `rgba` names the byte order they arrive in.
190    pub fn new(settings: &CaptureSettings, rgba: bool) -> Result<Self, String> {
191        let _ = rgba;
192        let width = settings.width.max(1) as usize;
193        let height = settings.height.max(1) as usize;
194        let rate = RateSettings::new(settings);
195        let rate = if cfg!(svtav1_sequence_qp) {
196            rate
197        } else {
198            rate.uncapped()
199        };
200        let peak = (rate.capped && !rate.cbr).then(|| {
201            PeakOffset::new(
202                rate.bitrate_kbps,
203                (rate.vbv() / 1000) as i32,
204                rate.fps.fps(),
205            )
206        });
207        let mut me = Self {
208            handle: ptr::null_mut(),
209            config: Box::new(unsafe { std::mem::zeroed() }),
210            input: Box::new(unsafe { std::mem::zeroed() }),
211            planes: Planes::new(
212                width,
213                height,
214                false,
215                if settings.video_bit_depth >= 10 {
216                    10
217                } else {
218                    8
219                },
220            ),
221            threads: encode_threads(settings),
222            quality: Quality::new(Codec::Av1.quantizer(settings.video_crf)),
223            rate,
224            omit_headers: settings.omit_stripe_headers,
225            fresh: true,
226            events: Events::default(),
227            preset: PRESET,
228            references: None,
229            anchors: Vec::new(),
230            last_reference: Reference::Untracked,
231            next_pts: 0,
232            pending: Pending::default(),
233            held: None,
234            restate_target: false,
235            constant: Vec::new(),
236            last_quality: None,
237            peak,
238            bound_since_open: false,
239            last_capped: false,
240            last_crf: settings.video_crf.max(0) as u32,
241            quality_index: settings.video_crf.max(0) as u32,
242        };
243        me.open()?;
244        Ok(me)
245    }
246
247    /// The quantizer level the library takes for a quantizer index of AV1's domain.
248    fn level(q: u32) -> u32 {
249        vpx_level(Codec::Av1, q).max(RTC_MIN_LEVEL)
250    }
251
252    fn set(&mut self, name: &str, value: &str) -> Result<(), String> {
253        let (n, v) = (CString::new(name).unwrap(), CString::new(value).unwrap());
254        let code =
255            unsafe { svt_av1_enc_parse_parameter(&mut *self.config, n.as_ptr(), v.as_ptr()) };
256        if code != EB_ErrorNone {
257            return Err(error(&format!("SVT-AV1 refused {name}={value}"), code));
258        }
259        Ok(())
260    }
261
262    /// Stand the encoder up with the live settings. The first frame it codes is a key frame.
263    fn open(&mut self) -> Result<(), String> {
264        self.close();
265        let mut live = LIFECYCLE.lock().unwrap_or_else(|e| e.into_inner());
266        let code = unsafe { init_handle(&mut self.handle, &mut *self.config) };
267        if code != EB_ErrorNone || self.handle.is_null() {
268            return Err(error("SVT-AV1 handed out no encoder handle", code));
269        }
270        *live += 1;
271        let rate = self.rate;
272        let bps = rate.bps();
273        let tracks = HAS_EVENTS && rate.cbr;
274        {
275            let cfg = &mut *self.config;
276            cfg.enc_mode = if tracks { KEY_PRESET } else { PRESET };
277            self.preset = cfg.enc_mode;
278            cfg.source_width = self.planes.width as u32;
279            cfg.source_height = self.planes.height as u32;
280            cfg.frame_rate_numerator = rate.fps.num;
281            cfg.frame_rate_denominator = rate.fps.den;
282            cfg.encoder_bit_depth = self.planes.bit_depth;
283            cfg.encoder_color_format = EB_YUV420;
284            cfg.color_primaries = EB_CICP_CP_BT_709;
285            cfg.transfer_characteristics = 1;
286            cfg.matrix_coefficients = 1;
287            cfg.color_range = 0;
288            cfg.intra_refresh_type = 2;
289            #[cfg(svtav1_sequence_qp)]
290            {
291                cfg.use_qp_file = self.peak.is_some();
292            }
293            if rate.cbr {
294                let (lo, hi) = (
295                    Codec::Av1.quantizer_bound(rate.min_qp),
296                    Codec::Av1.quantizer_bound(rate.max_qp),
297                );
298                cfg.target_bit_rate = bps as u32;
299                cfg.rate_control_mode = 2;
300                cfg.max_qp_allowed = if hi > 0 { Self::level(hi) } else { 63 };
301                cfg.min_qp_allowed = if lo > 0 {
302                    Self::level(lo)
303                } else {
304                    RTC_MIN_LEVEL
305                };
306                // The library refuses a rate-control buffer shorter than 20 ms, which the
307                // 1.5-frame VBV falls under above 75 fps.
308                let vbv = u64::from(rate.vbv()).max(bps / 50);
309                cfg.maximum_buffer_size_ms = (vbv * 1000 / bps.max(1)) as i64;
310            }
311            if tracks {
312                set_managed_refs(cfg, MANAGED_REFS);
313            }
314        }
315        // `lp` is a level of parallelism, 0..=6, not a thread count.
316        if HAS_RTC {
317            self.set("rtc", "1")?;
318        }
319        self.set("pred-struct", "1")?;
320        self.set("lookahead", "0")?;
321        self.set("keyint", "-1")?;
322        self.set("tile-columns", "0")?;
323        self.set("tile-rows", "0")?;
324        self.set("lp", &self.threads.min(6).to_string())?;
325        if tracks {
326            self.set("hierarchical-levels", "0")?;
327        }
328        if rate.cbr {
329            self.set("rc", "2")?;
330        } else {
331            self.set("rc", "0")?;
332            self.set("qp", &Self::level(self.quality.current).to_string())?;
333        }
334        let code = unsafe { svt_av1_enc_set_parameter(self.handle, &mut *self.config) };
335        if code != EB_ErrorNone {
336            return Err(error("SVT-AV1 refused the parameters", code));
337        }
338        let code = unsafe { svt_av1_enc_init(self.handle) };
339        if code != EB_ErrorNone {
340            return Err(error("SVT-AV1 refused the session", code));
341        }
342        let (header, io) = &mut *self.input;
343        *header = unsafe { std::mem::zeroed() };
344        header.size = std::mem::size_of::<EbBufferHeaderType>() as u32;
345        header.p_buffer = io as *mut EbSvtIOFormat as *mut u8;
346        let [y, u, v] = self.planes.pointers();
347        io.luma = y;
348        io.cb = u;
349        io.cr = v;
350        io.y_stride = self.planes.width as u32;
351        io.cb_stride = self.planes.chroma_width() as u32;
352        io.cr_stride = self.planes.chroma_width() as u32;
353        header.n_alloc_len = self.planes.byte_len() as u32;
354        self.fresh = true;
355        self.events = Events::default();
356        self.references = tracks.then(ReferenceSlots::new);
357        self.anchors.clear();
358        self.bound_since_open = self.peak.is_some_and(|p| p.offset > 0.0);
359        Ok(())
360    }
361
362    /// Tell the library the stream ended, drain what it still holds, and free the encoder.
363    fn close(&mut self) {
364        if self.handle.is_null() {
365            return;
366        }
367        unsafe {
368            if self.fresh && FEED_BEFORE_RELEASE {
369                let header = &mut self.input.0;
370                header.n_filled_len = header.n_alloc_len;
371                header.flags = 0;
372                header.pic_type = EB_AV1_KEY_PICTURE;
373                self.fresh = svt_av1_enc_send_picture(self.handle, header) != EB_ErrorNone;
374            }
375            if !self.fresh {
376                let mut last: EbBufferHeaderType = std::mem::zeroed();
377                last.pic_type = EB_AV1_INVALID_PICTURE;
378                last.flags = EB_BUFFERFLAG_EOS;
379                svt_av1_enc_send_picture(self.handle, &mut last);
380                loop {
381                    let mut packet: *mut EbBufferHeaderType = ptr::null_mut();
382                    if svt_av1_enc_get_packet(self.handle, &mut packet, 1) != EB_ErrorNone {
383                        break;
384                    }
385                    let Some(header) = packet.as_ref() else { break };
386                    let done = header.flags & EB_BUFFERFLAG_EOS != 0;
387                    svt_av1_enc_release_out_buffer(&mut packet);
388                    if done {
389                        break;
390                    }
391                }
392            }
393            let mut live = LIFECYCLE.lock().unwrap_or_else(|e| e.into_inner());
394            svt_av1_enc_deinit(self.handle);
395            svt_av1_enc_deinit_handle(self.handle);
396            *live -= 1;
397            RELEASED.notify_all();
398        }
399        self.handle = ptr::null_mut();
400    }
401
402    pub fn codec(&self) -> Codec {
403        Codec::Av1
404    }
405
406    /// The library behind the session, as the logs name it.
407    pub fn library(&self) -> &'static str {
408        "svt-av1"
409    }
410
411    pub fn is_fullcolor(&self) -> bool {
412        false
413    }
414
415    /// The bits per sample the session codes.
416    pub fn bit_depth(&self) -> u32 {
417        self.planes.bit_depth
418    }
419
420    pub fn is_full_range(&self) -> bool {
421        false
422    }
423
424    pub fn last_reference(&self) -> Reference {
425        if self.references.is_some() {
426            self.last_reference
427        } else {
428            Reference::Untracked
429        }
430    }
431
432    /// Whether `hold_quantizer` holds a frame at its quantizer: at a constant rate where the
433    /// release takes a new target with a picture. A constant-quantizer session is cleaned up
434    /// through its own quality instead (`pipeline::decide_constant_quality`): a per-picture
435    /// quantizer (`use_qp_file`) drops the library's key-frame and layer offsets from every
436    /// picture it names, and a cleanup key frame held that way came out at 49.6 dB where the
437    /// session's own key frame at the paint-over quality reached 61.0.
438    pub fn holds_quantizer(&self) -> bool {
439        self.rate.cbr && HAS_EVENTS
440    }
441
442    /// The quality index the rate control last coded a frame at, held frames aside.
443    pub fn last_quality(&self) -> Option<u32> {
444        self.last_quality
445    }
446
447    /// Leave frame `frame_id` and every frame after it out of the predictions where the session
448    /// names its references; refused otherwise, so the caller codes a key frame.
449    pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
450        let Some(references) = self.references.as_mut() else {
451            return false;
452        };
453        references.invalidate(frame_id);
454        true
455    }
456
457    /// Every consumer holds frame `frame_id`, or where not `held` was sent it
458    /// (`ReferenceSlots::acknowledge`), where the session names its references.
459    pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
460        if let Some(references) = self.references.as_mut() {
461            references.acknowledge(frame_id, held);
462        }
463    }
464
465    /// Encode the next frame at the quantizer the quality index `crf` selects, leaving the
466    /// session's own quantizer for the frame after: the cleanup of a still screen, at a constant
467    /// rate, where a held key picture is planned with a raised target (`holds_quantizer`). A
468    /// constant-quantizer session codes the frame at its own quantizer.
469    /// Mark the blocks of the next picture, in raster order, that held still and are clean
470    /// (`BlockLedger::constant`): the library copies only the other blocks, and those changed
471    /// since its buffer last held the picture, into the buffers it codes from
472    /// (`EbBufferHeaderType.constant_blocks`), and at a constant quantizer codes a superblock
473    /// of them as a zero-motion skip without analysis (`constant_blocks_skip`), which only a
474    /// session that names no references takes, since only there does every frame predict from
475    /// the one before (a loss is answered with a key frame). A constant rate
476    /// leaves its mode decision to find the skips: its refresh of a still picture at a finer
477    /// quantizer runs through the blocks a map would hold. An empty slice marks none.
478    pub fn constant_blocks(&mut self, constant: &[bool]) {
479        let blocks = self.planes.width.div_ceil(16) * self.planes.height.div_ceil(16);
480        self.constant.clear();
481        if constant.len() == blocks && constant.contains(&true) {
482            self.constant.extend(constant.iter().map(|&c| u8::from(c)));
483        }
484    }
485
486    pub fn hold_quantizer(&mut self, crf: u32) {
487        self.held = Some(Codec::Av1.quantizer(crf as i32));
488    }
489
490    /// The quantizer level a capped constant quantizer forces the next picture at, or 0 for the
491    /// sequence's own (`svtav1_sequence_qp`): the quality index `crf` asks for raised by the
492    /// peak's offset while it binds, and from the first binding since the encoder opened, any
493    /// quality other than the one it opened at, which it would otherwise re-open for. A session
494    /// that never binds forces none and codes as an uncapped one.
495    ///
496    /// A forced picture takes none of the library's key-frame or layer offsets, which code its own
497    /// pictures finer than their quality says, and the shortfall grows with how far finer than the
498    /// session's the quality asked for stands: a refresh of a 320x240 page of text the peak left at
499    /// the coarsest quantizer came to 39.3 dB forced at its quality three steps finer, where the
500    /// uncapped session stood at 42.9, and to 43.7 four steps further; the paint-over seven steps
501    /// finer reached 46.4 forced four steps further and 51.8 eight steps further, against the
502    /// uncapped session's 51.6 from a key frame. So a quality finer than the session's is forced
503    /// one step past twice its distance below it, and at the library's own floor rather than
504    /// `RTC_MIN_LEVEL`, whose failure is a scroll's and such a picture codes a still page. A forced
505    /// picture asks for no constant-block skip (`constant_blocks_skip`): the refresh after a bound
506    /// scroll, its still superblocks coded as skips, decoded at 13.7 dB where it decoded at 39.3
507    /// without them.
508    #[cfg(svtav1_sequence_qp)]
509    fn forced_level(&self, crf: u32) -> u32 {
510        let Some(peak) = self.peak.filter(|_| self.bound_since_open) else {
511            return 0;
512        };
513        let session = f64::from(self.quality_index);
514        let finer = (session - f64::from(crf)).max(0.0);
515        if peak.offset <= 0.0
516            && finer == 0.0
517            && Codec::Av1.quantizer(crf as i32) == self.quality.current
518        {
519            return 0;
520        }
521        if finer > 0.0 {
522            let index = (session - 2.0 * finer - 1.0 + peak.offset)
523                .round()
524                .clamp(0.0, 51.0) as i32;
525            return vpx_level(Codec::Av1, Codec::Av1.quantizer(index)).max(FORCED_MIN_LEVEL);
526        }
527        let index = (f64::from(crf) + peak.offset).round().min(51.0) as i32;
528        Self::level(Codec::Av1.quantizer(index))
529    }
530
531    /// Whether the constant quantizer runs under a peak (`RateSettings::capped`).
532    pub fn capped(&self) -> bool {
533        self.peak.is_some()
534    }
535
536    /// Whether that peak binds the session: its quality stands above the session's.
537    pub fn peak_bound(&self) -> bool {
538        self.peak.is_some_and(|p| p.offset > 0.0)
539    }
540
541    /// Whether the last frame was coded coarser than the session's quality for its peak.
542    pub fn coded_capped(&self) -> bool {
543        self.last_capped
544    }
545
546    /// A tick that codes no frame (`FrameEncoder::idle`): the peak's bucket drains as for an
547    /// empty frame.
548    pub fn idle(&mut self) {
549        if let Some(peak) = self.peak.as_mut() {
550            peak.note(0.0, true, 51.0 - f64::from(self.last_crf));
551        }
552    }
553
554    /// Whether a change of the session's quality re-opens it: until its peak binds, as an
555    /// uncapped session does; from then until it next opens, the quality is forced on the
556    /// pictures it applies to (`forced_level`).
557    pub fn reopens_on_quality(&self) -> bool {
558        !(self.peak.is_some() && self.bound_since_open)
559    }
560
561    /// Apply a rate or frame-rate change: a new bitrate with the next picture where the library
562    /// takes one live, since the VBV it holds spans the same time at any bitrate, and a capped
563    /// constant quantizer's new peak beside the library; anything else re-opens the encoder.
564    pub fn reconfigure_rate(&mut self, settings: &CaptureSettings) -> Result<(), String> {
565        let Some(rate) = self.rate.changed(settings) else {
566            return Ok(());
567        };
568        if let Some(peak) = self.peak.as_mut() {
569            peak.follow(
570                rate.bitrate_kbps,
571                (rate.vbv() / 1000) as i32,
572                rate.fps.fps(),
573            );
574            let reopen = rate.fps != self.rate.fps;
575            self.rate = rate;
576            return if reopen { self.open() } else { Ok(()) };
577        }
578        let live = HAS_EVENTS
579            && rate.fps == self.rate.fps
580            && rate.vbv_multiplier == self.rate.vbv_multiplier
581            && rate.bps() <= MAX_BITRATE_BPS;
582        self.rate = rate;
583        if !live {
584            return self.open();
585        }
586        self.events.target_bit_rate = rate.bps() as u32;
587        Ok(())
588    }
589
590    /// Encode one packed host frame at the quality index `crf`, as a key frame when `force_idr`.
591    pub fn encode_host(
592        &mut self,
593        pixels: &[u8],
594        stride: usize,
595        rgba: bool,
596        frame_number: u64,
597        crf: u32,
598        force_idr: bool,
599    ) -> Result<Vec<u8>, String> {
600        let forces = self.peak.is_some() && self.bound_since_open;
601        let quality_changed = !self.rate.cbr
602            && !forces
603            && self
604                .quality
605                .update(Codec::Av1.quantizer(crf as i32))
606                .is_some();
607        if quality_changed {
608            self.quality_index = crf;
609        }
610        if quality_changed || (force_idr && !self.fresh && KEYFRAME_BY_REOPEN) {
611            self.open()?;
612        }
613        self.planes
614            .convert(pixels, stride, rgba, false, false, self.threads as usize)?;
615        let pts = self.next_pts;
616        let mut key = force_idr || self.fresh;
617        let mut plan = SlotPlan::KEY;
618        if let Some(references) = &self.references {
619            plan = references.plan(key);
620            key = plan.predict_from == 0;
621            let anchor = |slot: u8| {
622                references
623                    .slot(slot)
624                    .map_or(0, |(_, held, _)| anchor_id(held))
625            };
626            let refreshed = |slot: u8| plan.refresh.refreshes(slot);
627            let stored = if refreshed(SlotRefresh::GOLDEN | SlotRefresh::ALTREF) {
628                anchor_id(references.next_pts())
629            } else {
630                0
631            };
632            let kept = [SlotRefresh::GOLDEN, SlotRefresh::ALTREF].map(|slot| {
633                if refreshed(slot) {
634                    stored
635                } else {
636                    anchor(slot)
637                }
638            });
639            let from = if key || plan.predict_from == SlotRefresh::LAST {
640                0
641            } else {
642                anchor(plan.predict_from)
643            };
644            let released = self
645                .anchors
646                .iter()
647                .copied()
648                .find(|a| !kept.contains(a) && *a != from);
649            self.events.store = stored;
650            self.events.predict_from = from;
651            let preset = if from != 0 { ANCHOR_PRESET } else { PRESET };
652            if preset != self.preset {
653                self.events.preset = preset;
654                self.preset = preset;
655            }
656            self.events.clear = if key { 0 } else { released.unwrap_or(0) };
657        }
658        #[cfg(svtav1_sequence_qp)]
659        let forced = self.forced_level(crf);
660        {
661            let header = &mut self.input.0;
662            header.n_filled_len = header.n_alloc_len;
663            header.pts = pts as i64;
664            header.flags = 0;
665            header.pic_type = if key {
666                EB_AV1_KEY_PICTURE
667            } else {
668                EB_AV1_INVALID_PICTURE
669            };
670            // Only a patched SVT-AV1 reads a constant-block map (`patches/svtav1`).
671            #[cfg(svtav1_constant_blocks)]
672            {
673                header.constant_blocks = if key || self.held.is_some() || self.constant.is_empty() {
674                    ptr::null()
675                } else {
676                    self.constant.as_ptr()
677                };
678            }
679            #[cfg(svtav1_constant_blocks_skip)]
680            {
681                #[cfg(svtav1_sequence_qp)]
682                let forced_skip_ok = forced == 0;
683                #[cfg(not(svtav1_sequence_qp))]
684                let forced_skip_ok = true;
685                header.constant_blocks_skip =
686                    u8::from(!self.rate.cbr && self.references.is_none() && forced_skip_ok);
687            }
688            #[cfg(svtav1_sequence_qp)]
689            {
690                header.qp = forced;
691            }
692        }
693        let offset = self.peak.map_or(0.0, |p| p.offset);
694        self.last_crf = crf;
695        if self.rate.cbr && HAS_EVENTS {
696            let held_key = key && self.held.is_some();
697            // The held key frame at the open preset; any other at its own, as the references
698            // planned it, or the anchor one where the session names none.
699            let preset = if held_key {
700                Some(KEY_PRESET)
701            } else if self.references.is_none() {
702                Some(ANCHOR_PRESET)
703            } else {
704                None
705            };
706            if let Some(preset) = preset
707                && preset != self.preset
708            {
709                self.events.preset = preset;
710                self.preset = preset;
711            }
712            if held_key || self.restate_target {
713                // The rate control takes no quantizer: a held key frame's picture is planned
714                // with a target that gives it `HELD_KEY_BUDGET_S` of the session's.
715                let factor = if held_key {
716                    super::HELD_KEY_BUDGET_S * self.rate.fps.fps()
717                } else {
718                    1.0
719                };
720                self.events.target_bit_rate =
721                    (self.rate.bps() as f64 * factor.max(1.0)).min(MAX_BITRATE_BPS as f64) as u32;
722            }
723            self.restate_target = held_key;
724        }
725        let held = self.held.take().is_some();
726        let sent = self.events;
727        let code = unsafe { send_picture(self.handle, &mut self.input.0, sent) };
728        self.constant.clear();
729        if code != EB_ErrorNone {
730            return Err(error("SVT-AV1 refused the frame", code));
731        }
732        self.events = Events::default();
733        self.next_pts += 1;
734        self.fresh = false;
735        self.pending.push(pts, frame_number as u16);
736
737        let mut output = Vec::new();
738        loop {
739            let mut packet: *mut EbBufferHeaderType = ptr::null_mut();
740            let code = unsafe { svt_av1_enc_get_packet(self.handle, &mut packet, 0) };
741            if code == EB_NoErrorEmptyQueue {
742                break;
743            }
744            if code != EB_ErrorNone {
745                return Err(error("SVT-AV1 handed out no packet", code));
746            }
747            let Some(p) = (unsafe { packet.as_ref() }) else {
748                break;
749            };
750            let bytes =
751                unsafe { std::slice::from_raw_parts(p.p_buffer, p.n_filled_len as usize) }.to_vec();
752            let packet_pts = p.pts.max(0) as u64;
753            if !held {
754                self.last_quality =
755                    Some(Codec::Av1.quality_index(u32::from(VPX_QINDEX[(p.qp as usize).min(63)])));
756            }
757            unsafe { svt_av1_enc_release_out_buffer(&mut packet) };
758            let id = self.pending.take(packet_pts).unwrap_or(frame_number as u16);
759            let is_key = av1_is_key(&bytes);
760            if let Some(peak) = self.peak.as_mut() {
761                self.last_capped = offset > 0.0;
762                let next = peak.note(bytes.len() as f64 * 8.0, !is_key, 51.0 - f64::from(crf));
763                self.bound_since_open |= next > 0.0;
764            }
765            let mut anchor = 0;
766            if let Some(references) = self.references.as_mut() {
767                if !is_key && references.shared_anchor(plan) {
768                    anchor = FRAME_ANCHOR;
769                }
770                self.last_reference =
771                    references.record(id, if is_key { SlotPlan::KEY } else { plan });
772                if is_key {
773                    self.anchors.clear();
774                }
775                self.anchors.retain(|&a| a != sent.clear);
776                if sent.store != 0 {
777                    self.anchors.push(sent.store);
778                }
779            }
780            if !self.omit_headers {
781                output.reserve(VIDEO_HEADER_LEN + bytes.len());
782                push_video_header(
783                    &mut output,
784                    Codec::Av1,
785                    frame_type_from_key(is_key) | anchor,
786                    id,
787                    0,
788                    self.planes.width as u16,
789                    self.planes.height as u16,
790                    self.last_reference(),
791                );
792            }
793            output.extend_from_slice(&bytes);
794            if self.pending.is_empty() {
795                break;
796            }
797        }
798        Ok(output)
799    }
800}
801
802#[cfg(test)]
803mod tests {
804    use super::*;
805    #[cfg(svtav1_sequence_qp)]
806    use crate::encoders::codec::{FRAME_DELTA, parse_video_type};
807
808    /// A session let go before its first frame closes.
809    #[test]
810    fn a_session_let_go_before_its_first_frame_closes() {
811        let (done, closed) = std::sync::mpsc::channel();
812        std::thread::spawn(move || {
813            let settings = CaptureSettings {
814                width: 64,
815                height: 64,
816                target_fps: 30.0,
817                codec: Codec::Av1,
818                ..Default::default()
819            };
820            drop(SvtAv1Encoder::new(&settings, false).expect("session"));
821            let _ = done.send(());
822        });
823        assert!(
824            closed
825                .recv_timeout(std::time::Duration::from_secs(30))
826                .is_ok(),
827            "the release never returned"
828        );
829    }
830
831    /// The blocks marked constant are copied no more and, at a constant quantizer, coded as skips
832    /// without analysis: a still screen's stream comes out within a few bytes of the one the
833    /// mode decision codes, the GLOBALMV skip costing a bit more than its own skip, and decodes
834    /// to the same picture.
835    #[test]
836    #[cfg(svtav1_constant_blocks_skip)]
837    fn constant_blocks_code_a_still_screen_as_skips() {
838        use crate::webcam::decode::{Decoder as _, VideoDecoder};
839        let (w, h) = (320usize, 240usize);
840        let settings = CaptureSettings {
841            width: w as i32,
842            height: h as i32,
843            target_fps: 30.0,
844            codec: Codec::Av1,
845            video_crf: 25,
846            ..Default::default()
847        };
848        let mut bgra = vec![0u8; w * h * 4];
849        for (i, px) in bgra.as_chunks_mut::<4>().0.iter_mut().enumerate() {
850            let (x, y) = (i % w, i / w);
851            let text = (x / 7 + y / 11) % 3 == 0 && x % 7 < 5 && y % 11 < 8;
852            px.copy_from_slice(if text {
853                &[30, 30, 30, 255]
854            } else {
855                &[235, 235, 235, 255]
856            });
857        }
858        let mut plain = SvtAv1Encoder::new(&settings, false).expect("session");
859        let mut mapped = SvtAv1Encoder::new(&settings, false).expect("session");
860        let mut plain_dec = VideoDecoder::new(Codec::Av1).expect("decoder");
861        let mut mapped_dec = VideoDecoder::new(Codec::Av1).expect("decoder");
862        let blocks = w.div_ceil(16) * h.div_ceil(16);
863        let (mut plain_bytes, mut mapped_bytes) = (0, 0);
864        for n in 0..6u64 {
865            let a = plain
866                .encode_host(&bgra, w * 4, false, n, 25, n == 0)
867                .expect("encode");
868            mapped.constant_blocks(&vec![true; blocks]);
869            let b = mapped
870                .encode_host(&bgra, w * 4, false, n, 25, n == 0)
871                .expect("encode");
872            plain_bytes += a.len();
873            mapped_bytes += b.len();
874            assert!(plain_dec.decode(&a[VIDEO_HEADER_LEN..]).expect("decode"));
875            assert!(mapped_dec.decode(&b[VIDEO_HEADER_LEN..]).expect("decode"));
876        }
877        assert!(
878            mapped_bytes <= plain_bytes + 64,
879            "{mapped_bytes} bytes with the map, {plain_bytes} without"
880        );
881        let (pa, pb) = (
882            plain_dec.frame().expect("picture"),
883            mapped_dec.frame().expect("picture"),
884        );
885        let distance =
886            pa.y.chunks(pa.y_stride)
887                .zip(pb.y.chunks(pb.y_stride))
888                .take(h)
889                .flat_map(|(ra, rb)| {
890                    ra[..w]
891                        .iter()
892                        .zip(&rb[..w])
893                        .map(|(&x, &y)| (f64::from(x) - f64::from(y)).abs())
894                })
895                .sum::<f64>()
896                / (w * h) as f64;
897        assert!(
898            distance < 0.5,
899            "the pictures differ by {distance} luma levels on average"
900        );
901    }
902
903    /// A box that changes every frame beside blocks marked constant, at a size no superblock
904    /// divides: the library's partial copy merges the active blocks and the partial edge blocks
905    /// into runs, clipping the chroma rows it copies to the planes, and at a constant quantizer
906    /// the skip leaves the superblocks the box touches coded. Under a constant rate the copy runs
907    /// alone. Either way the box and the still screen decode as close to the source as without
908    /// the map.
909    #[test]
910    #[cfg(svtav1_constant_blocks)]
911    fn constant_blocks_leave_a_change_beside_them_coded() {
912        use super::super::mixed_screen;
913        for cbr in [false, true] {
914            let settings = CaptureSettings {
915                width: mixed_screen::W as i32,
916                height: mixed_screen::H as i32,
917                target_fps: 30.0,
918                codec: Codec::Av1,
919                video_crf: 25,
920                video_cbr_mode: cbr,
921                video_bitrate_kbps: 4000,
922                ..Default::default()
923            };
924            let mut plain = SvtAv1Encoder::new(&settings, false).expect("session");
925            let mut mapped = SvtAv1Encoder::new(&settings, false).expect("session");
926            mixed_screen::assert_decodes_alike(
927                Codec::Av1,
928                if cbr {
929                    "constant rate"
930                } else {
931                    "constant quantizer"
932                },
933                |src, n| {
934                    plain
935                        .encode_host(src, mixed_screen::W * 4, false, n, 25, n == 0)
936                        .expect("encode")
937                },
938                |src, n, map| {
939                    mapped.constant_blocks(map);
940                    mapped
941                        .encode_host(src, mixed_screen::W * 4, false, n, 25, n == 0)
942                        .expect("encode")
943                },
944            );
945        }
946    }
947
948    /// The library is configured at the capture's rate as the fraction it names, a live change
949    /// of rate included.
950    #[test]
951    fn the_frame_rate_reaches_the_library_as_its_fraction() {
952        for (num, den) in [(60000u32, 1001u32), (120000, 1001), (144000, 1001), (60, 1)] {
953            let fps = f64::from(num) / f64::from(den);
954            let settings = CaptureSettings {
955                width: 64,
956                height: 64,
957                target_fps: fps,
958                codec: Codec::Av1,
959                video_cbr_mode: true,
960                video_bitrate_kbps: 2000,
961                ..Default::default()
962            };
963            let mut enc = SvtAv1Encoder::new(&settings, false).expect("session");
964            assert_eq!(
965                (
966                    enc.config.frame_rate_numerator,
967                    enc.config.frame_rate_denominator
968                ),
969                (num, den)
970            );
971            enc.reconfigure_rate(&CaptureSettings {
972                target_fps: 30.0,
973                ..settings.clone()
974            })
975            .expect("30 fps");
976            enc.reconfigure_rate(&settings).expect("back");
977            assert_eq!(
978                (
979                    enc.config.frame_rate_numerator,
980                    enc.config.frame_rate_denominator
981                ),
982                (num, den)
983            );
984        }
985    }
986
987    /// A constant-rate session that manages its presets opens at `KEY_PRESET`, codes a still
988    /// screen's held key frame there, and every other frame, its first key frame included, at its
989    /// own.
990    #[test]
991    fn a_held_key_frame_is_coded_at_the_key_preset_and_no_other_frame_is() {
992        if !HAS_EVENTS {
993            return;
994        }
995        let settings = CaptureSettings {
996            width: 64,
997            height: 64,
998            target_fps: 30.0,
999            codec: Codec::Av1,
1000            video_cbr_mode: true,
1001            video_bitrate_kbps: 2000,
1002            ..Default::default()
1003        };
1004        let mut enc = SvtAv1Encoder::new(&settings, false).expect("session");
1005        assert_eq!(enc.config.enc_mode, KEY_PRESET);
1006        let frame = vec![0x40u8; 64 * 64 * 4];
1007        enc.encode_host(&frame, 64 * 4, false, 0, 30, true)
1008            .expect("first frame");
1009        assert_ne!(enc.preset, KEY_PRESET, "the first key frame");
1010        enc.encode_host(&frame, 64 * 4, false, 1, 30, false)
1011            .expect("a frame");
1012        assert_ne!(enc.preset, KEY_PRESET);
1013        enc.hold_quantizer(10);
1014        enc.encode_host(&frame, 64 * 4, false, 2, 30, true)
1015            .expect("held key frame");
1016        assert_eq!(enc.preset, KEY_PRESET);
1017        enc.encode_host(&frame, 64 * 4, false, 3, 30, false)
1018            .expect("the frame after");
1019        assert_ne!(enc.preset, KEY_PRESET);
1020    }
1021
1022    /// A 640x360 session at 30 fps and quality 25, capped at `peak_kbps` where one is given.
1023    #[cfg(svtav1_sequence_qp)]
1024    fn scroll_settings(peak_kbps: Option<i32>) -> CaptureSettings {
1025        let plain = CaptureSettings {
1026            width: 640,
1027            height: 360,
1028            target_fps: 30.0,
1029            codec: Codec::Av1,
1030            video_crf: 25,
1031            ..Default::default()
1032        };
1033        match peak_kbps {
1034            Some(kbps) => CaptureSettings {
1035                video_bitrate_kbps: kbps,
1036                video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
1037                ..plain
1038            },
1039            None => plain,
1040        }
1041    }
1042
1043    /// Frame `t` of a 640x360 page of text scrolling a step a frame.
1044    #[cfg(svtav1_sequence_qp)]
1045    fn scrolled(t: usize) -> Vec<u8> {
1046        crate::encoders::scrolled_text(640, 360, t)
1047    }
1048
1049    /// The kbps a run of frames of `bits` comes to at 30 fps.
1050    #[cfg(svtav1_sequence_qp)]
1051    fn kbps(bits: &[usize]) -> f64 {
1052        bits.iter().sum::<usize>() as f64 * 30.0 / bits.len() as f64 / 1000.0
1053    }
1054
1055    /// A capped constant quantizer under a peak its picture never reaches forces no picture's
1056    /// quantizer and codes every frame as the uncapped session does, its key frames and a
1057    /// change of quality among them.
1058    #[test]
1059    #[cfg(svtav1_sequence_qp)]
1060    fn a_capped_quantizer_codes_as_uncapped_under_a_peak_it_never_reaches() {
1061        let mut plain = SvtAv1Encoder::new(&scroll_settings(None), false).expect("session");
1062        let mut capped =
1063            SvtAv1Encoder::new(&scroll_settings(Some(100_000)), false).expect("session");
1064        assert!(capped.capped() && !plain.capped());
1065        for t in 0..40 {
1066            let key = t == 0 || t == 15;
1067            let (at, crf) = if t < 30 { (t, 25) } else { (30, 18) };
1068            let a = plain
1069                .encode_host(&scrolled(at), 640 * 4, false, t as u64, crf, key)
1070                .unwrap();
1071            let b = capped
1072                .encode_host(&scrolled(at), 640 * 4, false, t as u64, crf, key)
1073                .unwrap();
1074            assert_eq!(a, b, "frame {t}");
1075        }
1076        assert!(!capped.peak_bound(), "bound to a peak it never reached");
1077    }
1078
1079    /// A capped constant quantizer holds a scroll its quality codes at twice the peak to the
1080    /// peak, coding no key frame past the first: once the bucket has drained the first key frame,
1081    /// the scroll runs within the peak and at no less than nine tenths of it, where the uncapped
1082    /// session runs at twice it.
1083    #[test]
1084    #[cfg(svtav1_sequence_qp)]
1085    fn a_capped_quantizer_holds_a_scroll_at_its_peak() {
1086        const FRAMES: usize = 450;
1087        const SETTLED: usize = 250;
1088        let run = |settings: &CaptureSettings| {
1089            let mut enc = SvtAv1Encoder::new(settings, false).expect("session");
1090            let mut bits = Vec::new();
1091            let mut capped = false;
1092            for t in 0..FRAMES {
1093                let out = enc
1094                    .encode_host(&scrolled(t), 640 * 4, false, t as u64, 25, t == 0)
1095                    .unwrap();
1096                assert!(
1097                    t == 0 || parse_video_type(out[1]).map(|(_, k)| k) == Some(FRAME_DELTA),
1098                    "frame {t} is a key frame"
1099                );
1100                bits.push((out.len() - VIDEO_HEADER_LEN) * 8);
1101                capped |= enc.coded_capped();
1102            }
1103            (kbps(&bits[SETTLED..]), capped)
1104        };
1105        let (uncapped, _) = run(&scroll_settings(None));
1106        let peak = (uncapped * 0.5) as i32;
1107        let (held, bound) = run(&scroll_settings(Some(peak)));
1108        println!("uncapped {uncapped:.0} kbps, capped at {peak} kbps {held:.0} kbps");
1109        assert!(bound, "the peak never bound");
1110        assert!(
1111            (0.9 * f64::from(peak)..=1.1 * f64::from(peak)).contains(&held),
1112            "the scroll ran at {held:.0} kbps under a {peak} kbps peak"
1113        );
1114    }
1115
1116    /// A capped constant quantizer takes each new peak live, as congestion control steers it: no
1117    /// key frame, and a scroll after a cut to 60% of what it ran at under the high peak codes
1118    /// within the lower one.
1119    #[test]
1120    #[cfg(svtav1_sequence_qp)]
1121    fn a_capped_quantizer_moves_its_peak_without_a_key_frame() {
1122        const CUT: usize = 20;
1123        const AFTER: usize = 150;
1124        let mut s = scroll_settings(Some(100_000));
1125        let mut enc = SvtAv1Encoder::new(&s, false).expect("session");
1126        let mut bits = Vec::new();
1127        for t in 0..CUT + AFTER {
1128            if t == CUT {
1129                s.video_bitrate_kbps = (kbps(&bits[1..]) * 0.6) as i32;
1130            }
1131            enc.reconfigure_rate(&s).expect("rate");
1132            let out = enc
1133                .encode_host(&scrolled(t), 640 * 4, false, t as u64, 25, t == 0)
1134                .unwrap();
1135            assert!(
1136                t == 0 || parse_video_type(out[1]).map(|(_, k)| k) == Some(FRAME_DELTA),
1137                "frame {t} is a key frame"
1138            );
1139            bits.push((out.len() - VIDEO_HEADER_LEN) * 8);
1140        }
1141        let after = kbps(&bits[CUT + 10..]);
1142        assert!(
1143            after <= 1.1 * f64::from(s.video_bitrate_kbps),
1144            "the scroll ran at {after:.0} kbps under a {} kbps peak",
1145            s.video_bitrate_kbps
1146        );
1147    }
1148
1149    /// Sessions opened and closed on many threads at once each encode their frame.
1150    #[test]
1151    fn sessions_open_and_close_on_many_threads_at_once() {
1152        let settings = CaptureSettings {
1153            width: 64,
1154            height: 64,
1155            target_fps: 30.0,
1156            codec: Codec::Av1,
1157            ..Default::default()
1158        };
1159        let frame = vec![0u8; 64 * 64 * 4];
1160        std::thread::scope(|s| {
1161            for _ in 0..4 {
1162                s.spawn(|| {
1163                    for n in 0..4 {
1164                        let mut enc = SvtAv1Encoder::new(&settings, false).expect("session");
1165                        enc.encode_host(&frame, 64 * 4, false, n, 30, true)
1166                            .expect("frame");
1167                    }
1168                });
1169            }
1170        });
1171    }
1172}