1use std::ffi::CString;
35
36use super::codec::{Codec, VIDEO_HEADER_LEN, h265_frame_type, push_video_header};
37use super::reference::Reference;
38use super::session::{Pending, Planes, Quality, RateSettings, encode_threads};
39use crate::CaptureSettings;
40
41pub struct HevcEncoder {
43 backend: Backend,
44 planes: Planes,
45 threads: i32,
46 quality: Quality,
47 rate: RateSettings,
48 omit_headers: bool,
49 fresh: bool,
52 next_pts: u64,
53 pending: Pending,
54 held: Option<u32>,
56 refinement: Option<crate::ledger::Refinement>,
59 offsets: Vec<f32>,
60 constant: Vec<u8>,
63 last_quality: Option<u32>,
65 last_bytes: Option<usize>,
67}
68
69unsafe impl Send for HevcEncoder {}
70
71const HEVC_REACH_FINER: u32 = 6;
79
80pub fn ten_bit() -> bool {
83 Backend::ten_bit()
84}
85
86struct Coded {
88 bytes: Vec<u8>,
89 pts: u64,
90 qp: u32,
91 spent: usize,
94}
95
96impl HevcEncoder {
97 pub fn new(settings: &CaptureSettings, rgba: bool) -> Result<Self, String> {
99 let _ = rgba;
100 let fullcolor = settings.video_fullcolor && Backend::FULLCOLOR;
101 let bit_depth = if settings.video_bit_depth >= 10 && Backend::ten_bit() {
102 10
103 } else {
104 8
105 };
106 let threads = encode_threads(settings);
107 let rate = RateSettings::new(settings).uncapped();
108 let quality = Quality::new(Codec::H265.quantizer(settings.video_crf));
109 let width = settings.width.max(1) as usize;
110 let height = settings.height.max(1) as usize;
111 let backend = Backend::open(
112 width,
113 height,
114 threads,
115 fullcolor,
116 bit_depth,
117 rate,
118 quality.current,
119 )?;
120 Ok(Self {
121 backend,
122 planes: Planes::new(width, height, fullcolor, bit_depth),
123 threads,
124 quality,
125 rate,
126 omit_headers: settings.omit_stripe_headers,
127 fresh: true,
128 next_pts: 0,
129 pending: Pending::default(),
130 held: None,
131 refinement: None,
132 offsets: Vec::new(),
133 constant: Vec::new(),
134 last_quality: None,
135 last_bytes: None,
136 })
137 }
138
139 pub fn codec(&self) -> Codec {
140 Codec::H265
141 }
142
143 pub fn library(&self) -> &'static str {
145 Backend::LIBRARY
146 }
147
148 pub fn is_fullcolor(&self) -> bool {
149 self.planes.i444
150 }
151
152 pub fn bit_depth(&self) -> u32 {
154 self.planes.bit_depth
155 }
156
157 pub fn is_full_range(&self) -> bool {
159 self.planes.i444
160 }
161
162 pub fn last_reference(&self) -> Reference {
163 Reference::Untracked
164 }
165
166 pub fn holds_quantizer(&self) -> bool {
173 Backend::HOLDS_QUANTIZER && !self.rate.cbr
174 }
175
176 pub fn reach_finer(&self) -> u32 {
179 if self.rate.cbr { HEVC_REACH_FINER } else { 0 }
180 }
181
182 pub fn refines(&self) -> bool {
186 Backend::REFINES
187 }
188
189 pub fn refine_finer(&self) -> u32 {
192 Backend::REFINE_FINER
193 }
194
195 pub fn refine_blocks(&mut self, refinement: &crate::ledger::Refinement) {
199 self.refinement = self.refines().then(|| refinement.clone());
200 }
201
202 pub fn constant_blocks(&mut self, constant: &[bool]) {
207 let blocks = self.planes.width.div_ceil(16) * self.planes.height.div_ceil(16);
208 self.constant.clear();
209 if Backend::CONSTANT_BLOCKS && constant.len() == blocks && constant.contains(&true) {
210 self.constant.extend(constant.iter().map(|&c| u8::from(c)));
211 }
212 }
213
214 pub fn last_quality(&self) -> Option<u32> {
217 self.last_quality
218 }
219
220 pub fn last_size(&self) -> Option<usize> {
223 self.last_bytes
224 }
225
226 pub fn invalidate_reference(&mut self, _frame_id: u16) -> bool {
228 false
229 }
230
231 fn reopen(&mut self) -> Result<(), String> {
233 self.backend = Backend::open(
234 self.planes.width,
235 self.planes.height,
236 self.threads,
237 self.planes.i444,
238 self.planes.bit_depth,
239 self.rate,
240 self.quality.current,
241 )?;
242 self.fresh = true;
243 Ok(())
244 }
245
246 pub fn hold_quantizer(&mut self, crf: u32) {
253 if !self.rate.cbr {
254 self.held = Some(Codec::H265.quantizer(crf as i32));
255 }
256 }
257
258 pub fn reconfigure_rate(&mut self, settings: &CaptureSettings) -> Result<(), String> {
261 let Some(rate) = self.rate.changed(settings) else {
262 return Ok(());
263 };
264 self.rate = rate;
265 self.reopen()
266 }
267
268 pub fn encode_host(
270 &mut self,
271 pixels: &[u8],
272 stride: usize,
273 rgba: bool,
274 frame_number: u64,
275 crf: u32,
276 force_idr: bool,
277 ) -> Result<Vec<u8>, String> {
278 if !self.rate.cbr
279 && self
280 .quality
281 .update(Codec::H265.quantizer(crf as i32))
282 .is_some()
283 {
284 self.reopen()?;
285 }
286 let held = self.held.take();
287 if force_idr && !self.fresh && !Backend::KEY_ON_REQUEST {
288 self.reopen()?;
289 }
290 let key = force_idr || self.fresh;
291 let full_range = self.is_full_range();
292 let pts = self.next_pts;
293 let refinement = self.refinement.take().filter(|_| held.is_none() && !key);
294 let offset = refinement.as_ref().map_or(0.0, |r| {
295 let q = |index: i32| Codec::H265.quantizer(index) as f32;
296 (q(r.target) - q(r.from)).min(0.0)
297 });
298 let cols = self.planes.width.div_ceil(16);
299 let rows = self.planes.height.div_ceil(16);
300 self.offsets.clear();
301 self.offsets.extend((0..rows).flat_map(|row| {
302 let refinement = refinement.as_ref();
303 (0..cols).map(move |col| {
304 if refinement.is_some_and(|r| r.plan.refines(col, row)) {
305 offset
306 } else {
307 0.0
308 }
309 })
310 }));
311 let constant = (!key && held.is_none() && !self.constant.is_empty())
312 .then_some(self.constant.as_mut_slice());
313 let coded = self.backend.encode(
314 &mut self.planes,
315 pixels,
316 stride,
317 rgba,
318 full_range,
319 self.threads as usize,
320 pts,
321 key,
322 held,
323 Some(self.offsets.as_mut_slice()),
324 constant,
325 );
326 self.constant.clear();
327 let coded = coded?;
328 self.next_pts += 1;
329 self.fresh = false;
330 self.pending.push(pts, frame_number as u16);
331 let Some(coded) = coded else {
332 return Ok(Vec::new());
333 };
334 if held.is_none() {
335 self.last_quality = Some(coded.qp);
336 self.last_bytes = Some(coded.spent);
337 }
338 let id = self.pending.take(coded.pts).unwrap_or(frame_number as u16);
339 let mut output = Vec::with_capacity(VIDEO_HEADER_LEN + coded.bytes.len());
340 if !self.omit_headers {
341 push_video_header(
342 &mut output,
343 Codec::H265,
344 h265_frame_type(&coded.bytes),
345 id,
346 0,
347 self.planes.width as u16,
348 self.planes.height as u16,
349 Reference::Untracked,
350 );
351 }
352 output.extend_from_slice(&coded.bytes);
353 Ok(output)
354 }
355}
356
357fn qp_bounds(rate: RateSettings) -> (u32, u32) {
359 (
360 Codec::H265.quantizer_bound(rate.min_qp),
361 Codec::H265.quantizer_bound(rate.max_qp),
362 )
363}
364
365#[cfg(not(feature = "gpl"))]
366use kvazaar::Backend;
367#[cfg(feature = "gpl")]
368use x265::Backend;
369
370#[cfg(feature = "gpl")]
371mod x265 {
372 use std::ffi::c_int;
375 use std::ptr;
376
377 use codec_sys::x265::*;
378
379 use super::*;
380
381 const FILLER_NAL: u32 = 38;
383
384 #[derive(Clone, Copy, Default)]
389 struct InputLayout {
390 stride: [c_int; 3],
391 offset: [c_int; 3],
392 size: [c_int; 3],
393 align: c_int,
394 }
395
396 impl InputLayout {
397 #[cfg(not(x265_input_layout))]
399 fn plain(width: usize, height: usize, i444: bool, bit_depth: u32) -> Self {
400 let bytes = if bit_depth > 8 { 2 } else { 1 };
401 let (cw, ch) = if i444 {
402 (width, height)
403 } else {
404 (width.div_ceil(2), height.div_ceil(2))
405 };
406 let (luma, chroma) = (
407 (width * bytes).next_multiple_of(64),
408 (cw * bytes).next_multiple_of(64),
409 );
410 InputLayout {
411 stride: [luma as c_int, chroma as c_int, chroma as c_int],
412 offset: [0; 3],
413 size: [
414 (luma * height) as c_int,
415 (chroma * ch) as c_int,
416 (chroma * ch) as c_int,
417 ],
418 align: 64,
419 }
420 }
421 }
422
423 pub struct Backend {
424 api: *const x265_api,
425 params: *mut x265_param,
426 encoder: *mut x265_encoder,
427 layout: InputLayout,
429 pictures: [[Vec<u64>; 3]; 2],
433 aligned: [[usize; 3]; 2],
434 current: usize,
435 }
436
437 impl Drop for Backend {
438 fn drop(&mut self) {
439 unsafe {
440 let api = &*self.api;
441 if !self.encoder.is_null() {
442 (api.encoder_close.unwrap())(self.encoder);
443 }
444 if !self.params.is_null() {
445 (api.param_free.unwrap())(self.params);
446 }
447 }
448 }
449 }
450
451 impl Backend {
452 pub const LIBRARY: &'static str = "x265";
453 pub const FULLCOLOR: bool = true;
454
455 pub fn ten_bit() -> bool {
458 !unsafe { x265_api_get(10) }.is_null()
459 }
460
461 pub const KEY_ON_REQUEST: bool = true;
462 pub const HOLDS_QUANTIZER: bool = true;
463 pub const REFINES: bool = true;
464 pub const CONSTANT_BLOCKS: bool = true;
465 pub const REFINE_FINER: u32 = 6;
472
473 #[cfg(test)]
475 pub fn frame_rate(&self) -> (u32, u32) {
476 unsafe { ((*self.params).fpsNum, (*self.params).fpsDenom) }
477 }
478
479 #[cfg(test)]
481 pub fn min_cu_size(&self) -> u32 {
482 unsafe { (*self.params).minCUSize }
483 }
484
485 fn set(&self, name: &str, value: &str) -> Result<(), String> {
486 let (n, v) = (CString::new(name).unwrap(), CString::new(value).unwrap());
487 match unsafe { ((*self.api).param_parse.unwrap())(self.params, n.as_ptr(), v.as_ptr()) }
488 {
489 0 => Ok(()),
490 X265_PARAM_BAD_NAME => Err(format!("x265 knows no option {name}")),
491 _ => Err(format!("x265 refused {name}={value}")),
492 }
493 }
494
495 fn rate_options(rate: RateSettings, q: u32) -> Vec<(&'static str, String)> {
500 let mut options = Vec::new();
501 if rate.cbr {
502 let kbps = (rate.bps() / 1000).max(1);
503 let vbv_kbit = (rate.vbv() / 1000).max(1);
504 options.push(("bitrate", kbps.to_string()));
505 options.push(("vbv-maxrate", kbps.to_string()));
506 options.push(("vbv-bufsize", vbv_kbit.to_string()));
507 if rate.vbv() > 1000 {
508 options.push(("vbv-init", vbv_kbit.to_string()));
509 }
510 options.push(("strict-cbr", "1".into()));
511 let (lo, hi) = qp_bounds(rate);
512 if lo > 0 {
513 options.push(("qpmin", lo.to_string()));
514 }
515 options.push(("qpmax", if hi > 0 { hi } else { 51 }.to_string()));
516 } else {
517 options.push(("crf", q.to_string()));
518 }
519 options
520 }
521
522 pub fn open(
523 width: usize,
524 height: usize,
525 threads: i32,
526 i444: bool,
527 bit_depth: u32,
528 rate: RateSettings,
529 q: u32,
530 ) -> Result<Self, String> {
531 let api = unsafe { x265_api_get(bit_depth as c_int) };
532 if api.is_null() {
533 return Err(format!("x265 carries no {bit_depth}-bit encoder"));
534 }
535 let params = unsafe { ((*api).param_alloc.unwrap())() };
536 if params.is_null() {
537 return Err("x265 allocated no parameters".into());
538 }
539 let mut me = Self {
540 api,
541 params,
542 encoder: ptr::null_mut(),
543 layout: InputLayout::default(),
544 pictures: Default::default(),
545 aligned: [[0; 3]; 2],
546 current: 0,
547 };
548 let (preset, tune) = (c"ultrafast", c"zerolatency");
549 if unsafe {
550 ((*api).param_default_preset.unwrap())(params, preset.as_ptr(), tune.as_ptr())
551 } < 0
552 {
553 return Err("x265 refused the ultrafast zerolatency preset".into());
554 }
555 if width < 16 || height < 16 {
556 return Err(format!(
557 "x265 encodes no picture as small as {width}x{height}"
558 ));
559 }
560 let mut options: Vec<(&str, String)> = vec![
561 ("log-level", "none".into()),
562 ("input-res", format!("{width}x{height}")),
563 ("input-csp", if i444 { "i444" } else { "i420" }.into()),
564 ("fps", format!("{}/{}", rate.fps.num, rate.fps.den)),
565 ("range", if i444 { "full" } else { "limited" }.into()),
566 ("colorprim", "bt709".into()),
567 ("transfer", "bt709".into()),
568 ("colormatrix", "bt709".into()),
569 ("keyint", "-1".into()),
570 ("scenecut", "0".into()),
571 ("repeat-headers", "1".into()),
572 ("annexb", "1".into()),
573 ("aud", "0".into()),
574 ("rc-lookahead", "0".into()),
575 ("bframes", "0".into()),
576 ("frame-threads", "1".into()),
577 ("pools", threads.to_string()),
578 ("wpp", "1".into()),
579 ];
580 if width < 32 || height < 32 {
581 options.push(("ctu", "16".into()));
582 } else if width < 64 || height < 64 {
583 options.push(("ctu", "32".into()));
584 }
585 options.push(("min-cu-size", "8".into()));
590 options.push(("aq-mode", "1".into()));
594 options.push(("aq-strength", "0".into()));
595 #[cfg(x265_qp_offsets)]
596 options.push(("qp-offsets", "1".into()));
597 #[cfg(x265_input_layout)]
598 options.push(("copy-pic", "0".into()));
599 options.extend(Self::rate_options(rate, q));
600 for (name, value) in &options {
601 me.set(name, value)?;
602 }
603 let profile = match (i444, bit_depth) {
604 (true, 10) => Some(c"main444-10"),
605 (true, _) => Some(c"main444-8"),
606 (false, 10) => Some(c"main10"),
607 (false, _) => None,
608 };
609 if let Some(profile) = profile
610 && unsafe { ((*api).param_apply_profile.unwrap())(params, profile.as_ptr()) } < 0
611 {
612 return Err(format!(
613 "x265 refused the {} profile",
614 profile.to_string_lossy()
615 ));
616 }
617 me.encoder = unsafe { ((*api).encoder_open.unwrap())(params) };
618 if me.encoder.is_null() {
619 return Err("x265 refused the session".into());
620 }
621 #[cfg(x265_input_layout)]
623 {
624 let mut opened: x265_param = unsafe { std::mem::zeroed() };
625 unsafe { ((*api).encoder_parameters.unwrap())(me.encoder, &mut opened) };
626 let mut layout: x265_input_layout = unsafe { std::mem::zeroed() };
627 if unsafe { ((*api).param_input_layout.unwrap())(&opened, &mut layout) } < 0 {
628 return Err("x265 named no input layout".into());
629 }
630 me.layout = InputLayout {
631 stride: layout.stride,
632 offset: layout.offset,
633 size: layout.size,
634 align: layout.align,
635 };
636 }
637 #[cfg(not(x265_input_layout))]
638 {
639 me.layout = InputLayout::plain(width, height, i444, bit_depth);
640 }
641 let align = (me.layout.align.max(1) as usize).max(8);
642 for (picture, aligned) in me.pictures.iter_mut().zip(me.aligned.iter_mut()) {
643 for p in 0..3 {
644 let size = me.layout.size[p].max(0) as usize;
645 picture[p] = vec![0u64; (size + align).div_ceil(8)];
646 let start = picture[p].as_ptr() as usize;
647 aligned[p] = start.next_multiple_of(align) - start;
648 }
649 }
650 Ok(me)
651 }
652
653 fn plane(&mut self, picture: usize, p: usize) -> (*mut u8, usize) {
656 let offset = self.aligned[picture][p] + self.layout.offset[p].max(0) as usize;
657 let buf = &mut self.pictures[picture][p];
658 let len = buf.len() * 8 - offset;
659 (unsafe { buf.as_mut_ptr().cast::<u8>().add(offset) }, len)
660 }
661
662 #[allow(clippy::too_many_arguments)]
663 #[allow(clippy::too_many_arguments)]
666 pub fn encode(
667 &mut self,
668 planes: &mut Planes,
669 pixels: &[u8],
670 stride: usize,
671 rgba: bool,
672 full_range: bool,
673 threads: usize,
674 pts: u64,
675 key: bool,
676 held: Option<u32>,
677 offsets: Option<&mut [f32]>,
678 constant: Option<&mut [u8]>,
679 ) -> Result<Option<Coded>, String> {
680 let picture = self.current;
681 self.current ^= 1;
682 let (w, h) = (planes.width, planes.height);
683 let ch = if planes.i444 { h } else { h.div_ceil(2) };
684 let strides = (
685 self.layout.stride[0] as usize,
686 self.layout.stride[1] as usize,
687 );
688 let [(y, ylen), (u, ulen), (v, vlen)] = [
689 self.plane(picture, 0),
690 self.plane(picture, 1),
691 self.plane(picture, 2),
692 ];
693 super::super::session::check_host_frame(pixels, stride, w, h)?;
694 if planes.bit_depth > 8 {
695 let (ys, cs) = (strides.0 / 2, strides.1 / 2);
696 let (y16, u16, v16) = unsafe {
699 (
700 std::slice::from_raw_parts_mut(y.cast::<u16>(), (ys * h).min(ylen / 2)),
701 std::slice::from_raw_parts_mut(u.cast::<u16>(), (cs * ch).min(ulen / 2)),
702 std::slice::from_raw_parts_mut(v.cast::<u16>(), (cs * ch).min(vlen / 2)),
703 )
704 };
705 crate::encoders::software::convert_to_yuv10_mt(
706 pixels,
707 stride,
708 w,
709 h,
710 rgba,
711 planes.i444,
712 full_range,
713 y16,
714 u16,
715 v16,
716 (ys, cs),
717 threads,
718 );
719 } else {
720 let (y8, u8s, v8) = unsafe {
722 (
723 std::slice::from_raw_parts_mut(y, (strides.0 * h).min(ylen)),
724 std::slice::from_raw_parts_mut(u, (strides.1 * ch).min(ulen)),
725 std::slice::from_raw_parts_mut(v, (strides.1 * ch).min(vlen)),
726 )
727 };
728 super::super::session::convert_into(
729 pixels,
730 stride,
731 w,
732 h,
733 rgba,
734 planes.i444,
735 full_range,
736 false,
737 threads,
738 y8,
739 u8s,
740 v8,
741 strides,
742 )?;
743 }
744 let api = unsafe { &*self.api };
745 let mut pic: x265_picture = unsafe { std::mem::zeroed() };
746 let mut out: [x265_picture; 2] = unsafe { std::mem::zeroed() };
747 unsafe {
748 (api.picture_init.unwrap())(self.params, &mut pic);
749 for o in &mut out {
750 (api.picture_init.unwrap())(self.params, o);
751 }
752 }
753 pic.planes[0] = y.cast();
755 pic.planes[1] = u.cast();
756 pic.planes[2] = v.cast();
757 pic.stride[0] = self.layout.stride[0];
758 pic.stride[1] = self.layout.stride[1];
759 pic.stride[2] = self.layout.stride[2];
760 #[cfg(x265_constant_blocks)]
762 if let Some(constant) = constant {
763 pic.constantBlocks = constant.as_mut_ptr();
764 }
765 #[cfg(not(x265_constant_blocks))]
766 let _ = constant;
767 pic.pts = pts as i64;
768 pic.bitDepth = planes.bit_depth as c_int;
769 pic.sliceType = if key {
770 X265_TYPE_IDR as c_int
771 } else {
772 X265_TYPE_AUTO as c_int
773 };
774 pic.forceqp = held.map_or(0, |q| q as c_int + 1);
775 if let Some(offsets) = offsets {
780 pic.quantOffsets = offsets.as_mut_ptr();
781 }
782 let mut nals: *mut x265_nal = ptr::null_mut();
783 let mut count: u32 = 0;
784 let ret = unsafe {
785 encoder_encode(
786 api,
787 self.encoder,
788 &mut nals,
789 &mut count,
790 &mut pic,
791 out.as_mut_ptr(),
792 )
793 };
794 if ret < 0 {
795 return Err("x265 refused the frame".into());
796 }
797 if count == 0 {
798 return Ok(None);
799 }
800 let mut bytes = Vec::new();
804 let mut spent = 0;
805 for nal in unsafe { std::slice::from_raw_parts(nals, count as usize) } {
806 spent += nal.sizeBytes as usize;
807 if nal.type_ != FILLER_NAL {
808 bytes.extend_from_slice(unsafe {
809 std::slice::from_raw_parts(nal.payload, nal.sizeBytes as usize)
810 });
811 }
812 }
813 Ok(Some(Coded {
814 bytes,
815 pts: out[0].pts.max(0) as u64,
816 qp: out[0].frameData.qp.round().clamp(0.0, 51.0) as u32,
817 spent,
818 }))
819 }
820 }
821}
822
823#[cfg(not(feature = "gpl"))]
824mod kvazaar {
825 use std::ptr;
828
829 use codec_sys::kvazaar::*;
830
831 use super::*;
832
833 pub struct Backend {
834 api: *const kvz_api,
835 config: *mut kvz_config,
836 encoder: *mut kvz_encoder,
837 }
838
839 impl Drop for Backend {
840 fn drop(&mut self) {
841 unsafe {
842 let api = &*self.api;
843 if !self.encoder.is_null() {
844 (api.encoder_close.unwrap())(self.encoder);
845 }
846 if !self.config.is_null() {
847 (api.config_destroy.unwrap())(self.config);
848 }
849 }
850 }
851 }
852
853 impl Backend {
854 pub const LIBRARY: &'static str = "kvazaar";
855 pub const FULLCOLOR: bool = false;
856
857 pub fn ten_bit() -> bool {
859 false
860 }
861 pub const KEY_ON_REQUEST: bool = false;
862 pub const HOLDS_QUANTIZER: bool = false;
863 pub const REFINES: bool = true;
864 pub const CONSTANT_BLOCKS: bool = false;
865 pub const REFINE_FINER: u32 = 0;
872
873 #[cfg(test)]
875 pub fn frame_rate(&self) -> (u32, u32) {
876 unsafe {
877 (
878 (*self.config).framerate_num as u32,
879 (*self.config).framerate_denom as u32,
880 )
881 }
882 }
883
884 fn set(&self, name: &str, value: &str) -> Result<(), String> {
885 let (n, v) = (CString::new(name).unwrap(), CString::new(value).unwrap());
886 if unsafe { ((*self.api).config_parse.unwrap())(self.config, n.as_ptr(), v.as_ptr()) }
887 == 0
888 {
889 return Err(format!("kvazaar refused {name}={value}"));
890 }
891 Ok(())
892 }
893
894 pub fn open(
895 width: usize,
896 height: usize,
897 threads: i32,
898 i444: bool,
899 bit_depth: u32,
900 rate: RateSettings,
901 q: u32,
902 ) -> Result<Self, String> {
903 let _ = (i444, bit_depth);
904 if !width.is_multiple_of(8) || !height.is_multiple_of(8) {
905 return Err(format!(
906 "kvazaar takes only pictures a multiple of eight, not {width}x{height}"
907 ));
908 }
909 let api = unsafe { kvz_api_get(8) };
910 if api.is_null() {
911 return Err("kvazaar carries no 8-bit encoder".into());
912 }
913 let config = unsafe { ((*api).config_alloc.unwrap())() };
914 if config.is_null() {
915 return Err("kvazaar allocated no configuration".into());
916 }
917 let me = Self {
918 api,
919 config,
920 encoder: ptr::null_mut(),
921 };
922 unsafe {
923 if ((*api).config_init.unwrap())(config) == 0 {
924 return Err("kvazaar refused to initialize its configuration".into());
925 }
926 let cfg = &mut *config;
927 cfg.width = width as i32;
928 cfg.height = height as i32;
929 cfg.framerate_num = rate.fps.num as i32;
930 cfg.framerate_denom = rate.fps.den as i32;
931 cfg.vui.fullrange = 0;
932 cfg.vui.colorprim = 1;
933 cfg.vui.transfer = 1;
934 cfg.vui.colormatrix = 1;
935 }
936 let mut options: Vec<(&str, String)> = vec![
937 ("preset", "ultrafast".into()),
938 ("gop", "0".into()),
939 ("period", "0".into()),
940 ("vps-period", "0".into()),
941 ("threads", threads.to_string()),
942 ("owf", "0".into()),
943 ("wpp", "1".into()),
944 ];
945 if rate.cbr {
946 options.push(("bitrate", rate.bps().to_string()));
947 options.push(("rc-algorithm", "oba".into()));
948 } else {
949 options.push(("qp", q.to_string()));
950 }
951 for (name, value) in &options {
952 me.set(name, value)?;
953 }
954 let mut me = me;
955 me.encoder = unsafe { ((*api).encoder_open.unwrap())(config) };
956 if me.encoder.is_null() {
957 return Err("kvazaar refused the session".into());
958 }
959 Ok(me)
960 }
961
962 #[allow(clippy::too_many_arguments)]
963 pub fn encode(
964 &mut self,
965 planes: &mut Planes,
966 pixels: &[u8],
967 stride: usize,
968 rgba: bool,
969 full_range: bool,
970 threads: usize,
971 pts: u64,
972 _key: bool,
973 _held: Option<u32>,
974 offsets: Option<&mut [f32]>,
975 _constant: Option<&mut [u8]>,
976 ) -> Result<Option<Coded>, String> {
977 let api = unsafe { &*self.api };
978 let pic =
979 unsafe { (api.picture_alloc.unwrap())(planes.width as i32, planes.height as i32) };
980 if pic.is_null() {
981 return Err("kvazaar allocated no picture".into());
982 }
983 if let Some(offsets) = offsets {
987 let (cols16, cols, rows) = (
988 planes.width.div_ceil(16),
989 planes.width.div_ceil(64),
990 planes.height.div_ceil(64),
991 );
992 let rows16 = planes.height.div_ceil(16);
993 let map = unsafe { libc::malloc(cols * rows) as *mut i8 };
994 if !map.is_null() {
995 for row in 0..rows {
996 for col in 0..cols {
997 let mut dqp = 0f32;
998 for r in row * 4..(row * 4 + 4).min(rows16) {
999 for c in col * 4..(col * 4 + 4).min(cols16) {
1000 dqp = dqp.min(offsets[r * cols16 + c]);
1001 }
1002 }
1003 unsafe { *map.add(row * cols + col) = dqp.round() as i8 };
1004 }
1005 }
1006 unsafe {
1007 (*pic).roi.width = cols as i32;
1008 (*pic).roi.height = rows as i32;
1009 (*pic).roi.roi_array = map;
1010 }
1011 }
1012 }
1013 let result = unsafe {
1014 let p = &mut *pic;
1015 let luma_stride = p.stride as usize;
1016 let chroma_stride = luma_stride / 2;
1017 let (w, h) = (planes.width, planes.height);
1018 let y = std::slice::from_raw_parts_mut(p.y, luma_stride * h);
1019 let u = std::slice::from_raw_parts_mut(p.u, chroma_stride * h.div_ceil(2));
1020 let v = std::slice::from_raw_parts_mut(p.v, chroma_stride * h.div_ceil(2));
1021 p.pts = pts as i64;
1022 super::super::session::convert_into(
1023 pixels,
1024 stride,
1025 w,
1026 h,
1027 rgba,
1028 false,
1029 full_range,
1030 false,
1031 threads,
1032 y,
1033 u,
1034 v,
1035 (luma_stride, chroma_stride),
1036 )
1037 };
1038 if let Err(e) = result {
1039 unsafe { (api.picture_free.unwrap())(pic) };
1040 return Err(e);
1041 }
1042 let mut data: *mut kvz_data_chunk = ptr::null_mut();
1043 let mut len: u32 = 0;
1044 let mut recon: *mut kvz_picture = ptr::null_mut();
1045 let mut info: kvz_frame_info = unsafe { std::mem::zeroed() };
1046 let ok = unsafe {
1047 (api.encoder_encode.unwrap())(
1048 self.encoder,
1049 pic,
1050 &mut data,
1051 &mut len,
1052 &mut recon,
1053 ptr::null_mut(),
1054 &mut info,
1055 )
1056 };
1057 let mut coded = None;
1058 if ok != 0 && !data.is_null() {
1059 let mut bytes = Vec::with_capacity(len as usize);
1060 let mut chunk = data;
1061 while !chunk.is_null() {
1062 let c = unsafe { &*chunk };
1063 bytes.extend_from_slice(&c.data[..c.len as usize]);
1064 chunk = c.next;
1065 }
1066 let out_pts = if recon.is_null() {
1067 pts
1068 } else {
1069 unsafe { (*recon).pts.max(0) as u64 }
1070 };
1071 coded = Some(Coded {
1072 spent: bytes.len(),
1073 bytes,
1074 pts: out_pts,
1075 qp: info.qp.clamp(0, 51) as u32,
1076 });
1077 }
1078 unsafe {
1079 (api.picture_free.unwrap())(pic);
1080 (api.picture_free.unwrap())(recon);
1081 (api.chunk_free.unwrap())(data);
1082 }
1083 if ok == 0 {
1084 return Err("kvazaar refused the frame".into());
1085 }
1086 Ok(coded)
1087 }
1088 }
1089}
1090
1091#[cfg(test)]
1092mod tests {
1093 use super::*;
1094
1095 #[test]
1098 fn key_frames_come_on_request_and_deltas_name_no_reference() {
1099 let settings = CaptureSettings {
1100 width: 64,
1101 height: 48,
1102 target_fps: 30.0,
1103 codec: Codec::H265,
1104 video_crf: 25,
1105 ..Default::default()
1106 };
1107 let mut enc = HevcEncoder::new(&settings, false).expect("session");
1108 assert_eq!(enc.library(), Backend::LIBRARY);
1109 let bgra = vec![128u8; 64 * 48 * 4];
1110 let mut kinds = Vec::new();
1111 for t in 0..4u64 {
1112 let out = enc
1113 .encode_host(&bgra, 64 * 4, false, t, 25, t == 2)
1114 .expect("encode");
1115 kinds.push(super::super::codec::parse_video_type(out[1]).map(|(_, k)| k));
1116 assert_eq!(enc.last_reference(), Reference::Untracked);
1117 }
1118 assert_eq!(
1119 kinds,
1120 [
1121 Some(super::super::codec::FRAME_KEY),
1122 Some(super::super::codec::FRAME_DELTA),
1123 Some(super::super::codec::FRAME_KEY),
1124 Some(super::super::codec::FRAME_DELTA)
1125 ]
1126 );
1127 assert!(!enc.invalidate_reference(1));
1128 }
1129
1130 #[cfg(all(feature = "gpl", x265_constant_blocks))]
1134 #[test]
1135 fn constant_blocks_code_a_still_frame_as_skips() {
1136 for depth in [8, 10] {
1137 if depth == 10 && !ten_bit() {
1138 continue;
1139 }
1140 constant_blocks_skip_at(depth);
1141 }
1142 }
1143
1144 #[cfg(all(feature = "gpl", x265_constant_blocks))]
1145 fn constant_blocks_skip_at(depth: u32) {
1146 let (w, h) = (320usize, 240usize);
1147 let settings = CaptureSettings {
1148 width: w as i32,
1149 height: h as i32,
1150 target_fps: 30.0,
1151 codec: Codec::H265,
1152 video_crf: 25,
1153 video_bit_depth: depth as i32,
1154 ..Default::default()
1155 };
1156 let mut bgra = vec![0u8; w * h * 4];
1157 for (i, px) in bgra.as_chunks_mut::<4>().0.iter_mut().enumerate() {
1158 let (x, y) = (i % w, i / w);
1159 let text = (x / 7 + y / 11) % 3 == 0 && x % 7 < 5 && y % 11 < 8;
1160 px.copy_from_slice(if text {
1161 &[30, 30, 30, 255]
1162 } else {
1163 &[235, 235, 235, 255]
1164 });
1165 }
1166 let mut plain = HevcEncoder::new(&settings, false).expect("session");
1167 let mut mapped = HevcEncoder::new(&settings, false).expect("session");
1168 let blocks = w.div_ceil(16) * h.div_ceil(16);
1169 let (mut plain_bytes, mut mapped_bytes) = (0, 0);
1170 for n in 0..6u64 {
1171 plain_bytes = plain
1172 .encode_host(&bgra, w * 4, false, n, 25, n == 0)
1173 .expect("encode")
1174 .len();
1175 mapped.constant_blocks(&vec![true; blocks]);
1176 mapped_bytes = mapped
1177 .encode_host(&bgra, w * 4, false, n, 25, n == 0)
1178 .expect("encode")
1179 .len();
1180 }
1181 assert_eq!(mapped.bit_depth(), depth);
1182 assert!(
1183 mapped_bytes < plain_bytes,
1184 "{depth} bits: {mapped_bytes} bytes with the map, {plain_bytes} without"
1185 );
1186 }
1187
1188 #[cfg(all(feature = "gpl", x265_constant_blocks))]
1193 #[test]
1194 fn constant_blocks_leave_a_change_beside_them_coded() {
1195 use super::super::mixed_screen;
1196 let settings = CaptureSettings {
1197 width: mixed_screen::W as i32,
1198 height: mixed_screen::H as i32,
1199 target_fps: 30.0,
1200 codec: Codec::H265,
1201 video_crf: 25,
1202 ..Default::default()
1203 };
1204 let mut plain = HevcEncoder::new(&settings, false).expect("session");
1205 let mut mapped = HevcEncoder::new(&settings, false).expect("session");
1206 mixed_screen::assert_decodes_alike(
1207 Codec::H265,
1208 "x265",
1209 |src, n| {
1210 plain
1211 .encode_host(src, mixed_screen::W * 4, false, n, 25, n == 0)
1212 .expect("encode")
1213 },
1214 |src, n, map| {
1215 mapped.constant_blocks(map);
1216 mapped
1217 .encode_host(src, mixed_screen::W * 4, false, n, 25, n == 0)
1218 .expect("encode")
1219 },
1220 );
1221 }
1222
1223 #[test]
1226 fn a_constant_rate_cleanup_reaches_six_steps_finer() {
1227 let mut settings = CaptureSettings {
1228 width: 64,
1229 height: 48,
1230 target_fps: 30.0,
1231 codec: Codec::H265,
1232 video_crf: 25,
1233 ..Default::default()
1234 };
1235 assert_eq!(
1236 HevcEncoder::new(&settings, false)
1237 .expect("session")
1238 .reach_finer(),
1239 0
1240 );
1241 settings.video_cbr_mode = true;
1242 settings.video_bitrate_kbps = 500;
1243 assert_eq!(
1244 HevcEncoder::new(&settings, false)
1245 .expect("session")
1246 .reach_finer(),
1247 6
1248 );
1249 }
1250
1251 #[cfg(feature = "gpl")]
1254 #[test]
1255 fn x265_codes_with_8x8_coding_units() {
1256 for (width, height) in [(1920, 1080), (64, 48), (16, 16)] {
1257 let settings = CaptureSettings {
1258 width,
1259 height,
1260 target_fps: 30.0,
1261 codec: Codec::H265,
1262 video_crf: 25,
1263 ..Default::default()
1264 };
1265 let enc = HevcEncoder::new(&settings, false).expect("session");
1266 assert_eq!(enc.backend.min_cu_size(), 8, "{width}x{height}");
1267 }
1268 }
1269
1270 #[cfg(feature = "gpl")]
1273 #[test]
1274 fn a_constant_rates_filler_stays_off_the_wire() {
1275 let settings = CaptureSettings {
1276 width: 320,
1277 height: 240,
1278 target_fps: 30.0,
1279 codec: Codec::H265,
1280 video_cbr_mode: true,
1281 video_bitrate_kbps: 1000,
1282 ..Default::default()
1283 };
1284 let mut enc = HevcEncoder::new(&settings, false).expect("session");
1285 let bgra: Vec<u8> = (0..320 * 240 * 4).map(|i| ((i * 7) % 251) as u8).collect();
1286 let (mut sent, mut spent) = (0, 0);
1287 for t in 0..60u64 {
1288 let out = enc
1289 .encode_host(&bgra, 320 * 4, false, t, 25, false)
1290 .expect("encode");
1291 if t >= 30 && !out.is_empty() {
1292 let payload = &out[VIDEO_HEADER_LEN..];
1293 let mut at = 0;
1294 while let Some(i) = payload[at..].windows(3).position(|w| w == [0, 0, 1]) {
1295 at += i + 3;
1296 assert_ne!((payload[at] >> 1) & 0x3f, 38, "frame {t} carries filler");
1297 }
1298 sent += payload.len();
1299 spent += enc.last_size().unwrap_or(0);
1300 }
1301 }
1302 assert!(spent > 2 * sent, "{sent} bytes sent of {spent} spent");
1303 }
1304
1305 #[test]
1307 fn the_frame_rate_reaches_the_library_as_its_fraction() {
1308 for (num, den) in [(60000u32, 1001u32), (120000, 1001), (144000, 1001), (60, 1)] {
1309 let settings = CaptureSettings {
1310 width: 64,
1311 height: 48,
1312 target_fps: f64::from(num) / f64::from(den),
1313 codec: Codec::H265,
1314 video_cbr_mode: true,
1315 video_bitrate_kbps: 2000,
1316 ..Default::default()
1317 };
1318 let enc = HevcEncoder::new(&settings, false).expect("session");
1319 assert_eq!(enc.backend.frame_rate(), (num, den), "{}", Backend::LIBRARY);
1320 }
1321 }
1322}