1use 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
68const RTC_MIN_LEVEL: u32 = 10;
72
73#[cfg(svtav1_sequence_qp)]
76const FORCED_MIN_LEVEL: u32 = 3;
77
78const PRESET: i8 = 11;
82
83const ANCHOR_PRESET: i8 = 10;
89
90const KEY_PRESET: i8 = 9;
97
98const MAX_BITRATE_BPS: u64 = 100_000_000;
100
101const MANAGED_REFS: u8 = 4;
104
105const KEYFRAME_BY_REOPEN: bool = SVT_AV1_VERSION_MAJOR < 2;
108
109const FEED_BEFORE_RELEASE: bool =
112 SVT_AV1_VERSION_MAJOR < 1 || (SVT_AV1_VERSION_MAJOR == 1 && SVT_AV1_VERSION_MINOR < 5);
113
114pub(crate) static LIFECYCLE: Mutex<usize> = Mutex::new(0);
120
121pub(crate) static RELEASED: Condvar = Condvar::new();
123
124pub struct SvtAv1Encoder {
126 handle: *mut EbComponentType,
127 config: Box<EbSvtAv1EncConfiguration>,
128 input: Box<(EbBufferHeaderType, EbSvtIOFormat)>,
130 planes: Planes,
131 threads: i32,
132 quality: Quality,
133 rate: RateSettings,
134 omit_headers: bool,
135 fresh: bool,
136 events: Events,
138 preset: i8,
140 references: Option<ReferenceSlots>,
142 anchors: Vec<u32>,
144 last_reference: Reference,
145 next_pts: u64,
146 pending: Pending,
147 held: Option<u32>,
149 restate_target: bool,
151 constant: Vec<u8>,
154 last_quality: Option<u32>,
156 peak: Option<PeakOffset>,
159 bound_since_open: bool,
162 last_capped: bool,
164 last_crf: u32,
166 quality_index: u32,
169}
170
171fn 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 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 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 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 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 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 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 pub fn library(&self) -> &'static str {
408 "svt-av1"
409 }
410
411 pub fn is_fullcolor(&self) -> bool {
412 false
413 }
414
415 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 pub fn holds_quantizer(&self) -> bool {
439 self.rate.cbr && HAS_EVENTS
440 }
441
442 pub fn last_quality(&self) -> Option<u32> {
444 self.last_quality
445 }
446
447 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 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 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 #[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 pub fn capped(&self) -> bool {
533 self.peak.is_some()
534 }
535
536 pub fn peak_bound(&self) -> bool {
538 self.peak.is_some_and(|p| p.offset > 0.0)
539 }
540
541 pub fn coded_capped(&self) -> bool {
543 self.last_capped
544 }
545
546 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 pub fn reopens_on_quality(&self) -> bool {
558 !(self.peak.is_some() && self.bound_since_open)
559 }
560
561 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 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 #[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 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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[cfg(svtav1_sequence_qp)]
1045 fn scrolled(t: usize) -> Vec<u8> {
1046 crate::encoders::scrolled_text(640, 360, t)
1047 }
1048
1049 #[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 #[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 #[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 #[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 #[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}