1#![allow(clippy::too_many_arguments)]
2
3use blit_compositor::PixelData;
4use blit_remote::{
5 CODEC_SUPPORT_AV1, CODEC_SUPPORT_H264, SURFACE_FRAME_CODEC_AV1, SURFACE_FRAME_CODEC_H264,
6};
7use openh264::encoder::Encoder as OpenH264Encoder;
8use openh264::formats::YUVBuffer;
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum SurfaceEncoderPreference {
12 VulkanVideoH264,
13 VulkanVideoAV1,
14 H264Software,
15 H264Vaapi,
16 AV1Vaapi,
17 NvencH264,
18 NvencAV1,
19 AV1Software,
20}
21
22pub type SurfaceH264EncoderPreference = SurfaceEncoderPreference;
24
25const H264_MAX_WIDTH: u16 = 3840;
27const H264_MAX_HEIGHT: u16 = 2160;
28
29impl SurfaceEncoderPreference {
30 pub fn parse(value: &str) -> Option<Self> {
31 match value.trim() {
32 "h264-vulkan" => Some(Self::VulkanVideoH264),
33 "av1-vulkan" => Some(Self::VulkanVideoAV1),
34 "h264-software" | "software" => Some(Self::H264Software),
35 "h264-vaapi" | "vaapi" => Some(Self::H264Vaapi),
36 "av1-vaapi" => Some(Self::AV1Vaapi),
37 "h264-nvenc" => Some(Self::NvencH264),
38 "av1-nvenc" => Some(Self::NvencAV1),
39 "av1-software" => Some(Self::AV1Software),
40 _ => None,
41 }
42 }
43
44 pub fn parse_list(value: &str) -> Result<Vec<Self>, String> {
46 let mut result = Vec::new();
47 for item in value.split(',') {
48 let item = item.trim();
49 if item.is_empty() {
50 continue;
51 }
52 result.push(Self::parse(item).ok_or_else(|| format!("unknown encoder: {item}"))?);
53 }
54 Ok(result)
55 }
56
57 pub fn defaults() -> Vec<Self> {
62 if let Some(list) = std::env::var("BLIT_SURFACE_ENCODERS")
63 .ok()
64 .and_then(|v| Self::parse_list(&v).ok())
65 {
66 return list;
67 }
68 vec![
69 Self::NvencAV1,
74 Self::NvencH264,
75 Self::AV1Vaapi,
76 Self::H264Vaapi,
77 Self::H264Software,
78 Self::AV1Software,
79 ]
80 }
81
82 pub fn supported_by_client(self, codec_support: u8) -> bool {
85 if codec_support == 0 {
86 return true;
87 }
88 match self {
89 Self::H264Software | Self::H264Vaapi | Self::NvencH264 | Self::VulkanVideoH264 => {
90 codec_support & CODEC_SUPPORT_H264 != 0
91 }
92 Self::AV1Vaapi | Self::AV1Software | Self::NvencAV1 | Self::VulkanVideoAV1 => {
93 codec_support & CODEC_SUPPORT_AV1 != 0
94 }
95 }
96 }
97
98 pub fn max_dimensions(self) -> Option<(u16, u16)> {
101 match self {
102 Self::H264Software | Self::H264Vaapi | Self::NvencH264 | Self::VulkanVideoH264 => {
103 Some((H264_MAX_WIDTH, H264_MAX_HEIGHT))
104 }
105 Self::AV1Vaapi | Self::NvencAV1 | Self::AV1Software | Self::VulkanVideoAV1 => None,
106 }
107 }
108
109 pub fn max_dimensions_for_list(prefs: &[Self]) -> Option<(u16, u16)> {
111 let mut result: Option<(u16, u16)> = None;
112 for p in prefs {
113 if let Some((w, h)) = p.max_dimensions() {
114 result = Some(match result {
115 Some((rw, rh)) => (rw.min(w), rh.min(h)),
116 None => (w, h),
117 });
118 }
119 }
120 result
121 }
122
123 pub fn is_vulkan_video(self) -> bool {
125 matches!(self, Self::VulkanVideoH264 | Self::VulkanVideoAV1)
126 }
127
128 pub fn vulkan_codec(self) -> u8 {
130 match self {
131 Self::VulkanVideoAV1 => 0x02,
132 _ => 0x01,
133 }
134 }
135
136 pub fn codec_flag(self) -> u8 {
138 match self {
139 Self::H264Software | Self::H264Vaapi | Self::NvencH264 | Self::VulkanVideoH264 => {
140 SURFACE_FRAME_CODEC_H264
141 }
142 Self::AV1Vaapi | Self::AV1Software | Self::NvencAV1 | Self::VulkanVideoAV1 => {
143 SURFACE_FRAME_CODEC_AV1
144 }
145 }
146 }
147}
148
149#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
159pub enum SurfaceQuality {
160 Low,
161 #[default]
162 Medium,
163 High,
164 Ultra,
165 Custom {
168 quantizer: u8,
169 },
170}
171
172impl SurfaceQuality {
173 pub fn parse(value: &str) -> Option<Self> {
174 match value {
175 "low" => Some(Self::Low),
176 "medium" => Some(Self::Medium),
177 "high" => Some(Self::High),
178 "ultra" | "lossless" => Some(Self::Ultra),
179 _ => None,
180 }
181 }
182
183 pub fn from_wire(value: u8) -> Option<Self> {
190 match value {
191 1 => Some(Self::Low),
192 2 => Some(Self::Medium),
193 3 => Some(Self::High),
194 4 => Some(Self::Ultra),
195 v @ 10..=255 => Some(Self::Custom { quantizer: v }),
196 _ => None,
197 }
198 }
199
200 fn av1_speed(self) -> u8 {
202 match self {
203 Self::Low => 10,
204 Self::Medium => 10,
205 Self::High => 8,
206 Self::Ultra => 6,
207 Self::Custom { quantizer } => {
208 if quantizer <= 40 {
209 6
210 } else if quantizer <= 80 {
211 8
212 } else {
213 10
214 }
215 }
216 }
217 }
218
219 fn av1_quantizer(self) -> usize {
222 match self {
223 Self::Low => 180,
224 Self::Medium => 120,
225 Self::High => 80,
226 Self::Ultra => 1,
227 Self::Custom { quantizer } => quantizer as usize,
228 }
229 }
230
231 fn av1_min_quantizer(self) -> u8 {
233 match self {
234 Self::Low => 120,
235 Self::Medium => 80,
236 Self::High => 40,
237 Self::Ultra => 0,
238 Self::Custom { quantizer } => quantizer.saturating_sub(40),
239 }
240 }
241
242 pub fn h264_qp(self) -> u8 {
245 match self {
246 Self::Low => 35,
247 Self::Medium => 28,
248 Self::High => 20,
249 Self::Ultra => 10,
250 Self::Custom { quantizer } => ((quantizer as u32) * 51 / 255).min(51) as u8,
251 }
252 }
253
254 pub fn nvenc_av1_qp(self) -> u32 {
257 self.av1_quantizer() as u32
258 }
259
260 pub fn av1_qp_for_vulkan(self) -> u8 {
263 self.av1_quantizer().min(255) as u8
264 }
265
266 fn openh264_bitrate(self) -> u32 {
269 match self {
270 Self::Low => 500_000,
271 Self::Medium => 2_000_000,
272 Self::High => 8_000_000,
273 Self::Ultra => 20_000_000,
274 Self::Custom { quantizer } => {
275 let q = quantizer as u32;
277 20_000_000 - q * (20_000_000 - 500_000) / 255
278 }
279 }
280 }
281}
282
283pub struct SurfaceEncoder {
284 width: u32,
286 height: u32,
287 source_width: u32,
289 source_height: u32,
290 kind: SurfaceEncoderKind,
291}
292
293enum SurfaceEncoderKind {
294 H264Software(Box<SoftwareH264Encoder>),
295 NvencH264(Box<crate::nvenc_encode::NvencDirectEncoder>),
296 NvencAV1(Box<crate::nvenc_encode::NvencDirectEncoder>),
297 #[cfg(target_os = "linux")]
298 H264Vaapi(Box<crate::vaapi_encode::VaapiDirectEncoder>),
299 #[cfg(target_os = "linux")]
300 AV1Vaapi(Box<crate::vaapi_encode::VaapiAv1Encoder>),
301 AV1Software(Box<SoftwareAV1Encoder>),
302}
303
304impl SurfaceEncoder {
305 pub fn new(
309 preferences: &[SurfaceEncoderPreference],
310 width: u32,
311 height: u32,
312 vaapi_device: &str,
313 quality: SurfaceQuality,
314 verbose: bool,
315 codec_support: u8,
316 ) -> Result<Self, String> {
317 let source_width = width;
318 let source_height = height;
319 let mut last_err = String::from("no encoders configured");
320
321 for &pref in preferences {
322 if pref.is_vulkan_video() {
324 continue;
325 }
326 if !pref.supported_by_client(codec_support) {
327 continue;
328 }
329 match Self::try_one(
330 pref,
331 width,
332 height,
333 source_width,
334 source_height,
335 vaapi_device,
336 quality,
337 verbose,
338 ) {
339 Ok(enc) => {
340 if verbose {
341 eprintln!(
342 "[surface-encoder] using {:?} for {source_width}x{source_height}",
343 pref
344 );
345 }
346 return Ok(enc);
347 }
348 Err(err) => {
349 if verbose {
350 eprintln!(
351 "[surface-encoder] {:?} unavailable for {source_width}x{source_height}: {err}",
352 pref
353 );
354 }
355 last_err = err;
356 }
357 }
358 }
359 Err(last_err)
360 }
361
362 fn try_one(
363 pref: SurfaceEncoderPreference,
364 width: u32,
365 height: u32,
366 source_width: u32,
367 source_height: u32,
368 vaapi_device: &str,
369 quality: SurfaceQuality,
370 verbose: bool,
371 ) -> Result<Self, String> {
372 let _ = vaapi_device;
373 validate_surface_dimensions(width, height, pref)?;
374
375 match pref {
376 SurfaceEncoderPreference::VulkanVideoH264
377 | SurfaceEncoderPreference::VulkanVideoAV1 => {
378 Err("Vulkan Video encoders are managed by the compositor".into())
379 }
380 SurfaceEncoderPreference::NvencH264 => {
381 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
382 let qp = quality.h264_qp() as u32;
383 Ok(Self {
384 width,
385 height,
386 source_width,
387 source_height,
388 kind: SurfaceEncoderKind::NvencH264(Box::new(
389 crate::nvenc_encode::NvencDirectEncoder::try_new(
390 "h264", width, height, qp, verbose,
391 )?,
392 )),
393 })
394 }
395 SurfaceEncoderPreference::NvencAV1 => {
396 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
400 let qp = quality.nvenc_av1_qp();
401 Ok(Self {
402 width,
403 height,
404 source_width,
405 source_height,
406 kind: SurfaceEncoderKind::NvencAV1(Box::new(
407 crate::nvenc_encode::NvencDirectEncoder::try_new(
408 "av1", width, height, qp, verbose,
409 )?,
410 )),
411 })
412 }
413 #[cfg(target_os = "linux")]
414 SurfaceEncoderPreference::H264Vaapi => {
415 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
416 Ok(Self {
417 width,
418 height,
419 source_width,
420 source_height,
421 kind: SurfaceEncoderKind::H264Vaapi(Box::new(
422 crate::vaapi_encode::VaapiDirectEncoder::try_new(
423 width,
424 height,
425 vaapi_device,
426 quality.h264_qp(),
427 verbose,
428 )?,
429 )),
430 })
431 }
432 #[cfg(not(target_os = "linux"))]
433 SurfaceEncoderPreference::H264Vaapi => Err("VA-API is only available on Unix".into()),
434 #[cfg(target_os = "linux")]
435 SurfaceEncoderPreference::AV1Vaapi => {
436 let (width, height) = (width.div_ceil(64) * 64, height.div_ceil(64) * 64);
437 Ok(Self {
438 width,
439 height,
440 source_width,
441 source_height,
442 kind: SurfaceEncoderKind::AV1Vaapi(Box::new(
443 crate::vaapi_encode::VaapiAv1Encoder::try_new(
444 width,
445 height,
446 source_width,
447 source_height,
448 vaapi_device,
449 quality.av1_quantizer() as u8,
450 verbose,
451 )?,
452 )),
453 })
454 }
455 #[cfg(not(target_os = "linux"))]
456 SurfaceEncoderPreference::AV1Vaapi => Err("VA-API is only available on Linux".into()),
457 SurfaceEncoderPreference::AV1Software => Ok(Self {
458 width,
459 height,
460 source_width,
461 source_height,
462 kind: SurfaceEncoderKind::AV1Software(Box::new(SoftwareAV1Encoder::new(
463 width, height, quality,
464 )?)),
465 }),
466 SurfaceEncoderPreference::H264Software => {
467 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
468 Ok(Self {
469 width,
470 height,
471 source_width,
472 source_height,
473 kind: SurfaceEncoderKind::H264Software(Box::new(SoftwareH264Encoder::new(
474 quality,
475 )?)),
476 })
477 }
478 }
479 }
480
481 pub fn source_dimensions(&self) -> (u32, u32) {
483 (self.source_width, self.source_height)
484 }
485
486 pub fn encoder_dimensions(&self) -> (u32, u32) {
489 (self.width, self.height)
490 }
491
492 pub fn encoder_name(&self) -> &'static str {
495 match &self.kind {
496 SurfaceEncoderKind::H264Software(_) => "h264-software",
497 SurfaceEncoderKind::NvencH264(_) => "h264-nvenc",
498 SurfaceEncoderKind::NvencAV1(_) => "av1-nvenc",
499 #[cfg(target_os = "linux")]
500 SurfaceEncoderKind::H264Vaapi(_) => "h264-vaapi",
501 #[cfg(target_os = "linux")]
502 SurfaceEncoderKind::AV1Vaapi(_) => "av1-vaapi",
503 SurfaceEncoderKind::AV1Software(_) => "av1-software",
504 }
505 }
506
507 pub fn codec_flag(&self) -> u8 {
508 match &self.kind {
509 SurfaceEncoderKind::H264Software(_) => SURFACE_FRAME_CODEC_H264,
510 #[cfg(target_os = "linux")]
511 SurfaceEncoderKind::H264Vaapi(_) => SURFACE_FRAME_CODEC_H264,
512 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
513 enc.codec_flag()
514 }
515 #[cfg(target_os = "linux")]
516 SurfaceEncoderKind::AV1Vaapi(_) => SURFACE_FRAME_CODEC_AV1,
517 SurfaceEncoderKind::AV1Software(_) => SURFACE_FRAME_CODEC_AV1,
518 }
519 }
520
521 pub fn request_keyframe(&mut self) {
522 match &mut self.kind {
523 SurfaceEncoderKind::H264Software(enc) => enc.request_keyframe(),
524 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
525 enc.request_keyframe()
526 }
527 #[cfg(target_os = "linux")]
528 SurfaceEncoderKind::H264Vaapi(enc) => enc.request_keyframe(),
529 #[cfg(target_os = "linux")]
530 SurfaceEncoderKind::AV1Vaapi(enc) => enc.request_keyframe(),
531 SurfaceEncoderKind::AV1Software(enc) => enc.request_keyframe(),
532 }
533 }
534
535 #[cfg(target_os = "linux")]
537 pub fn gbm_buffers(&self) -> &[crate::vaapi_encode::GbmExportedBuffer] {
538 match &self.kind {
539 SurfaceEncoderKind::H264Vaapi(enc) => enc.gbm_buffers(),
540 SurfaceEncoderKind::AV1Vaapi(enc) => enc.gbm_buffers(),
541 _ => &[],
542 }
543 }
544
545 #[cfg(target_os = "linux")]
546 pub fn gbm_nv12_buffers(&self) -> &[crate::vaapi_encode::GbmNv12Buffer] {
547 match &self.kind {
548 SurfaceEncoderKind::H264Vaapi(enc) => enc.gbm_nv12_buffers(),
549 SurfaceEncoderKind::AV1Vaapi(enc) => enc.gbm_nv12_buffers(),
550 _ => &[],
551 }
552 }
553
554 #[cfg(target_os = "linux")]
555 pub fn allocate_nv12_buffers(&mut self, drm_fd: std::os::fd::RawFd, count: usize) {
556 match &mut self.kind {
557 SurfaceEncoderKind::H264Vaapi(enc) => {
558 if let Some(vpp) = &mut enc.vpp {
559 vpp.allocate_nv12_buffers(drm_fd, count);
560 }
561 }
562 SurfaceEncoderKind::AV1Vaapi(enc) => {
563 if let Some(vpp) = &mut enc.vpp {
564 vpp.allocate_nv12_buffers(drm_fd, count);
565 }
566 }
567 _ => {}
568 }
569 }
570
571 #[cfg(target_os = "linux")]
572 pub fn drm_fd_raw(&self) -> std::os::fd::RawFd {
573 use std::os::fd::AsRawFd;
574 match &self.kind {
575 SurfaceEncoderKind::H264Vaapi(enc) => enc._drm_fd.as_raw_fd(),
576 SurfaceEncoderKind::AV1Vaapi(enc) => enc._drm_fd.as_raw_fd(),
577 _ => -1,
578 }
579 }
580
581 #[cfg(target_os = "linux")]
583 #[allow(dead_code)]
584 pub fn va_display_usize(&self) -> usize {
585 match &self.kind {
586 SurfaceEncoderKind::H264Vaapi(enc) => enc.va_display_usize(),
587 SurfaceEncoderKind::AV1Vaapi(enc) => enc.va_display_usize(),
588 _ => 0,
589 }
590 }
591
592 pub fn encode(&mut self, rgba: &[u8]) -> Option<(Vec<u8>, bool)> {
593 if let SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) =
599 &mut self.kind
600 {
601 let (sw, sh) = (self.source_width as usize, self.source_height as usize);
602 let mut result = enc.encode_rgba_padded(rgba, sw, sh);
603 self.fixup_keyframe(&mut result);
604 return result;
605 }
606
607 let enc_len = expected_rgba_len(self.width, self.height);
608 let enc_len = match enc_len {
609 Some(v) => v,
610 None => {
611 eprintln!(
612 "[surface-encoder] expected_rgba_len overflow {}x{}",
613 self.width, self.height
614 );
615 return None;
616 }
617 };
618 let rgba = if rgba.len() == enc_len {
619 std::borrow::Cow::Borrowed(rgba)
620 } else {
621 let total_px = rgba.len() / 4;
625 if total_px == 0 {
626 return None;
627 }
628 let src_w = [self.width as usize, (self.width - 1) as usize]
630 .into_iter()
631 .find(|&w| w > 0 && total_px.is_multiple_of(w))?;
632 let src_h = total_px / src_w;
633 if src_h == 0 {
634 return None;
635 }
636 let dst_w = self.width as usize;
637 let dst_h = self.height as usize;
638 let mut padded = vec![0u8; enc_len];
639 for row in 0..dst_h {
640 let src_row = row.min(src_h - 1);
641 for col in 0..dst_w {
642 let src_col = col.min(src_w - 1);
643 let si = (src_row * src_w + src_col) * 4;
644 let di = (row * dst_w + col) * 4;
645 padded[di..di + 4].copy_from_slice(&rgba[si..si + 4]);
646 }
647 }
648 std::borrow::Cow::Owned(padded)
649 };
650
651 match &mut self.kind {
652 SurfaceEncoderKind::H264Software(encoder) => {
653 encoder.encode(&rgba, self.width, self.height)
654 }
655 SurfaceEncoderKind::NvencH264(_) | SurfaceEncoderKind::NvencAV1(_) => unreachable!(),
657 #[cfg(target_os = "linux")]
658 SurfaceEncoderKind::H264Vaapi(enc) => {
659 let mut bgra = rgba.into_owned();
660 for px in bgra.chunks_exact_mut(4) {
661 px.swap(0, 2);
662 }
663 let (sw, sh) = (self.source_width as usize, self.source_height as usize);
664 enc.encode_bgra_padded(&bgra, sw, sh)
665 }
666 #[cfg(target_os = "linux")]
667 SurfaceEncoderKind::AV1Vaapi(enc) => {
668 let mut bgra = rgba.into_owned();
669 for px in bgra.chunks_exact_mut(4) {
670 px.swap(0, 2);
671 }
672 let (sw, sh) = (self.source_width as usize, self.source_height as usize);
673 enc.encode_bgra_padded(&bgra, sw, sh)
674 }
675 SurfaceEncoderKind::AV1Software(encoder) => encoder.encode(&rgba),
676 }
677 }
678
679 pub fn encode_pixels(&mut self, pixels: &PixelData) -> Option<(Vec<u8>, bool)> {
682 match pixels {
683 PixelData::Nv12 {
684 data,
685 y_stride,
686 uv_stride,
687 } => self.encode_nv12(data, *y_stride, *uv_stride),
688 PixelData::Bgra(bgra) => self.encode_bgra(bgra),
689 PixelData::Rgba(rgba) => self.encode(rgba),
690 #[cfg(target_os = "linux")]
691 PixelData::DmaBuf {
692 fd,
693 fourcc,
694 modifier,
695 stride,
696 offset,
697 ..
698 } => self
699 .encode_dmabuf(fd, *fourcc, *modifier, *stride, *offset)
700 .or_else(|| {
701 let w = self.width;
704 let h = self.height;
705 let rgba = pixels.to_rgba(w, h);
706 if !rgba.is_empty() {
707 self.encode(&rgba)
708 } else {
709 None
710 }
711 }),
712 #[cfg(not(target_os = "linux"))]
713 PixelData::DmaBuf { .. } => None,
714 #[cfg(target_os = "linux")]
715 PixelData::Nv12DmaBuf {
716 fd,
717 stride,
718 uv_offset,
719 width,
720 height,
721 sync_fd,
722 } => {
723 if let Some(sfd) = sync_fd {
727 use std::os::fd::AsRawFd;
728 let mut pfd = libc::pollfd {
729 fd: sfd.as_raw_fd(),
730 events: libc::POLLIN,
731 revents: 0,
732 };
733 unsafe { libc::poll(&mut pfd, 1, 5000) };
734 }
735 self.encode_nv12_dmabuf(fd, *stride, *uv_offset, *width, *height)
736 }
737 .or_else(|| {
738 use std::os::fd::AsRawFd;
741 let h = *height as usize;
742 let s = *stride as usize;
743 let uv_off = *uv_offset as usize;
744 let raw = fd.as_raw_fd();
745 let map_size = uv_off + s * h.div_ceil(2);
746 let ptr = unsafe {
747 libc::mmap(
748 std::ptr::null_mut(),
749 map_size,
750 libc::PROT_READ,
751 libc::MAP_SHARED,
752 raw,
753 0,
754 )
755 };
756 if ptr == libc::MAP_FAILED || ptr.is_null() {
757 return None;
758 }
759 let data = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_size) };
760 let result = self.encode_nv12(data, s, s);
761 unsafe { libc::munmap(ptr, map_size) };
762 result
763 }),
764 #[cfg(not(target_os = "linux"))]
765 PixelData::Nv12DmaBuf { .. } => None,
766 PixelData::VaSurface { .. } => None,
767 PixelData::Encoded { .. } => None,
770 }
771 }
772
773 #[cfg(target_os = "linux")]
777 fn encode_nv12_dmabuf(
778 &mut self,
779 fd: &std::sync::Arc<std::os::fd::OwnedFd>,
780 _stride: u32,
781 _uv_offset: u32,
782 _width: u32,
783 _height: u32,
784 ) -> Option<(Vec<u8>, bool)> {
785 use std::os::fd::AsRawFd;
786 let raw_fd = fd.as_raw_fd();
787 let find_surface = |nv12s: &[crate::vaapi_encode::GbmNv12Buffer]| -> Option<u32> {
788 let buf = nv12s.iter().find(|n| n.fd.as_raw_fd() == raw_fd)?;
789 if buf.va_surface == 0 {
791 return None;
792 }
793 Some(buf.va_surface)
794 };
795 let mut result = match &mut self.kind {
796 SurfaceEncoderKind::AV1Vaapi(enc) => {
797 let surf = find_surface(enc.gbm_nv12_buffers())?;
798 enc.encode_surface(surf)
799 }
800 SurfaceEncoderKind::H264Vaapi(enc) => {
801 let surf = find_surface(enc.gbm_nv12_buffers())?;
802 enc.encode_surface(surf)
803 }
804 _ => None,
805 };
806 self.fixup_keyframe(&mut result);
807 result
808 }
809
810 #[cfg(target_os = "linux")]
813 fn encode_dmabuf(
814 &mut self,
815 fd: &std::os::fd::OwnedFd,
816 fourcc: u32,
817 modifier: u64,
818 stride: u32,
819 offset: u32,
820 ) -> Option<(Vec<u8>, bool)> {
821 use std::os::fd::AsRawFd;
822
823 let src_w = self.source_width;
826 let src_h = self.source_height;
827
828 let mut gpu_result = match &mut self.kind {
832 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => enc
833 .encode_dmabuf_fd(
834 fd.as_raw_fd(),
835 fourcc,
836 modifier,
837 stride,
838 offset,
839 src_w,
840 src_h,
841 ),
842 _ => None,
843 };
844 if gpu_result.is_some() {
845 self.fixup_keyframe(&mut gpu_result);
846 return gpu_result;
847 }
848
849 self.encode_dmabuf_cpu_fallback(fd, fourcc, stride, offset)
854 }
855
856 #[cfg(target_os = "linux")]
859 fn encode_dmabuf_cpu_fallback(
860 &mut self,
861 fd: &std::os::fd::OwnedFd,
862 fourcc: u32,
863 stride: u32,
864 _offset: u32,
865 ) -> Option<(Vec<u8>, bool)> {
866 use std::os::fd::AsRawFd;
867
868 let w = self.source_width as usize;
869 let h = self.source_height as usize;
870 let stride = stride as usize;
871 let raw_fd = fd.as_raw_fd();
872
873 let file_size = unsafe { libc::lseek(raw_fd, 0, libc::SEEK_END) };
875 if file_size <= 0 {
876 return None;
877 }
878 let map_len = file_size as usize;
879
880 #[repr(C)]
882 struct DmaBufSync {
883 flags: u64,
884 }
885 const DMA_BUF_SYNC_READ: u64 = 1;
886 const DMA_BUF_SYNC_START: u64 = 0;
887 const DMA_BUF_SYNC_END: u64 = 4;
888 const DMA_BUF_IOCTL_SYNC: libc::c_ulong = 0x40086200;
892
893 {
897 let mut pfd = libc::pollfd {
898 fd: raw_fd,
899 events: libc::POLLIN,
900 revents: 0,
901 };
902 let ready = unsafe { libc::poll(&mut pfd, 1, 0) };
903 if ready <= 0 {
904 } else {
906 let sync_start = DmaBufSync {
907 flags: DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ,
908 };
909 unsafe {
910 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_start);
911 }
912 }
913 }
914
915 let ptr = unsafe {
917 libc::mmap(
918 std::ptr::null_mut(),
919 map_len,
920 libc::PROT_READ,
921 libc::MAP_SHARED,
922 raw_fd,
923 0,
924 )
925 };
926 if ptr == libc::MAP_FAILED {
927 let sync_end = DmaBufSync {
928 flags: DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ,
929 };
930 unsafe {
931 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_end);
932 }
933 return None;
934 }
935 let plane_data = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_len) };
936
937 let is_gl_fbo = {
940 let mut link = [0u8; 128];
941 let path = format!("/proc/self/fd/{raw_fd}\0");
942 let n = unsafe {
943 libc::readlink(path.as_ptr() as *const _, link.as_mut_ptr() as *mut _, 127)
944 };
945 !(n > 0 && link[..n as usize].starts_with(b"/dev/dri/"))
946 };
947
948 let result = if fourcc == blit_compositor::drm_fourcc::ARGB8888
949 || fourcc == blit_compositor::drm_fourcc::XRGB8888
950 {
951 let mut packed = Vec::with_capacity(w * h * 4);
953 for i in 0..h {
954 let row = if is_gl_fbo { h - 1 - i } else { i };
956 let start = row * stride;
957 let end = start + w * 4;
958 if end <= plane_data.len() {
959 packed.extend_from_slice(&plane_data[start..end]);
960 }
961 }
962 self.encode_bgra(&packed)
963 } else if fourcc == blit_compositor::drm_fourcc::ABGR8888
964 || fourcc == blit_compositor::drm_fourcc::XBGR8888
965 {
966 let mut packed = Vec::with_capacity(w * h * 4);
968 for i in 0..h {
969 let row = if is_gl_fbo { h - 1 - i } else { i };
970 let start = row * stride;
971 let end = start + w * 4;
972 if end <= plane_data.len() {
973 packed.extend_from_slice(&plane_data[start..end]);
974 }
975 }
976 self.encode(&packed)
977 } else if fourcc == blit_compositor::drm_fourcc::NV12 {
978 let uv_stride = stride; let y_size = stride * h;
983 let uv_h = h.div_ceil(2);
984 let uv_size = uv_stride * uv_h;
985 if map_len >= y_size + uv_size {
986 let out_stride = w;
988 let mut data = vec![0u8; out_stride * h + out_stride * uv_h];
989 for row in 0..h {
990 let src = row * stride;
991 let dst = row * out_stride;
992 if src + w <= plane_data.len() {
993 data[dst..dst + w].copy_from_slice(&plane_data[src..src + w]);
994 }
995 }
996 let uv_dst_base = out_stride * h;
997 for row in 0..uv_h {
998 let src = y_size + row * uv_stride;
999 let dst = uv_dst_base + row * out_stride;
1000 if src + w <= plane_data.len() {
1001 data[dst..dst + w].copy_from_slice(&plane_data[src..src + w]);
1002 }
1003 }
1004 self.encode_nv12(&data, out_stride, out_stride)
1005 } else {
1006 None
1007 }
1008 } else {
1009 None
1010 };
1011
1012 unsafe {
1014 libc::munmap(ptr, map_len);
1015 }
1016 let sync_end = DmaBufSync {
1018 flags: DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ,
1019 };
1020 unsafe {
1021 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_end);
1022 }
1023
1024 result
1025 }
1026
1027 fn fixup_keyframe(&self, result: &mut Option<(Vec<u8>, bool)>) {
1032 if let Some((data, is_key)) = result.as_mut()
1033 && !*is_key
1034 {
1035 *is_key = match &self.kind {
1036 SurfaceEncoderKind::NvencH264(_) => h264_stream_contains_idr(data),
1037 SurfaceEncoderKind::NvencAV1(_) => av1_stream_contains_keyframe(data),
1038 #[cfg(target_os = "linux")]
1039 SurfaceEncoderKind::H264Vaapi(_) => h264_stream_contains_idr(data),
1040 #[cfg(target_os = "linux")]
1041 SurfaceEncoderKind::AV1Vaapi(_) => av1_stream_contains_keyframe(data),
1042 _ => false,
1043 };
1044 }
1045 }
1046
1047 fn encode_bgra(&mut self, bgra: &[u8]) -> Option<(Vec<u8>, bool)> {
1049 let enc_w = self.width as usize;
1050 let enc_h = self.height as usize;
1051 let src_w = self.source_width as usize;
1052 let src_h = self.source_height as usize;
1053
1054 let mut result = match &mut self.kind {
1055 SurfaceEncoderKind::H264Software(encoder) => {
1056 let yuv = bgra_to_yuv420_padded(bgra, src_w, src_h, enc_w, enc_h);
1057 let yuv_buf = YUVBuffer::from_vec(yuv, enc_w, enc_h);
1058 encoder.encode_yuv(&yuv_buf, self.width, self.height)
1059 }
1060 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
1061 enc.encode_bgra_padded(bgra, src_w, src_h)
1062 }
1063 #[cfg(target_os = "linux")]
1064 SurfaceEncoderKind::H264Vaapi(enc) => enc.encode_bgra_padded(bgra, src_w, src_h),
1065 #[cfg(target_os = "linux")]
1066 SurfaceEncoderKind::AV1Vaapi(enc) => enc.encode_bgra_padded(bgra, src_w, src_h),
1067 SurfaceEncoderKind::AV1Software(encoder) => {
1068 let yuv = bgra_to_yuv420_padded(bgra, src_w, src_h, enc_w, enc_h);
1069 encoder.encode_yuv_planes(&yuv)
1070 }
1071 };
1072 self.fixup_keyframe(&mut result);
1073 result
1074 }
1075
1076 fn encode_nv12(
1079 &mut self,
1080 data: &[u8],
1081 y_stride: usize,
1082 uv_stride: usize,
1083 ) -> Option<(Vec<u8>, bool)> {
1084 let src_w = self.source_width as usize;
1086 let src_h = self.source_height as usize;
1087
1088 let mut result = match &mut self.kind {
1089 SurfaceEncoderKind::H264Software(encoder) => {
1090 let enc_w = self.width as usize;
1091 let enc_h = self.height as usize;
1092 if enc_w == src_w && enc_h == src_h {
1093 let yuv = nv12_to_yuv420(data, y_stride, uv_stride, src_w, src_h);
1094 let yuv_buf = YUVBuffer::from_vec(yuv, enc_w, enc_h);
1095 encoder.encode_yuv(&yuv_buf, self.width, self.height)
1096 } else {
1097 let pd = PixelData::Nv12 {
1098 data: std::sync::Arc::new(data.to_vec()),
1099 y_stride,
1100 uv_stride,
1101 };
1102 let rgba = pd.to_rgba(self.source_width, self.source_height);
1103 return self.encode(&rgba);
1104 }
1105 }
1106 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
1107 enc.encode_nv12(data, y_stride, uv_stride, src_h)
1109 }
1110 #[cfg(target_os = "linux")]
1111 SurfaceEncoderKind::H264Vaapi(enc) => {
1112 let uv_offset = y_stride * src_h;
1113 let y_data = &data[..uv_offset];
1114 let uv_data = &data[uv_offset..];
1115 enc.encode_nv12(y_data, uv_data, y_stride, uv_stride)
1116 }
1117 #[cfg(target_os = "linux")]
1118 SurfaceEncoderKind::AV1Vaapi(enc) => {
1119 let uv_offset = y_stride * src_h;
1120 let y_data = &data[..uv_offset];
1121 let uv_data = &data[uv_offset..];
1122 enc.encode_nv12(y_data, uv_data, y_stride, uv_stride)
1123 }
1124 SurfaceEncoderKind::AV1Software(encoder) => {
1125 encoder.encode_nv12(data, y_stride, uv_stride, src_w, src_h)
1126 }
1127 };
1128 self.fixup_keyframe(&mut result);
1129 result
1130 }
1131}
1132
1133fn validate_surface_dimensions(
1134 width: u32,
1135 height: u32,
1136 _preference: SurfaceEncoderPreference,
1137) -> Result<(), String> {
1138 if width == 0 || height == 0 {
1139 return Err("surface encoder requires non-zero dimensions".into());
1140 }
1141 let _ = expected_rgba_len(width, height)
1144 .ok_or_else(|| format!("surface encoder dimensions overflow for {width}x{height}"))?;
1145 Ok(())
1146}
1147
1148fn expected_rgba_len(width: u32, height: u32) -> Option<usize> {
1149 (width as usize)
1150 .checked_mul(height as usize)?
1151 .checked_mul(4)
1152}
1153
1154#[inline(always)]
1160fn rgb_to_y(r: i32, g: i32, b: i32) -> u8 {
1161 ((66 * r + 129 * g + 25 * b + 128) >> 8)
1162 .wrapping_add(16)
1163 .clamp(0, 255) as u8
1164}
1165
1166#[inline(always)]
1167fn rgb_to_u(r: i32, g: i32, b: i32) -> u8 {
1168 ((-38 * r - 74 * g + 112 * b + 128) >> 8)
1169 .wrapping_add(128)
1170 .clamp(0, 255) as u8
1171}
1172
1173#[inline(always)]
1174fn rgb_to_v(r: i32, g: i32, b: i32) -> u8 {
1175 ((112 * r - 94 * g - 18 * b + 128) >> 8)
1176 .wrapping_add(128)
1177 .clamp(0, 255) as u8
1178}
1179
1180#[inline(always)]
1189fn compute_y_plane(
1190 src: &[u8],
1191 width: usize,
1192 height: usize,
1193 y_plane: &mut [u8],
1194 r_off: usize,
1195 g_off: usize,
1196 b_off: usize,
1197) {
1198 let total = width * height;
1199 for (px, y_out) in y_plane[..total].iter_mut().enumerate() {
1200 let i = px * 4;
1201 let r = src[i + r_off] as i32;
1202 let g = src[i + g_off] as i32;
1203 let b = src[i + b_off] as i32;
1204 *y_out = rgb_to_y(r, g, b);
1205 }
1206}
1207
1208#[inline(always)]
1210fn compute_uv_planes(
1211 src: &[u8],
1212 width: usize,
1213 height: usize,
1214 u_plane: &mut [u8],
1215 v_plane: &mut [u8],
1216 r_off: usize,
1217 g_off: usize,
1218 b_off: usize,
1219) {
1220 let chroma_w = width.div_ceil(2);
1221 let chroma_h = height.div_ceil(2);
1222 for cy in 0..chroma_h {
1223 for cx in 0..chroma_w {
1224 let row = cy * 2;
1225 let col = cx * 2;
1226 let mut u_sum = 0i32;
1228 let mut v_sum = 0i32;
1229 for dy in 0..2u32 {
1230 for dx in 0..2u32 {
1231 let sr = (row + dy as usize).min(height - 1);
1232 let sc = (col + dx as usize).min(width - 1);
1233 let i = (sr * width + sc) * 4;
1234 let r = src[i + r_off] as i32;
1235 let g = src[i + g_off] as i32;
1236 let b = src[i + b_off] as i32;
1237 u_sum += rgb_to_u(r, g, b) as i32;
1238 v_sum += rgb_to_v(r, g, b) as i32;
1239 }
1240 }
1241 let idx = cy * chroma_w + cx;
1242 u_plane[idx] = (u_sum / 4) as u8;
1243 v_plane[idx] = (v_sum / 4) as u8;
1244 }
1245 }
1246}
1247
1248#[inline(always)]
1251fn compute_y_plane_padded(
1252 src: &[u8],
1253 src_w: usize,
1254 src_h: usize,
1255 enc_w: usize,
1256 enc_h: usize,
1257 y_plane: &mut [u8],
1258 r_off: usize,
1259 g_off: usize,
1260 b_off: usize,
1261) {
1262 for row in 0..enc_h {
1263 let sr = row.min(src_h - 1);
1264 for col in 0..enc_w {
1265 let sc = col.min(src_w - 1);
1266 let i = (sr * src_w + sc) * 4;
1267 let r = src[i + r_off] as i32;
1268 let g = src[i + g_off] as i32;
1269 let b = src[i + b_off] as i32;
1270 y_plane[row * enc_w + col] = rgb_to_y(r, g, b);
1271 }
1272 }
1273}
1274
1275#[inline(always)]
1278fn compute_uv_planes_padded(
1279 src: &[u8],
1280 src_w: usize,
1281 src_h: usize,
1282 enc_w: usize,
1283 enc_h: usize,
1284 u_plane: &mut [u8],
1285 v_plane: &mut [u8],
1286 r_off: usize,
1287 g_off: usize,
1288 b_off: usize,
1289) {
1290 let chroma_w = enc_w.div_ceil(2);
1291 let chroma_h = enc_h.div_ceil(2);
1292 for cy in 0..chroma_h {
1293 for cx in 0..chroma_w {
1294 let row = cy * 2;
1295 let col = cx * 2;
1296 let mut u_sum = 0i32;
1297 let mut v_sum = 0i32;
1298 for dy in 0..2u32 {
1299 for dx in 0..2u32 {
1300 let sr = (row + dy as usize).min(src_h - 1);
1301 let sc = (col + dx as usize).min(src_w - 1);
1302 let i = (sr * src_w + sc) * 4;
1303 let r = src[i + r_off] as i32;
1304 let g = src[i + g_off] as i32;
1305 let b = src[i + b_off] as i32;
1306 u_sum += rgb_to_u(r, g, b) as i32;
1307 v_sum += rgb_to_v(r, g, b) as i32;
1308 }
1309 }
1310 let idx = cy * chroma_w + cx;
1311 u_plane[idx] = (u_sum / 4) as u8;
1312 v_plane[idx] = (v_sum / 4) as u8;
1313 }
1314 }
1315}
1316
1317fn bgra_to_yuv420_padded(
1321 bgra: &[u8],
1322 src_w: usize,
1323 src_h: usize,
1324 enc_w: usize,
1325 enc_h: usize,
1326) -> Vec<u8> {
1327 let y_size = enc_w * enc_h;
1328 let uv_w = enc_w.div_ceil(2);
1333 let uv_size = uv_w * enc_h.div_ceil(2);
1334 let mut yuv = vec![0u8; y_size + uv_size * 2];
1335 let (y_plane, uv) = yuv.split_at_mut(y_size);
1336 let (u_plane, v_plane) = uv.split_at_mut(uv_size);
1337 compute_y_plane_padded(bgra, src_w, src_h, enc_w, enc_h, y_plane, 2, 1, 0);
1339 compute_uv_planes_padded(bgra, src_w, src_h, enc_w, enc_h, u_plane, v_plane, 2, 1, 0);
1340 yuv
1341}
1342
1343fn rgba_to_yuv420(rgba: &[u8], width: usize, height: usize) -> Vec<u8> {
1345 let y_size = width * height;
1346 let uv_w = width.div_ceil(2);
1347 let uv_size = uv_w * height.div_ceil(2);
1348 let mut yuv = vec![0u8; y_size + uv_size * 2];
1349 let (y_plane, uv) = yuv.split_at_mut(y_size);
1350 let (u_plane, v_plane) = uv.split_at_mut(uv_size);
1351 compute_y_plane(rgba, width, height, y_plane, 0, 1, 2);
1353 compute_uv_planes(rgba, width, height, u_plane, v_plane, 0, 1, 2);
1354 yuv
1355}
1356
1357fn nv12_to_yuv420(
1361 data: &[u8],
1362 y_stride: usize,
1363 uv_stride: usize,
1364 width: usize,
1365 height: usize,
1366) -> Vec<u8> {
1367 let y_size = width * height;
1368 let uv_w = width.div_ceil(2);
1369 let uv_h = height.div_ceil(2);
1370 let uv_size = uv_w * uv_h;
1371 let mut yuv = vec![0u8; y_size + uv_size * 2];
1372 let (y_out, uv_out) = yuv.split_at_mut(y_size);
1373 let (u_out, v_out) = uv_out.split_at_mut(uv_size);
1374
1375 let uv_offset = y_stride * height;
1376
1377 for row in 0..height {
1379 let src = row * y_stride;
1380 let dst = row * width;
1381 y_out[dst..dst + width].copy_from_slice(&data[src..src + width]);
1382 }
1383
1384 let src_uv_pairs = width / 2;
1388 for row in 0..uv_h {
1389 let src_start = uv_offset + row.min(height / 2 - 1) * uv_stride;
1390 let dst_start = row * uv_w;
1391 for col in 0..uv_w {
1392 let sc = col.min(src_uv_pairs.saturating_sub(1));
1393 u_out[dst_start + col] = data[src_start + sc * 2];
1394 v_out[dst_start + col] = data[src_start + sc * 2 + 1];
1395 }
1396 }
1397
1398 yuv
1399}
1400
1401fn h264_stream_contains_idr(data: &[u8]) -> bool {
1403 annex_b_contains_nal(data, |byte| (byte & 0x1f) == 5)
1404}
1405
1406fn annex_b_contains_nal(data: &[u8], pred: impl Fn(u8) -> bool) -> bool {
1408 let mut i = 0usize;
1409 while i < data.len() {
1410 let start_code_len = if data[i..].starts_with(&[0, 0, 0, 1]) {
1411 4
1412 } else if data[i..].starts_with(&[0, 0, 1]) {
1413 3
1414 } else {
1415 i += 1;
1416 continue;
1417 };
1418
1419 let nal_header = i + start_code_len;
1420 if let Some(&byte) = data.get(nal_header)
1421 && pred(byte)
1422 {
1423 return true;
1424 }
1425
1426 i = nal_header.saturating_add(1);
1427 }
1428
1429 false
1430}
1431
1432fn av1_stream_contains_keyframe(data: &[u8]) -> bool {
1440 let mut pos = 0;
1444 while pos < data.len() {
1445 let header = data[pos];
1446 let obu_type = (header >> 3) & 0xF;
1447 let has_extension = (header >> 2) & 1;
1448 let has_size = (header >> 1) & 1;
1449 pos += 1;
1450
1451 if has_extension != 0 {
1453 if pos >= data.len() {
1454 break;
1455 }
1456 pos += 1;
1457 }
1458
1459 if obu_type == 1 {
1461 return true;
1462 }
1463
1464 if has_size != 0 {
1467 let mut size: u64 = 0;
1468 let mut shift = 0u32;
1469 while pos < data.len() {
1470 let byte = data[pos];
1471 pos += 1;
1472 size |= ((byte & 0x7F) as u64) << shift;
1473 if byte & 0x80 == 0 {
1474 break;
1475 }
1476 shift += 7;
1477 if shift >= 56 {
1478 return false; }
1480 }
1481 pos = pos.saturating_add(size as usize);
1482 } else {
1483 break;
1486 }
1487 }
1488 false
1489}
1490
1491struct SoftwareH264Encoder {
1492 encoder: OpenH264Encoder,
1493}
1494
1495impl SoftwareH264Encoder {
1496 fn new(quality: SurfaceQuality) -> Result<Self, String> {
1497 use openh264::encoder::{EncoderConfig, RateControlMode};
1498 let config = EncoderConfig::new()
1499 .set_bitrate_bps(quality.openh264_bitrate())
1500 .rate_control_mode(RateControlMode::Bitrate);
1501 let encoder =
1502 OpenH264Encoder::with_api_config(openh264::OpenH264API::from_source(), config)
1503 .map_err(|err| format!("failed to create OpenH264 encoder: {err:?}"))?;
1504 Ok(Self { encoder })
1505 }
1506
1507 fn request_keyframe(&mut self) {
1508 self.encoder.force_intra_frame();
1509 }
1510
1511 fn encode(&mut self, rgba: &[u8], width: u32, height: u32) -> Option<(Vec<u8>, bool)> {
1512 let yuv = rgba_to_yuv420(rgba, width as usize, height as usize);
1513 let yuv_buf = YUVBuffer::from_vec(yuv, width as usize, height as usize);
1514 self.encode_yuv(&yuv_buf, width, height)
1515 }
1516
1517 fn encode_yuv(
1519 &mut self,
1520 yuv_buf: &YUVBuffer,
1521 width: u32,
1522 height: u32,
1523 ) -> Option<(Vec<u8>, bool)> {
1524 let bitstream = match self.encoder.encode(yuv_buf) {
1525 Ok(bs) => bs,
1526 Err(e) => {
1527 eprintln!("[surface-encoder] openh264 encode failed {width}x{height}: {e:?}");
1528 return None;
1529 }
1530 };
1531 let nal_data = bitstream.to_vec();
1532 if nal_data.is_empty() {
1533 eprintln!("[surface-encoder] openh264 produced empty NAL {width}x{height}");
1534 return None;
1535 }
1536 let is_keyframe = h264_stream_contains_idr(&nal_data);
1537 Some((nal_data, is_keyframe))
1538 }
1539}
1540
1541struct SoftwareAV1Encoder {
1546 ctx: rav1e::Context<u8>,
1547 width: usize,
1548 height: usize,
1549 force_keyframe: bool,
1550}
1551
1552impl SoftwareAV1Encoder {
1553 fn new(width: u32, height: u32, quality: SurfaceQuality) -> Result<Self, String> {
1554 use rav1e::prelude::*;
1555
1556 let mut speed = SpeedSettings::from_preset(quality.av1_speed());
1557 speed.rdo_lookahead_frames = 1;
1558 let enc = EncoderConfig {
1559 width: width as usize,
1560 height: height as usize,
1561 chroma_sampling: ChromaSampling::Cs420,
1562 chroma_sample_position: ChromaSamplePosition::Unknown,
1563 speed_settings: speed,
1564 low_latency: true,
1565 min_key_frame_interval: 0,
1566 max_key_frame_interval: 60,
1567 quantizer: quality.av1_quantizer(),
1568 min_quantizer: quality.av1_min_quantizer(),
1569 bitrate: 0,
1570 ..Default::default()
1571 };
1572 let cfg = Config::new().with_encoder_config(enc);
1573 let ctx = cfg
1574 .new_context()
1575 .map_err(|e| format!("rav1e context creation failed: {e}"))?;
1576 Ok(Self {
1577 ctx,
1578 width: width as usize,
1579 height: height as usize,
1580 force_keyframe: false,
1581 })
1582 }
1583
1584 fn request_keyframe(&mut self) {
1585 self.force_keyframe = true;
1586 }
1587
1588 fn encode(&mut self, rgba: &[u8]) -> Option<(Vec<u8>, bool)> {
1589 let yuv = rgba_to_yuv420(rgba, self.width, self.height);
1590 self.encode_yuv_planes(&yuv)
1591 }
1592
1593 fn encode_nv12(
1594 &mut self,
1595 data: &[u8],
1596 y_stride: usize,
1597 uv_stride: usize,
1598 width: usize,
1599 height: usize,
1600 ) -> Option<(Vec<u8>, bool)> {
1601 let yuv = nv12_to_yuv420(data, y_stride, uv_stride, width, height);
1602 self.encode_yuv_planes(&yuv)
1603 }
1604
1605 fn encode_yuv_planes(&mut self, yuv: &[u8]) -> Option<(Vec<u8>, bool)> {
1607 let width = self.width;
1608 let height = self.height;
1609 let y_size = width * height;
1610 let uv_w = width.div_ceil(2);
1611 let uv_h = height.div_ceil(2);
1612 let uv_size = uv_w * uv_h;
1613
1614 let y_plane = &yuv[..y_size];
1615 let u_plane = &yuv[y_size..y_size + uv_size];
1616 let v_plane = &yuv[y_size + uv_size..];
1617
1618 let mut frame = self.ctx.new_frame();
1619 frame.planes[0].copy_from_raw_u8(y_plane, width, 1);
1620 frame.planes[1].copy_from_raw_u8(u_plane, uv_w, 1);
1621 frame.planes[2].copy_from_raw_u8(v_plane, uv_w, 1);
1622
1623 self.send_and_receive(frame)
1624 }
1625
1626 fn send_and_receive(&mut self, frame: rav1e::Frame<u8>) -> Option<(Vec<u8>, bool)> {
1627 use rav1e::prelude::*;
1628
1629 if self.force_keyframe {
1630 let params = FrameParameters {
1631 frame_type_override: FrameTypeOverride::Key,
1632 ..Default::default()
1633 };
1634 if self.ctx.send_frame((frame, params)).is_ok() {
1635 self.force_keyframe = false;
1636 }
1637 } else {
1638 let _ = self.ctx.send_frame(frame);
1639 }
1640
1641 match self.ctx.receive_packet() {
1642 Ok(packet) => {
1643 let is_key = packet.frame_type == rav1e::prelude::FrameType::KEY;
1644 Some((packet.data, is_key))
1645 }
1646 Err(rav1e::EncoderStatus::Encoded) | Err(rav1e::EncoderStatus::NeedMoreData) => None,
1647 Err(_) => None,
1648 }
1649 }
1650}
1651
1652#[cfg(test)]
1653mod tests {
1654 use super::*;
1655
1656 fn make_obu(obu_type: u8, payload: &[u8]) -> Vec<u8> {
1658 let header = (obu_type & 0xF) << 3 | 0b10; let mut obu = vec![header];
1661 let mut size = payload.len();
1663 loop {
1664 let mut byte = (size & 0x7F) as u8;
1665 size >>= 7;
1666 if size > 0 {
1667 byte |= 0x80;
1668 }
1669 obu.push(byte);
1670 if size == 0 {
1671 break;
1672 }
1673 }
1674 obu.extend_from_slice(payload);
1675 obu
1676 }
1677
1678 #[test]
1679 fn av1_keyframe_with_sequence_header_only() {
1680 let data = make_obu(1, &[0xAA; 10]);
1682 assert!(av1_stream_contains_keyframe(&data));
1683 }
1684
1685 #[test]
1686 fn av1_keyframe_with_temporal_delimiter_prefix() {
1687 let mut data = make_obu(2, &[]); data.extend(make_obu(1, &[0xBB; 8])); data.extend(make_obu(6, &[0xCC; 20])); assert!(av1_stream_contains_keyframe(&data));
1693 }
1694
1695 #[test]
1696 fn av1_non_keyframe_with_temporal_delimiter() {
1697 let mut data = make_obu(2, &[]);
1699 data.extend(make_obu(6, &[0xDD; 15]));
1700 assert!(!av1_stream_contains_keyframe(&data));
1701 }
1702
1703 #[test]
1704 fn av1_non_keyframe_frame_header_only() {
1705 let data = make_obu(3, &[0xEE; 5]);
1707 assert!(!av1_stream_contains_keyframe(&data));
1708 }
1709
1710 #[test]
1711 fn av1_empty_stream() {
1712 assert!(!av1_stream_contains_keyframe(&[]));
1713 }
1714
1715 #[test]
1716 fn av1_keyframe_large_leb128_size() {
1717 let mut data = make_obu(2, &[0x00; 200]);
1720 data.extend(make_obu(1, &[0xFF; 4]));
1721 assert!(av1_stream_contains_keyframe(&data));
1722 }
1723}