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 constant-rate session on such a release names its references: it predicts every frame
14//! from the one before in a flat structure and has the library store anchors on the schedule
15//! of `ReferenceSlots`, its GOLDEN and ALTREF, so a frame a client lost is predicted past from
16//! an anchor older than it, at `ANCHOR_PRESET`. Any other session leaves the references to the library, and a
17//! frame a client lost costs the key frame the caller codes on the refusal, which SVT-AV1
18//! before 2.0 codes only from a re-open as well.
19
20use std::ffi::CString;
21use std::ptr;
22use std::sync::{Condvar, Mutex};
23
24use codec_sys::svtav1::*;
25
26use super::codec::{
27    Codec, FRAME_ANCHOR, VIDEO_HEADER_LEN, VPX_QINDEX, av1_is_key, frame_type_from_key,
28    push_video_header, vpx_level,
29};
30use super::reference::{Reference, ReferenceSlots, SlotPlan, SlotRefresh};
31use super::session::{Pending, Planes, Quality, RateSettings, encode_threads};
32use crate::RustCaptureSettings;
33
34/// The lowest quantizer level the real-time mode runs at. Below three the library faults, and
35/// below ten it codes every other frame of a scrolling text desktop with the lower third of
36/// the picture wrong, at three times the bytes of level ten.
37const RTC_MIN_LEVEL: u32 = 10;
38
39/// The preset frames are coded at: in the real-time mode, a quarter less encode time than preset
40/// 10 for more bytes at a fixed quantizer, which the quantizer table absorbs; the presets above
41/// it are no faster.
42const PRESET: i8 = 11;
43
44/// The preset a frame predicting from an anchor alone is coded at. At preset 11 the library keeps
45/// some blocks the picture changed since the anchor as they stand in the anchor, a stale copy
46/// every client shows (test_slow_page's bar, 14 frames back, left where it was in a 64-pixel
47/// block); preset 10 codes them. A session naming its references opens at it, since the library
48/// takes a preset change only toward a faster one, and codes every other frame at `PRESET`.
49const ANCHOR_PRESET: i8 = 10;
50
51/// The highest constant-rate target the library takes, at open and live alike.
52const MAX_BITRATE_BPS: u64 = 100_000_000;
53
54/// The anchors a session that names its references has the library hold: GOLDEN and ALTREF, and
55/// the ones they replaced until a picture releases them.
56const MANAGED_REFS: u8 = 4;
57
58/// SVT-AV1 before 2.0 codes a key frame asked for mid-stream only in its random-access quality
59/// mode; a session on one re-opens the encoder for it, and the first frame answers.
60const KEYFRAME_BY_REOPEN: bool = SVT_AV1_VERSION_MAJOR < 2;
61
62/// SVT-AV1 before 1.5 drains an encoder on release even when it took no frame, and waits for a
63/// packet that never comes; a session released before its first frame feeds it one.
64const FEED_BEFORE_RELEASE: bool =
65    SVT_AV1_VERSION_MAJOR < 1 || (SVT_AV1_VERSION_MAJOR == 1 && SVT_AV1_VERSION_MINOR < 5);
66
67/// The handles alive, held while one is created or released and while the probe forks: before
68/// 4.0 the library rebuilds a process-wide processor table in every handle it creates and frees
69/// it in every one it releases, and from 4.1 handle setup takes process-wide locks that a child
70/// forked meanwhile inherits held. The probe forks only while no handle is alive, since a live
71/// session's worker threads hold that state as well.
72pub(crate) static LIFECYCLE: Mutex<usize> = Mutex::new(0);
73
74/// Notified at each handle released, for a probe waiting for none to be alive.
75pub(crate) static RELEASED: Condvar = Condvar::new();
76
77/// One SVT-AV1 session for one capture.
78pub struct SvtAv1Encoder {
79    handle: *mut EbComponentType,
80    config: Box<EbSvtAv1EncConfiguration>,
81    /// The input picture the library reads: the header and the plane pointers it carries.
82    input: Box<(EbBufferHeaderType, EbSvtIOFormat)>,
83    planes: Planes,
84    threads: i32,
85    quality: Quality,
86    rate: RateSettings,
87    omit_headers: bool,
88    fresh: bool,
89    /// The change the next picture carries to the running encoder.
90    events: Events,
91    /// The preset the running encoder codes the next picture at unless told otherwise.
92    preset: i8,
93    /// The buffers of a session that names its references, LAST the library's own.
94    references: Option<ReferenceSlots>,
95    /// The anchors the library holds, by the id each was stored under.
96    anchors: Vec<u32>,
97    last_reference: Reference,
98    next_pts: u64,
99    pending: Pending,
100    /// The quantizer the next frame is held at whatever the rate control (`hold_quantizer`).
101    held: Option<u32>,
102    /// Whether the frame after a held key frame of a constant-rate session restates its target.
103    restate_target: bool,
104    /// The quality index the rate control last coded a frame at, held frames aside.
105    last_quality: Option<u32>,
106}
107
108/// The id an anchor is stored under: its timestamp, off the zero the library reserves.
109fn anchor_id(pts: u64) -> u32 {
110    (pts % u32::MAX as u64) as u32 + 1
111}
112
113unsafe impl Send for SvtAv1Encoder {}
114
115fn error(what: &str, code: EbErrorType) -> String {
116    format!("{what} (SVT-AV1 error {code:#x})")
117}
118
119impl Drop for SvtAv1Encoder {
120    fn drop(&mut self) {
121        self.close();
122    }
123}
124
125impl SvtAv1Encoder {
126    /// Open a session on host frames; `rgba` names the byte order they arrive in.
127    pub fn new(settings: &RustCaptureSettings, rgba: bool) -> Result<Self, String> {
128        let _ = rgba;
129        let width = settings.width.max(1) as usize;
130        let height = settings.height.max(1) as usize;
131        let mut me = Self {
132            handle: ptr::null_mut(),
133            config: Box::new(unsafe { std::mem::zeroed() }),
134            input: Box::new(unsafe { std::mem::zeroed() }),
135            planes: Planes::new(
136                width,
137                height,
138                false,
139                if settings.video_bit_depth >= 10 {
140                    10
141                } else {
142                    8
143                },
144            ),
145            threads: encode_threads(),
146            quality: Quality::new(Codec::Av1.quantizer(settings.video_crf)),
147            rate: RateSettings::new(settings),
148            omit_headers: settings.omit_stripe_headers,
149            fresh: true,
150            events: Events::default(),
151            preset: PRESET,
152            references: None,
153            anchors: Vec::new(),
154            last_reference: Reference::Untracked,
155            next_pts: 0,
156            pending: Pending::default(),
157            held: None,
158            restate_target: false,
159            last_quality: None,
160        };
161        me.open()?;
162        Ok(me)
163    }
164
165    /// The quantizer level the library takes for a quantizer index of AV1's domain.
166    fn level(q: u32) -> u32 {
167        vpx_level(Codec::Av1, q).max(RTC_MIN_LEVEL)
168    }
169
170    fn set(&mut self, name: &str, value: &str) -> Result<(), String> {
171        let (n, v) = (CString::new(name).unwrap(), CString::new(value).unwrap());
172        let code =
173            unsafe { svt_av1_enc_parse_parameter(&mut *self.config, n.as_ptr(), v.as_ptr()) };
174        if code != EB_ErrorNone {
175            return Err(error(&format!("SVT-AV1 refused {name}={value}"), code));
176        }
177        Ok(())
178    }
179
180    /// Stand the encoder up with the live settings. The first frame it codes is a key frame.
181    fn open(&mut self) -> Result<(), String> {
182        self.close();
183        let mut live = LIFECYCLE.lock().unwrap_or_else(|e| e.into_inner());
184        let code = unsafe { init_handle(&mut self.handle, &mut *self.config) };
185        if code != EB_ErrorNone || self.handle.is_null() {
186            return Err(error("SVT-AV1 handed out no encoder handle", code));
187        }
188        *live += 1;
189        let rate = self.rate;
190        let bps = rate.bps();
191        let tracks = HAS_EVENTS && rate.cbr;
192        {
193            let cfg = &mut *self.config;
194            cfg.enc_mode = if tracks { ANCHOR_PRESET } else { PRESET };
195            self.preset = cfg.enc_mode;
196            cfg.source_width = self.planes.width as u32;
197            cfg.source_height = self.planes.height as u32;
198            cfg.frame_rate_numerator = rate.fps.num;
199            cfg.frame_rate_denominator = rate.fps.den;
200            cfg.encoder_bit_depth = self.planes.bit_depth;
201            cfg.encoder_color_format = EB_YUV420;
202            cfg.color_primaries = EB_CICP_CP_BT_709;
203            cfg.transfer_characteristics = 1;
204            cfg.matrix_coefficients = 1;
205            cfg.color_range = 0;
206            cfg.intra_refresh_type = 2;
207            if rate.cbr {
208                let (lo, hi) = (
209                    Codec::Av1.quantizer_bound(rate.min_qp),
210                    Codec::Av1.quantizer_bound(rate.max_qp),
211                );
212                cfg.target_bit_rate = bps as u32;
213                cfg.rate_control_mode = 2;
214                cfg.max_qp_allowed = if hi > 0 { Self::level(hi) } else { 63 };
215                cfg.min_qp_allowed = if lo > 0 {
216                    Self::level(lo)
217                } else {
218                    RTC_MIN_LEVEL
219                };
220                // The library refuses a rate-control buffer shorter than 20 ms, which the
221                // 1.5-frame VBV falls under above 75 fps.
222                let vbv = (rate.vbv() as u64).max(bps / 50);
223                cfg.maximum_buffer_size_ms = (vbv * 1000 / bps.max(1)) as i64;
224            }
225            if tracks {
226                set_managed_refs(cfg, MANAGED_REFS);
227            }
228        }
229        // `lp` is a level of parallelism, 0..=6, not a thread count.
230        if HAS_RTC {
231            self.set("rtc", "1")?;
232        }
233        self.set("pred-struct", "1")?;
234        self.set("lookahead", "0")?;
235        self.set("keyint", "-1")?;
236        self.set("tile-columns", "0")?;
237        self.set("tile-rows", "0")?;
238        self.set("lp", &self.threads.min(6).to_string())?;
239        if tracks {
240            self.set("hierarchical-levels", "0")?;
241        }
242        if rate.cbr {
243            self.set("rc", "2")?;
244        } else {
245            self.set("rc", "0")?;
246            self.set("qp", &Self::level(self.quality.current).to_string())?;
247        }
248        let code = unsafe { svt_av1_enc_set_parameter(self.handle, &mut *self.config) };
249        if code != EB_ErrorNone {
250            return Err(error("SVT-AV1 refused the parameters", code));
251        }
252        let code = unsafe { svt_av1_enc_init(self.handle) };
253        if code != EB_ErrorNone {
254            return Err(error("SVT-AV1 refused the session", code));
255        }
256        let (header, io) = &mut *self.input;
257        *header = unsafe { std::mem::zeroed() };
258        header.size = std::mem::size_of::<EbBufferHeaderType>() as u32;
259        header.p_buffer = io as *mut EbSvtIOFormat as *mut u8;
260        let [y, u, v] = self.planes.pointers();
261        io.luma = y;
262        io.cb = u;
263        io.cr = v;
264        io.y_stride = self.planes.width as u32;
265        io.cb_stride = self.planes.chroma_width() as u32;
266        io.cr_stride = self.planes.chroma_width() as u32;
267        header.n_alloc_len = self.planes.byte_len() as u32;
268        self.fresh = true;
269        self.events = Events::default();
270        self.references = tracks.then(ReferenceSlots::new);
271        self.anchors.clear();
272        Ok(())
273    }
274
275    /// Tell the library the stream ended, drain what it still holds, and free the encoder.
276    fn close(&mut self) {
277        if self.handle.is_null() {
278            return;
279        }
280        unsafe {
281            if self.fresh && FEED_BEFORE_RELEASE {
282                let header = &mut self.input.0;
283                header.n_filled_len = header.n_alloc_len;
284                header.flags = 0;
285                header.pic_type = EB_AV1_KEY_PICTURE;
286                self.fresh = svt_av1_enc_send_picture(self.handle, header) != EB_ErrorNone;
287            }
288            if !self.fresh {
289                let mut last: EbBufferHeaderType = std::mem::zeroed();
290                last.pic_type = EB_AV1_INVALID_PICTURE;
291                last.flags = EB_BUFFERFLAG_EOS;
292                svt_av1_enc_send_picture(self.handle, &mut last);
293                loop {
294                    let mut packet: *mut EbBufferHeaderType = ptr::null_mut();
295                    if svt_av1_enc_get_packet(self.handle, &mut packet, 1) != EB_ErrorNone {
296                        break;
297                    }
298                    let Some(header) = packet.as_ref() else { break };
299                    let done = header.flags & EB_BUFFERFLAG_EOS != 0;
300                    svt_av1_enc_release_out_buffer(&mut packet);
301                    if done {
302                        break;
303                    }
304                }
305            }
306            let mut live = LIFECYCLE.lock().unwrap_or_else(|e| e.into_inner());
307            svt_av1_enc_deinit(self.handle);
308            svt_av1_enc_deinit_handle(self.handle);
309            *live -= 1;
310            RELEASED.notify_all();
311        }
312        self.handle = ptr::null_mut();
313    }
314
315    pub fn codec(&self) -> Codec {
316        Codec::Av1
317    }
318
319    /// The library behind the session, as the logs name it.
320    pub fn library(&self) -> &'static str {
321        "svt-av1"
322    }
323
324    pub fn is_fullcolor(&self) -> bool {
325        false
326    }
327
328    /// The bits per sample the session codes.
329    pub fn bit_depth(&self) -> u32 {
330        self.planes.bit_depth
331    }
332
333    pub fn is_full_range(&self) -> bool {
334        false
335    }
336
337    pub fn last_reference(&self) -> Reference {
338        if self.references.is_some() {
339            self.last_reference
340        } else {
341            Reference::Untracked
342        }
343    }
344
345    /// Whether `hold_quantizer` holds a frame at its quantizer: at a constant rate where the
346    /// release takes a new target with a picture. A constant-quantizer session is cleaned up
347    /// through its own quality instead (`pipeline::decide_constant_quality`): a per-picture
348    /// quantizer (`use_qp_file`) drops the library's key-frame and layer offsets from every
349    /// picture it names, and a cleanup key frame held that way came out at 49.6 dB where the
350    /// session's own key frame at the paint-over quality reached 61.0.
351    pub fn holds_quantizer(&self) -> bool {
352        self.rate.cbr && HAS_EVENTS
353    }
354
355    /// The quality index the rate control last coded a frame at, held frames aside.
356    pub fn last_quality(&self) -> Option<u32> {
357        self.last_quality
358    }
359
360    /// Leave frame `frame_id` and every frame after it out of the predictions where the session
361    /// names its references; refused otherwise, so the caller codes a key frame.
362    pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
363        let Some(references) = self.references.as_mut() else {
364            return false;
365        };
366        references.invalidate(frame_id);
367        true
368    }
369
370    /// Every consumer holds frame `frame_id`, or where not `held` was sent it
371    /// (`ReferenceSlots::acknowledge`), where the session names its references.
372    pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
373        if let Some(references) = self.references.as_mut() {
374            references.acknowledge(frame_id, held);
375        }
376    }
377
378    /// Encode the next frame at the quantizer the quality index `crf` selects, leaving the
379    /// session's own quantizer for the frame after: the cleanup of a still screen, at a constant
380    /// rate, where a held key picture is planned with a raised target (`holds_quantizer`). A
381    /// constant-quantizer session codes the frame at its own quantizer.
382    pub fn hold_quantizer(&mut self, crf: u32) {
383        self.held = Some(Codec::Av1.quantizer(crf as i32));
384    }
385
386    /// Apply a rate or frame-rate change: a new bitrate with the next picture where the library
387    /// takes one live, since the VBV it holds spans the same time at any bitrate; anything else
388    /// re-opens the encoder.
389    pub fn reconfigure_rate(&mut self, settings: &RustCaptureSettings) -> Result<(), String> {
390        let Some(rate) = self.rate.changed(settings) else {
391            return Ok(());
392        };
393        let live = HAS_EVENTS
394            && rate.fps == self.rate.fps
395            && rate.vbv_multiplier == self.rate.vbv_multiplier
396            && rate.bps() <= MAX_BITRATE_BPS;
397        self.rate = rate;
398        if !live {
399            return self.open();
400        }
401        self.events.target_bit_rate = rate.bps() as u32;
402        Ok(())
403    }
404
405    /// Encode one packed host frame at the quality index `crf`, as a key frame when `force_idr`.
406    pub fn encode_host(
407        &mut self,
408        pixels: &[u8],
409        stride: usize,
410        rgba: bool,
411        frame_number: u64,
412        crf: u32,
413        force_idr: bool,
414    ) -> Result<Vec<u8>, String> {
415        let quality_changed = !self.rate.cbr
416            && self
417                .quality
418                .update(Codec::Av1.quantizer(crf as i32))
419                .is_some();
420        if quality_changed || (force_idr && !self.fresh && KEYFRAME_BY_REOPEN) {
421            self.open()?;
422        }
423        self.planes
424            .convert(pixels, stride, rgba, false, false, self.threads as usize)?;
425        let pts = self.next_pts;
426        let mut key = force_idr || self.fresh;
427        let mut plan = SlotPlan::KEY;
428        if let Some(references) = &self.references {
429            plan = references.plan(key);
430            key = plan.predict_from == 0;
431            let anchor = |slot: u8| {
432                references
433                    .slot(slot)
434                    .map_or(0, |(_, held, _)| anchor_id(held))
435            };
436            let refreshed = |slot: u8| plan.refresh.refreshes(slot);
437            let stored = if refreshed(SlotRefresh::GOLDEN | SlotRefresh::ALTREF) {
438                anchor_id(references.next_pts())
439            } else {
440                0
441            };
442            let kept = [SlotRefresh::GOLDEN, SlotRefresh::ALTREF].map(|slot| {
443                if refreshed(slot) {
444                    stored
445                } else {
446                    anchor(slot)
447                }
448            });
449            let from = if key || plan.predict_from == SlotRefresh::LAST {
450                0
451            } else {
452                anchor(plan.predict_from)
453            };
454            let released = self
455                .anchors
456                .iter()
457                .copied()
458                .find(|a| !kept.contains(a) && *a != from);
459            self.events.store = stored;
460            self.events.predict_from = from;
461            let preset = if from != 0 { ANCHOR_PRESET } else { PRESET };
462            if preset != self.preset {
463                self.events.preset = preset;
464                self.preset = preset;
465            }
466            self.events.clear = if key { 0 } else { released.unwrap_or(0) };
467        }
468        {
469            let header = &mut self.input.0;
470            header.n_filled_len = header.n_alloc_len;
471            header.pts = pts as i64;
472            header.flags = 0;
473            header.pic_type = if key {
474                EB_AV1_KEY_PICTURE
475            } else {
476                EB_AV1_INVALID_PICTURE
477            };
478        }
479        if self.rate.cbr && HAS_EVENTS {
480            let held_key = key && self.held.is_some();
481            if held_key || self.restate_target {
482                // The rate control takes no quantizer: a held key frame's picture is planned
483                // with a target that gives it `HELD_KEY_BUDGET_S` of the session's.
484                let factor = if held_key {
485                    super::HELD_KEY_BUDGET_S * self.rate.fps.fps()
486                } else {
487                    1.0
488                };
489                self.events.target_bit_rate =
490                    (self.rate.bps() as f64 * factor.max(1.0)).min(MAX_BITRATE_BPS as f64) as u32;
491            }
492            self.restate_target = held_key;
493        }
494        let held = self.held.take().is_some();
495        let sent = self.events;
496        let code = unsafe { send_picture(self.handle, &mut self.input.0, sent) };
497        if code != EB_ErrorNone {
498            return Err(error("SVT-AV1 refused the frame", code));
499        }
500        self.events = Events::default();
501        self.next_pts += 1;
502        self.fresh = false;
503        self.pending.push(pts, frame_number as u16);
504
505        let mut output = Vec::new();
506        loop {
507            let mut packet: *mut EbBufferHeaderType = ptr::null_mut();
508            let code = unsafe { svt_av1_enc_get_packet(self.handle, &mut packet, 0) };
509            if code == EB_NoErrorEmptyQueue {
510                break;
511            }
512            if code != EB_ErrorNone {
513                return Err(error("SVT-AV1 handed out no packet", code));
514            }
515            let Some(p) = (unsafe { packet.as_ref() }) else {
516                break;
517            };
518            let bytes =
519                unsafe { std::slice::from_raw_parts(p.p_buffer, p.n_filled_len as usize) }.to_vec();
520            let packet_pts = p.pts.max(0) as u64;
521            if !held {
522                self.last_quality =
523                    Some(Codec::Av1.quality_index(VPX_QINDEX[(p.qp as usize).min(63)] as u32));
524            }
525            unsafe { svt_av1_enc_release_out_buffer(&mut packet) };
526            let id = self.pending.take(packet_pts).unwrap_or(frame_number as u16);
527            let is_key = av1_is_key(&bytes);
528            let mut anchor = 0;
529            if let Some(references) = self.references.as_mut() {
530                if !is_key && references.shared_anchor(plan) {
531                    anchor = FRAME_ANCHOR;
532                }
533                self.last_reference =
534                    references.record(id, if is_key { SlotPlan::KEY } else { plan });
535                if is_key {
536                    self.anchors.clear();
537                }
538                self.anchors.retain(|&a| a != sent.clear);
539                if sent.store != 0 {
540                    self.anchors.push(sent.store);
541                }
542            }
543            if !self.omit_headers {
544                output.reserve(VIDEO_HEADER_LEN + bytes.len());
545                push_video_header(
546                    &mut output,
547                    Codec::Av1,
548                    frame_type_from_key(is_key) | anchor,
549                    id,
550                    0,
551                    self.planes.width as u16,
552                    self.planes.height as u16,
553                    self.last_reference(),
554                );
555            }
556            output.extend_from_slice(&bytes);
557            if self.pending.is_empty() {
558                break;
559            }
560        }
561        Ok(output)
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568
569    /// A session let go before its first frame closes.
570    #[test]
571    fn a_session_let_go_before_its_first_frame_closes() {
572        let (done, closed) = std::sync::mpsc::channel();
573        std::thread::spawn(move || {
574            let settings = RustCaptureSettings {
575                width: 64,
576                height: 64,
577                target_fps: 30.0,
578                codec: Codec::Av1,
579                ..Default::default()
580            };
581            drop(SvtAv1Encoder::new(&settings, false).expect("session"));
582            let _ = done.send(());
583        });
584        assert!(
585            closed
586                .recv_timeout(std::time::Duration::from_secs(30))
587                .is_ok(),
588            "the release never returned"
589        );
590    }
591
592    /// The library is configured at the capture's rate as the fraction it names, a live change
593    /// of rate included.
594    #[test]
595    fn the_frame_rate_reaches_the_library_as_its_fraction() {
596        for (num, den) in [(60000u32, 1001u32), (120000, 1001), (144000, 1001), (60, 1)] {
597            let fps = num as f64 / den as f64;
598            let settings = RustCaptureSettings {
599                width: 64,
600                height: 64,
601                target_fps: fps,
602                codec: Codec::Av1,
603                video_cbr_mode: true,
604                video_bitrate_kbps: 2000,
605                ..Default::default()
606            };
607            let mut enc = SvtAv1Encoder::new(&settings, false).expect("session");
608            assert_eq!(
609                (
610                    enc.config.frame_rate_numerator,
611                    enc.config.frame_rate_denominator
612                ),
613                (num, den)
614            );
615            enc.reconfigure_rate(&RustCaptureSettings {
616                target_fps: 30.0,
617                ..settings.clone()
618            })
619            .expect("30 fps");
620            enc.reconfigure_rate(&settings).expect("back");
621            assert_eq!(
622                (
623                    enc.config.frame_rate_numerator,
624                    enc.config.frame_rate_denominator
625                ),
626                (num, den)
627            );
628        }
629    }
630
631    /// Sessions opened and closed on many threads at once each encode their frame.
632    #[test]
633    fn sessions_open_and_close_on_many_threads_at_once() {
634        let settings = RustCaptureSettings {
635            width: 64,
636            height: 64,
637            target_fps: 30.0,
638            codec: Codec::Av1,
639            ..Default::default()
640        };
641        let frame = vec![0u8; 64 * 64 * 4];
642        std::thread::scope(|s| {
643            for _ in 0..4 {
644                s.spawn(|| {
645                    for n in 0..4 {
646                        let mut enc = SvtAv1Encoder::new(&settings, false).expect("session");
647                        enc.encode_host(&frame, 64 * 4, false, n, 30, true)
648                            .expect("frame");
649                    }
650                });
651            }
652        });
653    }
654}