1use super::codec::{Codec, push_jpeg_header};
18#[cfg(feature = "gpl")]
19use super::codec::{
20 FRAME_DELTA, FRAME_INTRA, FRAME_KEY, h264_dpb_frames, h264_level, h264_reference_level,
21 push_video_header,
22};
23#[cfg(feature = "gpl")]
24use super::frame_rate::FrameRate;
25use super::reference::Reference;
26#[cfg(feature = "gpl")]
27use super::reference::{Invalidation, ReferenceWindow};
28#[cfg(feature = "gpl")]
29use super::session::PeakOffset;
30#[cfg(feature = "gpl")]
31use super::sps::{WideFrameNum, h264_frame_num_range};
32use crate::CaptureSettings;
33use crate::ledger::{BlockLedger, RefinePace, RefreshPace};
34use crate::pipeline::{Cleanup, Damage};
35use rayon::prelude::*;
36use smithay::utils::{Physical, Rectangle};
37#[cfg(feature = "gpl")]
38use std::ffi::CString;
39#[cfg(feature = "gpl")]
40use std::ptr;
41use std::sync::Arc;
42use yuv::{
43 BufferStoreMut, YuvBiPlanarImageMut, YuvConversionMode, YuvPlanarImageMut, YuvRange,
44 YuvStandardMatrix,
45};
46
47pub const MAX_STRIPE_CAPACITY: usize = 64;
54
55#[allow(clippy::too_many_arguments)]
84pub(crate) fn convert_to_yuv_mt(
85 src: &[u8],
86 src_stride: u32,
87 width: usize,
88 height: usize,
89 rgba_input: bool,
90 i444: bool,
91 full_range: bool,
92 bt601: bool,
93 y_buf: &mut [u8],
94 u_buf: &mut [u8],
95 v_buf: &mut [u8],
96 strides: (usize, usize),
97 bands: usize,
98) -> Result<(), yuv::YuvError> {
99 let (y_stride, uv_stride) = strides;
100 let range = if full_range {
101 YuvRange::Full
102 } else {
103 YuvRange::Limited
104 };
105 let matrix = if bt601 {
106 YuvStandardMatrix::Bt601
107 } else {
108 YuvStandardMatrix::Bt709
109 };
110
111 let convert_band = |src_band: &[u8], y: &mut [u8], u: &mut [u8], v: &mut [u8], h: usize| {
112 let mut img = YuvPlanarImageMut {
113 y_plane: BufferStoreMut::Borrowed(y),
114 y_stride: y_stride as u32,
115 u_plane: BufferStoreMut::Borrowed(u),
116 u_stride: uv_stride as u32,
117 v_plane: BufferStoreMut::Borrowed(v),
118 v_stride: uv_stride as u32,
119 width: width as u32,
120 height: h as u32,
121 };
122 match (i444, rgba_input) {
123 (true, true) => yuv::rgba_to_yuv444(
124 &mut img,
125 src_band,
126 src_stride,
127 range,
128 matrix,
129 YuvConversionMode::Fast,
130 ),
131 (true, false) => yuv::bgra_to_yuv444(
132 &mut img,
133 src_band,
134 src_stride,
135 range,
136 matrix,
137 YuvConversionMode::Fast,
138 ),
139 (false, true) => yuv::rgba_to_yuv420(
140 &mut img,
141 src_band,
142 src_stride,
143 range,
144 matrix,
145 YuvConversionMode::Fast,
146 ),
147 (false, false) => yuv::bgra_to_yuv420(
148 &mut img,
149 src_band,
150 src_stride,
151 range,
152 matrix,
153 YuvConversionMode::Fast,
154 ),
155 }
156 };
157
158 let heights = band_heights(height, bands);
159 if heights.len() <= 1 {
160 return convert_band(src, y_buf, u_buf, v_buf, height);
161 }
162
163 let uv_rows = |rows: usize| if i444 { rows } else { rows / 2 };
164 let mut jobs = Vec::new();
165 let (mut src_rest, mut y_rest, mut u_rest, mut v_rest) = (src, y_buf, u_buf, v_buf);
166 for h in heights {
167 let (src_band, s_next) = src_rest.split_at(h * src_stride as usize);
168 let (y_band, y_next) = y_rest.split_at_mut(h * y_stride);
169 let (u_band, u_next) = u_rest.split_at_mut(uv_rows(h) * uv_stride);
170 let (v_band, v_next) = v_rest.split_at_mut(uv_rows(h) * uv_stride);
171 src_rest = s_next;
172 y_rest = y_next;
173 u_rest = u_next;
174 v_rest = v_next;
175 jobs.push((src_band, y_band, u_band, v_band, h));
176 }
177 on_band_pool(bands, || {
178 jobs.into_par_iter()
179 .map(|(src_band, y_band, u_band, v_band, h)| {
180 convert_band(src_band, y_band, u_band, v_band, h)
181 })
182 .collect::<Result<Vec<()>, _>>()
183 .map(|_| ())
184 })
185}
186
187fn band_heights(height: usize, bands: usize) -> Vec<usize> {
192 let band_h = ((height / bands.max(1)) & !1).max(2);
193 if bands <= 1 || height <= band_h {
194 return vec![height];
195 }
196 let mut heights = Vec::with_capacity(bands + 1);
197 let mut row = 0;
198 while row < height {
199 let h = if height - row < band_h + 2 {
200 height - row
201 } else {
202 band_h
203 };
204 heights.push(h);
205 row += h;
206 }
207 heights
208}
209
210const MAX_POOLED_BANDS: usize = 16;
214
215fn on_band_pool<R: Send>(bands: usize, convert: impl FnOnce() -> R + Send) -> R {
228 if rayon::current_thread_index().is_some() {
229 return convert();
230 }
231 static POOLS: [std::sync::OnceLock<Option<rayon::ThreadPool>>; MAX_POOLED_BANDS + 1] =
232 [const { std::sync::OnceLock::new() }; MAX_POOLED_BANDS + 1];
233 let pool = POOLS.get(bands).and_then(|pool| {
234 pool.get_or_init(|| {
235 rayon::ThreadPoolBuilder::new()
236 .num_threads(bands)
237 .thread_name(|_| "pixelflux-csc".into())
238 .build()
239 .map_err(|e| eprintln!("[software] no {bands}-thread conversion pool: {e}"))
240 .ok()
241 })
242 .as_ref()
243 });
244 match pool {
245 Some(pool) => pool.install(convert),
246 None => convert(),
247 }
248}
249
250#[allow(clippy::too_many_arguments)]
255pub(crate) fn convert_to_nv12_mt(
256 src: &[u8],
257 src_stride: u32,
258 width: usize,
259 height: usize,
260 rgba_input: bool,
261 y_buf: &mut [u8],
262 uv_buf: &mut [u8],
263 strides: (usize, usize),
264 bands: usize,
265) -> Result<(), yuv::YuvError> {
266 let (y_stride, uv_stride) = strides;
267 let convert_band = |src_band: &[u8], y: &mut [u8], uv: &mut [u8], h: usize| {
268 let mut img = YuvBiPlanarImageMut {
269 y_plane: BufferStoreMut::Borrowed(y),
270 y_stride: y_stride as u32,
271 uv_plane: BufferStoreMut::Borrowed(uv),
272 uv_stride: uv_stride as u32,
273 width: width as u32,
274 height: h as u32,
275 };
276 let convert = if rgba_input {
277 yuv::rgba_to_yuv_nv12
278 } else {
279 yuv::bgra_to_yuv_nv12
280 };
281 convert(
282 &mut img,
283 src_band,
284 src_stride,
285 YuvRange::Limited,
286 YuvStandardMatrix::Bt709,
287 YuvConversionMode::Fast,
288 )
289 };
290 let heights = band_heights(height, bands);
291 if heights.len() <= 1 {
292 return convert_band(src, y_buf, uv_buf, height);
293 }
294 let mut jobs = Vec::with_capacity(heights.len());
295 let (mut src_rest, mut y_rest, mut uv_rest) = (src, y_buf, uv_buf);
296 for h in heights {
297 let (src_band, s_next) = src_rest.split_at(h * src_stride as usize);
298 let (y_band, y_next) = y_rest.split_at_mut(h * y_stride);
299 let (uv_band, uv_next) = uv_rest.split_at_mut(h / 2 * uv_stride);
300 src_rest = s_next;
301 y_rest = y_next;
302 uv_rest = uv_next;
303 jobs.push((src_band, y_band, uv_band, h));
304 }
305 on_band_pool(bands, || {
306 jobs.into_par_iter()
307 .map(|(src_band, y_band, uv_band, h)| convert_band(src_band, y_band, uv_band, h))
308 .collect::<Result<Vec<()>, _>>()
309 .map(|_| ())
310 })
311}
312
313const YUV10_SHIFT: u32 = 14;
315
316fn yuv10_coefficients(full_range: bool) -> ([[i32; 3]; 3], i32) {
320 const KR: f64 = 0.2126;
321 const KB: f64 = 0.0722;
322 const KG: f64 = 1.0 - KR - KB;
323 let (luma, chroma, offset) = if full_range {
324 (1023.0 / 255.0, 1023.0 / 255.0, 0)
325 } else {
326 (876.0 / 255.0, 896.0 / 255.0, 64)
327 };
328 let fixed = |k: f64| (k * f64::from(1 << YUV10_SHIFT)).round() as i32;
329 let (cb, cr) = (chroma / (2.0 * (1.0 - KB)), chroma / (2.0 * (1.0 - KR)));
330 (
331 [
332 [fixed(luma * KR), fixed(luma * KG), fixed(luma * KB)],
333 [fixed(-cb * KR), fixed(-cb * KG), fixed(cb * (1.0 - KB))],
334 [fixed(cr * (1.0 - KR)), fixed(-cr * KG), fixed(-cr * KB)],
335 ],
336 offset,
337 )
338}
339
340#[allow(clippy::too_many_arguments)]
350pub(crate) fn convert_to_yuv10_mt(
351 src: &[u8],
352 src_stride: usize,
353 width: usize,
354 height: usize,
355 rgba_input: bool,
356 i444: bool,
357 full_range: bool,
358 y_buf: &mut [u16],
359 u_buf: &mut [u16],
360 v_buf: &mut [u16],
361 strides: (usize, usize),
362 bands: usize,
363) {
364 let (y_stride, uv_stride) = strides;
365 let (k, y_offset) = yuv10_coefficients(full_range);
366 let (ri, bi) = if rgba_input { (0, 2) } else { (2, 0) };
367 let half = 1 << (YUV10_SHIFT - 1);
368 let luma = move |r: i32, g: i32, b: i32| {
369 ((k[0][0] * r + k[0][1] * g + k[0][2] * b + half) >> YUV10_SHIFT) + y_offset
370 };
371 let chroma = move |row: &[i32; 3], r: i32, g: i32, b: i32, shift: u32| {
372 (((row[0] * r + row[1] * g + row[2] * b + (1 << (shift - 1))) >> shift) + 512)
373 .clamp(0, 1023) as u16
374 };
375
376 let convert_band = move |src: &[u8], y: &mut [u16], u: &mut [u16], v: &mut [u16], h: usize| {
377 for row in 0..h {
378 let line = &src[row * src_stride..row * src_stride + width * 4];
379 let out = &mut y[row * y_stride..row * y_stride + width];
380 for (px, y) in line.as_chunks::<4>().0.iter().zip(out.iter_mut()) {
381 *y = luma(i32::from(px[ri]), i32::from(px[1]), i32::from(px[bi])) as u16;
382 }
383 }
384 if i444 {
385 for row in 0..h {
386 let line = &src[row * src_stride..row * src_stride + width * 4];
387 let cb = &mut u[row * uv_stride..row * uv_stride + width];
388 let cr = &mut v[row * uv_stride..row * uv_stride + width];
389 for ((px, cb), cr) in line
390 .as_chunks::<4>()
391 .0
392 .iter()
393 .zip(cb.iter_mut())
394 .zip(cr.iter_mut())
395 {
396 let (r, g, b) = (i32::from(px[ri]), i32::from(px[1]), i32::from(px[bi]));
397 *cb = chroma(&k[1], r, g, b, YUV10_SHIFT);
398 *cr = chroma(&k[2], r, g, b, YUV10_SHIFT);
399 }
400 }
401 return;
402 }
403 let cw = width.div_ceil(2);
404 for row in 0..h.div_ceil(2) {
405 let top = &src[2 * row * src_stride..2 * row * src_stride + width * 4];
406 let below = (2 * row + 1).min(h - 1);
407 let bottom = &src[below * src_stride..below * src_stride + width * 4];
408 let cb = &mut u[row * uv_stride..row * uv_stride + cw];
409 let cr = &mut v[row * uv_stride..row * uv_stride + cw];
410 for (col, (cb, cr)) in cb.iter_mut().zip(cr.iter_mut()).enumerate() {
411 let left = 8 * col;
412 let right = (left + 4).min(width * 4 - 4);
413 let sum = |i: usize| {
414 i32::from(top[left + i])
415 + i32::from(top[right + i])
416 + i32::from(bottom[left + i])
417 + i32::from(bottom[right + i])
418 };
419 let (r, g, b) = (sum(ri), sum(1), sum(bi));
420 *cb = chroma(&k[1], r, g, b, YUV10_SHIFT + 2);
421 *cr = chroma(&k[2], r, g, b, YUV10_SHIFT + 2);
422 }
423 }
424 };
425
426 let heights = band_heights(height, bands);
427 if heights.len() <= 1 {
428 convert_band(src, y_buf, u_buf, v_buf, height);
429 return;
430 }
431 let uv_rows = |rows: usize| if i444 { rows } else { rows.div_ceil(2) };
432 let mut jobs = Vec::new();
433 let (mut src_rest, mut y_rest, mut u_rest, mut v_rest) = (src, y_buf, u_buf, v_buf);
434 let mut row = 0;
435 for h in heights {
436 let last = row + h >= height;
437 let take = |len: usize, rows: usize, stride: usize| if last { len } else { rows * stride };
438 let (src_band, s_next) = src_rest.split_at(take(src_rest.len(), h, src_stride));
439 let (y_band, y_next) = y_rest.split_at_mut(take(y_rest.len(), h, y_stride));
440 let (u_band, u_next) = u_rest.split_at_mut(take(u_rest.len(), uv_rows(h), uv_stride));
441 let (v_band, v_next) = v_rest.split_at_mut(take(v_rest.len(), uv_rows(h), uv_stride));
442 src_rest = s_next;
443 y_rest = y_next;
444 u_rest = u_next;
445 v_rest = v_next;
446 row += h;
447 jobs.push((src_band, y_band, u_band, v_band, h));
448 }
449 on_band_pool(bands, || {
450 jobs.into_par_iter()
451 .for_each(|(src_band, y_band, u_band, v_band, h)| {
452 convert_band(src_band, y_band, u_band, v_band, h)
453 })
454 });
455}
456
457thread_local! {
458 static JPEG_COMPRESSOR: std::cell::RefCell<Option<turbojpeg::Compressor>> =
466 const { std::cell::RefCell::new(None) };
467}
468
469#[cfg(feature = "gpl")]
477static X264_OPEN_CLOSE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
478
479#[cfg(feature = "gpl")]
484fn qp_offset(bit_depth: u32) -> i32 {
485 6 * (bit_depth as i32 - 8)
486}
487
488#[cfg(feature = "gpl")]
491fn x264_frame_rate(fps: f64) -> FrameRate {
492 FrameRate::of(if fps < 1.0 { 30.0 } else { fps })
493}
494
495#[cfg(feature = "gpl")]
500const KEY_STEPS: f64 = 10.0;
501
502#[cfg(feature = "gpl")]
508const OWED_FINER: u32 = crate::ledger::REFINE_FINER + 2;
509
510#[cfg(feature = "gpl")]
518const X264_REFRESH_FINER: u32 = crate::ledger::REFRESH_FINER + 1;
519
520#[cfg(feature = "gpl")]
532pub struct H264EncoderWrapper {
533 encoder: *mut codec_sys::x264::x264_t,
534 pub width: i32,
535 pub height: i32,
536 current_crf: i32,
537 pub is_i444: bool,
538 pub bit_depth: u32,
540 is_cbr: bool,
541 current_bitrate: i32,
542 current_vbv: i32,
543 current_fps: FrameRate,
544 threads: i32,
547 min_qp: i32,
548 max_qp: i32,
549 references: ReferenceWindow,
552 reference_frames: u32,
555 last_reference: Reference,
556 frame_num: WideFrameNum,
558 current_min_qp: i32,
561 held_qp: Option<i32>,
563 last_qp: Option<u32>,
565 rf_constant: f64,
567 peak: Option<PeakOffset>,
577 open_qp_max: i32,
578 coded: Option<Coded>,
581 last_capped: bool,
582 bound_once: bool,
584 last_key: Option<LastKey>,
586 #[cfg(test)]
588 encodes: usize,
589 quant_offsets: Vec<f32>,
592 mb_info: Vec<u8>,
595 held_input: Option<*const u8>,
597}
598
599#[cfg(feature = "gpl")]
602#[derive(Clone, Copy, Debug)]
603struct LastKey {
604 bits: f64,
605 qp: f64,
606 rf: f64,
607 held: bool,
608}
609
610#[cfg(feature = "gpl")]
613#[derive(Clone, Copy, Debug)]
614struct Coded {
615 bits: f64,
616 qp: f64,
617 key: bool,
618 held: bool,
619}
620
621#[cfg(feature = "gpl")]
624#[derive(Clone, Copy)]
625enum Input<'a> {
626 Planar {
627 planes: [&'a [u8]; 3],
628 strides: [i32; 3],
629 },
630 Nv12(&'a Nv12Planes),
631}
632
633#[cfg(feature = "gpl")]
634unsafe impl Send for H264EncoderWrapper {}
635
636#[cfg(feature = "gpl")]
637impl Drop for H264EncoderWrapper {
638 fn drop(&mut self) {
639 if !self.encoder.is_null() {
640 let _guard = X264_OPEN_CLOSE_LOCK
641 .lock()
642 .unwrap_or_else(|e| e.into_inner());
643 unsafe { codec_sys::x264::x264_encoder_close(self.encoder) };
644 self.encoder = ptr::null_mut();
645 }
646 }
647}
648
649#[cfg(feature = "gpl")]
650impl H264EncoderWrapper {
651 #[allow(clippy::too_many_arguments)]
705 pub fn new(
706 width: i32,
707 height: i32,
708 crf: i32,
709 is_i444: bool,
710 fps: f64,
711 threads: i32,
712 cbr_mode: bool,
713 bitrate_kbps: i32,
714 vbv_kbit: i32,
715 min_qp: i32,
716 max_qp: i32,
717 ) -> Option<Self> {
718 Self::open(
719 width,
720 height,
721 crf,
722 is_i444,
723 8,
724 fps,
725 threads,
726 cbr_mode,
727 bitrate_kbps,
728 vbv_kbit,
729 min_qp,
730 max_qp,
731 0,
732 None,
733 )
734 }
735
736 #[allow(clippy::too_many_arguments)]
740 pub fn with_depth(
741 width: i32,
742 height: i32,
743 crf: i32,
744 is_i444: bool,
745 bit_depth: u32,
746 fps: f64,
747 threads: i32,
748 cbr_mode: bool,
749 bitrate_kbps: i32,
750 vbv_kbit: i32,
751 min_qp: i32,
752 max_qp: i32,
753 reference_frames: u32,
754 ) -> Option<Self> {
755 Self::open(
756 width,
757 height,
758 crf,
759 is_i444,
760 bit_depth,
761 fps,
762 threads,
763 cbr_mode,
764 bitrate_kbps,
765 vbv_kbit,
766 min_qp,
767 max_qp,
768 reference_frames,
769 None,
770 )
771 }
772
773 pub fn ten_bit() -> bool {
776 static TEN_BIT: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
777 *TEN_BIT.get_or_init(|| {
778 Self::with_depth(64, 64, 25, false, 10, 30.0, 1, false, 0, 0, 0, 0, 0).is_some()
779 })
780 }
781
782 #[allow(clippy::too_many_arguments)]
787 fn open(
788 width: i32,
789 height: i32,
790 crf: i32,
791 is_i444: bool,
792 bit_depth: u32,
793 fps: f64,
794 threads: i32,
795 cbr_mode: bool,
796 bitrate_kbps: i32,
797 vbv_kbit: i32,
798 min_qp: i32,
799 max_qp: i32,
800 reference_frames: u32,
801 key: Option<(i32, i32)>,
802 ) -> Option<Self> {
803 unsafe {
804 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
805 let preset = CString::new("ultrafast").unwrap();
806 let tune = CString::new("zerolatency").unwrap();
807
808 if codec_sys::x264::x264_param_default_preset(
809 &mut param,
810 preset.as_ptr(),
811 tune.as_ptr(),
812 ) < 0
813 {
814 return None;
815 }
816
817 let frame_rate = x264_frame_rate(fps);
818 param.i_width = width;
819 param.i_height = height;
820 param.i_fps_num = frame_rate.num;
821 param.i_fps_den = frame_rate.den;
822 param.i_keyint_max = codec_sys::x264::X264_KEYINT_MAX_INFINITE as i32;
823 param.i_scenecut_threshold = 0;
824 let bitrate_bps = if cbr_mode {
825 bitrate_kbps.saturating_abs() as u64 * 1000
826 } else {
827 0
828 };
829 let dpb = h264_dpb_frames(
830 h264_reference_level(
831 h264_level(width as u32, height as u32, frame_rate.ceil(), bitrate_bps),
832 width as u32,
833 height as u32,
834 ),
835 width as u32,
836 height as u32,
837 );
838 let dpb = match reference_frames {
839 0 => dpb,
840 n => dpb.min(n),
841 };
842 param.i_dpb_size = dpb as i32;
843 if cbr_mode {
844 let bk = bitrate_kbps.saturating_abs();
845 param.rc.i_rc_method = codec_sys::x264::X264_RC_ABR as i32;
846 param.rc.i_bitrate = bk;
847 param.rc.i_vbv_max_bitrate = bk;
848 param.rc.i_vbv_buffer_size = vbv_kbit.max(1);
849 if let Some((budget, level)) = key {
850 let rate = ((f64::from(budget) * frame_rate.fps()) as i32).max(bk);
851 param.rc.i_bitrate = rate;
852 param.rc.i_vbv_max_bitrate = rate;
853 param.rc.i_vbv_buffer_size = budget.max(1);
854 param.rc.f_vbv_buffer_init = 1.0;
855 param.i_level_idc = level;
856 }
857 param.rc.b_filler = 0;
858 if min_qp > 0 {
859 param.rc.i_qp_min = min_qp.min(51) + qp_offset(bit_depth);
860 }
861 param.rc.i_qp_max =
862 if max_qp > 0 { max_qp.min(51) } else { 51 } + qp_offset(bit_depth);
863 } else {
864 param.rc.i_rc_method = codec_sys::x264::X264_RC_CRF as i32;
865 param.rc.f_rf_constant = crf as f32;
866 }
867 let depth_flag = if bit_depth > 8 {
868 codec_sys::x264::X264_CSP_HIGH_DEPTH
869 } else {
870 0
871 };
872 param.i_csp = (if is_i444 {
873 codec_sys::x264::X264_CSP_I444
874 } else {
875 codec_sys::x264::X264_CSP_I420
876 } | depth_flag) as i32;
877 param.i_bitdepth = bit_depth as i32;
878 param.vui.b_fullrange = if is_i444 { 1 } else { 0 };
879 param.vui.i_colorprim = 1;
880 param.vui.i_transfer = 1;
881 param.vui.i_colmatrix = 1;
882
883 let profile = CString::new(match (is_i444, bit_depth > 8) {
884 (true, _) => "high444",
885 (false, true) => "high10",
886 (false, false) => "baseline",
887 })
888 .unwrap();
889 codec_sys::x264::x264_param_apply_profile(&mut param, profile.as_ptr());
890 #[cfg(x264_qp_offsets)]
893 {
894 param.rc.b_qp_offsets = 1;
895 }
896 param.analyse.b_mb_info = 1;
897 param.analyse.i_me_method = codec_sys::x264::X264_ME_UMH as i32;
898 param.analyse.i_me_range = 64;
899
900 param.i_threads = threads;
901 param.b_repeat_headers = 1;
902 param.b_annexb = 1;
903 param.i_log_level = codec_sys::x264::X264_LOG_NONE;
904
905 let encoder = {
906 let _guard = X264_OPEN_CLOSE_LOCK
907 .lock()
908 .unwrap_or_else(|e| e.into_inner());
909 codec_sys::x264::x264_encoder_open(&mut param)
910 };
911 if encoder.is_null() {
912 None
913 } else {
914 codec_sys::x264::x264_encoder_parameters(encoder, &mut param);
915 Some(Self {
916 encoder,
917 width,
918 height,
919 current_crf: crf,
920 is_i444,
921 bit_depth,
922 is_cbr: cbr_mode,
923 current_bitrate: bitrate_kbps.saturating_abs(),
924 current_vbv: if cbr_mode { vbv_kbit } else { 0 },
925 current_fps: frame_rate,
926 threads,
927 min_qp,
928 max_qp,
929 references: ReferenceWindow::new(dpb),
930 reference_frames,
931 last_reference: Reference::Untracked,
932 frame_num: WideFrameNum::default(),
933 current_min_qp: min_qp,
934 held_qp: None,
935 last_qp: None,
936 rf_constant: f64::from(param.rc.f_rf_constant),
937 peak: (!cbr_mode && vbv_kbit > 0 && bitrate_kbps != 0).then(|| {
938 PeakOffset::new(bitrate_kbps.saturating_abs(), vbv_kbit, frame_rate.fps())
939 }),
940 open_qp_max: param.rc.i_qp_max,
941 coded: None,
942 last_capped: false,
943 bound_once: false,
944 last_key: None,
945 #[cfg(test)]
946 encodes: 0,
947 quant_offsets: Vec::new(),
948 mb_info: Vec::new(),
949 held_input: None,
950 })
951 }
952 }
953 }
954
955 pub fn reconfigure_crf(&mut self, new_crf: i32) {
965 if self.is_cbr || self.current_crf == new_crf {
966 return;
967 }
968 let offset = self.peak.map_or(0.0, |p| p.offset);
969 if self.set_rate_factor(new_crf, offset) {
970 self.current_crf = new_crf;
971 }
972 }
973
974 fn set_rate_factor(&mut self, crf: i32, offset: f64) -> bool {
977 unsafe {
978 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
979 codec_sys::x264::x264_encoder_parameters(self.encoder, &mut param);
980 param.rc.f_rf_constant = (f64::from(crf) + offset) as f32;
981 param.rc.i_qp_max = if offset > 0.0 {
982 51 + qp_offset(self.bit_depth)
983 } else {
984 self.open_qp_max
985 };
986 let taken = codec_sys::x264::x264_encoder_reconfig(self.encoder, &mut param) == 0;
987 if taken {
988 self.rf_constant = f64::from(param.rc.f_rf_constant);
989 }
990 taken
991 }
992 }
993
994 pub fn reconfigure_rate(&mut self, bitrate_kbps: i32, vbv_kbit: i32, fps: f64) {
1011 let bk = bitrate_kbps.saturating_abs();
1012 let new_fps = x264_frame_rate(fps);
1013 if !self.is_cbr {
1014 self.follow_peak(bk, vbv_kbit, new_fps.fps());
1015 }
1016 let rate_changed =
1017 self.is_cbr && (self.current_bitrate != bk || self.current_vbv != vbv_kbit);
1018 let reopen = self.current_fps != new_fps;
1019 if !rate_changed && !reopen {
1020 return;
1021 }
1022 if reopen {
1023 if let Some(fresh) = H264EncoderWrapper::with_depth(
1024 self.width,
1025 self.height,
1026 self.current_crf,
1027 self.is_i444,
1028 self.bit_depth,
1029 new_fps.fps(),
1030 self.threads,
1031 self.is_cbr,
1032 bk,
1033 vbv_kbit,
1034 self.min_qp,
1035 self.max_qp,
1036 self.reference_frames,
1037 ) {
1038 let peak = self.peak;
1040 *self = fresh;
1041 if let (Some(fresh), Some(peak)) = (self.peak.as_mut(), peak) {
1042 fresh.fill = peak.fill;
1043 }
1044 }
1045 return;
1046 }
1047 unsafe {
1048 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
1049 codec_sys::x264::x264_encoder_parameters(self.encoder, &mut param);
1050 param.rc.i_bitrate = bk;
1051 param.rc.i_vbv_max_bitrate = bk;
1052 param.rc.i_vbv_buffer_size = vbv_kbit.max(1);
1053 if codec_sys::x264::x264_encoder_reconfig(self.encoder, &mut param) == 0 {
1054 self.current_bitrate = bk;
1055 self.current_vbv = vbv_kbit;
1056 }
1057 }
1058 }
1059
1060 pub fn last_reference(&self) -> Reference {
1062 self.last_reference
1063 }
1064
1065 pub fn last_qp(&self) -> Option<u32> {
1067 self.last_qp
1068 }
1069
1070 pub fn peak_bound(&self) -> bool {
1073 self.peak.is_some_and(|p| p.offset > 0.0)
1074 }
1075
1076 pub fn capped(&self) -> bool {
1078 self.peak.is_some()
1079 }
1080
1081 pub fn idle(&mut self, settings: &CaptureSettings) {
1085 if !self.is_cbr {
1086 let (bitrate, vbv) = rate_control(settings);
1087 self.follow_peak(bitrate, vbv, x264_frame_rate(settings.target_fps).fps());
1088 }
1089 if self.peak.is_some() {
1090 self.fold_frame(Coded {
1091 bits: 0.0,
1092 qp: self.rf_constant,
1093 key: false,
1094 held: false,
1095 });
1096 }
1097 }
1098
1099 pub fn coded_capped(&self) -> bool {
1102 self.last_capped
1103 }
1104
1105 #[allow(clippy::too_many_arguments)]
1111 pub fn refresh_blocks(
1112 &mut self,
1113 qp: Option<i32>,
1114 now: &crate::ledger::BlockPlan,
1115 finer: f32,
1116 key: &crate::ledger::BlockPlan,
1117 key_finer: f32,
1118 wait: &crate::ledger::BlockPlan,
1119 ) {
1120 if let Some(qp) = qp {
1121 self.hold_quantizer(qp);
1122 }
1123 let cols = (self.width as usize).div_ceil(16);
1124 let rows = (self.height as usize).div_ceil(16);
1125 self.quant_offsets.clear();
1126 self.quant_offsets.extend((0..rows).flat_map(|row| {
1127 (0..cols).map(move |col| {
1128 if key.refines(col, row) {
1129 -key_finer
1130 } else if now.refines(col, row) {
1131 -finer
1132 } else if wait.refines(col, row) {
1133 51.0
1134 } else {
1135 0.0
1136 }
1137 })
1138 }));
1139 }
1140
1141 fn follow_peak(&mut self, bitrate_kbps: i32, vbv_kbit: i32, fps: f64) {
1144 self.current_bitrate = bitrate_kbps;
1145 match (self.peak.as_mut(), vbv_kbit > 0 && bitrate_kbps > 0) {
1146 (Some(peak), true) => peak.follow(bitrate_kbps, vbv_kbit, fps),
1147 (None, true) => self.peak = Some(PeakOffset::new(bitrate_kbps, vbv_kbit, fps)),
1148 (Some(peak), false) => {
1149 let bound = peak.offset > 0.0;
1150 self.peak = None;
1151 if bound {
1152 self.set_rate_factor(self.current_crf, 0.0);
1153 }
1154 }
1155 (None, false) => {}
1156 }
1157 }
1158
1159 pub fn reconfigure_floor(&mut self, min_qp: i32) {
1168 if !self.is_cbr || min_qp == self.current_min_qp {
1169 return;
1170 }
1171 unsafe {
1172 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
1173 codec_sys::x264::x264_encoder_parameters(self.encoder, &mut param);
1174 param.rc.i_qp_min = if min_qp > 0 {
1175 min_qp.min(51) + qp_offset(self.bit_depth)
1176 } else {
1177 0
1178 };
1179 if codec_sys::x264::x264_encoder_reconfig(self.encoder, &mut param) == 0 {
1180 self.current_min_qp = min_qp;
1181 }
1182 }
1183 }
1184
1185 pub fn hold_quantizer(&mut self, qp: i32) {
1194 self.held_qp = Some(qp.clamp(0, 51));
1195 }
1196
1197 fn open_for_held_key(&mut self) -> bool {
1201 let level = unsafe {
1202 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
1203 codec_sys::x264::x264_encoder_parameters(self.encoder, &mut param);
1204 param.i_level_idc
1205 };
1206 let budget = (f64::from(self.current_bitrate) * super::HELD_KEY_BUDGET_S).round() as i32;
1207 let fresh = Self::open(
1208 self.width,
1209 self.height,
1210 self.current_crf,
1211 self.is_i444,
1212 self.bit_depth,
1213 self.current_fps.fps(),
1214 self.threads,
1215 self.is_cbr,
1216 self.current_bitrate,
1217 self.current_vbv,
1218 self.min_qp,
1219 self.max_qp,
1220 self.reference_frames,
1221 Some((budget, level)),
1222 );
1223 let Some(mut fresh) = fresh else { return false };
1224 fresh.held_qp = self.held_qp;
1225 *self = fresh;
1226 true
1227 }
1228
1229 pub fn refine_blocks(&mut self, plan: &crate::ledger::BlockPlan, offset: f32) {
1234 let cols = (self.width as usize).div_ceil(16);
1235 let rows = (self.height as usize).div_ceil(16);
1236 self.quant_offsets.clear();
1237 self.quant_offsets.extend((0..rows).flat_map(|row| {
1238 (0..cols).map(move |col| if plan.refines(col, row) { offset } else { 0.0 })
1239 }));
1240 }
1241
1242 pub fn constant_blocks(&mut self, unchanged: impl Iterator<Item = bool>) {
1246 self.mb_info.clear();
1247 self.mb_info.extend(unchanged.map(|c| {
1248 if c {
1249 codec_sys::x264::X264_MBINFO_CONSTANT as u8
1250 } else {
1251 0
1252 }
1253 }));
1254 }
1255
1256 fn restore_rate(&mut self) {
1258 unsafe {
1259 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
1260 codec_sys::x264::x264_encoder_parameters(self.encoder, &mut param);
1261 param.rc.i_bitrate = self.current_bitrate;
1262 param.rc.i_vbv_max_bitrate = self.current_bitrate;
1263 param.rc.i_vbv_buffer_size = self.current_vbv.max(1);
1264 codec_sys::x264::x264_encoder_reconfig(self.encoder, &mut param);
1265 }
1266 }
1267
1268 fn fold_frame(&mut self, frame: Coded) {
1271 let max_offset = 51.0 - f64::from(self.current_crf);
1272 let Some(peak) = self.peak.as_mut() else {
1273 return;
1274 };
1275 let was = peak.offset;
1276 let offset = peak.note(frame.bits, !frame.key && !frame.held, max_offset);
1277 if offset != was {
1278 self.set_rate_factor(self.current_crf, offset);
1279 }
1280 }
1281
1282 pub fn invalidate_reference(&mut self, frame_id: u16) -> bool {
1285 match self.references.invalidate(frame_id) {
1286 Invalidation::Forget(pts) => unsafe {
1287 codec_sys::x264::x264_encoder_invalidate_reference(self.encoder, pts as i64) == 0
1288 },
1289 Invalidation::KeyFrame | Invalidation::Ignored => true,
1290 }
1291 }
1292
1293 #[allow(clippy::too_many_arguments)]
1318 pub fn encode_with_headers(
1319 &mut self,
1320 y: &[u8],
1321 u: &[u8],
1322 v: &[u8],
1323 y_stride: i32,
1324 u_stride: i32,
1325 v_stride: i32,
1326 frame_id: u16,
1327 force_idr: bool,
1328 omit_headers: bool,
1329 output_buf: &mut Vec<u8>,
1330 ) -> bool {
1331 self.encode_input(
1332 Input::Planar {
1333 planes: [y, u, v],
1334 strides: [y_stride, u_stride, v_stride],
1335 },
1336 frame_id,
1337 force_idr,
1338 omit_headers,
1339 output_buf,
1340 )
1341 }
1342
1343 pub fn encode_nv12_with_headers(
1348 &mut self,
1349 planes: &Nv12Planes,
1350 frame_id: u16,
1351 force_idr: bool,
1352 omit_headers: bool,
1353 output_buf: &mut Vec<u8>,
1354 ) -> bool {
1355 self.encode_input(
1356 Input::Nv12(planes),
1357 frame_id,
1358 force_idr,
1359 omit_headers,
1360 output_buf,
1361 )
1362 }
1363
1364 pub fn held_input(&self) -> Option<*const u8> {
1368 self.held_input
1369 }
1370
1371 pub fn release_input(&mut self) {
1373 #[cfg(x264_in_place)]
1375 unsafe {
1376 codec_sys::x264::x264_encoder_release_input(self.encoder)
1377 };
1378 self.held_input = None;
1379 }
1380
1381 fn encode_input(
1382 &mut self,
1383 input: Input<'_>,
1384 frame_id: u16,
1385 force_idr: bool,
1386 omit_headers: bool,
1387 output_buf: &mut Vec<u8>,
1388 ) -> bool {
1389 let key_budget =
1390 self.is_cbr && force_idr && self.held_qp.is_some() && self.open_for_held_key();
1391 self.last_capped = self.peak_bound();
1392 let key = force_idr || !self.references.has_reference();
1393 let aimed = (key && self.held_qp.is_none())
1394 .then(|| self.aim_key())
1395 .flatten();
1396 if let Some(q) = aimed {
1397 self.held_qp = Some(q as i32);
1398 }
1399 let rf = self.rf_constant;
1400 let mut coded = self.encode_picture(input, frame_id, force_idr, omit_headers, output_buf);
1401 if key_budget {
1402 self.restore_rate();
1403 }
1404 if let Some(frame) = self.coded.filter(|c| c.key) {
1406 self.last_key = Some(LastKey {
1407 bits: frame.bits,
1408 qp: frame.qp,
1409 rf,
1410 held: aimed.is_some(),
1411 });
1412 }
1413 let mut held = aimed;
1414 if coded && aimed.is_none() {
1415 (coded, held) = self.fit_key(input, frame_id, omit_headers, output_buf);
1416 }
1417 if let (Some(q), Some(peak)) = (held, self.peak.as_mut()) {
1418 self.last_capped = true;
1421 let crf = f64::from(self.current_crf);
1422 let offset = (q - crf).clamp(peak.offset, (51.0 - crf).max(0.0));
1423 if offset != peak.offset {
1424 peak.offset = offset;
1425 self.set_rate_factor(self.current_crf, offset);
1426 }
1427 }
1428 self.bound_once |= self.last_capped;
1429 if let Some(frame) = self.coded.take() {
1430 self.fold_frame(frame);
1431 }
1432 coded
1433 }
1434
1435 pub fn holds_cleanup(&self, crf: i32) -> bool {
1441 let (Some(peak), Some(last)) = (self.peak.filter(|_| self.bound_once), self.last_key)
1442 else {
1443 return true;
1444 };
1445 let from = if last.held { last.qp } else { last.rf };
1446 last.bits * 2f64.powf((from - f64::from(crf)) / KEY_STEPS) <= peak.room()
1447 }
1448
1449 fn aim_key(&self) -> Option<f64> {
1455 let room = self.peak?.room().max(1.0);
1456 let last = self.last_key?;
1457 let from = if last.held { last.qp } else { last.rf };
1460 let expected = last.bits * 2f64.powf((from - self.rf_constant) / KEY_STEPS);
1461 (expected > room).then(|| {
1462 (last.qp + KEY_STEPS * (2.0 * last.bits / room).log2())
1463 .ceil()
1464 .min(51.0)
1465 })
1466 }
1467
1468 fn fit_key(
1474 &mut self,
1475 input: Input<'_>,
1476 frame_id: u16,
1477 omit_headers: bool,
1478 output_buf: &mut Vec<u8>,
1479 ) -> (bool, Option<f64>) {
1480 let Some(room) = self.peak.map(|p| p.room().max(1.0)) else {
1481 return (true, None);
1482 };
1483 let Some(first) = self.coded.filter(|c| c.key && c.bits > room && c.qp < 51.0) else {
1484 return (true, None);
1485 };
1486 let q = (first.qp + KEY_STEPS * (2.0 * first.bits / room).log2())
1487 .ceil()
1488 .min(51.0);
1489 self.held_qp = Some(q as i32);
1490 let coded = self.encode_picture(input, frame_id, true, omit_headers, output_buf);
1491 (coded, Some(q))
1492 }
1493
1494 fn encode_picture(
1496 &mut self,
1497 input: Input<'_>,
1498 frame_id: u16,
1499 force_idr: bool,
1500 omit_headers: bool,
1501 output_buf: &mut Vec<u8>,
1502 ) -> bool {
1503 #[cfg(test)]
1504 {
1505 self.encodes += 1;
1506 }
1507 unsafe {
1508 let mut pic_in: codec_sys::x264::x264_picture_t = std::mem::zeroed();
1509 codec_sys::x264::x264_picture_init(&mut pic_in);
1510
1511 self.held_input = None;
1512 self.coded = None;
1513 match input {
1514 Input::Planar { planes, strides } => {
1515 pic_in.img.i_csp = (if self.is_i444 {
1516 codec_sys::x264::X264_CSP_I444
1517 } else {
1518 codec_sys::x264::X264_CSP_I420
1519 } | if self.bit_depth > 8 {
1520 codec_sys::x264::X264_CSP_HIGH_DEPTH
1521 } else {
1522 0
1523 }) as i32;
1524 pic_in.img.i_plane = 3;
1525 for p in 0..3 {
1526 pic_in.img.plane[p] = planes[p].as_ptr() as *mut u8;
1527 pic_in.img.i_stride[p] = strides[p];
1528 }
1529 }
1530 Input::Nv12(nv12) => {
1531 #[cfg(x264_in_place)]
1533 let in_place = if nv12.in_place {
1534 codec_sys::x264::X264_CSP_IN_PLACE
1535 } else {
1536 0
1537 };
1538 #[cfg(not(x264_in_place))]
1539 let in_place = 0;
1540 pic_in.img.i_csp = (codec_sys::x264::X264_CSP_NV12 | in_place) as i32;
1541 pic_in.img.i_plane = 2;
1542 let (y, uv) = nv12.planes();
1543 pic_in.img.plane[0] = y.as_ptr() as *mut u8;
1544 pic_in.img.plane[1] = uv.as_ptr() as *mut u8;
1545 pic_in.img.i_stride[0] = nv12.stride[0] as i32;
1546 pic_in.img.i_stride[1] = nv12.stride[1] as i32;
1547 self.held_input = nv12.in_place.then(|| nv12.id());
1548 }
1549 }
1550 pic_in.i_pts = self.references.next_pts() as i64;
1551 pic_in.i_type = if force_idr || !self.references.has_reference() {
1552 codec_sys::x264::X264_TYPE_IDR
1553 } else {
1554 codec_sys::x264::X264_TYPE_AUTO
1555 } as i32;
1556 let held = self.held_qp.take();
1557 let offset = qp_offset(self.bit_depth);
1558 pic_in.i_qpplus1 =
1559 held.map_or(codec_sys::x264::X264_QP_AUTO as i32, |q| q + offset + 1);
1560 if !self.quant_offsets.is_empty() {
1562 pic_in.prop.quant_offsets = self.quant_offsets.as_mut_ptr();
1563 }
1564 let blocks = (self.width as usize).div_ceil(16) * (self.height as usize).div_ceil(16);
1565 if self.mb_info.len() == blocks {
1566 pic_in.prop.mb_info = self.mb_info.as_mut_ptr();
1567 }
1568
1569 let mut pic_out: codec_sys::x264::x264_picture_t = std::mem::zeroed();
1570 let mut nals: *mut codec_sys::x264::x264_nal_t = ptr::null_mut();
1571 let mut i_nals: i32 = 0;
1572
1573 let frame_size = codec_sys::x264::x264_encoder_encode(
1574 self.encoder,
1575 &mut nals,
1576 &mut i_nals,
1577 &mut pic_in,
1578 &mut pic_out,
1579 );
1580 self.quant_offsets.clear();
1581
1582 if frame_size > 0 {
1583 if held.is_none() {
1589 self.last_qp = (pic_out.i_qpplus1 > 0).then(|| {
1590 let planned = f64::from(pic_out.i_qpplus1 - 1);
1591 let coded = planned + pic_out.prop.f_crf_avg - self.rf_constant;
1592 (coded.round() as i32 - offset).clamp(0, 51) as u32
1593 });
1594 }
1595 let frame_type = if pic_out.i_type == codec_sys::x264::X264_TYPE_IDR as i32 {
1596 FRAME_KEY
1597 } else if pic_out.i_type == codec_sys::x264::X264_TYPE_I as i32 {
1598 FRAME_INTRA
1599 } else {
1600 FRAME_DELTA
1601 };
1602 self.coded = Some(Coded {
1603 bits: f64::from(frame_size) * 8.0,
1604 qp: f64::from(pic_out.i_qpplus1 - 1 - offset),
1605 key: frame_type == FRAME_KEY,
1606 held: held.is_some(),
1607 });
1608 self.last_reference = self.references.record(frame_id, frame_type != FRAME_DELTA);
1609 output_buf.clear();
1610 output_buf.reserve(super::codec::VIDEO_HEADER_LEN + frame_size as usize);
1611 if !omit_headers {
1612 push_video_header(
1613 output_buf,
1614 Codec::H264,
1615 frame_type,
1616 frame_id,
1617 0,
1618 self.width as u16,
1619 self.height as u16,
1620 self.last_reference,
1621 );
1622 }
1623
1624 let nal_slice = std::slice::from_raw_parts(nals, i_nals as usize);
1625 let mut widened = true;
1626 for nal in nal_slice {
1627 let payload = std::slice::from_raw_parts(nal.p_payload, nal.i_payload as usize);
1628 widened &= self.frame_num.push(payload, output_buf);
1629 }
1630 if !widened {
1631 self.references.reset();
1634 }
1635 if frame_type == FRAME_KEY {
1636 let stream = &output_buf[if omit_headers {
1637 0
1638 } else {
1639 super::codec::VIDEO_HEADER_LEN
1640 }..];
1641 self.references
1642 .set_frame_num_range(h264_frame_num_range(stream).unwrap_or(0));
1643 }
1644 return true;
1645 }
1646 }
1647 false
1648 }
1649}
1650
1651#[derive(Default)]
1698pub struct StripeState {
1699 pub no_motion_frame_count: u32,
1700 pub paint_over_sent: bool,
1701 pub dirty_run: u32,
1702 pub change_mass: f32,
1703 pub moved: bool,
1705 pub key_owed: bool,
1708 pub unclean_frames: u32,
1709 pub cleanup_keyed: bool,
1711 pub motion: f32,
1712 pub burst_held: bool,
1713 pub clean_quality: bool,
1714 pub rc_bytes: usize,
1715 pub rc_debt: f64,
1716 pub idle_frames: u32,
1717 pub over_frames: u32,
1718 pub same_frames: u32,
1719 pub finest: Option<u32>,
1720 pub finest_frames: u32,
1721 pub settled: bool,
1722 pub fine_frames: u32,
1723 pub measured: Option<f32>,
1724 pub measuring: bool,
1725 pub level_checks: u32,
1726 pub measure_in: u32,
1727 pub sweep: Option<(f64, f64)>,
1728 pub sweep_frames: u32,
1729 pub sweep_coarser: u32,
1730 pub sweep_cost: f64,
1731 pub sweep_first: f64,
1732 pub sweep_held: bool,
1733 pub cleanup_debt: f64,
1734 pub cleanup_paced: bool,
1735 #[cfg(feature = "gpl")]
1736 pub h264_encoder: Option<H264EncoderWrapper>,
1737 #[cfg(not(feature = "gpl"))]
1738 pub h264_encoder: Option<crate::encoders::oh264::Openh264Encoder>,
1739 pub h264_burst_frames_remaining: i32,
1740 #[cfg(feature = "gpl")]
1741 pub y_buf: Vec<u8>,
1742 #[cfg(feature = "gpl")]
1743 pub u_buf: Vec<u8>,
1744 #[cfg(feature = "gpl")]
1745 pub v_buf: Vec<u8>,
1746 #[cfg(feature = "gpl")]
1748 pub yuv16: [Vec<u16>; 3],
1749 #[cfg(feature = "gpl")]
1752 pub nv12: Vec<Nv12Planes>,
1753 pub packet_buf: Vec<u8>,
1754 pub last_hash: u64,
1755 pub consecutive_changes: u32,
1756 pub in_damage_block: bool,
1757 pub damage_block_frames_remaining: i32,
1758 pub hash_changed: bool,
1761 pub ledger: BlockLedger,
1764 pub refine: RefinePace,
1767 pub refresh: RefreshPace,
1768}
1769
1770pub fn h264_bit_depth(settings: &CaptureSettings) -> u32 {
1773 #[cfg(feature = "gpl")]
1774 if settings.codec == Codec::H264
1775 && settings.video_bit_depth >= 10
1776 && H264EncoderWrapper::ten_bit()
1777 {
1778 return 10;
1779 }
1780 let _ = settings;
1781 8
1782}
1783
1784fn fast_hash(bytes: &[u8]) -> u64 {
1792 xxhash_rust::xxh3::xxh3_64_with_seed(bytes, 0)
1793}
1794
1795impl StripeState {
1796 pub fn content_dirty(&mut self, bytes: &[u8], threshold: u32, duration: i32) -> bool {
1819 self.dirty_by(|| fast_hash(bytes), threshold, duration)
1820 }
1821
1822 pub fn hash_dirty(&mut self, hash: u64, threshold: u32, duration: i32) -> bool {
1825 self.dirty_by(|| hash, threshold, duration)
1826 }
1827
1828 fn dirty_by(&mut self, fast_hash: impl FnOnce() -> u64, threshold: u32, duration: i32) -> bool {
1829 self.hash_changed = false;
1830 if self.in_damage_block {
1831 self.damage_block_frames_remaining -= 1;
1832 if self.damage_block_frames_remaining == 1 {
1833 self.last_hash = fast_hash();
1834 } else if self.damage_block_frames_remaining <= 0 {
1835 let h = fast_hash();
1836 if h != self.last_hash {
1837 self.hash_changed = true;
1838 self.damage_block_frames_remaining = duration;
1839 } else {
1840 self.in_damage_block = false;
1841 self.consecutive_changes = 0;
1842 }
1843 self.last_hash = h;
1844 }
1845 return true;
1846 }
1847 let h = fast_hash();
1848 let changed = h != self.last_hash;
1849 self.last_hash = h;
1850 self.hash_changed = changed;
1851 if changed {
1852 self.consecutive_changes += 1;
1853 if self.consecutive_changes >= threshold {
1854 self.in_damage_block = true;
1855 self.damage_block_frames_remaining = duration;
1856 }
1857 } else {
1858 self.consecutive_changes = 0;
1859 }
1860 changed
1861 }
1862}
1863
1864pub struct EncodedStripe {
1880 pub data: Arc<Vec<u8>>,
1881 pub codec: Codec,
1882 pub stripe_y_start: i32,
1883 pub stripe_height: i32,
1884 pub frame_id: i32,
1885 pub timing: FrameTiming,
1886 pub reference: Reference,
1887}
1888
1889impl EncodedStripe {
1890 pub fn as_bytes(&self) -> &[u8] {
1892 &self.data
1893 }
1894
1895 pub fn data_type(&self) -> i32 {
1897 self.codec.data_type()
1898 }
1899
1900 pub fn reference_frame_id(&self) -> i32 {
1903 self.reference.frame_id()
1904 }
1905}
1906
1907#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
1912pub struct FrameTiming {
1913 pub capture_ns: i64,
1914 pub encode_start_ns: i64,
1915 pub encode_end_ns: i64,
1916}
1917
1918impl FrameTiming {
1919 pub fn stamp(stripes: &mut [EncodedStripe], capture_ns: i64, encode_start_ns: i64) {
1922 let timing = FrameTiming {
1923 capture_ns,
1924 encode_start_ns,
1925 encode_end_ns: crate::wayland::host::now_ns(),
1926 };
1927 for stripe in stripes {
1928 stripe.timing = timing;
1929 }
1930 }
1931}
1932
1933const MIN_STRIPE_HEIGHT: i32 = 64;
1935const CLEANUP_STAGGER_FRAMES: usize = 4;
1937
1938pub fn stripes_hold_quantizer(settings: &CaptureSettings) -> bool {
1943 cfg!(feature = "gpl") || !settings.video_cbr_mode
1944}
1945
1946pub fn stripes_held_still(stripes: &[StripeState], settings: &CaptureSettings) -> bool {
1950 !settings.video_cbr_mode
1951 && settings.use_paint_over_quality
1952 && settings.video_paintover_crf < settings.video_crf
1953 && stripes.iter().any(|st| {
1954 st.clean_quality
1955 || (st.burst_held && st.h264_burst_frames_remaining > 0)
1956 || crate::pipeline::cleanup_pending(
1957 st,
1958 settings.paint_over_trigger(),
1959 settings.paint_over_trigger(),
1960 true,
1961 true,
1962 )
1963 })
1964}
1965
1966fn ledger_frame(
1970 ledger: &mut BlockLedger,
1971 changed: &[Rectangle<i32, Physical>],
1972 (y, height): (i32, i32),
1973 dirty: bool,
1974 observed: bool,
1975) {
1976 let local: Vec<Rectangle<i32, Physical>> = changed
1977 .iter()
1978 .filter(|r| r.loc.y < y + height && r.loc.y + r.size.h > y)
1979 .map(|r| Rectangle::new((r.loc.x, r.loc.y - y).into(), r.size))
1980 .collect();
1981 ledger.frame_reported(&local, dirty, observed);
1982}
1983
1984#[allow(clippy::too_many_arguments)]
2082pub fn encode_cpu(
2083 stripes: &mut Vec<StripeState>,
2084 raw_pixels: &[u8],
2085 width: i32,
2086 height: i32,
2087 damage_rects: &[Rectangle<i32, Physical>],
2088 settings: &CaptureSettings,
2089 frame_counter: u16,
2090 use_gpu: bool,
2091 hash_damage: bool,
2092 requested_idr: bool,
2093) -> Vec<EncodedStripe> {
2094 encode_cpu_from(
2095 stripes,
2096 raw_pixels,
2097 None,
2098 width,
2099 height,
2100 damage_rects,
2101 true,
2102 settings,
2103 frame_counter,
2104 use_gpu,
2105 hash_damage,
2106 requested_idr,
2107 )
2108}
2109
2110pub struct Nv12Planes {
2116 buf: Vec<u8>,
2117 start: [usize; 2],
2118 pub stride: [usize; 2],
2119 pub width: usize,
2120 pub height: usize,
2121 #[cfg(feature = "gpl")]
2122 in_place: bool,
2123}
2124
2125impl Nv12Planes {
2126 pub fn new(width: usize, height: usize) -> Self {
2128 let layout = x264_input_layout(width, height);
2129 #[cfg(feature = "gpl")]
2130 let in_place = layout.is_some();
2131 let layout = layout.unwrap_or(InputLayout {
2132 stride: [width, width],
2133 offset: [0, 0],
2134 size: [width * height, width * height / 2],
2135 });
2136 const ALIGN: usize = 64;
2137 let luma = layout.size[0].next_multiple_of(ALIGN);
2138 let mut buf = vec![128u8; ALIGN + luma + layout.size[1]];
2139 let base = buf.as_ptr().align_offset(ALIGN);
2140 buf[base..base + layout.size[0]].fill(16);
2141 Self {
2142 buf,
2143 start: [base + layout.offset[0], base + luma + layout.offset[1]],
2144 stride: layout.stride,
2145 width,
2146 height,
2147 #[cfg(feature = "gpl")]
2148 in_place,
2149 }
2150 }
2151
2152 pub fn planes(&self) -> (&[u8], &[u8]) {
2155 let (y, uv) = self.extents();
2156 (&self.buf[y], &self.buf[uv])
2157 }
2158
2159 pub fn planes_mut(&mut self) -> (&mut [u8], &mut [u8]) {
2161 let (y, uv) = self.extents();
2162 let (head, tail) = self.buf.split_at_mut(uv.start);
2163 (&mut head[y], &mut tail[..uv.len()])
2164 }
2165
2166 pub fn id(&self) -> *const u8 {
2169 self.buf[self.start[0]..].as_ptr()
2170 }
2171
2172 fn extents(&self) -> (std::ops::Range<usize>, std::ops::Range<usize>) {
2173 let y = self.start[0]..self.start[0] + self.stride[0] * self.height;
2174 let uv = self.start[1]..self.start[1] + self.stride[1] * self.height / 2;
2175 (y, uv)
2176 }
2177}
2178
2179struct InputLayout {
2182 stride: [usize; 2],
2183 offset: [usize; 2],
2184 size: [usize; 2],
2185}
2186
2187#[cfg(all(feature = "gpl", x264_in_place))]
2190fn x264_input_layout(width: usize, height: usize) -> Option<InputLayout> {
2191 unsafe {
2192 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
2193 codec_sys::x264::x264_param_default(&mut param);
2194 param.i_width = width as i32;
2195 param.i_height = height as i32;
2196 param.i_csp = codec_sys::x264::X264_CSP_I420 as i32;
2197 param.i_bitdepth = 8;
2198 let (mut stride, mut offset, mut size) = ([0i32; 2], [0i32; 2], [0i64; 2]);
2199 if codec_sys::x264::x264_param_input_layout(
2200 ¶m,
2201 stride.as_mut_ptr(),
2202 offset.as_mut_ptr(),
2203 size.as_mut_ptr(),
2204 ) < 0
2205 {
2206 return None;
2207 }
2208 Some(InputLayout {
2209 stride: stride.map(|v| v as usize),
2210 offset: offset.map(|v| v as usize),
2211 size: size.map(|v| v as usize),
2212 })
2213 }
2214}
2215
2216#[cfg(not(all(feature = "gpl", x264_in_place)))]
2217fn x264_input_layout(_width: usize, _height: usize) -> Option<InputLayout> {
2218 None
2219}
2220
2221#[allow(clippy::too_many_arguments)]
2232pub fn encode_cpu_from(
2233 stripes: &mut Vec<StripeState>,
2234 raw_pixels: &[u8],
2235 planes: Option<&Nv12Planes>,
2236 width: i32,
2237 height: i32,
2238 damage_rects: &[Rectangle<i32, Physical>],
2239 current: bool,
2240 settings: &CaptureSettings,
2241 frame_counter: u16,
2242 use_gpu: bool,
2243 hash_damage: bool,
2244 requested_idr: bool,
2245) -> Vec<EncodedStripe> {
2246 #[cfg(not(feature = "gpl"))]
2247 let _ = planes;
2248 let codec = settings.codec;
2249 let scheduled = codec.is_video() && crate::pipeline::periodic_idr_due(settings, frame_counter);
2250 let n_processing_stripes = stripe_count(height, codec);
2251
2252 if stripes.len() != n_processing_stripes {
2253 stripes.resize_with(n_processing_stripes, StripeState::default);
2254 }
2255
2256 let stripe_geometries = compute_stripe_geometries(height as usize, n_processing_stripes, codec);
2257
2258 let coded_quality = |st: &StripeState| -> Option<u32> {
2269 cfg_if::cfg_if! {
2270 if #[cfg(feature = "gpl")] {
2271 st.h264_encoder.as_ref().and_then(H264EncoderWrapper::last_qp)
2272 } else {
2273 let _ = st;
2274 None
2275 }
2276 }
2277 };
2278 let holds = stripes_hold_quantizer(settings);
2279 let converges = codec.is_video() && settings.video_cbr_mode;
2283 let motion_floor = if settings.video_min_qp > 0 || !converges {
2284 settings.video_min_qp
2285 } else {
2286 settings.video_paintover_crf.max(0)
2287 };
2288 let paint_over_armed = |st: &StripeState| {
2289 if !codec.is_video() {
2290 return settings.use_paint_over_quality
2291 && settings.paint_over_jpeg_quality > settings.jpeg_quality;
2292 }
2293 crate::pipeline::paint_over_improves(
2294 settings,
2295 crate::pipeline::EncoderQuality {
2296 last: coded_quality(st),
2297 bytes: None,
2298 holds,
2299 reopens: false,
2300 keys: true,
2301 band: None,
2302 measures: false,
2303 psnr: None,
2304 refit: None,
2305 follows_cap: false,
2306 finer: 0,
2307 ends_on_key: false,
2308 },
2309 )
2310 };
2311 let trigger_frames = settings.paint_over_trigger();
2312 let start_frames = trigger_frames;
2315 let idle_candidate = damage_rects.is_empty()
2316 && !requested_idr
2317 && !(codec.is_video() && settings.video_streaming_mode);
2318 if idle_candidate {
2319 let refresh_due = |st: &StripeState| {
2322 cfg_if::cfg_if! {
2323 if #[cfg(feature = "gpl")] {
2324 st.ledger.coarse()
2325 && st.h264_encoder.as_ref().is_some_and(|e| e.capped() && !e.peak_bound())
2326 } else {
2327 let _ = st;
2328 false
2329 }
2330 }
2331 };
2332 let no_pending_send = |st: &StripeState| {
2333 (!codec.is_video() || st.h264_burst_frames_remaining <= 0)
2334 && !crate::pipeline::cleanup_pending(
2335 st,
2336 trigger_frames,
2337 start_frames,
2338 paint_over_armed(st),
2339 false,
2340 )
2341 && !refresh_due(st)
2342 };
2343 let quiescent = if !hash_damage {
2344 stripes.iter().all(no_pending_send)
2345 } else {
2346 let width_bytes = width as usize * 4;
2347 let hint = stripes
2348 .iter()
2349 .enumerate()
2350 .min_by_key(|(_, st)| st.no_motion_frame_count)
2351 .map(|(i, _)| i)
2352 .unwrap_or(0);
2353 let clean = |i: usize| {
2354 let st = &stripes[i];
2355 if !no_pending_send(st) || st.in_damage_block {
2356 return false;
2357 }
2358 let (y, h) = stripe_geometries[i];
2359 let bytes = &raw_pixels[y * width_bytes..(y + h) * width_bytes];
2360 fast_hash(bytes) == st.last_hash
2361 };
2362 clean(hint) && (0..stripes.len()).filter(|&i| i != hint).all(clean)
2363 };
2364 if quiescent {
2365 for st in stripes.iter_mut() {
2366 let armed = paint_over_armed(st);
2367 crate::pipeline::cleanup_due(
2368 st,
2369 trigger_frames,
2370 start_frames,
2371 armed,
2372 true,
2373 false,
2374 Damage::None,
2375 );
2376 st.consecutive_changes = 0;
2377 st.key_owed |= scheduled;
2378 st.ledger.frame([]);
2379 #[cfg(feature = "gpl")]
2380 if let Some(enc) = st.h264_encoder.as_mut() {
2381 enc.idle(settings);
2382 }
2383 }
2384 return Vec::new();
2385 }
2386 }
2387 let mut stripe_is_dirty = vec![false; n_processing_stripes];
2388 if !damage_rects.is_empty() {
2389 for rect in damage_rects {
2390 let r_y_start = rect.loc.y.max(0) as usize;
2391 let r_y_end = (rect.loc.y + rect.size.h).min(height) as usize;
2392 if r_y_start < r_y_end {
2393 for (i, &(s_y, s_h)) in stripe_geometries.iter().enumerate() {
2394 let s_end = s_y + s_h;
2395 if r_y_start < s_end && r_y_end > s_y {
2396 stripe_is_dirty[i] = true;
2397 }
2398 }
2399 }
2400 }
2401 }
2402
2403 let width_usize = width as usize;
2404 let video = codec.is_video();
2405 let video_crf = settings.video_crf;
2406 let video_po_crf = settings.video_paintover_crf;
2407 let video_burst = settings.video_paintover_burst_frames;
2408 let video_fullcolor = settings.video_fullcolor;
2409 #[cfg(feature = "gpl")]
2410 let bit_depth = h264_bit_depth(settings);
2411 let video_streaming = settings.video_streaming_mode;
2412 let jpeg_q = settings.jpeg_quality;
2413 let paint_q = settings.paint_over_jpeg_quality;
2414 let target_fps = settings.target_fps;
2415 let omit_headers = settings.omit_stripe_headers;
2416 let damage_block_threshold = settings.damage_block_threshold;
2417 let damage_block_duration = settings.damage_block_duration as i32;
2418 let video_cbr = settings.video_cbr_mode;
2419 #[cfg_attr(not(feature = "gpl"), allow(unused_variables))]
2421 let (video_bitrate, video_vbv) = rate_control(settings);
2422 #[cfg(feature = "gpl")]
2423 let cleanup_vbv = cleanup_vbv_kbit(settings, video_bitrate).max(video_vbv);
2424 #[cfg(feature = "gpl")]
2426 let h264_threads = crate::encoders::session::h264_fullframe_threads(settings);
2427 #[cfg(feature = "gpl")]
2429 let empty_bytes = crate::encoders::codec::VIDEO_HEADER_LEN
2430 + crate::pipeline::EMPTY_SLICE_BYTES * h264_threads as usize;
2431 #[cfg(not(feature = "gpl"))]
2432 let empty_bytes = 0;
2433 #[cfg(feature = "gpl")]
2434 let csc_bands = h264_threads.max(1) as usize;
2435 if video && video_fullcolor && !crate::encoders::software_fullcolor(Codec::H264) {
2436 static FULLCOLOR_LOGGED: std::sync::atomic::AtomicBool =
2437 std::sync::atomic::AtomicBool::new(false);
2438 if !FULLCOLOR_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
2439 eprintln!(
2440 "[software] 4:4:4 full-color requested; OpenH264 is 4:2:0-only, encoding 4:2:0."
2441 );
2442 }
2443 }
2444
2445 let per_frame = stripes.len().div_ceil(CLEANUP_STAGGER_FRAMES).max(1);
2449 let refining = stripes
2450 .iter()
2451 .filter(|st| converges && st.h264_burst_frames_remaining > 0)
2452 .count();
2453 let mut granted = 0;
2454 let cleanup_allowed: Vec<bool> = stripes
2455 .iter()
2456 .map(|st| {
2457 if !crate::pipeline::cleanup_pending(
2458 st,
2459 trigger_frames,
2460 start_frames,
2461 paint_over_armed(st),
2462 false,
2463 ) {
2464 return true;
2465 }
2466 granted += 1;
2467 granted <= per_frame && refining + granted <= stripes.len()
2468 })
2469 .collect();
2470
2471 let stripe_body = |(i, stripe_state): (usize, &mut StripeState)| -> Option<EncodedStripe> {
2472 if i >= stripe_geometries.len() {
2473 return None;
2474 }
2475 let (y_start, actual_height) = stripe_geometries[i];
2476 let start_idx = y_start * width_usize * 4;
2477 let end_idx = start_idx + (actual_height * width_usize * 4);
2478 let stripe_bytes = &raw_pixels[start_idx..end_idx];
2479
2480 let is_dirty = if !hash_damage {
2481 stripe_is_dirty[i]
2482 } else {
2483 stripe_state.content_dirty(stripe_bytes, damage_block_threshold, damage_block_duration)
2484 };
2485 stripe_state.ledger.fit(width, actual_height as i32);
2486 let observed = if hash_damage {
2487 stripe_state.hash_changed
2488 } else {
2489 is_dirty
2490 };
2491 ledger_frame(
2492 &mut stripe_state.ledger,
2493 damage_rects,
2494 (y_start as i32, actual_height as i32),
2495 is_dirty,
2496 observed,
2497 );
2498 let armed = paint_over_armed(stripe_state);
2499 let cleanup = crate::pipeline::cleanup_due(
2500 stripe_state,
2501 trigger_frames,
2502 start_frames,
2503 armed,
2504 cleanup_allowed[i],
2505 false,
2506 if is_dirty {
2507 Damage::Unknown
2508 } else {
2509 Damage::None
2510 },
2511 );
2512 #[cfg(feature = "gpl")]
2520 let cleanup = if cleanup == Cleanup::Refresh
2521 && video
2522 && !video_cbr
2523 && stripe_state.h264_encoder.as_ref().is_some_and(|e| {
2524 e.capped()
2525 && (e.peak_bound()
2526 || stripe_state.ledger.coarse()
2527 || stripe_state.ledger.fresh()
2528 || !e.holds_cleanup(video_po_crf))
2529 }) {
2530 if stripe_state.ledger.spent() {
2531 stripe_state.refresh = Default::default();
2532 }
2533 Cleanup::None
2534 } else {
2535 cleanup
2536 };
2537 let force_idr_all = requested_idr || stripe_state.scheduled_key(scheduled, is_dirty);
2538 let mut send_this_stripe = is_dirty || cleanup != Cleanup::None || force_idr_all;
2539 let mut quality_or_crf = if !video { jpeg_q } else { video_crf };
2540 let mut force_idr = video && (force_idr_all || cleanup == Cleanup::Key);
2541 if force_idr {
2542 stripe_state.key_owed = false;
2543 }
2544 if requested_idr && video && cleanup == Cleanup::None {
2547 stripe_state.lost_frame();
2548 }
2549 let mut hold = None;
2550 let quality = crate::pipeline::EncoderQuality {
2551 last: coded_quality(stripe_state),
2552 bytes: None,
2553 holds,
2554 reopens: false,
2555 keys: true,
2556 band: None,
2557 measures: false,
2558 psnr: None,
2559 refit: None,
2560 follows_cap: false,
2561 finer: 0,
2562 ends_on_key: false,
2563 };
2564 let refresh_crf = crate::pipeline::held_refresh_quality(settings, quality) as i32;
2565 if cleanup != Cleanup::None {
2566 if !video {
2567 quality_or_crf = paint_q;
2568 } else if cleanup == Cleanup::Key {
2569 hold = Some(video_po_crf);
2570 } else if holds && !converges {
2571 hold = Some(refresh_crf);
2572 }
2573 } else if force_idr_all && !is_dirty && !video && armed && stripe_state.paint_over_sent {
2574 quality_or_crf = paint_q;
2575 }
2576 if converges
2577 && stripe_state.h264_burst_frames_remaining > 0
2578 && !is_dirty
2579 && cleanup == Cleanup::None
2580 {
2581 let budget = crate::pipeline::frame_budget(video_bitrate, target_fps);
2582 let bytes = (stripe_state.rc_bytes > 0).then_some(stripe_state.rc_bytes);
2583 stripe_state.idle_frames = if bytes.is_some_and(|b| b <= empty_bytes) {
2584 stripe_state.idle_frames.saturating_add(1)
2585 } else {
2586 0
2587 };
2588 stripe_state.fine_frames = if crate::pipeline::convergence(
2592 quality.last,
2593 bytes,
2594 settings.video_paintover_crf.max(0) as u32,
2595 budget,
2596 ) == crate::pipeline::Convergence::Converged
2597 {
2598 stripe_state.fine_frames.saturating_add(1)
2599 } else {
2600 0
2601 };
2602 if stripe_state.fine_frames >= trigger_frames {
2603 stripe_state.ledger.cleaned();
2604 }
2605 if stripe_state.fine_frames >= trigger_frames
2606 || f64::from(stripe_state.idle_frames) >= crate::pipeline::EMPTY_S * target_fps
2607 {
2608 stripe_state.h264_burst_frames_remaining = 0;
2609 }
2610 }
2611 let mut cleaning = cleanup != Cleanup::None;
2613 if is_dirty {
2614 stripe_state.h264_burst_frames_remaining = 0;
2615 } else if video
2616 && converges
2617 && (cleanup != Cleanup::None || (force_idr && video_burst > 0 && armed))
2618 {
2619 stripe_state.h264_burst_frames_remaining = crate::pipeline::refine_frames(settings);
2623 stripe_state.burst_held = false;
2624 stripe_state.idle_frames = 0;
2625 stripe_state.fine_frames = 0;
2626 } else if video && (force_idr || cleanup != Cleanup::None) && video_burst > 0 {
2627 stripe_state.h264_burst_frames_remaining = video_burst;
2628 stripe_state.burst_held =
2629 holds && (cleanup != Cleanup::None || (armed && !settings.video_cbr_mode));
2630 } else if video && stripe_state.h264_burst_frames_remaining > 0 {
2631 stripe_state.h264_burst_frames_remaining -= 1;
2632 send_this_stripe = true;
2633 cleaning = true;
2634 if stripe_state.burst_held && armed {
2635 hold = Some(refresh_crf);
2636 }
2637 }
2638 if video && !video_cbr {
2644 if is_dirty || !armed {
2645 stripe_state.clean_quality = false;
2646 }
2647 if cleanup != Cleanup::None {
2648 stripe_state.clean_quality = true;
2649 }
2650 if stripe_state.clean_quality && hold.is_none() && !force_idr_all {
2651 hold = Some(video_po_crf);
2652 }
2653 }
2654 if video && video_streaming {
2655 send_this_stripe = true;
2656 }
2657 #[cfg(feature = "gpl")]
2663 let refresh = if video && !video_cbr && !force_idr && !cleaning {
2664 let unbound = stripe_state
2665 .h264_encoder
2666 .as_ref()
2667 .is_some_and(|e| e.capped() && !e.peak_bound());
2668 let blocks = stripe_state.ledger.blocks();
2669 let most = if unbound && stripe_state.ledger.coarse() {
2670 stripe_state.refresh.most(blocks)
2671 } else {
2672 0
2673 };
2674 if most == 0 {
2675 None
2676 } else if !is_dirty && let Some((now, wait)) = stripe_state.ledger.refresh_owed(most) {
2677 let key = now.none_like();
2678 Some((now, key, wait, true))
2679 } else {
2680 stripe_state
2681 .ledger
2682 .refresh_keyed(most)
2683 .map(|(now, key, wait)| (now, key, wait, false))
2684 }
2685 } else {
2686 None
2687 };
2688 #[cfg(feature = "gpl")]
2689 if refresh.is_some() {
2690 send_this_stripe = true;
2691 }
2692 #[cfg(feature = "gpl")]
2693 if video
2694 && !send_this_stripe
2695 && let Some(enc) = stripe_state.h264_encoder.as_mut()
2696 {
2697 enc.idle(settings);
2698 }
2699
2700 if send_this_stripe {
2701 if !video {
2702 let pixel_format = if use_gpu {
2703 turbojpeg::PixelFormat::RGBA
2704 } else {
2705 turbojpeg::PixelFormat::BGRA
2706 };
2707 let img = turbojpeg::Image {
2708 pixels: stripe_bytes,
2709 width: width_usize,
2710 pitch: width_usize * 4,
2711 height: actual_height,
2712 format: pixel_format,
2713 };
2714 JPEG_COMPRESSOR.with(|cell| -> Option<EncodedStripe> {
2715 let mut slot = cell.borrow_mut();
2716 if slot.is_none() {
2717 *slot = Some(turbojpeg::Compressor::new().ok()?);
2718 }
2719 let compressor = slot.as_mut().unwrap();
2720 compressor.set_quality(quality_or_crf).ok()?;
2721 let jpeg = compressor.compress_to_vec(img).ok()?;
2722 let data = if omit_headers {
2723 jpeg
2724 } else {
2725 stripe_state.packet_buf.clear();
2726 push_jpeg_header(
2727 &mut stripe_state.packet_buf,
2728 frame_counter,
2729 y_start as u16,
2730 );
2731 stripe_state.packet_buf.extend_from_slice(&jpeg);
2732 std::mem::take(&mut stripe_state.packet_buf)
2733 };
2734 Some(EncodedStripe {
2735 data: Arc::new(data),
2736 codec: Codec::Jpeg,
2737 stripe_y_start: y_start as i32,
2738 stripe_height: actual_height as i32,
2739 frame_id: i32::from(frame_counter),
2740 timing: FrameTiming::default(),
2741 reference: Reference::Untracked,
2742 })
2743 })
2744 } else {
2745 cfg_if::cfg_if! {
2746 if #[cfg(feature = "gpl")] {
2747 let needs_reinit = if let Some(ref enc) = stripe_state.h264_encoder {
2748 enc.width != width_usize as i32
2749 || enc.height != actual_height as i32
2750 || enc.is_i444 != video_fullcolor
2751 || enc.bit_depth != bit_depth
2752 || enc.reference_frames != settings.video_reference_frames
2753 } else {
2754 true
2755 };
2756
2757 let x264_crf = if video_cbr { quality_or_crf } else { hold.unwrap_or(quality_or_crf) };
2760 if needs_reinit {
2761 stripe_state.h264_encoder = H264EncoderWrapper::with_depth(
2762 width_usize as i32,
2763 actual_height as i32,
2764 x264_crf,
2765 video_fullcolor,
2766 bit_depth,
2767 target_fps,
2768 h264_threads,
2769 video_cbr,
2770 video_bitrate,
2771 video_vbv,
2772 settings.video_min_qp,
2773 settings.video_max_qp,
2774 settings.video_reference_frames,
2775 );
2776 force_idr = true;
2777 } else if let Some(ref mut enc) = stripe_state.h264_encoder {
2778 enc.reconfigure_crf(x264_crf);
2779 let cleaning = converges && stripe_state.h264_burst_frames_remaining > 0;
2780 let vbv = if cleaning { cleanup_vbv } else { video_vbv };
2781 enc.reconfigure_rate(video_bitrate, vbv, target_fps);
2782 enc.reconfigure_floor(if cleaning { settings.video_min_qp } else { motion_floor });
2783 }
2784
2785 let peak_bound = stripe_state
2791 .h264_encoder
2792 .as_ref()
2793 .is_some_and(H264EncoderWrapper::peak_bound);
2794 let refines = video
2795 && armed
2796 && is_dirty
2797 && cleanup == Cleanup::None
2798 && hold.is_none()
2799 && !force_idr
2800 && !peak_bound
2801 && refresh.is_none();
2802 let plan = refines
2803 .then(|| {
2804 let blocks = stripe_state.ledger.blocks();
2805 let most = stripe_state.refine.most(blocks, video_cbr);
2806 stripe_state.ledger.plan(trigger_frames, most, usize::MAX)
2807 })
2808 .flatten();
2809 if let Some(ref mut enc) = stripe_state.h264_encoder {
2810 if let Some(q) = hold.filter(|_| video_cbr) {
2811 enc.hold_quantizer(q);
2812 }
2813 let refinement = plan.map(|plan| {
2814 let refinement = crate::ledger::Refinement {
2815 plan,
2816 target: video_po_crf - crate::ledger::REFINE_FINER as i32,
2817 reach: u32::MAX,
2818 from: if video_cbr {
2819 enc.last_qp().map_or(video_crf, |q| q as i32)
2820 } else {
2821 video_crf
2822 },
2823 };
2824 enc.refine_blocks(&refinement.plan, refinement.offset() as f32);
2825 refinement
2826 });
2827 if let Some((now, key, wait, paint)) = &refresh {
2834 let (quality, finer) = if *paint {
2835 (
2836 video_po_crf,
2837 OWED_FINER as i32,
2838 )
2839 } else {
2840 (x264_crf, X264_REFRESH_FINER as i32)
2841 };
2842 let finer = if is_dirty {
2843 (x264_crf - quality).max(0) + finer
2844 } else {
2845 finer
2846 };
2847 enc.refresh_blocks(
2848 (!is_dirty).then_some(quality),
2849 now,
2850 finer as f32,
2851 key,
2852 (crate::ledger::REFRESH_FINER + crate::ledger::REFRESH_KEY_FINER)
2853 as f32,
2854 wait,
2855 );
2856 }
2857 if hash_damage || !current {
2858 enc.constant_blocks(std::iter::empty());
2859 } else {
2860 enc.constant_blocks(stripe_state.ledger.unchanged());
2861 }
2862 let y_size = width_usize * actual_height;
2863 let uv_size = if video_fullcolor { y_size } else { y_size / 4 };
2864 let y_stride = width_usize as i32;
2865 let uv_stride =
2866 (if video_fullcolor { width_usize } else { width_usize / 2 }) as i32;
2867 let wide = bit_depth > 8;
2868 let nv12 = !wide && !video_fullcolor;
2869 let planes = planes.filter(|p| {
2870 nv12 && y_start == 0 && p.width == width_usize && p.height == actual_height
2871 });
2872 let own = if nv12 && planes.is_none() {
2875 let held = enc.held_input();
2876 let buffers = &mut stripe_state.nv12;
2877 buffers.retain(|p| p.width == width_usize && p.height == actual_height);
2878 while buffers.len() < 2 {
2879 buffers.push(Nv12Planes::new(width_usize, actual_height));
2880 }
2881 Some(usize::from(held == Some(buffers[0].id())))
2882 } else {
2883 None
2884 };
2885 let conversion_result = if planes.is_some() {
2886 Ok(())
2887 } else if let Some(i) = own {
2888 let target = &mut stripe_state.nv12[i];
2889 let strides = (target.stride[0], target.stride[1]);
2890 let (y, uv) = target.planes_mut();
2891 convert_to_nv12_mt(
2892 stripe_bytes,
2893 (width_usize * 4) as u32,
2894 width_usize,
2895 actual_height,
2896 use_gpu,
2897 y,
2898 uv,
2899 strides,
2900 csc_bands,
2901 )
2902 } else if wide {
2903 let [y16, u16, v16] = &mut stripe_state.yuv16;
2904 y16.resize(y_size, 0);
2905 u16.resize(uv_size, 0);
2906 v16.resize(uv_size, 0);
2907 convert_to_yuv10_mt(
2908 stripe_bytes,
2909 width_usize * 4,
2910 width_usize,
2911 actual_height,
2912 use_gpu,
2913 video_fullcolor,
2914 video_fullcolor,
2915 y16,
2916 u16,
2917 v16,
2918 (y_stride as usize, uv_stride as usize),
2919 csc_bands,
2920 );
2921 Ok(())
2922 } else {
2923 if stripe_state.y_buf.len() != y_size {
2924 stripe_state.y_buf.resize(y_size, 0);
2925 }
2926 if stripe_state.u_buf.len() != uv_size {
2927 stripe_state.u_buf.resize(uv_size, 0);
2928 }
2929 if stripe_state.v_buf.len() != uv_size {
2930 stripe_state.v_buf.resize(uv_size, 0);
2931 }
2932 convert_to_yuv_mt(
2933 stripe_bytes,
2934 (width_usize * 4) as u32,
2935 width_usize,
2936 actual_height,
2937 use_gpu,
2938 video_fullcolor,
2939 video_fullcolor,
2940 false,
2941 &mut stripe_state.y_buf,
2942 &mut stripe_state.u_buf,
2943 &mut stripe_state.v_buf,
2944 (y_stride as usize, uv_stride as usize),
2945 csc_bands,
2946 )
2947 };
2948
2949 if let Err(e) = conversion_result {
2950 eprintln!(
2951 "[software] YUV conversion failed for {}x{} stripe: {:?}; skipping",
2952 width_usize, actual_height, e
2953 );
2954 return None;
2955 }
2956
2957 let bytes = |plane: &[u16]| unsafe {
2958 std::slice::from_raw_parts(plane.as_ptr().cast::<u8>(), plane.len() * 2)
2959 };
2960 let coded = if let Some(p) = planes.or(own.map(|i| &stripe_state.nv12[i])) {
2961 enc.encode_nv12_with_headers(
2962 p,
2963 frame_counter,
2964 force_idr,
2965 omit_headers,
2966 &mut stripe_state.packet_buf,
2967 )
2968 } else {
2969 let (y, u, v, scale) = if wide {
2970 let [y16, u16, v16] = &stripe_state.yuv16;
2971 (bytes(y16), bytes(u16), bytes(v16), 2)
2972 } else {
2973 (
2974 stripe_state.y_buf.as_slice(),
2975 stripe_state.u_buf.as_slice(),
2976 stripe_state.v_buf.as_slice(),
2977 1,
2978 )
2979 };
2980 enc.encode_with_headers(
2981 y,
2982 u,
2983 v,
2984 y_stride * scale,
2985 uv_stride * scale,
2986 uv_stride * scale,
2987 frame_counter,
2988 force_idr,
2989 omit_headers,
2990 &mut stripe_state.packet_buf,
2991 )
2992 };
2993 if coded {
2994 if hold.is_none() || !video_cbr {
2995 stripe_state.rc_bytes = stripe_state.packet_buf.len();
2996 }
2997 let capped = enc.coded_capped();
2998 stripe_state.ledger.coded(capped, force_idr);
2999 if let Some((now, _, _, _)) = refresh.as_ref().filter(|_| !capped) {
3000 stripe_state.ledger.refreshed(now);
3001 let blocks = stripe_state.ledger.blocks();
3002 stripe_state.refresh.coded(
3003 stripe_state.packet_buf.len(),
3004 crate::pipeline::frame_budget(video_bitrate, target_fps),
3005 blocks,
3006 );
3007 }
3008 if let Some(refinement) = &refinement {
3009 let coded = if video_cbr {
3010 enc.last_qp().map_or(i32::MAX, |q| q as i32)
3011 } else {
3012 video_crf
3013 };
3014 stripe_state.ledger.refinement_coded(refinement, coded);
3015 }
3016 if video_cbr
3017 && hold.is_none()
3018 && !force_idr
3019 && let Some(coded) = enc.last_qp().map(|q| q as i32)
3020 {
3021 let budget = crate::pipeline::frame_budget(video_bitrate, target_fps);
3022 let blocks = stripe_state.ledger.blocks();
3023 stripe_state.refine.coded(
3024 refinement.is_some(),
3025 coded,
3026 stripe_state.packet_buf.len(),
3027 budget,
3028 blocks,
3029 );
3030 }
3031 if hold.is_some_and(|q| q <= video_po_crf) && !capped && refresh.is_none() {
3032 stripe_state.ledger.cleaned();
3033 } else if force_idr {
3034 stripe_state.ledger.coarsened();
3035 }
3036 if peak_bound
3039 && !video_cbr
3040 && cleaning
3041 && hold.is_some_and(|q| q < video_crf)
3042 && stripe_state.ledger.spent()
3043 {
3044 stripe_state.refresh = Default::default();
3045 }
3046 Some(EncodedStripe {
3047 data: Arc::new(std::mem::take(&mut stripe_state.packet_buf)),
3048 codec: Codec::H264,
3049 stripe_y_start: y_start as i32,
3050 stripe_height: actual_height as i32,
3051 frame_id: i32::from(frame_counter),
3052 timing: FrameTiming::default(),
3053 reference: enc.last_reference(),
3054 })
3055 } else {
3056 None
3057 }
3058 } else {
3059 None
3060 }
3061 } else {
3062 use crate::encoders::oh264::Openh264Encoder;
3063 let needs_reinit = stripe_state.h264_encoder.as_ref().is_none_or(|enc| {
3064 enc.width() != width_usize || enc.height() != actual_height
3065 });
3066 if needs_reinit {
3067 stripe_state.h264_encoder = Openh264Encoder::with_size(
3068 settings,
3069 width_usize,
3070 actual_height,
3071 quality_or_crf,
3072 video_bitrate,
3073 );
3074 if stripe_state.h264_encoder.is_none() {
3075 static INIT_FAILED_LOGGED: std::sync::atomic::AtomicBool =
3078 std::sync::atomic::AtomicBool::new(false);
3079 if !INIT_FAILED_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
3080 eprintln!(
3081 "[software] OpenH264 init failed for a {}x{} frame; no software H.264 for it",
3082 width_usize, actual_height
3083 );
3084 }
3085 }
3086 force_idr = true;
3087 } else if let Some(ref mut enc) = stripe_state.h264_encoder {
3088 if let Some(q) = hold {
3093 if enc.update_qp(q.max(0) as u32) {
3094 stripe_state.change_mass = 0.0;
3095 }
3096 } else if is_dirty {
3097 enc.update_qp(quality_or_crf.max(0) as u32);
3098 }
3099 enc.reconfigure_rate(video_bitrate, target_fps);
3100 }
3101
3102 let coded = stripe_state.h264_encoder.as_mut()?.encode_host_argb(
3103 stripe_bytes,
3104 width_usize * 4,
3105 u64::from(frame_counter),
3106 force_idr,
3107 use_gpu,
3108 );
3109 stripe_state.rc_bytes = coded.as_ref().map_or(0, Vec::len);
3110 let enc = stripe_state.h264_encoder.as_mut()?;
3111 match coded {
3112 Ok(data) if !data.is_empty() => Some(EncodedStripe {
3113 data: Arc::new(data),
3114 codec: Codec::H264,
3115 stripe_y_start: y_start as i32,
3116 stripe_height: actual_height as i32,
3117 frame_id: i32::from(frame_counter),
3118 timing: FrameTiming::default(),
3119 reference: enc.last_reference(),
3120 }),
3121 Ok(_) => None,
3122 Err(e) => {
3123 eprintln!("[software] OpenH264 encode failed: {e}");
3124 None
3125 }
3126 }
3127 }
3128 }
3129 }
3130 } else {
3131 None
3132 }
3133 };
3134 if !stripes_at_once(settings, n_processing_stripes) {
3135 stripes
3136 .iter_mut()
3137 .enumerate()
3138 .filter_map(&stripe_body)
3139 .collect()
3140 } else {
3141 stripes
3142 .par_iter_mut()
3143 .enumerate()
3144 .filter_map(&stripe_body)
3145 .collect()
3146 }
3147}
3148
3149fn stripes_at_once(settings: &CaptureSettings, n: usize) -> bool {
3153 n > 1 && settings.cpu_encode_threading
3154}
3155
3156pub fn invalidate_reference(stripes: &mut [StripeState], frame_id: u16) -> bool {
3160 let mut forgotten = true;
3161 for enc in stripes.iter_mut().filter_map(|s| s.h264_encoder.as_mut()) {
3162 forgotten &= enc.invalidate_reference(frame_id);
3163 }
3164 forgotten
3165}
3166
3167pub fn held_input(stripes: &[StripeState]) -> Option<*const u8> {
3171 #[cfg(feature = "gpl")]
3172 return stripes
3173 .iter()
3174 .find_map(|s| s.h264_encoder.as_ref()?.held_input());
3175 #[cfg(not(feature = "gpl"))]
3176 {
3177 let _ = stripes;
3178 None
3179 }
3180}
3181
3182pub fn release_input(stripes: &mut [StripeState]) {
3184 #[cfg(feature = "gpl")]
3185 for enc in stripes.iter_mut().filter_map(|s| s.h264_encoder.as_mut()) {
3186 enc.release_input();
3187 }
3188 #[cfg(not(feature = "gpl"))]
3189 let _ = stripes;
3190}
3191
3192pub fn acknowledge_reference(stripes: &mut [StripeState], frame_id: u16, held: bool) {
3195 #[cfg(not(feature = "gpl"))]
3196 for enc in stripes.iter_mut().filter_map(|s| s.h264_encoder.as_mut()) {
3197 enc.acknowledge_reference(frame_id, held);
3198 }
3199 #[cfg(feature = "gpl")]
3200 let _ = (stripes, frame_id, held);
3201}
3202
3203const MAX_STRIPES: usize = 8;
3214
3215pub fn stripe_count(height: i32, codec: Codec) -> usize {
3222 if !codec.stripes() || height < MIN_STRIPE_HEIGHT {
3223 return 1;
3224 }
3225 let cores = std::thread::available_parallelism()
3226 .map(|n| n.get())
3227 .unwrap_or(1);
3228 cores
3229 .min(MAX_STRIPES)
3230 .min((height / MIN_STRIPE_HEIGHT) as usize)
3231 .max(1)
3232}
3233
3234#[cfg(feature = "gpl")]
3244const CLEANUP_VBV_FRAMES: f64 = 4.0;
3245
3246#[cfg(feature = "gpl")]
3249fn cleanup_vbv_kbit(settings: &CaptureSettings, bitrate_kbps: i32) -> i32 {
3250 let frames = crate::encoders::vbv_bits(
3251 (bitrate_kbps as u32).saturating_mul(1000),
3252 settings.target_fps,
3253 0.0,
3254 CLEANUP_VBV_FRAMES,
3255 ) / 1000;
3256 let seconds =
3257 (f64::from(bitrate_kbps.max(0)) * crate::pipeline::CLEANUP_FRAME_S).round() as u32;
3258 frames.max(seconds).max(1) as i32
3259}
3260
3261pub const CBR_VBV_FRAMES: f64 = 8.0;
3269
3270fn rate_control(settings: &CaptureSettings) -> (i32, i32) {
3274 let bitrate = settings.video_bitrate_kbps.max(1);
3275 if !settings.video_cbr_mode && settings.video_vbv_multiplier <= 0.0 {
3276 return (bitrate, 0);
3277 }
3278 let vbv = (crate::encoders::vbv_bits(
3279 (bitrate as u32).saturating_mul(1000),
3280 settings.target_fps,
3281 settings.keyframe_interval_s,
3282 if settings.video_cbr_mode && settings.video_vbv_multiplier <= 0.0 {
3283 CBR_VBV_FRAMES
3284 } else {
3285 settings.video_vbv_multiplier
3286 },
3287 ) / 1000)
3288 .max(1) as i32;
3289 (bitrate, vbv)
3290}
3291
3292fn compute_stripe_geometries(height: usize, n: usize, codec: Codec) -> Vec<(usize, usize)> {
3301 if codec.is_video() {
3302 return vec![(0, height & !1)];
3303 }
3304 let mut geoms = Vec::with_capacity(n);
3305 let mut current_y = 0;
3306 let base_h = height / n;
3307 let remainder = height - base_h * n;
3308 for i in 0..n {
3309 let s_h = base_h + if i < remainder { 1 } else { 0 };
3310 geoms.push((current_y, s_h));
3311 current_y += s_h;
3312 }
3313 geoms
3314}
3315
3316#[cfg(test)]
3317mod stripe_count_tests {
3318 use super::{Codec, MAX_STRIPES, MIN_STRIPE_HEIGHT, stripe_count};
3319
3320 #[test]
3324 fn a_tall_frame_is_not_cut_into_one_stripe_per_core() {
3325 let n = stripe_count(2160, Codec::Jpeg);
3326 assert!(n <= MAX_STRIPES, "a 4K frame cut into {n} stripes");
3327 assert!(n >= 1, "a 4K frame cut into none");
3328 }
3329
3330 #[test]
3333 fn a_short_frame_is_cut_by_its_height() {
3334 assert_eq!(stripe_count(MIN_STRIPE_HEIGHT - 1, Codec::Jpeg), 1);
3335 assert!(stripe_count(MIN_STRIPE_HEIGHT * 2, Codec::Jpeg) <= 2);
3336 }
3337
3338 #[test]
3340 fn a_video_frame_is_one_region() {
3341 for codec in Codec::VIDEO {
3342 assert_eq!(stripe_count(2160, codec), 1, "{codec:?}");
3343 }
3344 }
3345}
3346
3347#[cfg(test)]
3348mod tests {
3349 #[test]
3351 fn stripes_are_coded_at_once_only_with_cpu_encode_threading() {
3352 use super::stripes_at_once;
3353 use crate::CaptureSettings;
3354 let on = CaptureSettings::default();
3355 let off = CaptureSettings {
3356 cpu_encode_threading: false,
3357 ..Default::default()
3358 };
3359 assert!(stripes_at_once(&on, 8));
3360 assert!(!stripes_at_once(&on, 1));
3361 assert!(!stripes_at_once(&off, 8));
3362 assert!(!stripes_at_once(&off, 1));
3363 }
3364
3365 #[test]
3368 fn a_crf_session_takes_a_vbv_only_where_a_multiplier_asks() {
3369 use crate::CaptureSettings;
3370 let crf = |mult| CaptureSettings {
3371 video_bitrate_kbps: 8000,
3372 video_vbv_multiplier: mult,
3373 target_fps: 30.0,
3374 ..Default::default()
3375 };
3376 assert_eq!(super::rate_control(&crf(0.0)), (8000, 0));
3377 assert_eq!(super::rate_control(&crf(12.0)), (8000, 3200));
3378 let cbr = CaptureSettings {
3379 video_cbr_mode: true,
3380 ..crf(0.0)
3381 };
3382 assert!(super::rate_control(&cbr).1 > 0);
3383 }
3384
3385 use super::{Codec, StripeState, compute_stripe_geometries};
3386
3387 #[cfg(not(feature = "gpl"))]
3392 #[test]
3393 fn openh264_cleans_up_past_the_frames_it_skips() {
3394 use crate::CaptureSettings;
3395 use openh264::decoder::Decoder;
3396 use openh264::formats::YUVSource;
3397 let (w, h) = (640, 352);
3398 let settings = CaptureSettings {
3399 width: w,
3400 height: h,
3401 codec: Codec::H264,
3402 target_fps: 30.0,
3403 video_cbr_mode: true,
3404 video_bitrate_kbps: 150,
3405 video_streaming_mode: false,
3406 omit_stripe_headers: true,
3407 ..Default::default()
3408 };
3409 let flat = vec![128u8; (w * h * 4) as usize];
3410 let page: Vec<u8> = (0..(w * h) as usize)
3411 .flat_map(|i| {
3412 let v = if ((i * 2654435761usize) >> 7).is_multiple_of(3) {
3413 230
3414 } else {
3415 20
3416 };
3417 [v, v, v, 255]
3418 })
3419 .collect();
3420 let mut stripes = Vec::new();
3421 let mut dec = Decoder::new().expect("decoder");
3422 let (mut sent, mut skipped_before_last, mut last) = (0usize, false, None);
3423 for i in 0..900u16 {
3424 let px = if i < 10 { &flat } else { &page };
3425 let out = super::encode_cpu(
3426 &mut stripes,
3427 px,
3428 w,
3429 h,
3430 &[],
3431 &settings,
3432 i,
3433 false,
3434 true,
3435 i == 0,
3436 );
3437 for stripe in &out {
3438 if i >= 10 {
3439 sent += 1;
3440 }
3441 if let Ok(Some(img)) = dec.decode(&stripe.data) {
3442 let stride = img.strides().0;
3443 last = Some(
3444 (0..h as usize)
3445 .flat_map(|r| img.y()[r * stride..r * stride + w as usize].to_vec())
3446 .collect::<Vec<u8>>(),
3447 );
3448 }
3449 }
3450 if i > 10
3451 && out.is_empty()
3452 && stripes
3453 .first()
3454 .is_some_and(|st| st.h264_burst_frames_remaining > 0)
3455 {
3456 skipped_before_last = true;
3457 }
3458 }
3459 let luma: Vec<u8> = page.chunks(4).map(|p| p[0]).collect();
3460 let picture = last.expect("a picture");
3461 let mse = picture
3462 .iter()
3463 .zip(&luma)
3464 .map(|(&a, &b)| {
3465 let d = (f64::from(a) - 16.0) * 255.0 / 219.0 - f64::from(b);
3466 d * d
3467 })
3468 .sum::<f64>()
3469 / luma.len() as f64;
3470 let psnr = 10.0 * (255.0f64 * 255.0 / mse.max(1e-9)).log10();
3471 println!(
3472 "frames sent after the page: {sent}, skipped inside the cleanup: {skipped_before_last}, {psnr:.1} dB"
3473 );
3474 assert!(
3475 skipped_before_last,
3476 "the rate control skipped frames inside the cleanup"
3477 );
3478 assert!(
3479 sent > 20,
3480 "the cleanup refined past the skipped frames: {sent} frames"
3481 );
3482 assert!(psnr > 30.0, "{psnr:.1} dB");
3483 }
3484
3485 #[test]
3488 fn a_scheduled_key_frame_waits_for_the_change() {
3489 use crate::CaptureSettings;
3490 let (w, h) = (128, 512);
3491 let settings = CaptureSettings {
3492 width: w,
3493 height: h,
3494 codec: Codec::H264,
3495 video_crf: 25,
3496 use_paint_over_quality: false,
3497 video_streaming_mode: false,
3498 keyframe_interval_s: 1.0,
3499 target_fps: 60.0,
3500 ..Default::default()
3501 };
3502 let mut stripes = Vec::new();
3503 let px: Vec<u8> = (0..(w * h * 4) as usize).map(|i| (i % 251) as u8).collect();
3504 let mut encode = |pixels: &[u8], frame: u16| {
3505 super::encode_cpu(
3506 &mut stripes,
3507 pixels,
3508 w,
3509 h,
3510 &[],
3511 &settings,
3512 frame,
3513 false,
3514 true,
3515 false,
3516 )
3517 };
3518 assert_eq!(encode(&px, 1).len(), 1, "the first frame is sent");
3519 assert!(
3520 (2..200).all(|frame| encode(&px, frame).is_empty()),
3521 "a still screen sends nothing through its intervals"
3522 );
3523 let mut moved = px.clone();
3524 for b in moved.iter_mut().take((w * 8 * 4) as usize) {
3525 *b = b.wrapping_add(97);
3526 }
3527 let key = encode(&moved, 200);
3528 assert_eq!(key.len(), 1, "the change sends the frame");
3529 assert_eq!(key[0].data[1], 0x11, "as the key frame owed");
3530 for b in moved.iter_mut().take((w * 8 * 4) as usize) {
3531 *b = b.wrapping_add(31);
3532 }
3533 let next = encode(&moved, 201);
3534 assert_eq!(next.len(), 1);
3535 assert_ne!(next[0].data[1], 0x11, "once");
3536 }
3537
3538 #[cfg(not(feature = "gpl"))]
3544 #[test]
3545 fn openh264_codes_the_whole_frame() {
3546 use crate::CaptureSettings;
3547 use openh264::decoder::Decoder;
3548 use openh264::formats::YUVSource;
3549 let (w, h) = (128, 512);
3550 let settings = CaptureSettings {
3551 width: w,
3552 height: h,
3553 codec: Codec::H264,
3554 video_crf: 25,
3555 use_paint_over_quality: false,
3556 video_streaming_mode: false,
3557 ..Default::default()
3558 };
3559 let mut stripes = Vec::new();
3560 let px: Vec<u8> = (0..(w * h * 4) as usize).map(|i| (i % 251) as u8).collect();
3561 let mut encode = |pixels: &[u8], frame: u16| {
3562 super::encode_cpu(
3563 &mut stripes,
3564 pixels,
3565 w,
3566 h,
3567 &[],
3568 &settings,
3569 frame,
3570 false,
3571 true,
3572 false,
3573 )
3574 };
3575 let first = encode(&px, 0);
3576 assert_eq!(first.len(), 1, "the first frame is sent whole");
3577 let d = &first[0].data;
3578 assert_eq!(d[0], 0x04, "video tag");
3579 assert_eq!(d[1], 0x11, "the first frame is an H.264 key frame");
3580 assert_eq!(u16::from_be_bytes([d[2], d[3]]), 0, "frame number");
3581 assert_eq!(u16::from_be_bytes([d[4], d[5]]), 0, "y-start");
3582 assert_eq!(i32::from(u16::from_be_bytes([d[6], d[7]])), w, "width");
3583 assert_eq!(i32::from(u16::from_be_bytes([d[8], d[9]])), h, "height");
3584 assert_eq!((first[0].stripe_y_start, first[0].stripe_height), (0, h));
3585 let mut dec = Decoder::new().expect("decoder");
3586 let img = dec
3587 .decode(&d[crate::encoders::codec::VIDEO_HEADER_LEN..])
3588 .expect("decode")
3589 .expect("an IDR decodes on its own");
3590 assert_eq!(img.dimensions(), (w as usize, h as usize));
3591 assert!(encode(&px, 1).is_empty(), "a static frame sends nothing");
3592 let mut moved = px.clone();
3593 for b in moved.iter_mut().take((w * 8 * 4) as usize) {
3594 *b = b.wrapping_add(97);
3595 }
3596 let top = encode(&moved, 2);
3597 assert_eq!(top.len(), 1, "motion in the top rows re-sends the frame");
3598 assert_eq!(
3599 top[0].data[1], 0x10,
3600 "an unforced follow-up is an H.264 delta frame"
3601 );
3602 assert_eq!(
3603 u16::from_be_bytes([top[0].data[2], top[0].data[3]]),
3604 2,
3605 "frame number"
3606 );
3607 }
3608
3609 #[test]
3612 fn hash_dirty_reads_as_content_dirty() {
3613 let frames: Vec<Vec<u8>> = [1u8, 1, 2, 1, 1, 1, 1, 1, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7]
3614 .iter()
3615 .map(|&v| vec![v; 256])
3616 .collect();
3617 let (mut here, mut there) = (StripeState::default(), StripeState::default());
3618 for frame in &frames {
3619 assert_eq!(
3620 here.content_dirty(frame, 2, 3),
3621 there.hash_dirty(super::fast_hash(frame), 2, 3)
3622 );
3623 assert_eq!(here.in_damage_block, there.in_damage_block);
3624 }
3625 }
3626
3627 #[test]
3631 fn content_dirty_detects_change_and_damage_block() {
3632 let mut st = StripeState::default();
3633 let a = vec![1u8; 256];
3634 let b = vec![2u8; 256];
3635 assert!(st.content_dirty(&a, 2, 3));
3636 assert!(!st.content_dirty(&a, 2, 3));
3637 assert!(st.content_dirty(&b, 2, 3));
3638 assert!(st.content_dirty(&a, 2, 3));
3639 assert!(st.in_damage_block);
3640 assert!(st.content_dirty(&a, 2, 3));
3641 assert!(st.content_dirty(&a, 2, 3));
3642 assert!(st.content_dirty(&a, 2, 3));
3643 assert!(!st.in_damage_block);
3644 assert!(!st.content_dirty(&a, 2, 3));
3645
3646 let frames: Vec<Vec<u8>> = (0..12u8).map(|i| vec![i; 256]).collect();
3648 let mut st = StripeState::default();
3649 for f in &frames {
3650 assert!(st.content_dirty(f, 2, 3));
3651 }
3652 assert!(
3653 st.in_damage_block,
3654 "a region moving every frame stays in its block"
3655 );
3656 let still = frames.last().unwrap();
3657 let dirty: Vec<bool> = (0..6).map(|_| st.content_dirty(still, 2, 3)).collect();
3658 assert_eq!(
3659 dirty,
3660 [true, true, false, false, false, false],
3661 "clean once the block that saw it stop ends"
3662 );
3663 }
3664
3665 #[test]
3670 fn clean_frames_countdown_fire_paintover_then_go_quiescent() {
3671 use crate::CaptureSettings;
3672 let (w, h) = (64, 128);
3673 let pixels = vec![128u8; (w * h * 4) as usize];
3674 let settings = CaptureSettings {
3675 width: w,
3676 height: h,
3677 codec: Codec::Jpeg,
3678 jpeg_quality: 60,
3679 paint_over_jpeg_quality: 90,
3680 use_paint_over_quality: true,
3681 paint_over_trigger_frames: 5,
3682 ..Default::default()
3683 };
3684 let mut stripes = Vec::new();
3685 let full = [smithay::utils::Rectangle::new((0, 0).into(), (w, h).into())];
3686 let dirty = super::encode_cpu(
3687 &mut stripes,
3688 &pixels,
3689 w,
3690 h,
3691 &full,
3692 &settings,
3693 0,
3694 false,
3695 false,
3696 false,
3697 );
3698 assert!(!dirty.is_empty(), "damaged frame must encode");
3699
3700 let mut painted = Vec::new();
3701 for frame in 1..=20u16 {
3702 let out = super::encode_cpu(
3703 &mut stripes,
3704 &pixels,
3705 w,
3706 h,
3707 &[],
3708 &settings,
3709 frame,
3710 false,
3711 false,
3712 false,
3713 );
3714 painted.extend(out.iter().map(|s| (frame, s.stripe_y_start)));
3715 }
3716 let per_frame = stripes.len().div_ceil(super::CLEANUP_STAGGER_FRAMES).max(1);
3717 assert_eq!(
3718 painted.len(),
3719 stripes.len(),
3720 "each stripe is painted over exactly once: {painted:?}"
3721 );
3722 assert_eq!(
3723 painted[0].0, settings.paint_over_trigger_frames as u16,
3724 "starting at the trigger"
3725 );
3726 for (n, (frame, _)) in painted.iter().enumerate() {
3727 assert_eq!(
3728 *frame as usize,
3729 settings.paint_over_trigger_frames as usize + n / per_frame,
3730 "{per_frame} a frame"
3731 );
3732 }
3733 assert!(
3734 stripes.iter().all(|st| st.paint_over_sent),
3735 "all stripes latched after the repaint"
3736 );
3737 }
3738
3739 #[test]
3744 fn hash_scan_idles_after_paintover_and_wakes_on_change() {
3745 use crate::CaptureSettings;
3746 let (w, h) = (64, 128);
3747 let static_px = vec![128u8; (w * h * 4) as usize];
3748 let changed_px = vec![200u8; (w * h * 4) as usize];
3749 let settings = CaptureSettings {
3750 width: w,
3751 height: h,
3752 codec: Codec::Jpeg,
3753 jpeg_quality: 60,
3754 paint_over_jpeg_quality: 90,
3755 use_paint_over_quality: true,
3756 paint_over_trigger_frames: 5,
3757 damage_block_threshold: 10,
3758 damage_block_duration: 10,
3759 ..Default::default()
3760 };
3761 let mut stripes = Vec::new();
3762 let first = super::encode_cpu(
3763 &mut stripes,
3764 &static_px,
3765 w,
3766 h,
3767 &[],
3768 &settings,
3769 0,
3770 false,
3771 true,
3772 false,
3773 );
3774 assert!(
3775 !first.is_empty(),
3776 "first frame hashes as changed and encodes"
3777 );
3778
3779 let mut painted = Vec::new();
3780 for frame in 1..=20u16 {
3781 let out = super::encode_cpu(
3782 &mut stripes,
3783 &static_px,
3784 w,
3785 h,
3786 &[],
3787 &settings,
3788 frame,
3789 false,
3790 true,
3791 false,
3792 );
3793 painted.extend(out.iter().map(|s| (frame, s.stripe_y_start)));
3794 }
3795 assert_eq!(
3796 painted.len(),
3797 stripes.len(),
3798 "each stripe is painted over exactly once while static: {painted:?}"
3799 );
3800 assert_eq!(painted[0].0, settings.paint_over_trigger_frames as u16);
3801
3802 let woke = super::encode_cpu(
3803 &mut stripes,
3804 &changed_px,
3805 w,
3806 h,
3807 &[],
3808 &settings,
3809 21,
3810 false,
3811 true,
3812 false,
3813 );
3814 assert!(!woke.is_empty(), "content change after idle must encode");
3815 }
3816
3817 #[test]
3821 fn a_blinking_caret_starves_no_stripe_of_its_cleanup() {
3822 use crate::CaptureSettings;
3823 let (w, h) = (64i32, 256i32);
3824 let settings = CaptureSettings {
3825 width: w,
3826 height: h,
3827 codec: Codec::Jpeg,
3828 jpeg_quality: 40,
3829 paint_over_jpeg_quality: 90,
3830 use_paint_over_quality: true,
3831 paint_over_trigger_frames: 5,
3832 damage_block_threshold: 10,
3833 damage_block_duration: 10,
3834 ..Default::default()
3835 };
3836 let mut stripes = Vec::new();
3837 let frame = |caret: bool| {
3838 let mut px = vec![128u8; (w * h * 4) as usize];
3839 if caret {
3840 px[(w * 4 * 3) as usize..(w * 4 * 3) as usize + 8].fill(0);
3841 }
3842 px
3843 };
3844 let dc_quant = |data: &[u8]| {
3846 let at = data
3847 .windows(2)
3848 .position(|m| m == [0xFF, 0xDB])
3849 .expect("a quantization table");
3850 data[at + 5]
3851 };
3852 let mut cleaned = std::collections::HashMap::new();
3853 for n in 0..60u16 {
3854 let out = super::encode_cpu(
3855 &mut stripes,
3856 &frame((n / 4) % 2 == 0),
3857 w,
3858 h,
3859 &[],
3860 &settings,
3861 n,
3862 false,
3863 true,
3864 false,
3865 );
3866 for s in &out {
3867 if dc_quant(&s.data) < 8 {
3868 cleaned.entry(s.stripe_y_start).or_insert(n);
3869 }
3870 }
3871 }
3872 let rows = stripes.len();
3873 assert!(rows > 1, "the frame is striped");
3874 let caret_stripe = 0;
3875 let others: Vec<u16> = cleaned
3876 .iter()
3877 .filter(|(y, _)| **y != caret_stripe)
3878 .map(|(_, n)| *n)
3879 .collect();
3880 assert_eq!(
3881 others.len(),
3882 rows - 1,
3883 "every still stripe is cleaned up: {cleaned:?}"
3884 );
3885 assert!(
3886 others
3887 .iter()
3888 .all(|&n| n <= 5 + super::CLEANUP_STAGGER_FRAMES as u16),
3889 "at the trigger: {cleaned:?}"
3890 );
3891 let at = *cleaned
3892 .get(&caret_stripe)
3893 .expect("the caret's stripe is cleaned up too");
3894 assert!(
3895 (19..=23).contains(&at),
3896 "once the low-motion window passes: {at}"
3897 );
3898 }
3899
3900 fn covered(geoms: &[(usize, usize)]) -> usize {
3902 geoms.iter().map(|&(_, h)| h).sum()
3903 }
3904
3905 fn assert_contiguous(geoms: &[(usize, usize)]) {
3908 let mut y = 0;
3909 for &(sy, sh) in geoms {
3910 assert_eq!(sy, y, "stripes must be contiguous");
3911 y += sh;
3912 }
3913 }
3914
3915 #[test]
3918 fn jpeg_covers_every_row_including_odd() {
3919 for &h in &[1usize, 63, 720, 721, 1079, 1080, 1081] {
3920 for &n in &[1usize, 2, 3, 8, 16] {
3921 let g = compute_stripe_geometries(h, n, Codec::Jpeg);
3922 assert_eq!(g.len(), n);
3923 assert_eq!(
3924 covered(&g),
3925 h,
3926 "JPEG must cover full height h={} n={}",
3927 h,
3928 n
3929 );
3930 assert_contiguous(&g);
3931 }
3932 }
3933 }
3934
3935 #[test]
3938 fn a_video_frame_is_one_even_region() {
3939 for &h in &[64usize, 720, 721, 1080, 1081] {
3940 for &n in &[1usize, 2, 8] {
3941 let g = compute_stripe_geometries(h, n, Codec::H264);
3942 assert_eq!(g, vec![(0, h & !1)], "h={h} n={n}");
3943 }
3944 }
3945 }
3946
3947 #[test]
3951 fn bands_convert_on_a_pool_of_as_many_threads() {
3952 use super::{MAX_POOLED_BANDS, on_band_pool};
3953 let on_pool = || {
3954 (
3955 rayon::current_num_threads(),
3956 rayon::current_thread_index().is_some(),
3957 )
3958 };
3959 for bands in [2usize, 4, 7] {
3960 assert_eq!(on_band_pool(bands, on_pool), (bands, true), "{bands} bands");
3961 let named = on_band_pool(bands, || std::thread::current().name().map(str::to_owned));
3962 assert_eq!(named.as_deref(), Some("pixelflux-csc"));
3963 }
3964 let past = MAX_POOLED_BANDS + 1;
3965 assert_eq!(
3966 on_band_pool(past, on_pool),
3967 (rayon::current_num_threads(), false),
3968 "{past} bands run where they are called"
3969 );
3970 let own = rayon::ThreadPoolBuilder::new()
3971 .num_threads(1)
3972 .build()
3973 .expect("pool");
3974 assert_eq!(
3975 own.install(|| on_band_pool(4, on_pool)),
3976 (1, true),
3977 "a conversion on a pool keeps to it"
3978 );
3979 }
3980
3981 #[test]
3984 fn the_host_convert_sites_chroma_at_the_block_center() {
3985 use super::convert_to_yuv_mt;
3986 let (w, h) = (64usize, 64usize);
3987 let bgra = crate::encoders::chroma_siting::bgra(w, h);
3988 for bands in [1usize, 4, 7] {
3989 let (mut yp, mut up, mut vp) =
3990 (vec![0u8; w * h], vec![0u8; w * h / 4], vec![0u8; w * h / 4]);
3991 convert_to_yuv_mt(
3992 &bgra,
3993 (w * 4) as u32,
3994 w,
3995 h,
3996 false,
3997 false,
3998 false,
3999 false,
4000 &mut yp,
4001 &mut up,
4002 &mut vp,
4003 (w, w / 2),
4004 bands,
4005 )
4006 .expect("convert");
4007 let worst = up
4008 .iter()
4009 .zip(&vp)
4010 .map(|(&u, &v)| (f64::from(u) - 128.0).hypot(f64::from(v) - 128.0))
4011 .fold(0.0f64, f64::max);
4012 assert!(
4013 worst <= 2.0,
4014 "{bands} bands: chroma sits {worst:.1} off neutral"
4015 );
4016 }
4017 }
4018
4019 #[test]
4022 fn the_jpeg_path_sites_chroma_at_the_block_center() {
4023 use super::Codec;
4024 use crate::webcam::decode::new_decoder;
4025 let (w, h) = (64usize, 64usize);
4026 let bgra = crate::encoders::chroma_siting::bgra(w, h);
4027 let mut comp = turbojpeg::Compressor::new().expect("turbojpeg compressor");
4028 comp.set_quality(90).expect("quality");
4029 let img = turbojpeg::Image {
4030 pixels: &bgra[..],
4031 width: w,
4032 pitch: w * 4,
4033 height: h,
4034 format: turbojpeg::PixelFormat::BGRA,
4035 };
4036 let jpeg = comp.compress_to_vec(img).expect("compress");
4037 let mut dec = new_decoder(Codec::Jpeg).expect("jpeg decoder");
4038 assert!(
4039 dec.decode(&jpeg).expect("decode"),
4040 "the stripe decoded nothing"
4041 );
4042 let worst = crate::encoders::chroma_siting::worst(&dec.frame().expect("frame"));
4043 assert!(worst <= 4.0, "JPEG chroma sits {worst:.1} off neutral");
4044 }
4045
4046 #[test]
4050 fn ten_bit_conversion_matches_bt709() {
4051 use super::convert_to_yuv10_mt;
4052 let (w, h) = (34usize, 18usize);
4053 let mut bgra = vec![0u8; w * h * 4];
4054 let mut seed = 0x2545_f491u32;
4055 for px in bgra.as_chunks_mut::<4>().0 {
4056 for c in px.iter_mut().take(3) {
4057 seed ^= seed << 13;
4058 seed ^= seed >> 17;
4059 seed ^= seed << 5;
4060 *c = (seed >> 11) as u8;
4061 }
4062 px[3] = 255;
4063 }
4064 let reference = |r: f64, g: f64, b: f64, full: bool| {
4065 let y = 0.2126 * r + 0.7152 * g + 0.0722 * b;
4066 let (cb, cr) = ((b - y) / 1.8556, (r - y) / 1.5748);
4067 if full {
4068 (
4069 y * 1023.0 / 255.0,
4070 512.0 + cb * 1023.0 / 255.0,
4071 512.0 + cr * 1023.0 / 255.0,
4072 )
4073 } else {
4074 (
4075 64.0 + y * 876.0 / 255.0,
4076 512.0 + cb * 896.0 / 255.0,
4077 512.0 + cr * 896.0 / 255.0,
4078 )
4079 }
4080 };
4081 let rgb = |x: usize, y: usize| {
4082 let p = &bgra[(y * w + x) * 4..];
4083 (f64::from(p[2]), f64::from(p[1]), f64::from(p[0]))
4084 };
4085 for full in [false, true] {
4086 for i444 in [false, true] {
4087 let (cw, ch) = if i444 { (w, h) } else { (w / 2, h / 2) };
4088 let convert = |bands: usize| {
4089 let (mut y, mut u, mut v) =
4090 (vec![0u16; w * h], vec![0u16; cw * ch], vec![0u16; cw * ch]);
4091 convert_to_yuv10_mt(
4092 &bgra,
4093 w * 4,
4094 w,
4095 h,
4096 false,
4097 i444,
4098 full,
4099 &mut y,
4100 &mut u,
4101 &mut v,
4102 (w, cw),
4103 bands,
4104 );
4105 (y, u, v)
4106 };
4107 let (y, u, v) = convert(1);
4108 assert_eq!(
4109 (y.clone(), u.clone(), v.clone()),
4110 convert(4),
4111 "bands change nothing"
4112 );
4113 for row in 0..h {
4114 for col in 0..w {
4115 let (r, g, b) = rgb(col, row);
4116 let want = reference(r, g, b, full).0;
4117 assert!((f64::from(y[row * w + col]) - want).abs() <= 1.0, "luma");
4118 }
4119 }
4120 for row in 0..ch {
4121 for col in 0..cw {
4122 let (r, g, b) = if i444 {
4123 rgb(col, row)
4124 } else {
4125 let mut sum = (0.0, 0.0, 0.0);
4126 for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
4127 let p = rgb(2 * col + dx, 2 * row + dy);
4128 sum = (sum.0 + p.0 / 4.0, sum.1 + p.1 / 4.0, sum.2 + p.2 / 4.0);
4129 }
4130 sum
4131 };
4132 let (_, cb, cr) = reference(r, g, b, full);
4133 assert!((f64::from(u[row * cw + col]) - cb).abs() <= 1.0, "cb");
4134 assert!((f64::from(v[row * cw + col]) - cr).abs() <= 1.0, "cr");
4135 }
4136 }
4137 }
4138 }
4139 }
4140
4141 #[cfg(feature = "gpl")]
4145 #[test]
4146 fn x264_quantizers_keep_their_scale_at_ten_bits() {
4147 use super::H264EncoderWrapper;
4148 if !H264EncoderWrapper::ten_bit() {
4149 println!("this libx264 was built for 8 bits alone; nothing to check");
4150 return;
4151 }
4152 let (w, h) = (640usize, 360usize);
4153 let mut seed = 7u32;
4154 let mut noise = |n: usize, amplitude: u32, base: u32| -> Vec<u32> {
4155 (0..n)
4156 .map(|_| {
4157 seed ^= seed << 13;
4158 seed ^= seed >> 17;
4159 seed ^= seed << 5;
4160 base + seed % amplitude
4161 })
4162 .collect()
4163 };
4164 let (luma, chroma) = (noise(w * h, 60, 90), noise(w * h / 4, 20, 118));
4165 let mut seen = Vec::new();
4166 for depth in [8u32, 10] {
4167 let plane = |samples: &[u32]| -> Vec<u8> {
4168 if depth == 10 {
4169 samples
4170 .iter()
4171 .flat_map(|s| ((s * 4) as u16).to_le_bytes())
4172 .collect()
4173 } else {
4174 samples.iter().map(|s| *s as u8).collect()
4175 }
4176 };
4177 let (y, c) = (plane(&luma), plane(&chroma));
4178 let bytes = (depth as usize).div_ceil(8);
4179 let mut enc = H264EncoderWrapper::with_depth(
4180 w as i32, h as i32, 25, false, depth, 30.0, 1, false, 0, 0, 0, 0, 0,
4181 )
4182 .expect("x264 init");
4183 let encode = |enc: &mut H264EncoderWrapper, id: u16| {
4184 let mut out = Vec::new();
4185 assert!(enc.encode_with_headers(
4186 &y,
4187 &c,
4188 &c,
4189 (w * bytes) as i32,
4190 (w / 2 * bytes) as i32,
4191 (w / 2 * bytes) as i32,
4192 id,
4193 true,
4194 true,
4195 &mut out,
4196 ));
4197 out.len()
4198 };
4199 encode(&mut enc, 0);
4200 let rate_factor_qp = enc.last_qp().expect("a quantizer");
4201 enc.hold_quantizer(45);
4202 seen.push((rate_factor_qp, encode(&mut enc, 1)));
4203 }
4204 let ((qp8, held8), (qp10, held10)) = (seen[0], seen[1]);
4205 assert!(
4206 qp8.abs_diff(qp10) <= 2,
4207 "rate factor quantizers {qp8} and {qp10}"
4208 );
4209 assert!(
4210 held10 < 2 * held8,
4211 "held key frames of {held8} and {held10} bytes"
4212 );
4213 }
4214
4215 #[cfg(feature = "gpl")]
4218 #[test]
4219 fn x264_codes_ten_bits() {
4220 use super::H264EncoderWrapper;
4221 if !H264EncoderWrapper::ten_bit() {
4222 println!("this libx264 was built for 8 bits alone; nothing to check");
4223 return;
4224 }
4225 let (w, h) = (64usize, 64usize);
4226 for (i444, profile) in [(false, 110u8), (true, 244u8)] {
4227 let mut enc = H264EncoderWrapper::with_depth(
4228 w as i32, h as i32, 25, i444, 10, 30.0, 1, false, 0, 0, 0, 0, 0,
4229 )
4230 .expect("x264 init");
4231 assert_eq!(enc.bit_depth, 10);
4232 let (cw, ch) = if i444 { (w, h) } else { (w / 2, h / 2) };
4233 let planes = [
4234 vec![400u16; w * h],
4235 vec![512u16; cw * ch],
4236 vec![600u16; cw * ch],
4237 ];
4238 let bytes = |p: &[u16]| unsafe {
4239 std::slice::from_raw_parts(p.as_ptr().cast::<u8>(), p.len() * 2).to_vec()
4240 };
4241 let (y, u, v) = (bytes(&planes[0]), bytes(&planes[1]), bytes(&planes[2]));
4242 let mut out = Vec::new();
4243 assert!(enc.encode_with_headers(
4244 &y,
4245 &u,
4246 &v,
4247 (w * 2) as i32,
4248 (cw * 2) as i32,
4249 (cw * 2) as i32,
4250 0,
4251 true,
4252 true,
4253 &mut out,
4254 ));
4255 let sps = crate::encoders::codec::annexb_nals(&out)
4256 .find(|n| n[0] & 0x1f == 7)
4257 .expect("a sequence parameter set");
4258 assert_eq!(sps[1], profile, "the profile declared");
4259 }
4260 }
4261
4262 #[test]
4265 fn the_nv12_convert_writes_the_i420_samples_interleaved() {
4266 use super::{Nv12Planes, convert_to_nv12_mt, convert_to_yuv_mt};
4267 let mut seed = 0x2545_f491_4f6c_dd1du64;
4268 for (w, h) in [(64usize, 64usize), (130, 66), (1920, 1080)] {
4269 let pixels: Vec<u8> = (0..w * h * 4)
4270 .map(|_| {
4271 seed ^= seed << 13;
4272 seed ^= seed >> 7;
4273 seed ^= seed << 17;
4274 seed as u8
4275 })
4276 .collect();
4277 for rgba in [false, true] {
4278 let (mut yp, mut up, mut vp) =
4279 (vec![0u8; w * h], vec![0u8; w * h / 4], vec![0u8; w * h / 4]);
4280 convert_to_yuv_mt(
4281 &pixels,
4282 (w * 4) as u32,
4283 w,
4284 h,
4285 rgba,
4286 false,
4287 false,
4288 false,
4289 &mut yp,
4290 &mut up,
4291 &mut vp,
4292 (w, w / 2),
4293 1,
4294 )
4295 .expect("I420");
4296 for bands in [1usize, 4, 7] {
4297 let mut nv12 = Nv12Planes::new(w, h);
4298 let strides = (nv12.stride[0], nv12.stride[1]);
4299 let (y, uv) = nv12.planes_mut();
4300 convert_to_nv12_mt(&pixels, (w * 4) as u32, w, h, rgba, y, uv, strides, bands)
4301 .expect("NV12");
4302 let (y, uv) = nv12.planes();
4303 for row in 0..h {
4304 assert_eq!(
4305 &y[row * strides.0..][..w],
4306 &yp[row * w..][..w],
4307 "{w}x{h} rgba {rgba}: luma row {row}"
4308 );
4309 }
4310 for row in 0..h / 2 {
4311 let line = &uv[row * strides.1..][..w];
4312 let (u, v) = (&up[row * w / 2..][..w / 2], &vp[row * w / 2..][..w / 2]);
4313 assert!(
4314 line.chunks(2)
4315 .zip(u.iter().zip(v))
4316 .all(|(c, (u, v))| c == [*u, *v]),
4317 "{w}x{h} rgba {rgba}, {bands} bands: chroma row {row}"
4318 );
4319 }
4320 }
4321 }
4322 }
4323 }
4324
4325 #[cfg(feature = "gpl")]
4329 #[test]
4330 #[cfg(x264_in_place)]
4331 fn x264_encodes_an_nv12_picture_in_place_as_it_codes_a_copy() {
4332 use super::{H264EncoderWrapper, Nv12Planes, convert_to_nv12_mt, convert_to_yuv_mt};
4333 let (w, h) = (200usize, 120usize);
4334 let frames: Vec<Vec<u8>> = (0..6)
4335 .map(|f| {
4336 (0..w * h * 4)
4337 .map(|i| {
4338 let (x, y) = ((i / 4) % w, (i / 4) / w);
4339 ((x + 3 * f) * 7 + y * 3 + (i % 4) * 50) as u8
4340 })
4341 .collect()
4342 })
4343 .collect();
4344 let open = || {
4345 H264EncoderWrapper::new(w as i32, h as i32, 25, false, 30.0, 4, false, 0, 0, 0, 0)
4346 .expect("x264 init")
4347 };
4348 let (mut copied, mut in_place) = (open(), open());
4349 let mut pictures = [Nv12Planes::new(w, h), Nv12Planes::new(w, h)];
4350 assert!(
4351 pictures[0].in_place,
4352 "the linked libx264 lays out a picture to encode in place"
4353 );
4354 let layout = super::x264_input_layout(w, h).expect("layout");
4355 let align = codec_sys::x264::X264_IN_PLACE_ALIGN as usize;
4356 for p in &pictures {
4357 let (y, uv) = p.planes();
4358 assert!(
4359 (y.as_ptr().addr() - layout.offset[0]).is_multiple_of(align)
4360 && (uv.as_ptr().addr() - layout.offset[1]).is_multiple_of(align),
4361 "each plane lies its layout offset into a buffer aligned as x264 asks"
4362 );
4363 }
4364 let (mut a, mut b) = (Vec::new(), Vec::new());
4365 for (n, bgra) in frames.iter().enumerate() {
4366 let (mut yp, mut up, mut vp) =
4367 (vec![0u8; w * h], vec![0u8; w * h / 4], vec![0u8; w * h / 4]);
4368 convert_to_yuv_mt(
4369 bgra,
4370 (w * 4) as u32,
4371 w,
4372 h,
4373 false,
4374 false,
4375 false,
4376 false,
4377 &mut yp,
4378 &mut up,
4379 &mut vp,
4380 (w, w / 2),
4381 1,
4382 )
4383 .unwrap();
4384 let mut out = Vec::new();
4385 assert!(copied.encode_with_headers(
4386 &yp,
4387 &up,
4388 &vp,
4389 w as i32,
4390 (w / 2) as i32,
4391 (w / 2) as i32,
4392 n as u16,
4393 false,
4394 true,
4395 &mut out,
4396 ));
4397 a.extend_from_slice(&out);
4398
4399 let mb_w = w.div_ceil(16) * 16;
4400 let picture = &mut pictures[n % 2];
4401 let strides = (picture.stride[0], picture.stride[1]);
4402 let (y, uv) = picture.planes_mut();
4403 convert_to_nv12_mt(bgra, (w * 4) as u32, w, h, false, y, uv, strides, 2).unwrap();
4404 y[mb_w] = !y[w - 1];
4405 if n == 4 {
4406 in_place.release_input();
4407 assert_eq!(in_place.held_input(), None);
4408 }
4409 let picture = &pictures[n % 2];
4410 assert!(in_place.encode_nv12_with_headers(picture, n as u16, false, true, &mut out));
4411 b.extend_from_slice(&out);
4412 assert_eq!(in_place.held_input(), Some(picture.id()));
4413 let (y, _) = picture.planes();
4414 assert_eq!(
4415 y[mb_w],
4416 y[w - 1],
4417 "x264 padded the picture where it lies, past its last macroblock"
4418 );
4419 }
4420 assert!(a == b, "the stream the copy codes");
4421 }
4422}
4423
4424#[cfg(test)]
4425mod qp_bound_sweep {
4426 #[cfg(feature = "gpl")]
4432 use super::H264EncoderWrapper;
4433 use crate::CaptureSettings;
4434 use crate::encoders::Codec;
4435 use crate::encoders::oh264::Openh264Encoder;
4436 use openh264::decoder::Decoder;
4437 use openh264::formats::YUVSource;
4438
4439 const W: usize = 1280;
4440 const H: usize = 720;
4441 const FRAMES: usize = 60;
4442
4443 fn text_luma(frame: usize) -> Vec<u8> {
4447 let mut y = vec![18u8; W * H];
4448 let scroll = frame * 4;
4449 for row in 0..H {
4450 let srow = row + scroll;
4451 let cell_y = srow / 12;
4452 let in_glyph_y = srow % 12;
4453 if in_glyph_y >= 10 {
4454 continue;
4455 }
4456 for col in 0..W {
4457 let cell_x = col / 8;
4458 let in_glyph_x = col % 8;
4459 if in_glyph_x >= 7 {
4460 continue;
4461 }
4462 let mut s = (cell_x as u32)
4463 .wrapping_mul(2654435761)
4464 .wrapping_add((cell_y as u32).wrapping_mul(40503))
4465 .wrapping_add(1);
4466 s ^= s << 13;
4467 s ^= s >> 17;
4468 s ^= s << 5;
4469 if (s >> ((in_glyph_y * 3 + in_glyph_x) % 29)) & 1 == 1 {
4470 y[row * W + col] = 224;
4471 }
4472 }
4473 }
4474 y
4475 }
4476
4477 #[cfg(feature = "gpl")]
4480 fn encode_x264(cbr: bool, kbps: i32, crf: i32, min_qp: i32, max_qp: i32) -> Vec<Vec<u8>> {
4481 let mut enc = H264EncoderWrapper::new(
4482 W as i32, H as i32, crf, false, 60.0, 4, cbr, kbps, 50, min_qp, max_qp,
4483 )
4484 .expect("x264 init");
4485 let u = vec![128u8; (W / 2) * (H / 2)];
4486 let v = vec![128u8; (W / 2) * (H / 2)];
4487 (0..FRAMES)
4488 .map(|i| {
4489 let y = text_luma(i);
4490 let mut out = Vec::new();
4491 enc.encode_with_headers(
4492 &y,
4493 &u,
4494 &v,
4495 W as i32,
4496 (W / 2) as i32,
4497 (W / 2) as i32,
4498 i as u16,
4499 i == 0,
4500 true,
4501 &mut out,
4502 );
4503 out
4504 })
4505 .collect()
4506 }
4507
4508 #[cfg(feature = "gpl")]
4511 #[test]
4512 fn x264_declares_the_conversion_matrix() {
4513 use crate::encoders::codec::annexb_nals;
4514 use crate::encoders::sps::read_color;
4515 use crate::webcam::decode::{ColorTags, Decoder as _, VideoDecoder};
4516 for (i444, want) in [
4517 (false, ColorTags::BT709_LIMITED),
4518 (true, ColorTags::BT709_FULL),
4519 ] {
4520 let (w, h) = (128usize, 96usize);
4521 let mut enc =
4522 H264EncoderWrapper::new(w as i32, h as i32, 25, i444, 30.0, 1, false, 0, 0, 0, 0)
4523 .expect("x264 init");
4524 let (cw, ch) = if i444 { (w, h) } else { (w / 2, h / 2) };
4525 let y = vec![90u8; w * h];
4526 let u = vec![128u8; cw * ch];
4527 let v = vec![160u8; cw * ch];
4528 let mut out = Vec::new();
4529 assert!(enc.encode_with_headers(
4530 &y, &u, &v, w as i32, cw as i32, cw as i32, 0, true, true, &mut out
4531 ));
4532 let sps = annexb_nals(&out)
4533 .find(|n| n[0] & 0x1f == 7)
4534 .expect("an SPS");
4535 let declared = read_color(sps).map(|s| ColorTags {
4536 matrix: s.matrix,
4537 full_range: s.full_range,
4538 });
4539 assert_eq!(declared, Some(want), "i444={i444}");
4540 if !i444 {
4541 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
4542 assert!(dec.decode(&out).expect("decode"));
4543 assert_eq!(dec.color_tags(), Some(want));
4544 }
4545 }
4546 }
4547
4548 #[cfg(feature = "gpl")]
4551 #[test]
4552 fn x264_takes_the_frame_rate_as_its_fraction() {
4553 use crate::encoders::frame_rate::FrameRate;
4554 use crate::encoders::sps::h264_timing;
4555 let (w, h) = (64usize, 64usize);
4556 let (y, uv) = (vec![90u8; w * h], vec![128u8; w * h / 4]);
4557 for (num, den) in [(60000u32, 1001u32), (120000, 1001), (144000, 1001), (60, 1)] {
4558 let mut enc = H264EncoderWrapper::new(
4559 w as i32,
4560 h as i32,
4561 25,
4562 false,
4563 f64::from(num) / f64::from(den),
4564 1,
4565 true,
4566 4000,
4567 100,
4568 0,
4569 0,
4570 )
4571 .expect("x264 init");
4572 let param = unsafe {
4573 let mut param: codec_sys::x264::x264_param_t = std::mem::zeroed();
4574 codec_sys::x264::x264_encoder_parameters(enc.encoder, &mut param);
4575 param
4576 };
4577 assert_eq!((param.i_fps_num, param.i_fps_den), (num, den));
4578 let mut out = Vec::new();
4579 assert!(enc.encode_with_headers(
4580 &y,
4581 &uv,
4582 &uv,
4583 w as i32,
4584 (w / 2) as i32,
4585 (w / 2) as i32,
4586 0,
4587 true,
4588 true,
4589 &mut out
4590 ));
4591 assert_eq!(
4592 h264_timing(&out),
4593 Some((den, 2 * num)),
4594 "{num}/{den}: the SPS declares the rate"
4595 );
4596 }
4597 let mut enc = H264EncoderWrapper::new(
4598 w as i32, h as i32, 25, false, 60.0, 1, true, 4000, 100, 0, 0,
4599 )
4600 .expect("x264 init");
4601 enc.reconfigure_rate(4000, 100, 60000.0 / 1001.0);
4602 assert_eq!(
4603 enc.current_fps,
4604 FrameRate {
4605 num: 60000,
4606 den: 1001
4607 },
4608 "60 fps moved to 59.94"
4609 );
4610 }
4611
4612 #[cfg(feature = "gpl")]
4616 #[test]
4617 fn x264_paints_the_chart_it_converts() {
4618 use super::convert_to_yuv_mt;
4619 use crate::encoders::chroma_siting::{BT709, chart_bgra, chart_error};
4620 use crate::webcam::decode::{Decoder as _, VideoDecoder};
4621 let (w, h) = (256usize, 128usize);
4622 let bgra = chart_bgra(w, h);
4623 let (mut y, mut u, mut v) = (vec![0u8; w * h], vec![0u8; w * h / 4], vec![0u8; w * h / 4]);
4624 convert_to_yuv_mt(
4625 &bgra,
4626 (w * 4) as u32,
4627 w,
4628 h,
4629 false,
4630 false,
4631 false,
4632 false,
4633 &mut y,
4634 &mut u,
4635 &mut v,
4636 (w, w / 2),
4637 4,
4638 )
4639 .expect("convert");
4640 let mut enc =
4641 H264EncoderWrapper::new(w as i32, h as i32, 20, false, 30.0, 1, false, 0, 0, 0, 0)
4642 .expect("x264 init");
4643 let mut out = Vec::new();
4644 assert!(enc.encode_with_headers(
4645 &y,
4646 &u,
4647 &v,
4648 w as i32,
4649 (w / 2) as i32,
4650 (w / 2) as i32,
4651 0,
4652 true,
4653 true,
4654 &mut out
4655 ));
4656 let mut dec = VideoDecoder::new(Codec::H264).expect("decoder");
4657 assert!(dec.decode(&out).expect("decode"));
4658 let worst = chart_error(&dec.frame().expect("frame"), BT709);
4659 println!("[chart] x264: worst |dRGB| {worst:.1}");
4660 assert!(
4661 worst <= 12.0,
4662 "the software H.264 path paints {worst:.1} off the chart"
4663 );
4664 }
4665
4666 #[cfg(feature = "gpl")]
4669 #[test]
4670 fn x264_bounds_reordering_at_zero() {
4671 use crate::encoders::sps::fixtures::assert_no_reorder;
4672 for (fullcolor, cbr) in [(false, false), (false, true), (true, false), (true, true)] {
4673 let mut enc = H264EncoderWrapper::new(
4674 W as i32, H as i32, 20, fullcolor, 60.0, 4, cbr, 8000, 0, 0, 0,
4675 )
4676 .expect("x264 init");
4677 let chroma = if fullcolor { W * H } else { W * H / 4 };
4678 let (u, v) = (vec![128u8; chroma], vec![128u8; chroma]);
4679 let stride = if fullcolor { W } else { W / 2 } as i32;
4680 let mut out = Vec::new();
4681 assert!(enc.encode_with_headers(
4682 &text_luma(0),
4683 &u,
4684 &v,
4685 W as i32,
4686 stride,
4687 stride,
4688 0,
4689 true,
4690 true,
4691 &mut out
4692 ));
4693 assert_no_reorder(&out, &format!("x264 fullcolor {fullcolor} cbr {cbr}"));
4694 }
4695 }
4696
4697 #[cfg(feature = "gpl")]
4701 #[test]
4702 fn x264_capped_crf_codes_a_key_frame_in_at_most_two_encodes() {
4703 const VBV_KBIT: i32 = 1500;
4704 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
4705 let mut enc = H264EncoderWrapper::new(
4706 W as i32, H as i32, 12, false, 60.0, 4, false, 8000, VBV_KBIT, 0, 0,
4707 )
4708 .expect("x264 init");
4709 let key = |enc: &mut H264EncoderWrapper, i: usize| {
4710 let before = enc.encodes;
4711 let mut out = Vec::new();
4712 assert!(enc.encode_with_headers(
4713 &text_luma(i),
4714 &u,
4715 &v,
4716 W as i32,
4717 (W / 2) as i32,
4718 (W / 2) as i32,
4719 i as u16,
4720 true,
4721 true,
4722 &mut out
4723 ));
4724 (enc.encodes - before, out.len() * 8)
4725 };
4726 let (first, first_bits) = key(&mut enc, 0);
4727 for _ in 0..120 {
4728 enc.idle(&CaptureSettings {
4729 video_bitrate_kbps: 8000,
4730 video_vbv_multiplier: f64::from(VBV_KBIT) * 1000.0 / (8_000_000.0 / 60.0),
4731 target_fps: 60.0,
4732 ..Default::default()
4733 });
4734 }
4735 let (later, later_bits) = key(&mut enc, 1);
4736 println!("first key: {first} encodes, {first_bits} bits; later: {later}, {later_bits}");
4737 assert!(first <= 2, "the first key frame took {first} encodes");
4738 assert_eq!(later, 1, "a later key frame took {later} encodes");
4739 assert!(first_bits <= VBV_KBIT as usize * 1000 && later_bits <= VBV_KBIT as usize * 1000);
4740 }
4741
4742 #[cfg(feature = "gpl")]
4745 #[test]
4746 fn x264_crf_bounds_a_key_frame_by_its_vbv() {
4747 const VBV_KBIT: i32 = 1500;
4748 let key = |vbv_kbit: i32| {
4749 let mut enc = H264EncoderWrapper::new(
4750 W as i32, H as i32, 12, false, 60.0, 4, false, 8000, vbv_kbit, 0, 0,
4751 )
4752 .expect("x264 init");
4753 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
4754 let mut out = Vec::new();
4755 assert!(enc.encode_with_headers(
4756 &text_luma(0),
4757 &u,
4758 &v,
4759 W as i32,
4760 (W / 2) as i32,
4761 (W / 2) as i32,
4762 0,
4763 true,
4764 true,
4765 &mut out
4766 ));
4767 out.len() * 8
4768 };
4769 let (uncapped, capped) = (key(0), key(VBV_KBIT));
4770 assert!(
4771 capped <= VBV_KBIT as usize * 1000,
4772 "a {capped}-bit key frame overflows the {VBV_KBIT} kbit buffer"
4773 );
4774 assert!(
4775 uncapped > 2 * capped,
4776 "the uncapped key frame ({uncapped} bits) is not the rate factor's own"
4777 );
4778 }
4779
4780 #[cfg(feature = "gpl")]
4783 #[test]
4784 fn x264_capped_crf_codes_no_quantizer_past_51() {
4785 let mut enc =
4786 H264EncoderWrapper::new(W as i32, H as i32, 23, false, 60.0, 4, false, 300, 10, 0, 0)
4787 .expect("x264 init");
4788 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
4789 let mut worst = 0;
4790 for i in 0..FRAMES {
4791 let mut out = Vec::new();
4792 enc.encode_with_headers(
4793 &text_luma(i),
4794 &u,
4795 &v,
4796 W as i32,
4797 (W / 2) as i32,
4798 (W / 2) as i32,
4799 i as u16,
4800 i == 0,
4801 true,
4802 &mut out,
4803 );
4804 worst = worst.max(enc.last_qp().unwrap_or(0));
4805 }
4806 assert!(worst <= 51, "a starved capped CRF coded quantizer {worst}");
4807 }
4808
4809 #[cfg(feature = "gpl")]
4814 #[test]
4815 fn x264_says_the_quantizer_a_still_screens_rows_were_coded_at() {
4816 const KBPS: i32 = 1000;
4817 const SCROLL: usize = 30;
4818 const STILL: usize = 300;
4819 const PAINT_QP: u32 = 18;
4820 let mut enc = H264EncoderWrapper::new(
4821 W as i32,
4822 H as i32,
4823 25,
4824 false,
4825 60.0,
4826 4,
4827 true,
4828 KBPS,
4829 KBPS * 4 / 60,
4830 0,
4831 0,
4832 )
4833 .expect("x264 init");
4834 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
4835 let still = text_luma(SCROLL);
4836 let mut dec = Decoder::new().expect("decoder");
4837 let mut picture = vec![0u8; W * H];
4838 let mut misread = Vec::new();
4839 for i in 0..SCROLL + STILL {
4840 let y = if i < SCROLL {
4841 text_luma(i)
4842 } else {
4843 still.clone()
4844 };
4845 let mut out = Vec::new();
4846 enc.encode_with_headers(
4847 &y,
4848 &u,
4849 &v,
4850 W as i32,
4851 (W / 2) as i32,
4852 (W / 2) as i32,
4853 i as u16,
4854 i == 0,
4855 true,
4856 &mut out,
4857 );
4858 if let Some(img) = dec.decode(&out).expect("decode") {
4859 let stride = img.strides().0;
4860 for r in 0..H {
4861 picture[r * W..r * W + W].copy_from_slice(&img.y()[r * stride..r * stride + W]);
4862 }
4863 }
4864 let psnr = mean_psnr(std::slice::from_ref(&picture), std::slice::from_ref(&still));
4865 if i >= SCROLL && enc.last_qp().is_some_and(|q| q <= PAINT_QP) && psnr < 40.0 {
4866 misread.push((i, enc.last_qp(), out.len(), psnr as i32));
4867 }
4868 }
4869 assert!(
4870 misread.is_empty(),
4871 "frames said to be at the paint-over quantizer: {:?}",
4872 &misread[..misread.len().min(8)]
4873 );
4874 }
4875
4876 #[cfg(feature = "gpl")]
4881 #[test]
4882 fn x264_refines_a_still_screen_after_a_scroll_in_a_cleanups_buffer() {
4883 const KBPS: i32 = 1000;
4884 const SCROLL: usize = 30;
4885 const STILL: usize = 600;
4886 let settings = CaptureSettings {
4887 target_fps: 60.0,
4888 ..Default::default()
4889 };
4890 let session_vbv =
4891 (crate::encoders::vbv_bits(KBPS as u32 * 1000, 60.0, 0.0, 0.0) / 1000) as i32;
4892 let mut enc = H264EncoderWrapper::new(
4893 W as i32,
4894 H as i32,
4895 25,
4896 false,
4897 60.0,
4898 4,
4899 true,
4900 KBPS,
4901 session_vbv,
4902 0,
4903 0,
4904 )
4905 .expect("x264 init");
4906 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
4907 let still = text_luma(SCROLL);
4908 let mut dec = Decoder::new().expect("decoder");
4909 let mut picture = vec![0u8; W * H];
4910 for i in 0..SCROLL + STILL {
4911 if i == SCROLL {
4912 enc.reconfigure_rate(KBPS, super::cleanup_vbv_kbit(&settings, KBPS), 60.0);
4913 }
4914 let y = if i < SCROLL {
4915 text_luma(i)
4916 } else {
4917 still.clone()
4918 };
4919 let mut out = Vec::new();
4920 enc.encode_with_headers(
4921 &y,
4922 &u,
4923 &v,
4924 W as i32,
4925 (W / 2) as i32,
4926 (W / 2) as i32,
4927 i as u16,
4928 i == 0,
4929 true,
4930 &mut out,
4931 );
4932 if let Some(img) = dec.decode(&out).expect("decode") {
4933 let stride = img.strides().0;
4934 for r in 0..H {
4935 picture[r * W..r * W + W].copy_from_slice(&img.y()[r * stride..r * stride + W]);
4936 }
4937 }
4938 }
4939 let psnr = mean_psnr(std::slice::from_ref(&picture), std::slice::from_ref(&still));
4940 assert!(psnr > 35.0, "a still screen refined to {psnr:.1} dB");
4941 }
4942
4943 #[cfg(feature = "gpl")]
4948 #[test]
4949 fn x264_capped_crf_codes_as_uncapped_under_a_peak_it_never_reaches() {
4950 use crate::encoders::codec::{FRAME_KEY, parse_video_type};
4951 let (w, h) = (W as i32, H as i32);
4952 let uncapped = CaptureSettings {
4953 width: w,
4954 height: h,
4955 codec: Codec::H264,
4956 target_fps: 60.0,
4957 video_crf: 25,
4958 video_paintover_crf: 18,
4959 use_paint_over_quality: true,
4960 video_bitrate_kbps: 8000,
4961 ..Default::default()
4962 };
4963 let capped = CaptureSettings {
4964 video_bitrate_kbps: 100_000,
4965 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
4966 ..uncapped.clone()
4967 };
4968 let whole = [smithay::utils::Rectangle::new((0, 0).into(), (w, h).into())];
4969 let (mut a, mut b) = (Vec::new(), Vec::new());
4970 let (mut keys, mut cleanup) = (0, 0);
4971 for t in 0..150u16 {
4972 let still = (30..90).contains(&t);
4973 let at = if still {
4974 30
4975 } else if t < 30 {
4976 t
4977 } else {
4978 t - 60
4979 };
4980 let frame = crate::encoders::scrolled_text(W, H, at as usize);
4981 let changed: &[_] = if still { &[] } else { &whole };
4982 let key = t == 0 || t == 60;
4983 let one = super::encode_cpu(
4984 &mut a, &frame, w, h, changed, &uncapped, t, false, false, key,
4985 );
4986 let two =
4987 super::encode_cpu(&mut b, &frame, w, h, changed, &capped, t, false, false, key);
4988 assert_eq!(one.len(), two.len(), "frame {t} coded on one side only");
4989 for (x, y) in one.iter().zip(&two) {
4990 assert!(
4991 x.data == y.data,
4992 "frame {t}: {} bytes uncapped, {} capped",
4993 x.data.len(),
4994 y.data.len()
4995 );
4996 keys += usize::from(parse_video_type(x.data[1]).map(|(_, k)| k) == Some(FRAME_KEY));
4997 cleanup += usize::from(still);
4998 }
4999 }
5000 assert!(keys >= 2, "{keys} key frames");
5001 assert!(cleanup > 0, "the still screen was never cleaned up");
5002 }
5003
5004 #[cfg(feature = "gpl")]
5009 fn scroll_then_still(
5010 peak: i32,
5011 then: i32,
5012 frames: usize,
5013 paint: bool,
5014 ) -> (Vec<Vec<u8>>, Vec<usize>, usize, bool) {
5015 let peaks = |t: usize| if t <= 60 || peak == 0 { peak } else { then };
5016 scroll_then_still_under(&peaks, frames, paint)
5017 }
5018
5019 #[cfg(feature = "gpl")]
5021 fn scroll_then_still_under(
5022 peaks: &dyn Fn(usize) -> i32,
5023 frames: usize,
5024 paint: bool,
5025 ) -> (Vec<Vec<u8>>, Vec<usize>, usize, bool) {
5026 use crate::encoders::codec::{FRAME_KEY, VIDEO_HEADER_LEN, parse_video_type};
5027 const SCROLL: usize = 60;
5028 let (w, h) = (W as i32, H as i32);
5029 let peak = peaks(0);
5030 let mut s = CaptureSettings {
5031 width: w,
5032 height: h,
5033 codec: Codec::H264,
5034 target_fps: 60.0,
5035 video_crf: 25,
5036 video_paintover_crf: 18,
5037 use_paint_over_quality: paint,
5038 video_bitrate_kbps: peak.max(8000),
5039 video_vbv_multiplier: if peak > 0 {
5040 crate::encoders::CAPPED_VBV_FRAMES
5041 } else {
5042 0.0
5043 },
5044 ..Default::default()
5045 };
5046 if peak > 0 {
5047 s.video_bitrate_kbps = peak;
5048 }
5049 let whole = [smithay::utils::Rectangle::new((0, 0).into(), (w, h).into())];
5050 let (mut stripes, mut stream, mut sizes, mut keys) =
5051 (Vec::new(), Vec::new(), Vec::new(), 0);
5052 for t in 0..frames {
5053 if peak > 0 {
5054 s.video_bitrate_kbps = peaks(t);
5055 }
5056 let frame = crate::encoders::scrolled_text(W, H, t.min(SCROLL));
5057 let changed: &[_] = if t <= SCROLL { &whole } else { &[] };
5058 let out = super::encode_cpu(
5059 &mut stripes,
5060 &frame,
5061 w,
5062 h,
5063 changed,
5064 &s,
5065 t as u16,
5066 false,
5067 false,
5068 t == 0,
5069 );
5070 let mut bytes = 0;
5071 for st in &out {
5072 keys += usize::from(
5073 t > 0 && parse_video_type(st.data[1]).map(|(_, k)| k) == Some(FRAME_KEY),
5074 );
5075 bytes += st.data.len() - VIDEO_HEADER_LEN;
5076 stream.push(st.data[VIDEO_HEADER_LEN..].to_vec());
5077 }
5078 sizes.push(bytes);
5079 }
5080 let coarse = stripes
5081 .first()
5082 .is_some_and(|st: &super::StripeState| st.ledger.coarse());
5083 (decode_luma(&stream), sizes, keys, coarse)
5084 }
5085
5086 #[cfg(feature = "gpl")]
5088 fn still_page_luma() -> Vec<u8> {
5089 crate::encoders::scrolled_text(W, H, 60)
5090 .as_chunks::<4>()
5091 .0
5092 .iter()
5093 .map(|px| (16.0 + f64::from(px[0]) * 219.0 / 255.0).round() as u8)
5094 .collect()
5095 }
5096
5097 #[cfg(feature = "gpl")]
5104 #[test]
5105 fn x264_capped_crf_refreshes_what_it_coded_bound_once_its_peak_lets_go() {
5106 const NEAR_DB: f64 = 0.5;
5107 const FRAMES: usize = 200;
5108 let page = still_page_luma();
5109 let (plain, _, _, _) = scroll_then_still(0, 0, FRAMES, false);
5110 let (capped, sizes, keys, coarse) = scroll_then_still(300, 100_000, FRAMES, false);
5111 let psnr = |p: &[Vec<u8>]| mean_psnr(&p[p.len() - 1..], std::slice::from_ref(&page));
5112 let (a, b) = (psnr(&plain), psnr(&capped));
5113 let refreshing = sizes[61..].iter().filter(|&&n| n > 0).count();
5114 println!(
5115 "uncapped {a:.2} dB, capped and refreshed {b:.2} dB, {refreshing} frames sent while still"
5116 );
5117 assert_eq!(keys, 0, "a key frame");
5118 assert!(!coarse, "coarse blocks left");
5119 assert!(refreshing > 0, "nothing refreshed the still page");
5120 assert!(
5121 b >= a - NEAR_DB,
5122 "refreshed to {b:.2} dB against {a:.2} uncapped"
5123 );
5124 }
5125
5126 #[cfg(feature = "gpl")]
5129 #[test]
5130 fn x264_capped_crf_refresh_leaves_the_paint_over_to_run() {
5131 const FRAMES: usize = 300;
5132 let page = still_page_luma();
5133 let psnr = |p: &[Vec<u8>]| mean_psnr(&p[p.len() - 1..], std::slice::from_ref(&page));
5134 let (plain, _, _, _) = scroll_then_still(0, 0, FRAMES, true);
5135 let (capped, _, keys, coarse) = scroll_then_still(300, 100_000, FRAMES, true);
5136 let (a, b) = (psnr(&plain), psnr(&capped));
5137 println!("paint-over: uncapped {a:.2} dB, capped and refreshed {b:.2} dB");
5138 assert_eq!(keys, 0, "a key frame");
5139 assert!(!coarse, "coarse blocks left");
5140 assert!(
5141 b >= a - 0.5,
5142 "{b:.2} dB against the uncapped paint-over's {a:.2}"
5143 );
5144 }
5145
5146 #[cfg(feature = "gpl")]
5151 #[test]
5152 fn x264_capped_crf_paints_over_within_the_peak() {
5153 const FRAMES: usize = 600;
5154 const THEN: i32 = 4000;
5155 let page = still_page_luma();
5156 let psnr = |p: &[Vec<u8>]| mean_psnr(&p[p.len() - 1..], std::slice::from_ref(&page));
5157 let (plain, plain_sizes, _, _) = scroll_then_still(0, 0, FRAMES, true);
5158 let (capped, sizes, keys, coarse) = scroll_then_still(300, THEN, FRAMES, true);
5159 let room =
5160 (crate::encoders::CAPPED_VBV_FRAMES * f64::from(THEN) * 1000.0 / 8.0 / 60.0) as usize;
5161 let still = &sizes[61..];
5162 let largest = still.iter().max().copied().unwrap_or(0);
5163 let sent = still.iter().filter(|&&n| n > 0).count();
5164 let bytes = |s: &[usize]| s[61..].iter().sum::<usize>();
5165 let (a, b) = (psnr(&plain), psnr(&capped));
5166 println!(
5167 "paint-over under {THEN} kbps: uncapped {a:.2} dB in {} bytes still, capped {b:.2} dB \
5168 in {sent} frames of {} bytes, the largest {largest} (room {room})",
5169 bytes(&plain_sizes),
5170 bytes(&sizes)
5171 );
5172 assert_eq!(keys, 0, "a key frame");
5173 assert!(!coarse, "coarse blocks left");
5174 assert!(
5175 largest <= room,
5176 "a still frame of {largest} bytes past the room"
5177 );
5178 assert!(
5179 b >= a - 0.5,
5180 "{b:.2} dB against the uncapped paint-over's {a:.2}"
5181 );
5182 }
5183
5184 #[cfg(feature = "gpl")]
5190 #[test]
5191 fn x264_capped_crf_holds_its_paint_over_to_the_room() {
5192 const FRAMES: usize = 600;
5193 const THEN: i32 = 4000;
5194 let page = still_page_luma();
5195 let psnr = |p: &[Vec<u8>]| mean_psnr(&p[p.len() - 1..], std::slice::from_ref(&page));
5196 let (plain, plain_sizes, _, _) = scroll_then_still(0, 0, FRAMES, true);
5197 let peaks = |t: usize| match t {
5198 0..5 => 300,
5199 5..=60 => 50_000,
5200 _ => THEN,
5201 };
5202 let (capped, sizes, keys, coarse) = scroll_then_still_under(&peaks, FRAMES, true);
5203 let room =
5204 (crate::encoders::CAPPED_VBV_FRAMES * f64::from(THEN) * 1000.0 / 8.0 / 60.0) as usize;
5205 let still = &sizes[61..];
5206 let largest = still.iter().max().copied().unwrap_or(0);
5207 let sent = still.iter().filter(|&&n| n > 0).count();
5208 let bytes = |s: &[usize]| s[61..].iter().sum::<usize>();
5209 let (a, b) = (psnr(&plain), psnr(&capped));
5210 println!(
5211 "paint-over under {THEN} kbps after a page coded at its own quality: uncapped {a:.2} \
5212 dB in {} bytes still, capped {b:.2} dB in {sent} frames of {} bytes, the largest \
5213 {largest} (room {room})",
5214 bytes(&plain_sizes),
5215 bytes(&sizes)
5216 );
5217 assert_eq!(keys, 0, "a key frame");
5218 assert!(!coarse, "coarse blocks left");
5219 assert!(
5220 largest <= room,
5221 "a still frame of {largest} bytes past the room"
5222 );
5223 assert!(
5224 b >= a - 0.5,
5225 "{b:.2} dB against the uncapped paint-over's {a:.2}"
5226 );
5227 }
5228
5229 #[cfg(feature = "gpl")]
5232 #[test]
5233 fn x264_capped_crf_paces_its_refresh_within_the_peak() {
5234 const FRAMES: usize = 600;
5235 const THEN: i32 = 2000;
5236 let (_, sizes, keys, coarse) = scroll_then_still(300, THEN, FRAMES, false);
5237 let sent: Vec<usize> = sizes[61..].iter().copied().filter(|&n| n > 0).collect();
5238 let kbps =
5239 sizes[61..].iter().sum::<usize>() as f64 * 8.0 * 60.0 / (FRAMES - 61) as f64 / 1000.0;
5240 let largest = sent.iter().copied().max().unwrap_or(0);
5241 println!(
5242 "{} still frames sent, largest {largest} bytes, {kbps:.0} kbps under a {THEN} kbps peak",
5243 sent.len()
5244 );
5245 assert_eq!(keys, 0, "a key frame");
5246 assert!(!coarse, "the refresh did not end");
5247 assert!(
5248 kbps <= 1.1 * f64::from(THEN),
5249 "{kbps:.0} kbps under a {THEN} kbps peak"
5250 );
5251 }
5252
5253 #[cfg(feature = "gpl")]
5260 #[test]
5261 fn x264_capped_crf_refresh_yields_to_motion_within_the_peak() {
5262 use crate::encoders::codec::{FRAME_KEY, VIDEO_HEADER_LEN, parse_video_type};
5263 const THEN: i32 = 2000;
5264 let (w, h) = (W as i32, H as i32);
5265 let mut s = CaptureSettings {
5266 width: w,
5267 height: h,
5268 codec: Codec::H264,
5269 target_fps: 60.0,
5270 video_crf: 25,
5271 use_paint_over_quality: false,
5272 video_bitrate_kbps: 300,
5273 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
5274 ..Default::default()
5275 };
5276 let whole = [smithay::utils::Rectangle::new((0, 0).into(), (w, h).into())];
5277 let (mut stripes, mut sizes, mut keys) = (Vec::new(), Vec::new(), 0);
5278 let mut refreshing = false;
5279 for t in 0..300usize {
5280 if t == 61 {
5281 s.video_bitrate_kbps = THEN;
5282 }
5283 let (at, moving) = match t {
5285 0..=60 => (t, true),
5286 61..=90 => (60, false),
5287 _ => (t - 30, true),
5288 };
5289 let frame = crate::encoders::scrolled_text(W, H, at);
5290 let changed: &[_] = if moving { &whole } else { &[] };
5291 let out = super::encode_cpu(
5292 &mut stripes,
5293 &frame,
5294 w,
5295 h,
5296 changed,
5297 &s,
5298 t as u16,
5299 false,
5300 false,
5301 t == 0,
5302 );
5303 let mut bytes = 0;
5304 for st in &out {
5305 keys += usize::from(
5306 t > 0 && parse_video_type(st.data[1]).map(|(_, k)| k) == Some(FRAME_KEY),
5307 );
5308 bytes += st.data.len() - VIDEO_HEADER_LEN;
5309 }
5310 refreshing |= t == 90 && stripes[0].ledger.coarse();
5311 sizes.push(bytes);
5312 }
5313 let kbps = sizes[120..].iter().sum::<usize>() as f64 * 8.0 * 60.0 / 180.0 / 1000.0;
5314 let largest = sizes[61..=90].iter().copied().max().unwrap_or(0);
5315 let bucket = crate::encoders::vbv_bits(
5316 THEN as u32 * 1000,
5317 60.0,
5318 0.0,
5319 crate::encoders::CAPPED_VBV_FRAMES,
5320 ) as usize
5321 / 8;
5322 println!(
5323 "{kbps:.0} kbps under a {THEN} kbps peak, largest refreshing frame {largest} bytes \
5324 (bucket {bucket}), the scroll's first {} bytes",
5325 sizes[91]
5326 );
5327 assert_eq!(keys, 0, "a key frame");
5328 assert!(
5329 refreshing,
5330 "the refresh had ended before the page moved again"
5331 );
5332 assert!(
5333 kbps <= 1.1 * f64::from(THEN),
5334 "{kbps:.0} kbps under a {THEN} kbps peak"
5335 );
5336 assert!(
5337 largest <= bucket,
5338 "a {largest}-byte refreshing frame past the {bucket}-byte bucket"
5339 );
5340 }
5341
5342 #[cfg(feature = "gpl")]
5345 #[test]
5346 fn x264_capped_crf_refines_nothing_while_its_peak_binds() {
5347 use crate::CaptureSettings;
5348 const KBPS: i32 = 1500;
5349 let (w, h) = (W as i32, H as i32);
5350 let settings = CaptureSettings {
5351 width: w,
5352 height: h,
5353 codec: Codec::H264,
5354 target_fps: 60.0,
5355 video_crf: 25,
5356 video_paintover_crf: 18,
5357 use_paint_over_quality: true,
5358 video_bitrate_kbps: KBPS,
5359 video_vbv_multiplier: crate::encoders::CAPPED_VBV_FRAMES,
5360 ..Default::default()
5361 };
5362 let still = crate::encoders::scrolled_text(W, H, 0);
5363 let moving = [smithay::utils::Rectangle::new(
5364 (0, 0).into(),
5365 (w, h / 2).into(),
5366 )];
5367 let mut stripes = Vec::new();
5368 let mut bits = 0usize;
5369 for t in 0..300u16 {
5370 let mut frame = still.clone();
5371 let scrolled = crate::encoders::scrolled_text(W, H, t as usize + 1);
5372 frame[..W * H * 2].copy_from_slice(&scrolled[..W * H * 2]);
5373 let out = super::encode_cpu(
5374 &mut stripes,
5375 &frame,
5376 w,
5377 h,
5378 if t == 0 { &[] } else { &moving },
5379 &settings,
5380 t,
5381 false,
5382 false,
5383 t == 0,
5384 );
5385 if t >= 120 {
5386 bits += out.iter().map(|s| s.data.len() * 8).sum::<usize>();
5387 }
5388 }
5389 let kbps = bits as f64 * 60.0 / 180.0 / 1000.0;
5390 assert!(
5391 kbps <= 1.05 * f64::from(KBPS),
5392 "{kbps:.0} kbps under a {KBPS} kbps peak"
5393 );
5394 }
5395
5396 #[cfg(feature = "gpl")]
5400 #[test]
5401 fn x264_capped_crf_moves_its_peak_without_a_key_frame() {
5402 use crate::encoders::codec::{FRAME_KEY, h264_frame_type};
5403 const CUT: usize = 30;
5404 const LOW_KBPS: i32 = 1000;
5405 let vbv = |kbps: i32| {
5406 (crate::encoders::vbv_bits(
5407 kbps as u32 * 1000,
5408 60.0,
5409 0.0,
5410 crate::encoders::CAPPED_VBV_FRAMES,
5411 ) / 1000) as i32
5412 };
5413 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
5414 let mut enc = H264EncoderWrapper::new(
5415 W as i32,
5416 H as i32,
5417 25,
5418 false,
5419 60.0,
5420 4,
5421 false,
5422 100_000,
5423 vbv(100_000),
5424 0,
5425 0,
5426 )
5427 .expect("x264 init");
5428 let mut sizes = Vec::new();
5429 for i in 0..CUT + FRAMES {
5430 if i == CUT {
5431 enc.reconfigure_rate(LOW_KBPS, vbv(LOW_KBPS), 60.0);
5432 }
5433 let mut out = Vec::new();
5434 assert!(enc.encode_with_headers(
5435 &text_luma(i),
5436 &u,
5437 &v,
5438 W as i32,
5439 (W / 2) as i32,
5440 (W / 2) as i32,
5441 i as u16,
5442 i == 0,
5443 true,
5444 &mut out
5445 ));
5446 assert!(
5447 i == 0 || h264_frame_type(&out) != FRAME_KEY,
5448 "frame {i} is a key frame"
5449 );
5450 sizes.push(out.len() * 8);
5451 }
5452 let kbps = |frames: &[usize]| {
5453 frames.iter().sum::<usize>() as f64 * 60.0 / frames.len() as f64 / 1000.0
5454 };
5455 let (before, after) = (kbps(&sizes[1..CUT]), kbps(&sizes[CUT + 10..]));
5456 assert!(
5457 before > 2.0 * f64::from(LOW_KBPS),
5458 "the scroll ran at {before:.0} kbps under the high peak"
5459 );
5460 assert!(
5461 after <= 1.1 * f64::from(LOW_KBPS),
5462 "the scroll ran at {after:.0} kbps under a {LOW_KBPS} kbps peak"
5463 );
5464 }
5465
5466 #[cfg(feature = "gpl")]
5468 #[test]
5469 fn x264_crf_takes_a_cap_set_or_lifted_live() {
5470 const VBV_KBIT: i32 = 1500;
5471 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
5472 let mut enc =
5473 H264EncoderWrapper::new(W as i32, H as i32, 12, false, 60.0, 4, false, 8000, 0, 0, 0)
5474 .expect("x264 init");
5475 let key = |enc: &mut H264EncoderWrapper| {
5476 let mut out = Vec::new();
5477 assert!(enc.encode_with_headers(
5478 &text_luma(0),
5479 &u,
5480 &v,
5481 W as i32,
5482 (W / 2) as i32,
5483 (W / 2) as i32,
5484 0,
5485 true,
5486 true,
5487 &mut out
5488 ));
5489 out.len() * 8
5490 };
5491 let uncapped = key(&mut enc);
5492 enc.reconfigure_rate(8000, VBV_KBIT, 60.0);
5493 let capped = key(&mut enc);
5494 enc.reconfigure_rate(8000, 0, 60.0);
5495 let lifted = key(&mut enc);
5496 assert!(
5497 capped <= VBV_KBIT as usize * 1000,
5498 "a {capped}-bit key frame after capping overflows the {VBV_KBIT} kbit buffer ({uncapped} bits before)"
5499 );
5500 assert!(
5501 lifted > 2 * capped,
5502 "the key frame after lifting the cap ({lifted} bits) is still capped"
5503 );
5504 }
5505
5506 #[cfg(feature = "gpl")]
5511 #[test]
5512 fn x264_predicts_past_a_lost_frame() {
5513 use crate::encoders::codec::{FRAME_DELTA, FRAME_KEY, h264_frame_type};
5514 use crate::encoders::reference::{REFERENCE_FRAMES, Reference};
5515 use crate::encoders::sps::h264_max_num_ref_frames;
5516 use crate::webcam::decode::{Decoder as _, VideoDecoder};
5517 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
5518 let mut enc =
5519 H264EncoderWrapper::new(W as i32, H as i32, 20, false, 60.0, 4, false, 0, 0, 0, 0)
5520 .expect("x264 init");
5521 let encode = |enc: &mut H264EncoderWrapper, i: usize| {
5522 let y = text_luma(i);
5523 let mut out = Vec::new();
5524 assert!(enc.encode_with_headers(
5525 &y,
5526 &u,
5527 &v,
5528 W as i32,
5529 (W / 2) as i32,
5530 (W / 2) as i32,
5531 i as u16,
5532 i == 0,
5533 true,
5534 &mut out
5535 ));
5536 (out, enc.last_reference())
5537 };
5538 let mut frames: Vec<Vec<u8>> = Vec::new();
5539 for i in 0..8 {
5540 let (out, reference) = encode(&mut enc, i);
5541 assert_eq!(
5542 reference,
5543 if i == 0 {
5544 Reference::None
5545 } else {
5546 Reference::Frame(i as u16 - 1)
5547 }
5548 );
5549 frames.push(out);
5550 }
5551 assert_eq!(
5552 h264_max_num_ref_frames(&frames[0]),
5553 Some(REFERENCE_FRAMES),
5554 "the SPS declares the DPB"
5555 );
5556 assert!(enc.invalidate_reference(5));
5558 let (out, reference) = encode(&mut enc, 8);
5559 assert_eq!(reference, Reference::Frame(4));
5560 assert_eq!(h264_frame_type(&out), FRAME_DELTA);
5561 frames.push(out);
5562 let (out, reference) = encode(&mut enc, 9);
5563 assert_eq!(reference, Reference::Frame(8));
5564 frames.push(out);
5565 let (mut whole, mut lossy) = (
5566 VideoDecoder::new(Codec::H264).unwrap(),
5567 VideoDecoder::new(Codec::H264).unwrap(),
5568 );
5569 for (i, f) in frames.iter().enumerate() {
5570 assert!(whole.decode(f).expect("decode"), "frame {i}");
5571 if !(5..8).contains(&i) {
5572 assert!(lossy.decode(f).expect("decode without 5-7"), "frame {i}");
5573 }
5574 }
5575 let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
5576 assert!(
5577 apart < 0.5,
5578 "the decoder that lost frames 5-7 shows frame 9 {apart:.2} off the one that saw them"
5579 );
5580 let source = text_luma(9);
5581 let off = lossy
5582 .frame()
5583 .unwrap()
5584 .y
5585 .chunks(lossy.frame().unwrap().y_stride)
5586 .take(H)
5587 .zip(source.chunks(W))
5588 .flat_map(|(row, src)| {
5589 row[..W]
5590 .iter()
5591 .zip(src)
5592 .map(|(&a, &b)| (f64::from(a) - f64::from(b)).abs())
5593 })
5594 .sum::<f64>()
5595 / (W * H) as f64;
5596 assert!(
5597 off < 6.0,
5598 "frame 9 decoded without frames 5-7 is {off:.2} off the picture painted"
5599 );
5600 for i in 10..20 {
5601 frames.push(encode(&mut enc, i).0);
5602 }
5603 assert!(enc.invalidate_reference(9));
5606 let (out, reference) = encode(&mut enc, 20);
5607 assert_eq!(reference, Reference::None);
5608 assert_eq!(h264_frame_type(&out), FRAME_KEY);
5609 }
5610
5611 #[cfg(feature = "gpl")]
5614 #[test]
5615 fn x264_keeps_the_reference_frames_it_is_given() {
5616 use crate::encoders::codec::{FRAME_KEY, h264_frame_type};
5617 use crate::encoders::sps::h264_max_num_ref_frames;
5618 let (u, v) = (vec![128u8; W * H / 4], vec![128u8; W * H / 4]);
5619 let mut enc = H264EncoderWrapper::with_depth(
5620 W as i32, H as i32, 20, false, 8, 60.0, 1, false, 0, 0, 0, 0, 1,
5621 )
5622 .expect("x264 init");
5623 let mut frames = Vec::new();
5624 for i in 0..4usize {
5625 let mut out = Vec::new();
5626 assert!(enc.encode_with_headers(
5627 &text_luma(i),
5628 &u,
5629 &v,
5630 W as i32,
5631 (W / 2) as i32,
5632 (W / 2) as i32,
5633 i as u16,
5634 i == 0,
5635 true,
5636 &mut out
5637 ));
5638 frames.push(out);
5639 }
5640 assert_eq!(h264_max_num_ref_frames(&frames[0]), Some(1));
5641 assert!(enc.invalidate_reference(3));
5642 let mut out = Vec::new();
5643 assert!(enc.encode_with_headers(
5644 &text_luma(4),
5645 &u,
5646 &v,
5647 W as i32,
5648 (W / 2) as i32,
5649 (W / 2) as i32,
5650 4,
5651 false,
5652 true,
5653 &mut out
5654 ));
5655 assert_eq!(h264_frame_type(&out), FRAME_KEY, "nothing older is held");
5656 }
5657
5658 #[cfg(feature = "gpl")]
5661 #[test]
5662 fn x264_reopens_at_a_changed_reference_depth() {
5663 use crate::encoders::codec::{FRAME_DELTA, FRAME_KEY, h264_frame_type};
5664 use crate::encoders::sps::h264_max_num_ref_frames;
5665 let mut settings = CaptureSettings {
5666 width: W as i32,
5667 height: H as i32,
5668 codec: Codec::H264,
5669 target_fps: 60.0,
5670 video_streaming_mode: true,
5671 omit_stripe_headers: true,
5672 video_reference_frames: 1,
5673 ..Default::default()
5674 };
5675 let mut stripes = Vec::new();
5676 let mut encode = |settings: &CaptureSettings, i: usize| {
5677 let argb: Vec<u8> = text_luma(i).iter().flat_map(|&l| [l, l, l, 255]).collect();
5678 let out = super::encode_cpu(
5679 &mut stripes,
5680 &argb,
5681 W as i32,
5682 H as i32,
5683 &[],
5684 settings,
5685 i as u16,
5686 false,
5687 true,
5688 i == 0,
5689 );
5690 assert_eq!(out.len(), 1, "frame {i} is one full-frame stripe");
5691 out[0].data.to_vec()
5692 };
5693 let first = encode(&settings, 0);
5694 assert_eq!(h264_max_num_ref_frames(&first), Some(1));
5695 assert_eq!(h264_frame_type(&encode(&settings, 1)), FRAME_DELTA);
5696 settings.video_reference_frames = 0;
5697 let reopened = encode(&settings, 2);
5698 assert_eq!(
5699 h264_frame_type(&reopened),
5700 FRAME_KEY,
5701 "the reopen codes a key frame"
5702 );
5703 assert!(
5704 h264_max_num_ref_frames(&reopened).is_some_and(|n| n > 1),
5705 "at the session's own depth"
5706 );
5707 assert_eq!(
5708 h264_frame_type(&encode(&settings, 3)),
5709 FRAME_DELTA,
5710 "and codes on"
5711 );
5712 }
5713
5714 #[test]
5719 #[cfg(feature = "gpl")]
5720 fn x264_answers_a_loss_at_the_frame_num_wrap_with_a_key_frame() {
5721 use crate::encoders::codec::{FRAME_DELTA, FRAME_KEY, h264_frame_type};
5722 use crate::encoders::reference::Reference;
5723 use crate::encoders::sps::h264_frame_num_range;
5724 use crate::webcam::decode::{Decoder as _, VideoDecoder};
5725 let (w, h) = (160usize, 96usize);
5726 let (u, v) = (vec![128u8; w * h / 4], vec![128u8; w * h / 4]);
5727 let mut enc =
5728 H264EncoderWrapper::new(w as i32, h as i32, 20, false, 60.0, 1, false, 0, 0, 0, 0)
5729 .expect("x264 init");
5730 let encode = |enc: &mut H264EncoderWrapper, i: usize| {
5731 let y: Vec<u8> = (0..w * h)
5732 .map(|p| ((p % w + p / w + 3 * i) * 5 % 256) as u8)
5733 .collect();
5734 let mut out = Vec::new();
5735 assert!(enc.encode_with_headers(
5736 &y,
5737 &u,
5738 &v,
5739 w as i32,
5740 (w / 2) as i32,
5741 (w / 2) as i32,
5742 i as u16,
5743 i == 0,
5744 true,
5745 &mut out
5746 ));
5747 (out, enc.last_reference())
5748 };
5749 let (mut whole, mut lossy) = (
5750 VideoDecoder::new(Codec::H264).unwrap(),
5751 VideoDecoder::new(Codec::H264).unwrap(),
5752 );
5753 let (first, _) = encode(&mut enc, 0);
5754 let range = h264_frame_num_range(&first).expect("the key frame carries the SPS") as usize;
5755 assert_eq!(range, 4096, "x264's sixteen values, a byte wider");
5756 assert!(whole.decode(&first).expect("decode") && lossy.decode(&first).expect("decode"));
5757 for i in 1..=18 {
5758 let (out, _) = encode(&mut enc, i);
5759 assert!(whole.decode(&out).expect("decode"), "frame {i}");
5760 if i < 16 {
5761 assert!(lossy.decode(&out).expect("decode"), "frame {i}");
5762 }
5763 }
5764 assert!(enc.invalidate_reference(16), "x264's wrap frame is lost");
5765 let (out, reference) = encode(&mut enc, 19);
5766 assert_eq!(reference, Reference::Frame(15));
5767 assert_eq!(h264_frame_type(&out), FRAME_DELTA);
5768 assert!(whole.decode(&out).expect("decode"));
5769 assert!(lossy.decode(&out).expect("decode past x264's wrap"));
5770 let apart = luma_distance(&whole.frame().unwrap(), &lossy.frame().unwrap());
5771 assert!(
5772 apart < 0.5,
5773 "the decoder that lost frames 16-18 shows frame 19 {apart:.2} off the one that saw them"
5774 );
5775 for i in 20..=range {
5776 let (out, _) = encode(&mut enc, i);
5777 if i < range {
5778 assert!(lossy.decode(&out).expect("decode"), "frame {i}");
5779 }
5780 }
5781 assert!(
5782 enc.invalidate_reference(range as u16),
5783 "the stream's wrap frame is reported lost"
5784 );
5785 let (out, reference) = encode(&mut enc, range + 1);
5786 assert_eq!(reference, Reference::None);
5787 assert_eq!(h264_frame_type(&out), FRAME_KEY);
5788 assert!(
5789 lossy.decode(&out).expect("decode past the wrap"),
5790 "the decoder that never saw the wrap frame shows the next one"
5791 );
5792 assert_eq!(
5793 encode(&mut enc, range + 2).1,
5794 Reference::Frame(range as u16 + 1)
5795 );
5796 assert!(
5797 enc.invalidate_reference(range as u16 + 2),
5798 "the count restarted at the key frame"
5799 );
5800 assert_eq!(
5801 encode(&mut enc, range + 3).1,
5802 Reference::Frame(range as u16 + 1)
5803 );
5804 }
5805
5806 #[cfg(feature = "gpl")]
5808 fn luma_distance(
5809 a: &crate::webcam::convert::I420View<'_>,
5810 b: &crate::webcam::convert::I420View<'_>,
5811 ) -> f64 {
5812 let rows =
5813 a.y.chunks(a.y_stride)
5814 .zip(b.y.chunks(b.y_stride))
5815 .take(a.height);
5816 rows.flat_map(|(ra, rb)| {
5817 ra[..a.width]
5818 .iter()
5819 .zip(&rb[..a.width])
5820 .map(|(&x, &y)| (f64::from(x) - f64::from(y)).abs())
5821 })
5822 .sum::<f64>()
5823 / (a.width * a.height) as f64
5824 }
5825
5826 fn encode_oh264(cbr: bool, kbps: i32, crf: i32, min_qp: i32, max_qp: i32) -> Vec<Vec<u8>> {
5830 let s = CaptureSettings {
5831 width: W as i32,
5832 height: H as i32,
5833 target_fps: 60.0,
5834 codec: Codec::H264,
5835 video_cbr_mode: cbr,
5836 video_bitrate_kbps: kbps,
5837 video_crf: crf,
5838 video_min_qp: min_qp,
5839 video_max_qp: max_qp,
5840 ..Default::default()
5841 };
5842 let mut enc = Openh264Encoder::new(&s).expect("oh264 init");
5843 (0..FRAMES)
5844 .map(|i| {
5845 let y = text_luma(i);
5846 let mut bgra = vec![255u8; W * H * 4];
5847 for (px, &l) in bgra.as_chunks_mut::<4>().0.iter_mut().zip(y.iter()) {
5848 px[0] = l;
5849 px[1] = l;
5850 px[2] = l;
5851 }
5852 enc.encode_host_argb(&bgra, W * 4, i as u64, i == 0, false)
5853 .expect("oh264 encode")
5854 })
5855 .collect()
5856 }
5857
5858 fn decode_luma(frames: &[Vec<u8>]) -> Vec<Vec<u8>> {
5861 let mut dec = Decoder::new().expect("decoder");
5862 let mut out = Vec::new();
5863 for f in frames {
5864 if f.is_empty() {
5865 continue;
5866 }
5867 if let Ok(Some(img)) = dec.decode(f) {
5868 let (w, h) = img.dimensions();
5869 let stride = img.strides().0;
5870 let mut y = vec![0u8; w * h];
5871 for r in 0..h {
5872 y[r * w..r * w + w].copy_from_slice(&img.y()[r * stride..r * stride + w]);
5873 }
5874 out.push(y);
5875 }
5876 }
5877 out
5878 }
5879
5880 fn mean_psnr(a: &[Vec<u8>], b: &[Vec<u8>]) -> f64 {
5883 let n = a.len().min(b.len());
5884 let mut acc = 0.0;
5885 for i in 0..n {
5886 let mse: f64 = a[i]
5887 .iter()
5888 .zip(b[i].iter())
5889 .map(|(&x, &y)| {
5890 let d = f64::from(x) - f64::from(y);
5891 d * d
5892 })
5893 .sum::<f64>()
5894 / a[i].len() as f64;
5895 acc += if mse <= 0.0 {
5896 99.0
5897 } else {
5898 10.0 * (255.0f64 * 255.0 / mse).log10()
5899 };
5900 }
5901 acc / n.max(1) as f64
5902 }
5903
5904 fn kbps(frames: &[Vec<u8>]) -> f64 {
5906 frames.iter().map(|f| f.len()).sum::<usize>() as f64 * 8.0 * 60.0 / FRAMES as f64 / 1000.0
5907 }
5908
5909 #[cfg(feature = "gpl")]
5919 #[test]
5920 fn cbr_qp_bound_sweep_x264() {
5921 let reference = decode_luma(&encode_x264(false, 0, 12, 0, 0));
5922
5923 println!("scrolling-text 720p60 @ 2 Mbps CBR, x264 (PSNR vs own CRF-12 decode):");
5924 let mut rows = Vec::new();
5925 for &max_qp in &[0i32, 45, 40, 35, 30] {
5926 let x = encode_x264(true, 2000, 25, 0, max_qp);
5927 let psnr = mean_psnr(&decode_luma(&x), &reference);
5928 println!(
5929 " max_qp {:>2}: {:>8.1} kbps / {:>5.2} dB",
5930 max_qp,
5931 kbps(&x),
5932 psnr
5933 );
5934 rows.push((max_qp, psnr));
5935 }
5936 println!("scrolling-text 720p60 @ 12 Mbps CBR, x264 min-QP sweep:");
5937 for &min_qp in &[0i32, 10, 15] {
5938 let x = encode_x264(true, 12000, 25, min_qp, 0);
5939 let psnr = mean_psnr(&decode_luma(&x), &reference);
5940 println!(
5941 " min_qp {:>2}: {:>8.1} kbps / {:>5.2} dB",
5942 min_qp,
5943 kbps(&x),
5944 psnr
5945 );
5946 }
5947
5948 let base = rows[0].1;
5949 let capped = rows.last().unwrap().1;
5950 assert!(
5951 capped > base + 0.5,
5952 "x264 max-QP clamp had no effect: {capped:.2} vs {base:.2} dB"
5953 );
5954 }
5955
5956 #[test]
5963 fn cbr_qp_bound_sweep_openh264() {
5964 let reference = decode_luma(&encode_oh264(false, 0, 12, 0, 0));
5965
5966 println!("scrolling-text 720p60 @ 2 Mbps CBR, oh264 (PSNR vs own QP-12 decode):");
5967 let mut rows = Vec::new();
5968 for &max_qp in &[0i32, 45, 40, 35, 30] {
5969 let o = encode_oh264(true, 2000, 25, 0, max_qp);
5970 let psnr = mean_psnr(&decode_luma(&o), &reference);
5971 println!(
5972 " max_qp {:>2}: {:>8.1} kbps / {:>5.2} dB",
5973 max_qp,
5974 kbps(&o),
5975 psnr
5976 );
5977 rows.push((max_qp, psnr));
5978 }
5979
5980 let base = rows[0].1;
5981 let capped = rows.last().unwrap().1;
5982 assert!(
5983 capped > base + 0.5,
5984 "oh264 max-QP clamp had no effect: {capped:.2} vs {base:.2} dB"
5985 );
5986 }
5987
5988 #[cfg(feature = "gpl")]
6001 #[test]
6002 fn cbr_bitrate_tracks_configured_rate_after_fps_change() {
6003 const TARGET_KBPS: i32 = 20000;
6004 const WARMUP: usize = 24;
6005 const MEASURED: usize = 96;
6006 let u = vec![128u8; (W / 2) * (H / 2)];
6007 let v = vec![128u8; (W / 2) * (H / 2)];
6008 let vbv = |fps: f64| {
6009 (crate::encoders::vbv_bits((TARGET_KBPS as u32) * 1000, fps, 0.0, 0.0) / 1000).max(1)
6010 as i32
6011 };
6012 let noise_luma = |frame: usize| -> Vec<u8> {
6016 let mut y = vec![0u8; W * H];
6017 let mut s = (frame as u32).wrapping_mul(2654435761).wrapping_add(1);
6018 for p in y.iter_mut() {
6019 s ^= s << 13;
6020 s ^= s >> 17;
6021 s ^= s << 5;
6022 *p = (s >> 24) as u8;
6023 }
6024 y
6025 };
6026
6027 let mut enc = H264EncoderWrapper::new(
6028 W as i32,
6029 H as i32,
6030 25,
6031 false,
6032 60.0,
6033 1,
6034 true,
6035 TARGET_KBPS,
6036 vbv(60.0),
6037 0,
6038 0,
6039 )
6040 .expect("x264 init");
6041
6042 let measure = |enc: &mut H264EncoderWrapper, start: usize| -> f64 {
6043 let mut bytes = 0usize;
6044 let mut counted = 0usize;
6045 for i in start..start + WARMUP + MEASURED {
6046 let y = noise_luma(i);
6047 let mut out = Vec::new();
6048 enc.encode_with_headers(
6049 &y,
6050 &u,
6051 &v,
6052 W as i32,
6053 (W / 2) as i32,
6054 (W / 2) as i32,
6055 i as u16,
6056 i == start,
6057 true,
6058 &mut out,
6059 );
6060 if i >= start + WARMUP && !out.is_empty() {
6061 bytes += out.len();
6062 counted += 1;
6063 }
6064 }
6065 bytes as f64 / counted.max(1) as f64
6066 };
6067
6068 let per_frame_60 = measure(&mut enc, 0);
6069 enc.reconfigure_rate(TARGET_KBPS, vbv(30.0), 30.0);
6070 let per_frame_30 = measure(&mut enc, 1000);
6071
6072 let eff_60 = per_frame_60 * 8.0 * 60.0 / 1000.0;
6073 let eff_30 = per_frame_30 * 8.0 * 30.0 / 1000.0;
6074 println!(
6075 "x264 CBR {TARGET_KBPS} kbps: 60fps {eff_60:.0} kbps ({per_frame_60:.0} B/frame), 30fps {eff_30:.0} kbps ({per_frame_30:.0} B/frame)"
6076 );
6077
6078 let ratio = per_frame_30 / per_frame_60.max(1.0);
6079 assert!(
6080 (1.5..2.6).contains(&ratio),
6081 "30fps per-frame size {per_frame_30:.0} B vs 60fps {per_frame_60:.0} B (ratio {ratio:.2}); halving fps should roughly double each frame"
6082 );
6083 assert!(
6084 eff_30 > eff_60 * 0.75,
6085 "30fps effective bitrate {eff_30:.0} kbps collapsed from the 60fps {eff_60:.0} kbps; fps change did not re-budget the bitrate"
6086 );
6087 }
6088}