1use std::ffi::CString;
15
16use super::codec::{Codec, VIDEO_HEADER_LEN, h265_frame_type, push_video_header};
17use super::reference::Reference;
18use super::session::{Pending, Planes, Quality, RateSettings, encode_threads};
19use crate::RustCaptureSettings;
20
21pub struct HevcEncoder {
23 backend: Backend,
24 planes: Planes,
25 threads: i32,
26 quality: Quality,
27 rate: RateSettings,
28 omit_headers: bool,
29 fresh: bool,
32 next_pts: u64,
33 pending: Pending,
34 held: Option<u32>,
36 last_quality: Option<u32>,
38 last_bytes: Option<usize>,
40}
41
42unsafe impl Send for HevcEncoder {}
43
44pub fn ten_bit() -> bool {
47 Backend::ten_bit()
48}
49
50struct Coded {
52 bytes: Vec<u8>,
53 pts: u64,
54 qp: u32,
55}
56
57impl HevcEncoder {
58 pub fn new(settings: &RustCaptureSettings, rgba: bool) -> Result<Self, String> {
60 let _ = rgba;
61 let fullcolor = settings.video_fullcolor && Backend::FULLCOLOR;
62 let bit_depth = if settings.video_bit_depth >= 10 && Backend::ten_bit() {
63 10
64 } else {
65 8
66 };
67 let threads = encode_threads();
68 let rate = RateSettings::new(settings);
69 let quality = Quality::new(Codec::H265.quantizer(settings.video_crf));
70 let width = settings.width.max(1) as usize;
71 let height = settings.height.max(1) as usize;
72 let backend = Backend::open(
73 width,
74 height,
75 threads,
76 fullcolor,
77 bit_depth,
78 rate,
79 quality.current,
80 )?;
81 Ok(Self {
82 backend,
83 planes: Planes::new(width, height, fullcolor, bit_depth),
84 threads,
85 quality,
86 rate,
87 omit_headers: settings.omit_stripe_headers,
88 fresh: true,
89 next_pts: 0,
90 pending: Pending::default(),
91 held: None,
92 last_quality: None,
93 last_bytes: None,
94 })
95 }
96
97 pub fn codec(&self) -> Codec {
98 Codec::H265
99 }
100
101 pub fn library(&self) -> &'static str {
103 Backend::LIBRARY
104 }
105
106 pub fn is_fullcolor(&self) -> bool {
107 self.planes.i444
108 }
109
110 pub fn bit_depth(&self) -> u32 {
112 self.planes.bit_depth
113 }
114
115 pub fn is_full_range(&self) -> bool {
117 self.planes.i444
118 }
119
120 pub fn last_reference(&self) -> Reference {
121 Reference::Untracked
122 }
123
124 pub fn holds_quantizer(&self) -> bool {
131 Backend::HOLDS_QUANTIZER && !self.rate.cbr
132 }
133
134 pub fn last_quality(&self) -> Option<u32> {
137 self.last_quality
138 }
139
140 pub fn last_size(&self) -> Option<usize> {
142 self.last_bytes
143 }
144
145 pub fn invalidate_reference(&mut self, _frame_id: u16) -> bool {
147 false
148 }
149
150 fn reopen(&mut self) -> Result<(), String> {
152 self.backend = Backend::open(
153 self.planes.width,
154 self.planes.height,
155 self.threads,
156 self.planes.i444,
157 self.planes.bit_depth,
158 self.rate,
159 self.quality.current,
160 )?;
161 self.fresh = true;
162 Ok(())
163 }
164
165 pub fn hold_quantizer(&mut self, crf: u32) {
172 if !self.rate.cbr {
173 self.held = Some(Codec::H265.quantizer(crf as i32));
174 }
175 }
176
177 pub fn reconfigure_rate(&mut self, settings: &RustCaptureSettings) -> Result<(), String> {
180 let Some(rate) = self.rate.changed(settings) else {
181 return Ok(());
182 };
183 self.rate = rate;
184 self.reopen()
185 }
186
187 pub fn encode_host(
189 &mut self,
190 pixels: &[u8],
191 stride: usize,
192 rgba: bool,
193 frame_number: u64,
194 crf: u32,
195 force_idr: bool,
196 ) -> Result<Vec<u8>, String> {
197 if !self.rate.cbr
198 && self
199 .quality
200 .update(Codec::H265.quantizer(crf as i32))
201 .is_some()
202 {
203 self.reopen()?;
204 }
205 let held = self.held.take();
206 if force_idr && !self.fresh && !Backend::KEY_ON_REQUEST {
207 self.reopen()?;
208 }
209 let key = force_idr || self.fresh;
210 let full_range = self.is_full_range();
211 let pts = self.next_pts;
212 let coded = self.backend.encode(
213 &mut self.planes,
214 pixels,
215 stride,
216 rgba,
217 full_range,
218 self.threads as usize,
219 pts,
220 key,
221 held,
222 )?;
223 self.next_pts += 1;
224 self.fresh = false;
225 self.pending.push(pts, frame_number as u16);
226 let Some(coded) = coded else {
227 return Ok(Vec::new());
228 };
229 if held.is_none() {
230 self.last_quality = Some(coded.qp);
231 self.last_bytes = Some(coded.bytes.len());
232 }
233 let id = self.pending.take(coded.pts).unwrap_or(frame_number as u16);
234 let mut output = Vec::with_capacity(VIDEO_HEADER_LEN + coded.bytes.len());
235 if !self.omit_headers {
236 push_video_header(
237 &mut output,
238 Codec::H265,
239 h265_frame_type(&coded.bytes),
240 id,
241 0,
242 self.planes.width as u16,
243 self.planes.height as u16,
244 Reference::Untracked,
245 );
246 }
247 output.extend_from_slice(&coded.bytes);
248 Ok(output)
249 }
250}
251
252fn qp_bounds(rate: RateSettings) -> (u32, u32) {
254 (
255 Codec::H265.quantizer_bound(rate.min_qp),
256 Codec::H265.quantizer_bound(rate.max_qp),
257 )
258}
259
260#[cfg(not(feature = "gpl"))]
261use kvazaar::Backend;
262#[cfg(feature = "gpl")]
263use x265::Backend;
264
265#[cfg(feature = "gpl")]
266mod x265 {
267 use std::ffi::c_int;
270 use std::ptr;
271
272 use codec_sys::x265::*;
273
274 use super::*;
275
276 pub struct Backend {
277 api: *const x265_api,
278 params: *mut x265_param,
279 encoder: *mut x265_encoder,
280 }
281
282 impl Drop for Backend {
283 fn drop(&mut self) {
284 unsafe {
285 let api = &*self.api;
286 if !self.encoder.is_null() {
287 (api.encoder_close.unwrap())(self.encoder);
288 }
289 if !self.params.is_null() {
290 (api.param_free.unwrap())(self.params);
291 }
292 }
293 }
294 }
295
296 impl Backend {
297 pub const LIBRARY: &'static str = "x265";
298 pub const FULLCOLOR: bool = true;
299
300 pub fn ten_bit() -> bool {
303 !unsafe { x265_api_get(10) }.is_null()
304 }
305
306 pub const KEY_ON_REQUEST: bool = true;
307 pub const HOLDS_QUANTIZER: bool = true;
308
309 #[cfg(test)]
311 pub fn frame_rate(&self) -> (u32, u32) {
312 unsafe { ((*self.params).fpsNum, (*self.params).fpsDenom) }
313 }
314
315 fn set(&self, name: &str, value: &str) -> Result<(), String> {
316 let (n, v) = (CString::new(name).unwrap(), CString::new(value).unwrap());
317 match unsafe { ((*self.api).param_parse.unwrap())(self.params, n.as_ptr(), v.as_ptr()) }
318 {
319 0 => Ok(()),
320 X265_PARAM_BAD_NAME => Err(format!("x265 knows no option {name}")),
321 _ => Err(format!("x265 refused {name}={value}")),
322 }
323 }
324
325 fn rate_options(rate: RateSettings, q: u32) -> Vec<(&'static str, String)> {
330 let mut options = Vec::new();
331 if rate.cbr {
332 let kbps = (rate.bps() / 1000).max(1);
333 let vbv_kbit = (rate.vbv() / 1000).max(1);
334 options.push(("bitrate", kbps.to_string()));
335 options.push(("vbv-maxrate", kbps.to_string()));
336 options.push(("vbv-bufsize", vbv_kbit.to_string()));
337 if rate.vbv() > 1000 {
338 options.push(("vbv-init", vbv_kbit.to_string()));
339 }
340 options.push(("strict-cbr", "1".into()));
341 let (lo, hi) = qp_bounds(rate);
342 if lo > 0 {
343 options.push(("qpmin", lo.to_string()));
344 }
345 options.push(("qpmax", if hi > 0 { hi } else { 51 }.to_string()));
346 } else {
347 options.push(("crf", q.to_string()));
348 }
349 options
350 }
351
352 pub fn open(
353 width: usize,
354 height: usize,
355 threads: i32,
356 i444: bool,
357 bit_depth: u32,
358 rate: RateSettings,
359 q: u32,
360 ) -> Result<Self, String> {
361 let api = unsafe { x265_api_get(bit_depth as c_int) };
362 if api.is_null() {
363 return Err(format!("x265 carries no {bit_depth}-bit encoder"));
364 }
365 let params = unsafe { ((*api).param_alloc.unwrap())() };
366 if params.is_null() {
367 return Err("x265 allocated no parameters".into());
368 }
369 let mut me = Self {
370 api,
371 params,
372 encoder: ptr::null_mut(),
373 };
374 let (preset, tune) = (c"ultrafast", c"zerolatency");
375 if unsafe {
376 ((*api).param_default_preset.unwrap())(params, preset.as_ptr(), tune.as_ptr())
377 } < 0
378 {
379 return Err("x265 refused the ultrafast zerolatency preset".into());
380 }
381 if width < 16 || height < 16 {
382 return Err(format!(
383 "x265 encodes no picture as small as {width}x{height}"
384 ));
385 }
386 let mut options: Vec<(&str, String)> = vec![
387 ("log-level", "none".into()),
388 ("input-res", format!("{width}x{height}")),
389 ("input-csp", if i444 { "i444" } else { "i420" }.into()),
390 ("fps", format!("{}/{}", rate.fps.num, rate.fps.den)),
391 ("range", if i444 { "full" } else { "limited" }.into()),
392 ("colorprim", "bt709".into()),
393 ("transfer", "bt709".into()),
394 ("colormatrix", "bt709".into()),
395 ("keyint", "-1".into()),
396 ("scenecut", "0".into()),
397 ("repeat-headers", "1".into()),
398 ("annexb", "1".into()),
399 ("aud", "0".into()),
400 ("rc-lookahead", "0".into()),
401 ("bframes", "0".into()),
402 ("frame-threads", "1".into()),
403 ("pools", threads.to_string()),
404 ("wpp", "1".into()),
405 ];
406 if width < 32 || height < 32 {
407 options.push(("ctu", "16".into()));
408 } else if width < 64 || height < 64 {
409 options.push(("ctu", "32".into()));
410 }
411 options.extend(Self::rate_options(rate, q));
412 for (name, value) in &options {
413 me.set(name, value)?;
414 }
415 let profile = match (i444, bit_depth) {
416 (true, 10) => Some(c"main444-10"),
417 (true, _) => Some(c"main444-8"),
418 (false, 10) => Some(c"main10"),
419 (false, _) => None,
420 };
421 if let Some(profile) = profile
422 && unsafe { ((*api).param_apply_profile.unwrap())(params, profile.as_ptr()) } < 0
423 {
424 return Err(format!(
425 "x265 refused the {} profile",
426 profile.to_string_lossy()
427 ));
428 }
429 me.encoder = unsafe { ((*api).encoder_open.unwrap())(params) };
430 if me.encoder.is_null() {
431 return Err("x265 refused the session".into());
432 }
433 Ok(me)
434 }
435
436 #[allow(clippy::too_many_arguments)]
437 pub fn encode(
438 &mut self,
439 planes: &mut Planes,
440 pixels: &[u8],
441 stride: usize,
442 rgba: bool,
443 full_range: bool,
444 threads: usize,
445 pts: u64,
446 key: bool,
447 held: Option<u32>,
448 ) -> Result<Option<Coded>, String> {
449 planes.convert(pixels, stride, rgba, full_range, false, threads)?;
450 let api = unsafe { &*self.api };
451 let mut pic: x265_picture = unsafe { std::mem::zeroed() };
452 let mut out: [x265_picture; 2] = unsafe { std::mem::zeroed() };
453 unsafe {
454 (api.picture_init.unwrap())(self.params, &mut pic);
455 for o in &mut out {
456 (api.picture_init.unwrap())(self.params, o);
457 }
458 }
459 let [y, u, v] = planes.pointers();
461 pic.planes[0] = y.cast();
462 pic.planes[1] = u.cast();
463 pic.planes[2] = v.cast();
464 let bytes = planes.sample_bytes();
465 pic.stride[0] = (planes.width * bytes) as c_int;
466 pic.stride[1] = (planes.chroma_width() * bytes) as c_int;
467 pic.stride[2] = (planes.chroma_width() * bytes) as c_int;
468 pic.pts = pts as i64;
469 pic.bitDepth = planes.bit_depth as c_int;
470 pic.sliceType = if key {
471 X265_TYPE_IDR as c_int
472 } else {
473 X265_TYPE_AUTO as c_int
474 };
475 pic.forceqp = held.map_or(0, |q| q as c_int + 1);
476 let mut nals: *mut x265_nal = ptr::null_mut();
477 let mut count: u32 = 0;
478 let ret = unsafe {
479 encoder_encode(
480 api,
481 self.encoder,
482 &mut nals,
483 &mut count,
484 &mut pic,
485 out.as_mut_ptr(),
486 )
487 };
488 if ret < 0 {
489 return Err("x265 refused the frame".into());
490 }
491 if count == 0 {
492 return Ok(None);
493 }
494 let mut bytes = Vec::new();
495 for nal in unsafe { std::slice::from_raw_parts(nals, count as usize) } {
496 bytes.extend_from_slice(unsafe {
497 std::slice::from_raw_parts(nal.payload, nal.sizeBytes as usize)
498 });
499 }
500 Ok(Some(Coded {
501 bytes,
502 pts: out[0].pts.max(0) as u64,
503 qp: out[0].frameData.qp.round().clamp(0.0, 51.0) as u32,
504 }))
505 }
506 }
507}
508
509#[cfg(not(feature = "gpl"))]
510mod kvazaar {
511 use std::ptr;
514
515 use codec_sys::kvazaar::*;
516
517 use super::*;
518
519 pub struct Backend {
520 api: *const kvz_api,
521 config: *mut kvz_config,
522 encoder: *mut kvz_encoder,
523 }
524
525 impl Drop for Backend {
526 fn drop(&mut self) {
527 unsafe {
528 let api = &*self.api;
529 if !self.encoder.is_null() {
530 (api.encoder_close.unwrap())(self.encoder);
531 }
532 if !self.config.is_null() {
533 (api.config_destroy.unwrap())(self.config);
534 }
535 }
536 }
537 }
538
539 impl Backend {
540 pub const LIBRARY: &'static str = "kvazaar";
541 pub const FULLCOLOR: bool = false;
542
543 pub fn ten_bit() -> bool {
545 false
546 }
547 pub const KEY_ON_REQUEST: bool = false;
548 pub const HOLDS_QUANTIZER: bool = false;
549
550 #[cfg(test)]
552 pub fn frame_rate(&self) -> (u32, u32) {
553 unsafe {
554 (
555 (*self.config).framerate_num as u32,
556 (*self.config).framerate_denom as u32,
557 )
558 }
559 }
560
561 fn set(&self, name: &str, value: &str) -> Result<(), String> {
562 let (n, v) = (CString::new(name).unwrap(), CString::new(value).unwrap());
563 if unsafe { ((*self.api).config_parse.unwrap())(self.config, n.as_ptr(), v.as_ptr()) }
564 == 0
565 {
566 return Err(format!("kvazaar refused {name}={value}"));
567 }
568 Ok(())
569 }
570
571 pub fn open(
572 width: usize,
573 height: usize,
574 threads: i32,
575 i444: bool,
576 bit_depth: u32,
577 rate: RateSettings,
578 q: u32,
579 ) -> Result<Self, String> {
580 let _ = (i444, bit_depth);
581 if !width.is_multiple_of(8) || !height.is_multiple_of(8) {
582 return Err(format!(
583 "kvazaar takes only pictures a multiple of eight, not {width}x{height}"
584 ));
585 }
586 let api = unsafe { kvz_api_get(8) };
587 if api.is_null() {
588 return Err("kvazaar carries no 8-bit encoder".into());
589 }
590 let config = unsafe { ((*api).config_alloc.unwrap())() };
591 if config.is_null() {
592 return Err("kvazaar allocated no configuration".into());
593 }
594 let me = Self {
595 api,
596 config,
597 encoder: ptr::null_mut(),
598 };
599 unsafe {
600 if ((*api).config_init.unwrap())(config) == 0 {
601 return Err("kvazaar refused to initialize its configuration".into());
602 }
603 let cfg = &mut *config;
604 cfg.width = width as i32;
605 cfg.height = height as i32;
606 cfg.framerate_num = rate.fps.num as i32;
607 cfg.framerate_denom = rate.fps.den as i32;
608 cfg.vui.fullrange = 0;
609 cfg.vui.colorprim = 1;
610 cfg.vui.transfer = 1;
611 cfg.vui.colormatrix = 1;
612 }
613 let mut options: Vec<(&str, String)> = vec![
614 ("preset", "ultrafast".into()),
615 ("gop", "0".into()),
616 ("period", "0".into()),
617 ("vps-period", "0".into()),
618 ("threads", threads.to_string()),
619 ("owf", "0".into()),
620 ("wpp", "1".into()),
621 ];
622 if rate.cbr {
623 options.push(("bitrate", rate.bps().to_string()));
624 options.push(("rc-algorithm", "oba".into()));
625 } else {
626 options.push(("qp", q.to_string()));
627 }
628 for (name, value) in &options {
629 me.set(name, value)?;
630 }
631 let mut me = me;
632 me.encoder = unsafe { ((*api).encoder_open.unwrap())(config) };
633 if me.encoder.is_null() {
634 return Err("kvazaar refused the session".into());
635 }
636 Ok(me)
637 }
638
639 #[allow(clippy::too_many_arguments)]
640 pub fn encode(
641 &mut self,
642 planes: &mut Planes,
643 pixels: &[u8],
644 stride: usize,
645 rgba: bool,
646 full_range: bool,
647 threads: usize,
648 pts: u64,
649 _key: bool,
650 _held: Option<u32>,
651 ) -> Result<Option<Coded>, String> {
652 let api = unsafe { &*self.api };
653 let pic =
654 unsafe { (api.picture_alloc.unwrap())(planes.width as i32, planes.height as i32) };
655 if pic.is_null() {
656 return Err("kvazaar allocated no picture".into());
657 }
658 let result = unsafe {
659 let p = &mut *pic;
660 let luma_stride = p.stride as usize;
661 let chroma_stride = luma_stride / 2;
662 let (w, h) = (planes.width, planes.height);
663 let y = std::slice::from_raw_parts_mut(p.y, luma_stride * h);
664 let u = std::slice::from_raw_parts_mut(p.u, chroma_stride * h.div_ceil(2));
665 let v = std::slice::from_raw_parts_mut(p.v, chroma_stride * h.div_ceil(2));
666 p.pts = pts as i64;
667 super::super::session::convert_into(
668 pixels,
669 stride,
670 w,
671 h,
672 rgba,
673 false,
674 full_range,
675 false,
676 threads,
677 y,
678 u,
679 v,
680 (luma_stride, chroma_stride),
681 )
682 };
683 if let Err(e) = result {
684 unsafe { (api.picture_free.unwrap())(pic) };
685 return Err(e);
686 }
687 let mut data: *mut kvz_data_chunk = ptr::null_mut();
688 let mut len: u32 = 0;
689 let mut recon: *mut kvz_picture = ptr::null_mut();
690 let mut info: kvz_frame_info = unsafe { std::mem::zeroed() };
691 let ok = unsafe {
692 (api.encoder_encode.unwrap())(
693 self.encoder,
694 pic,
695 &mut data,
696 &mut len,
697 &mut recon,
698 ptr::null_mut(),
699 &mut info,
700 )
701 };
702 let mut coded = None;
703 if ok != 0 && !data.is_null() {
704 let mut bytes = Vec::with_capacity(len as usize);
705 let mut chunk = data;
706 while !chunk.is_null() {
707 let c = unsafe { &*chunk };
708 bytes.extend_from_slice(&c.data[..c.len as usize]);
709 chunk = c.next;
710 }
711 let out_pts = if recon.is_null() {
712 pts
713 } else {
714 unsafe { (*recon).pts.max(0) as u64 }
715 };
716 coded = Some(Coded {
717 bytes,
718 pts: out_pts,
719 qp: info.qp.clamp(0, 51) as u32,
720 });
721 }
722 unsafe {
723 (api.picture_free.unwrap())(pic);
724 (api.picture_free.unwrap())(recon);
725 (api.chunk_free.unwrap())(data);
726 }
727 if ok == 0 {
728 return Err("kvazaar refused the frame".into());
729 }
730 Ok(coded)
731 }
732 }
733}
734
735#[cfg(test)]
736mod tests {
737 use super::*;
738
739 #[test]
742 fn key_frames_come_on_request_and_deltas_name_no_reference() {
743 let settings = RustCaptureSettings {
744 width: 64,
745 height: 48,
746 target_fps: 30.0,
747 codec: Codec::H265,
748 video_crf: 25,
749 ..Default::default()
750 };
751 let mut enc = HevcEncoder::new(&settings, false).expect("session");
752 assert_eq!(enc.library(), Backend::LIBRARY);
753 let bgra = vec![128u8; 64 * 48 * 4];
754 let mut kinds = Vec::new();
755 for t in 0..4u64 {
756 let out = enc
757 .encode_host(&bgra, 64 * 4, false, t, 25, t == 2)
758 .expect("encode");
759 kinds.push(super::super::codec::parse_video_type(out[1]).map(|(_, k)| k));
760 assert_eq!(enc.last_reference(), Reference::Untracked);
761 }
762 assert_eq!(
763 kinds,
764 [
765 Some(super::super::codec::FRAME_KEY),
766 Some(super::super::codec::FRAME_DELTA),
767 Some(super::super::codec::FRAME_KEY),
768 Some(super::super::codec::FRAME_DELTA)
769 ]
770 );
771 assert!(!enc.invalidate_reference(1));
772 }
773
774 #[test]
776 fn the_frame_rate_reaches_the_library_as_its_fraction() {
777 for (num, den) in [(60000u32, 1001u32), (120000, 1001), (144000, 1001), (60, 1)] {
778 let settings = RustCaptureSettings {
779 width: 64,
780 height: 48,
781 target_fps: num as f64 / den as f64,
782 codec: Codec::H265,
783 video_cbr_mode: true,
784 video_bitrate_kbps: 2000,
785 ..Default::default()
786 };
787 let enc = HevcEncoder::new(&settings, false).expect("session");
788 assert_eq!(enc.backend.frame_rate(), (num, den), "{}", Backend::LIBRARY);
789 }
790 }
791}