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 H264Software,
13 H264Vaapi,
14 AV1Vaapi,
15 NvencH264,
16 NvencAV1,
17 AV1Software,
18}
19
20pub type SurfaceH264EncoderPreference = SurfaceEncoderPreference;
22
23const H264_MAX_WIDTH: u16 = 3840;
25const H264_MAX_HEIGHT: u16 = 2160;
26
27impl SurfaceEncoderPreference {
28 pub fn parse(value: &str) -> Option<Self> {
29 match value.trim() {
30 "h264-software" | "software" => Some(Self::H264Software),
31 "h264-vaapi" | "vaapi" => Some(Self::H264Vaapi),
32 "av1-vaapi" => Some(Self::AV1Vaapi),
33 "h264-nvenc" => Some(Self::NvencH264),
34 "av1-nvenc" => Some(Self::NvencAV1),
35 "av1-software" => Some(Self::AV1Software),
36 _ => None,
37 }
38 }
39
40 pub fn parse_list(value: &str) -> Result<Vec<Self>, String> {
42 let mut result = Vec::new();
43 for item in value.split(',') {
44 let item = item.trim();
45 if item.is_empty() {
46 continue;
47 }
48 result.push(Self::parse(item).ok_or_else(|| format!("unknown encoder: {item}"))?);
49 }
50 Ok(result)
51 }
52
53 pub fn defaults() -> Vec<Self> {
58 if let Some(list) = std::env::var("BLIT_SURFACE_ENCODERS")
59 .ok()
60 .and_then(|v| Self::parse_list(&v).ok())
61 {
62 return list;
63 }
64 vec![
65 Self::NvencAV1,
66 Self::NvencH264,
67 Self::AV1Vaapi,
68 Self::H264Vaapi,
69 Self::H264Software,
70 Self::AV1Software,
71 ]
72 }
73
74 pub fn supported_by_client(self, codec_support: u8) -> bool {
77 if codec_support == 0 {
78 return true;
79 }
80 match self {
81 Self::H264Software | Self::H264Vaapi | Self::NvencH264 => {
82 codec_support & CODEC_SUPPORT_H264 != 0
83 }
84 Self::AV1Vaapi | Self::AV1Software | Self::NvencAV1 => {
85 codec_support & CODEC_SUPPORT_AV1 != 0
86 }
87 }
88 }
89
90 pub fn max_dimensions(self) -> Option<(u16, u16)> {
93 match self {
94 Self::H264Software | Self::H264Vaapi | Self::NvencH264 => {
95 Some((H264_MAX_WIDTH, H264_MAX_HEIGHT))
96 }
97 Self::AV1Vaapi | Self::NvencAV1 | Self::AV1Software => None,
98 }
99 }
100
101 pub fn max_dimensions_for_list(prefs: &[Self]) -> Option<(u16, u16)> {
103 let mut result: Option<(u16, u16)> = None;
104 for p in prefs {
105 if let Some((w, h)) = p.max_dimensions() {
106 result = Some(match result {
107 Some((rw, rh)) => (rw.min(w), rh.min(h)),
108 None => (w, h),
109 });
110 }
111 }
112 result
113 }
114}
115
116#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
125pub enum SurfaceQuality {
126 Low,
127 #[default]
128 Medium,
129 High,
130 Lossless,
131}
132
133impl SurfaceQuality {
134 pub fn parse(value: &str) -> Option<Self> {
135 match value {
136 "low" => Some(Self::Low),
137 "medium" => Some(Self::Medium),
138 "high" => Some(Self::High),
139 "lossless" => Some(Self::Lossless),
140 _ => None,
141 }
142 }
143
144 pub fn from_wire(value: u8) -> Option<Self> {
147 match value {
148 1 => Some(Self::Low),
149 2 => Some(Self::Medium),
150 3 => Some(Self::High),
151 4 => Some(Self::Lossless),
152 _ => None,
153 }
154 }
155
156 fn av1_speed(self) -> u8 {
158 match self {
159 Self::Low => 10,
160 Self::Medium => 10,
161 Self::High => 8,
162 Self::Lossless => 6,
163 }
164 }
165
166 fn av1_quantizer(self) -> usize {
168 match self {
169 Self::Low => 180,
170 Self::Medium => 120,
171 Self::High => 80,
172 Self::Lossless => 40,
173 }
174 }
175
176 fn av1_min_quantizer(self) -> u8 {
178 match self {
179 Self::Low => 120,
180 Self::Medium => 80,
181 Self::High => 40,
182 Self::Lossless => 0,
183 }
184 }
185
186 pub fn h264_qp(self) -> u8 {
189 match self {
190 Self::Low => 35,
191 Self::Medium => 28,
192 Self::High => 20,
193 Self::Lossless => 10,
194 }
195 }
196
197 pub fn nvenc_av1_qp(self) -> u32 {
200 self.av1_quantizer() as u32
201 }
202
203 fn openh264_bitrate(self) -> u32 {
206 match self {
207 Self::Low => 500_000,
208 Self::Medium => 2_000_000,
209 Self::High => 8_000_000,
210 Self::Lossless => 20_000_000,
211 }
212 }
213}
214
215pub struct SurfaceEncoder {
216 width: u32,
218 height: u32,
219 source_width: u32,
221 source_height: u32,
222 kind: SurfaceEncoderKind,
223}
224
225enum SurfaceEncoderKind {
226 H264Software(Box<SoftwareH264Encoder>),
227 NvencH264(Box<crate::nvenc_encode::NvencDirectEncoder>),
228 NvencAV1(Box<crate::nvenc_encode::NvencDirectEncoder>),
229 #[cfg(target_os = "linux")]
230 H264Vaapi(Box<crate::vaapi_encode::VaapiDirectEncoder>),
231 #[cfg(target_os = "linux")]
232 AV1Vaapi(Box<crate::vaapi_encode::VaapiAv1Encoder>),
233 AV1Software(Box<SoftwareAV1Encoder>),
234}
235
236impl SurfaceEncoder {
237 pub fn new(
241 preferences: &[SurfaceEncoderPreference],
242 width: u32,
243 height: u32,
244 vaapi_device: &str,
245 quality: SurfaceQuality,
246 verbose: bool,
247 codec_support: u8,
248 ) -> Result<Self, String> {
249 let source_width = width;
250 let source_height = height;
251 let mut last_err = String::from("no encoders configured");
252
253 for &pref in preferences {
254 if !pref.supported_by_client(codec_support) {
255 continue;
256 }
257 match Self::try_one(
258 pref,
259 width,
260 height,
261 source_width,
262 source_height,
263 vaapi_device,
264 quality,
265 verbose,
266 ) {
267 Ok(enc) => {
268 if verbose {
269 eprintln!(
270 "[surface-encoder] using {:?} for {source_width}x{source_height}",
271 pref
272 );
273 }
274 return Ok(enc);
275 }
276 Err(err) => {
277 if verbose {
278 eprintln!(
279 "[surface-encoder] {:?} unavailable for {source_width}x{source_height}: {err}",
280 pref
281 );
282 }
283 last_err = err;
284 }
285 }
286 }
287 Err(last_err)
288 }
289
290 fn try_one(
291 pref: SurfaceEncoderPreference,
292 width: u32,
293 height: u32,
294 source_width: u32,
295 source_height: u32,
296 vaapi_device: &str,
297 quality: SurfaceQuality,
298 verbose: bool,
299 ) -> Result<Self, String> {
300 let _ = vaapi_device;
301 validate_surface_dimensions(width, height, pref)?;
302
303 match pref {
304 SurfaceEncoderPreference::NvencH264 => {
305 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
306 let qp = quality.h264_qp() as u32;
307 Ok(Self {
308 width,
309 height,
310 source_width,
311 source_height,
312 kind: SurfaceEncoderKind::NvencH264(Box::new(
313 crate::nvenc_encode::NvencDirectEncoder::try_new(
314 "h264", width, height, qp, verbose,
315 )?,
316 )),
317 })
318 }
319 SurfaceEncoderPreference::NvencAV1 => {
320 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
324 let qp = quality.nvenc_av1_qp();
325 Ok(Self {
326 width,
327 height,
328 source_width,
329 source_height,
330 kind: SurfaceEncoderKind::NvencAV1(Box::new(
331 crate::nvenc_encode::NvencDirectEncoder::try_new(
332 "av1", width, height, qp, verbose,
333 )?,
334 )),
335 })
336 }
337 #[cfg(target_os = "linux")]
338 SurfaceEncoderPreference::H264Vaapi => {
339 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
340 Ok(Self {
341 width,
342 height,
343 source_width,
344 source_height,
345 kind: SurfaceEncoderKind::H264Vaapi(Box::new(
346 crate::vaapi_encode::VaapiDirectEncoder::try_new(
347 width,
348 height,
349 vaapi_device,
350 quality.h264_qp(),
351 verbose,
352 )?,
353 )),
354 })
355 }
356 #[cfg(not(target_os = "linux"))]
357 SurfaceEncoderPreference::H264Vaapi => Err("VA-API is only available on Unix".into()),
358 #[cfg(target_os = "linux")]
359 SurfaceEncoderPreference::AV1Vaapi => {
360 let (width, height) = (width.div_ceil(64) * 64, height.div_ceil(64) * 64);
361 Ok(Self {
362 width,
363 height,
364 source_width,
365 source_height,
366 kind: SurfaceEncoderKind::AV1Vaapi(Box::new(
367 crate::vaapi_encode::VaapiAv1Encoder::try_new(
368 width,
369 height,
370 source_width,
371 source_height,
372 vaapi_device,
373 quality.av1_quantizer() as u8,
374 verbose,
375 )?,
376 )),
377 })
378 }
379 #[cfg(not(target_os = "linux"))]
380 SurfaceEncoderPreference::AV1Vaapi => Err("VA-API is only available on Linux".into()),
381 SurfaceEncoderPreference::AV1Software => Ok(Self {
382 width,
383 height,
384 source_width,
385 source_height,
386 kind: SurfaceEncoderKind::AV1Software(Box::new(SoftwareAV1Encoder::new(
387 width, height, quality,
388 )?)),
389 }),
390 SurfaceEncoderPreference::H264Software => {
391 let (width, height) = ((width + 1) & !1, (height + 1) & !1);
392 Ok(Self {
393 width,
394 height,
395 source_width,
396 source_height,
397 kind: SurfaceEncoderKind::H264Software(Box::new(SoftwareH264Encoder::new(
398 quality,
399 )?)),
400 })
401 }
402 }
403 }
404
405 pub fn source_dimensions(&self) -> (u32, u32) {
407 (self.source_width, self.source_height)
408 }
409
410 pub fn encoder_name(&self) -> &'static str {
413 match &self.kind {
414 SurfaceEncoderKind::H264Software(_) => "h264-software",
415 SurfaceEncoderKind::NvencH264(_) => "h264-nvenc",
416 SurfaceEncoderKind::NvencAV1(_) => "av1-nvenc",
417 #[cfg(target_os = "linux")]
418 SurfaceEncoderKind::H264Vaapi(_) => "h264-vaapi",
419 #[cfg(target_os = "linux")]
420 SurfaceEncoderKind::AV1Vaapi(_) => "av1-vaapi",
421 SurfaceEncoderKind::AV1Software(_) => "av1-software",
422 }
423 }
424
425 pub fn codec_flag(&self) -> u8 {
426 match &self.kind {
427 SurfaceEncoderKind::H264Software(_) => SURFACE_FRAME_CODEC_H264,
428 #[cfg(target_os = "linux")]
429 SurfaceEncoderKind::H264Vaapi(_) => SURFACE_FRAME_CODEC_H264,
430 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
431 enc.codec_flag()
432 }
433 #[cfg(target_os = "linux")]
434 SurfaceEncoderKind::AV1Vaapi(_) => SURFACE_FRAME_CODEC_AV1,
435 SurfaceEncoderKind::AV1Software(_) => SURFACE_FRAME_CODEC_AV1,
436 }
437 }
438
439 pub fn request_keyframe(&mut self) {
440 match &mut self.kind {
441 SurfaceEncoderKind::H264Software(enc) => enc.request_keyframe(),
442 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
443 enc.request_keyframe()
444 }
445 #[cfg(target_os = "linux")]
446 SurfaceEncoderKind::H264Vaapi(enc) => enc.request_keyframe(),
447 #[cfg(target_os = "linux")]
448 SurfaceEncoderKind::AV1Vaapi(enc) => enc.request_keyframe(),
449 SurfaceEncoderKind::AV1Software(enc) => enc.request_keyframe(),
450 }
451 }
452
453 #[cfg(target_os = "linux")]
455 pub fn export_vpp_surfaces(&self) -> Vec<crate::vaapi_encode::ExportedVaSurface> {
456 match &self.kind {
457 SurfaceEncoderKind::H264Vaapi(enc) => enc.export_vpp_surfaces(),
458 SurfaceEncoderKind::AV1Vaapi(enc) => enc.export_vpp_surfaces(),
459 _ => Vec::new(),
460 }
461 }
462
463 #[cfg(target_os = "linux")]
465 pub fn va_display_usize(&self) -> usize {
466 match &self.kind {
467 SurfaceEncoderKind::H264Vaapi(enc) => enc.va_display_usize(),
468 SurfaceEncoderKind::AV1Vaapi(enc) => enc.va_display_usize(),
469 _ => 0,
470 }
471 }
472
473 pub fn encode(&mut self, rgba: &[u8]) -> Option<(Vec<u8>, bool)> {
474 if let SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) =
480 &mut self.kind
481 {
482 let (sw, sh) = (self.source_width as usize, self.source_height as usize);
483 let mut result = enc.encode_rgba_padded(rgba, sw, sh);
484 self.fixup_keyframe(&mut result);
485 return result;
486 }
487
488 let enc_len = expected_rgba_len(self.width, self.height);
489 let enc_len = match enc_len {
490 Some(v) => v,
491 None => {
492 eprintln!(
493 "[surface-encoder] expected_rgba_len overflow {}x{}",
494 self.width, self.height
495 );
496 return None;
497 }
498 };
499 let rgba = if rgba.len() == enc_len {
500 std::borrow::Cow::Borrowed(rgba)
501 } else {
502 let total_px = rgba.len() / 4;
506 if total_px == 0 {
507 return None;
508 }
509 let src_w = [self.width as usize, (self.width - 1) as usize]
511 .into_iter()
512 .find(|&w| w > 0 && total_px.is_multiple_of(w))?;
513 let src_h = total_px / src_w;
514 if src_h == 0 {
515 return None;
516 }
517 let dst_w = self.width as usize;
518 let dst_h = self.height as usize;
519 let mut padded = vec![0u8; enc_len];
520 for row in 0..dst_h {
521 let src_row = row.min(src_h - 1);
522 for col in 0..dst_w {
523 let src_col = col.min(src_w - 1);
524 let si = (src_row * src_w + src_col) * 4;
525 let di = (row * dst_w + col) * 4;
526 padded[di..di + 4].copy_from_slice(&rgba[si..si + 4]);
527 }
528 }
529 std::borrow::Cow::Owned(padded)
530 };
531
532 match &mut self.kind {
533 SurfaceEncoderKind::H264Software(encoder) => {
534 encoder.encode(&rgba, self.width, self.height)
535 }
536 SurfaceEncoderKind::NvencH264(_) | SurfaceEncoderKind::NvencAV1(_) => unreachable!(),
538 #[cfg(target_os = "linux")]
539 SurfaceEncoderKind::H264Vaapi(enc) => {
540 let mut bgra = rgba.into_owned();
541 for px in bgra.chunks_exact_mut(4) {
542 px.swap(0, 2);
543 }
544 let (sw, sh) = (self.source_width as usize, self.source_height as usize);
545 enc.encode_bgra_padded(&bgra, sw, sh)
546 }
547 #[cfg(target_os = "linux")]
548 SurfaceEncoderKind::AV1Vaapi(enc) => {
549 let mut bgra = rgba.into_owned();
550 for px in bgra.chunks_exact_mut(4) {
551 px.swap(0, 2);
552 }
553 let (sw, sh) = (self.source_width as usize, self.source_height as usize);
554 enc.encode_bgra_padded(&bgra, sw, sh)
555 }
556 SurfaceEncoderKind::AV1Software(encoder) => encoder.encode(&rgba),
557 }
558 }
559
560 pub fn encode_pixels(&mut self, pixels: &PixelData) -> Option<(Vec<u8>, bool)> {
563 match pixels {
564 PixelData::Nv12 {
565 data,
566 y_stride,
567 uv_stride,
568 } => self.encode_nv12(data, *y_stride, *uv_stride),
569 PixelData::Bgra(bgra) => self.encode_bgra(bgra),
570 PixelData::Rgba(rgba) => self.encode(rgba),
571 #[cfg(target_os = "linux")]
572 PixelData::DmaBuf {
573 fd,
574 fourcc,
575 modifier,
576 stride,
577 offset,
578 ..
579 } => self
580 .encode_dmabuf(fd, *fourcc, *modifier, *stride, *offset)
581 .or_else(|| {
582 let w = self.width;
585 let h = self.height;
586 let rgba = pixels.to_rgba(w, h);
587 if !rgba.is_empty() {
588 self.encode(&rgba)
589 } else {
590 None
591 }
592 }),
593 #[cfg(not(target_os = "linux"))]
594 PixelData::DmaBuf { .. } => None,
595 #[cfg(target_os = "linux")]
596 PixelData::VaSurface { surface_id, .. } => self.encode_va_surface(*surface_id),
597 #[cfg(not(target_os = "linux"))]
598 PixelData::VaSurface { .. } => None,
599 }
600 }
601
602 #[cfg(target_os = "linux")]
604 fn encode_va_surface(&mut self, surface_id: u32) -> Option<(Vec<u8>, bool)> {
605 let mut result = match &mut self.kind {
606 SurfaceEncoderKind::H264Vaapi(enc) => enc.encode_va_surface(surface_id),
607 SurfaceEncoderKind::AV1Vaapi(enc) => enc.encode_va_surface(surface_id),
608 _ => None,
609 };
610 self.fixup_keyframe(&mut result);
611 result
612 }
613
614 #[cfg(target_os = "linux")]
617 fn encode_dmabuf(
618 &mut self,
619 fd: &std::os::fd::OwnedFd,
620 fourcc: u32,
621 modifier: u64,
622 stride: u32,
623 offset: u32,
624 ) -> Option<(Vec<u8>, bool)> {
625 use std::os::fd::AsRawFd;
626
627 let src_w = self.source_width;
630 let src_h = self.source_height;
631
632 let mut gpu_result = match &mut self.kind {
634 SurfaceEncoderKind::H264Vaapi(enc) => enc.encode_dmabuf_fd(
635 fd.as_raw_fd(),
636 fourcc,
637 modifier,
638 stride,
639 offset,
640 src_w,
641 src_h,
642 ),
643 SurfaceEncoderKind::AV1Vaapi(enc) => enc.encode_dmabuf_fd(
644 fd.as_raw_fd(),
645 fourcc,
646 modifier,
647 stride,
648 offset,
649 src_w,
650 src_h,
651 ),
652 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => enc
653 .encode_dmabuf_fd(
654 fd.as_raw_fd(),
655 fourcc,
656 modifier,
657 stride,
658 offset,
659 src_w,
660 src_h,
661 ),
662 _ => None,
663 };
664 if gpu_result.is_some() {
665 self.fixup_keyframe(&mut gpu_result);
666 return gpu_result;
667 }
668
669 self.encode_dmabuf_cpu_fallback(fd, fourcc, stride, offset)
674 }
675
676 #[cfg(target_os = "linux")]
679 fn encode_dmabuf_cpu_fallback(
680 &mut self,
681 fd: &std::os::fd::OwnedFd,
682 fourcc: u32,
683 stride: u32,
684 _offset: u32,
685 ) -> Option<(Vec<u8>, bool)> {
686 use std::os::fd::AsRawFd;
687
688 let w = self.source_width as usize;
689 let h = self.source_height as usize;
690 let stride = stride as usize;
691 let raw_fd = fd.as_raw_fd();
692
693 let file_size = unsafe { libc::lseek(raw_fd, 0, libc::SEEK_END) };
695 if file_size <= 0 {
696 return None;
697 }
698 let map_len = file_size as usize;
699
700 #[repr(C)]
702 struct DmaBufSync {
703 flags: u64,
704 }
705 const DMA_BUF_SYNC_READ: u64 = 1;
706 const DMA_BUF_SYNC_START: u64 = 0;
707 const DMA_BUF_SYNC_END: u64 = 4;
708 const DMA_BUF_IOCTL_SYNC: libc::c_ulong = 0x40086200;
712
713 {
717 let mut pfd = libc::pollfd {
718 fd: raw_fd,
719 events: libc::POLLIN,
720 revents: 0,
721 };
722 let ready = unsafe { libc::poll(&mut pfd, 1, 0) };
723 if ready <= 0 {
724 } else {
726 let sync_start = DmaBufSync {
727 flags: DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ,
728 };
729 unsafe {
730 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_start);
731 }
732 }
733 }
734
735 let ptr = unsafe {
737 libc::mmap(
738 std::ptr::null_mut(),
739 map_len,
740 libc::PROT_READ,
741 libc::MAP_SHARED,
742 raw_fd,
743 0,
744 )
745 };
746 if ptr == libc::MAP_FAILED {
747 let sync_end = DmaBufSync {
748 flags: DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ,
749 };
750 unsafe {
751 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_end);
752 }
753 return None;
754 }
755 let plane_data = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_len) };
756
757 let is_gl_fbo = {
760 let mut link = [0u8; 128];
761 let path = format!("/proc/self/fd/{raw_fd}\0");
762 let n = unsafe {
763 libc::readlink(path.as_ptr() as *const _, link.as_mut_ptr() as *mut _, 127)
764 };
765 !(n > 0 && link[..n as usize].starts_with(b"/dev/dri/"))
766 };
767
768 let result = if fourcc == blit_compositor::drm_fourcc::ARGB8888
769 || fourcc == blit_compositor::drm_fourcc::XRGB8888
770 {
771 let mut packed = Vec::with_capacity(w * h * 4);
773 for i in 0..h {
774 let row = if is_gl_fbo { h - 1 - i } else { i };
776 let start = row * stride;
777 let end = start + w * 4;
778 if end <= plane_data.len() {
779 packed.extend_from_slice(&plane_data[start..end]);
780 }
781 }
782 self.encode_bgra(&packed)
783 } else if fourcc == blit_compositor::drm_fourcc::ABGR8888
784 || fourcc == blit_compositor::drm_fourcc::XBGR8888
785 {
786 let mut packed = Vec::with_capacity(w * h * 4);
788 for i in 0..h {
789 let row = if is_gl_fbo { h - 1 - i } else { i };
790 let start = row * stride;
791 let end = start + w * 4;
792 if end <= plane_data.len() {
793 packed.extend_from_slice(&plane_data[start..end]);
794 }
795 }
796 self.encode(&packed)
797 } else if fourcc == blit_compositor::drm_fourcc::NV12 {
798 let uv_stride = stride; let y_size = stride * h;
803 let uv_h = h.div_ceil(2);
804 let uv_size = uv_stride * uv_h;
805 if map_len >= y_size + uv_size {
806 let out_stride = w;
808 let mut data = vec![0u8; out_stride * h + out_stride * uv_h];
809 for row in 0..h {
810 let src = row * stride;
811 let dst = row * out_stride;
812 if src + w <= plane_data.len() {
813 data[dst..dst + w].copy_from_slice(&plane_data[src..src + w]);
814 }
815 }
816 let uv_dst_base = out_stride * h;
817 for row in 0..uv_h {
818 let src = y_size + row * uv_stride;
819 let dst = uv_dst_base + row * out_stride;
820 if src + w <= plane_data.len() {
821 data[dst..dst + w].copy_from_slice(&plane_data[src..src + w]);
822 }
823 }
824 self.encode_nv12(&data, out_stride, out_stride)
825 } else {
826 None
827 }
828 } else {
829 None
830 };
831
832 unsafe {
834 libc::munmap(ptr, map_len);
835 }
836 let sync_end = DmaBufSync {
838 flags: DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ,
839 };
840 unsafe {
841 libc::ioctl(raw_fd, DMA_BUF_IOCTL_SYNC as _, &sync_end);
842 }
843
844 result
845 }
846
847 fn fixup_keyframe(&self, result: &mut Option<(Vec<u8>, bool)>) {
852 if let Some((data, is_key)) = result.as_mut()
853 && !*is_key
854 {
855 *is_key = match &self.kind {
856 SurfaceEncoderKind::NvencH264(_) => h264_stream_contains_idr(data),
857 SurfaceEncoderKind::NvencAV1(_) => av1_stream_contains_keyframe(data),
858 #[cfg(target_os = "linux")]
859 SurfaceEncoderKind::H264Vaapi(_) => h264_stream_contains_idr(data),
860 #[cfg(target_os = "linux")]
861 SurfaceEncoderKind::AV1Vaapi(_) => av1_stream_contains_keyframe(data),
862 _ => false,
863 };
864 }
865 }
866
867 fn encode_bgra(&mut self, bgra: &[u8]) -> Option<(Vec<u8>, bool)> {
869 let enc_w = self.width as usize;
870 let enc_h = self.height as usize;
871 let src_w = self.source_width as usize;
872 let src_h = self.source_height as usize;
873
874 let mut result = match &mut self.kind {
875 SurfaceEncoderKind::H264Software(encoder) => {
876 let yuv = bgra_to_yuv420_padded(bgra, src_w, src_h, enc_w, enc_h);
877 let yuv_buf = YUVBuffer::from_vec(yuv, enc_w, enc_h);
878 encoder.encode_yuv(&yuv_buf, self.width, self.height)
879 }
880 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
881 enc.encode_bgra_padded(bgra, src_w, src_h)
882 }
883 #[cfg(target_os = "linux")]
884 SurfaceEncoderKind::H264Vaapi(enc) => enc.encode_bgra_padded(bgra, src_w, src_h),
885 #[cfg(target_os = "linux")]
886 SurfaceEncoderKind::AV1Vaapi(enc) => enc.encode_bgra_padded(bgra, src_w, src_h),
887 SurfaceEncoderKind::AV1Software(encoder) => {
888 let yuv = bgra_to_yuv420_padded(bgra, src_w, src_h, enc_w, enc_h);
889 encoder.encode_yuv_planes(&yuv)
890 }
891 };
892 self.fixup_keyframe(&mut result);
893 result
894 }
895
896 fn encode_nv12(
899 &mut self,
900 data: &[u8],
901 y_stride: usize,
902 uv_stride: usize,
903 ) -> Option<(Vec<u8>, bool)> {
904 let src_w = self.source_width as usize;
906 let src_h = self.source_height as usize;
907
908 let mut result = match &mut self.kind {
909 SurfaceEncoderKind::H264Software(encoder) => {
910 let enc_w = self.width as usize;
911 let enc_h = self.height as usize;
912 if enc_w == src_w && enc_h == src_h {
913 let yuv = nv12_to_yuv420(data, y_stride, uv_stride, src_w, src_h);
914 let yuv_buf = YUVBuffer::from_vec(yuv, enc_w, enc_h);
915 encoder.encode_yuv(&yuv_buf, self.width, self.height)
916 } else {
917 let pd = PixelData::Nv12 {
918 data: std::sync::Arc::new(data.to_vec()),
919 y_stride,
920 uv_stride,
921 };
922 let rgba = pd.to_rgba(self.source_width, self.source_height);
923 return self.encode(&rgba);
924 }
925 }
926 SurfaceEncoderKind::NvencH264(enc) | SurfaceEncoderKind::NvencAV1(enc) => {
927 enc.encode_nv12(data, y_stride, uv_stride, src_h)
929 }
930 #[cfg(target_os = "linux")]
931 SurfaceEncoderKind::H264Vaapi(enc) => {
932 let uv_offset = y_stride * src_h;
933 let y_data = &data[..uv_offset];
934 let uv_data = &data[uv_offset..];
935 enc.encode_nv12(y_data, uv_data, y_stride, uv_stride)
936 }
937 #[cfg(target_os = "linux")]
938 SurfaceEncoderKind::AV1Vaapi(enc) => {
939 let uv_offset = y_stride * src_h;
940 let y_data = &data[..uv_offset];
941 let uv_data = &data[uv_offset..];
942 enc.encode_nv12(y_data, uv_data, y_stride, uv_stride)
943 }
944 SurfaceEncoderKind::AV1Software(encoder) => {
945 encoder.encode_nv12(data, y_stride, uv_stride, src_w, src_h)
946 }
947 };
948 self.fixup_keyframe(&mut result);
949 result
950 }
951}
952
953fn validate_surface_dimensions(
954 width: u32,
955 height: u32,
956 _preference: SurfaceEncoderPreference,
957) -> Result<(), String> {
958 if width == 0 || height == 0 {
959 return Err("surface encoder requires non-zero dimensions".into());
960 }
961 let _ = expected_rgba_len(width, height)
964 .ok_or_else(|| format!("surface encoder dimensions overflow for {width}x{height}"))?;
965 Ok(())
966}
967
968fn expected_rgba_len(width: u32, height: u32) -> Option<usize> {
969 (width as usize)
970 .checked_mul(height as usize)?
971 .checked_mul(4)
972}
973
974#[inline(always)]
980fn rgb_to_y(r: i32, g: i32, b: i32) -> u8 {
981 ((66 * r + 129 * g + 25 * b + 128) >> 8)
982 .wrapping_add(16)
983 .clamp(0, 255) as u8
984}
985
986#[inline(always)]
987fn rgb_to_u(r: i32, g: i32, b: i32) -> u8 {
988 ((-38 * r - 74 * g + 112 * b + 128) >> 8)
989 .wrapping_add(128)
990 .clamp(0, 255) as u8
991}
992
993#[inline(always)]
994fn rgb_to_v(r: i32, g: i32, b: i32) -> u8 {
995 ((112 * r - 94 * g - 18 * b + 128) >> 8)
996 .wrapping_add(128)
997 .clamp(0, 255) as u8
998}
999
1000#[inline(always)]
1009fn compute_y_plane(
1010 src: &[u8],
1011 width: usize,
1012 height: usize,
1013 y_plane: &mut [u8],
1014 r_off: usize,
1015 g_off: usize,
1016 b_off: usize,
1017) {
1018 let total = width * height;
1019 for (px, y_out) in y_plane[..total].iter_mut().enumerate() {
1020 let i = px * 4;
1021 let r = src[i + r_off] as i32;
1022 let g = src[i + g_off] as i32;
1023 let b = src[i + b_off] as i32;
1024 *y_out = rgb_to_y(r, g, b);
1025 }
1026}
1027
1028#[inline(always)]
1030fn compute_uv_planes(
1031 src: &[u8],
1032 width: usize,
1033 height: usize,
1034 u_plane: &mut [u8],
1035 v_plane: &mut [u8],
1036 r_off: usize,
1037 g_off: usize,
1038 b_off: usize,
1039) {
1040 let chroma_w = width.div_ceil(2);
1041 let chroma_h = height.div_ceil(2);
1042 for cy in 0..chroma_h {
1043 for cx in 0..chroma_w {
1044 let row = cy * 2;
1045 let col = cx * 2;
1046 let mut u_sum = 0i32;
1048 let mut v_sum = 0i32;
1049 for dy in 0..2u32 {
1050 for dx in 0..2u32 {
1051 let sr = (row + dy as usize).min(height - 1);
1052 let sc = (col + dx as usize).min(width - 1);
1053 let i = (sr * width + sc) * 4;
1054 let r = src[i + r_off] as i32;
1055 let g = src[i + g_off] as i32;
1056 let b = src[i + b_off] as i32;
1057 u_sum += rgb_to_u(r, g, b) as i32;
1058 v_sum += rgb_to_v(r, g, b) as i32;
1059 }
1060 }
1061 let idx = cy * chroma_w + cx;
1062 u_plane[idx] = (u_sum / 4) as u8;
1063 v_plane[idx] = (v_sum / 4) as u8;
1064 }
1065 }
1066}
1067
1068#[inline(always)]
1071fn compute_y_plane_padded(
1072 src: &[u8],
1073 src_w: usize,
1074 src_h: usize,
1075 enc_w: usize,
1076 enc_h: usize,
1077 y_plane: &mut [u8],
1078 r_off: usize,
1079 g_off: usize,
1080 b_off: usize,
1081) {
1082 for row in 0..enc_h {
1083 let sr = row.min(src_h - 1);
1084 for col in 0..enc_w {
1085 let sc = col.min(src_w - 1);
1086 let i = (sr * src_w + sc) * 4;
1087 let r = src[i + r_off] as i32;
1088 let g = src[i + g_off] as i32;
1089 let b = src[i + b_off] as i32;
1090 y_plane[row * enc_w + col] = rgb_to_y(r, g, b);
1091 }
1092 }
1093}
1094
1095#[inline(always)]
1098fn compute_uv_planes_padded(
1099 src: &[u8],
1100 src_w: usize,
1101 src_h: usize,
1102 enc_w: usize,
1103 enc_h: usize,
1104 u_plane: &mut [u8],
1105 v_plane: &mut [u8],
1106 r_off: usize,
1107 g_off: usize,
1108 b_off: usize,
1109) {
1110 let chroma_w = enc_w.div_ceil(2);
1111 let chroma_h = enc_h.div_ceil(2);
1112 for cy in 0..chroma_h {
1113 for cx in 0..chroma_w {
1114 let row = cy * 2;
1115 let col = cx * 2;
1116 let mut u_sum = 0i32;
1117 let mut v_sum = 0i32;
1118 for dy in 0..2u32 {
1119 for dx in 0..2u32 {
1120 let sr = (row + dy as usize).min(src_h - 1);
1121 let sc = (col + dx as usize).min(src_w - 1);
1122 let i = (sr * src_w + sc) * 4;
1123 let r = src[i + r_off] as i32;
1124 let g = src[i + g_off] as i32;
1125 let b = src[i + b_off] as i32;
1126 u_sum += rgb_to_u(r, g, b) as i32;
1127 v_sum += rgb_to_v(r, g, b) as i32;
1128 }
1129 }
1130 let idx = cy * chroma_w + cx;
1131 u_plane[idx] = (u_sum / 4) as u8;
1132 v_plane[idx] = (v_sum / 4) as u8;
1133 }
1134 }
1135}
1136
1137fn bgra_to_yuv420_padded(
1141 bgra: &[u8],
1142 src_w: usize,
1143 src_h: usize,
1144 enc_w: usize,
1145 enc_h: usize,
1146) -> Vec<u8> {
1147 let y_size = enc_w * enc_h;
1148 let uv_w = enc_w.div_ceil(2);
1153 let uv_size = uv_w * enc_h.div_ceil(2);
1154 let mut yuv = vec![0u8; y_size + uv_size * 2];
1155 let (y_plane, uv) = yuv.split_at_mut(y_size);
1156 let (u_plane, v_plane) = uv.split_at_mut(uv_size);
1157 compute_y_plane_padded(bgra, src_w, src_h, enc_w, enc_h, y_plane, 2, 1, 0);
1159 compute_uv_planes_padded(bgra, src_w, src_h, enc_w, enc_h, u_plane, v_plane, 2, 1, 0);
1160 yuv
1161}
1162
1163fn rgba_to_yuv420(rgba: &[u8], width: usize, height: usize) -> Vec<u8> {
1165 let y_size = width * height;
1166 let uv_w = width.div_ceil(2);
1167 let uv_size = uv_w * height.div_ceil(2);
1168 let mut yuv = vec![0u8; y_size + uv_size * 2];
1169 let (y_plane, uv) = yuv.split_at_mut(y_size);
1170 let (u_plane, v_plane) = uv.split_at_mut(uv_size);
1171 compute_y_plane(rgba, width, height, y_plane, 0, 1, 2);
1173 compute_uv_planes(rgba, width, height, u_plane, v_plane, 0, 1, 2);
1174 yuv
1175}
1176
1177fn nv12_to_yuv420(
1181 data: &[u8],
1182 y_stride: usize,
1183 uv_stride: usize,
1184 width: usize,
1185 height: usize,
1186) -> Vec<u8> {
1187 let y_size = width * height;
1188 let uv_w = width.div_ceil(2);
1189 let uv_h = height.div_ceil(2);
1190 let uv_size = uv_w * uv_h;
1191 let mut yuv = vec![0u8; y_size + uv_size * 2];
1192 let (y_out, uv_out) = yuv.split_at_mut(y_size);
1193 let (u_out, v_out) = uv_out.split_at_mut(uv_size);
1194
1195 let uv_offset = y_stride * height;
1196
1197 for row in 0..height {
1199 let src = row * y_stride;
1200 let dst = row * width;
1201 y_out[dst..dst + width].copy_from_slice(&data[src..src + width]);
1202 }
1203
1204 let src_uv_pairs = width / 2;
1208 for row in 0..uv_h {
1209 let src_start = uv_offset + row.min(height / 2 - 1) * uv_stride;
1210 let dst_start = row * uv_w;
1211 for col in 0..uv_w {
1212 let sc = col.min(src_uv_pairs.saturating_sub(1));
1213 u_out[dst_start + col] = data[src_start + sc * 2];
1214 v_out[dst_start + col] = data[src_start + sc * 2 + 1];
1215 }
1216 }
1217
1218 yuv
1219}
1220
1221fn h264_stream_contains_idr(data: &[u8]) -> bool {
1223 annex_b_contains_nal(data, |byte| (byte & 0x1f) == 5)
1224}
1225
1226fn annex_b_contains_nal(data: &[u8], pred: impl Fn(u8) -> bool) -> bool {
1228 let mut i = 0usize;
1229 while i < data.len() {
1230 let start_code_len = if data[i..].starts_with(&[0, 0, 0, 1]) {
1231 4
1232 } else if data[i..].starts_with(&[0, 0, 1]) {
1233 3
1234 } else {
1235 i += 1;
1236 continue;
1237 };
1238
1239 let nal_header = i + start_code_len;
1240 if let Some(&byte) = data.get(nal_header)
1241 && pred(byte)
1242 {
1243 return true;
1244 }
1245
1246 i = nal_header.saturating_add(1);
1247 }
1248
1249 false
1250}
1251
1252fn av1_stream_contains_keyframe(data: &[u8]) -> bool {
1260 let mut pos = 0;
1264 while pos < data.len() {
1265 let header = data[pos];
1266 let obu_type = (header >> 3) & 0xF;
1267 let has_extension = (header >> 2) & 1;
1268 let has_size = (header >> 1) & 1;
1269 pos += 1;
1270
1271 if has_extension != 0 {
1273 if pos >= data.len() {
1274 break;
1275 }
1276 pos += 1;
1277 }
1278
1279 if obu_type == 1 {
1281 return true;
1282 }
1283
1284 if has_size != 0 {
1287 let mut size: u64 = 0;
1288 let mut shift = 0u32;
1289 while pos < data.len() {
1290 let byte = data[pos];
1291 pos += 1;
1292 size |= ((byte & 0x7F) as u64) << shift;
1293 if byte & 0x80 == 0 {
1294 break;
1295 }
1296 shift += 7;
1297 if shift >= 56 {
1298 return false; }
1300 }
1301 pos = pos.saturating_add(size as usize);
1302 } else {
1303 break;
1306 }
1307 }
1308 false
1309}
1310
1311struct SoftwareH264Encoder {
1312 encoder: OpenH264Encoder,
1313}
1314
1315impl SoftwareH264Encoder {
1316 fn new(quality: SurfaceQuality) -> Result<Self, String> {
1317 use openh264::encoder::{EncoderConfig, RateControlMode};
1318 let config = EncoderConfig::new()
1319 .set_bitrate_bps(quality.openh264_bitrate())
1320 .rate_control_mode(RateControlMode::Bitrate);
1321 let encoder =
1322 OpenH264Encoder::with_api_config(openh264::OpenH264API::from_source(), config)
1323 .map_err(|err| format!("failed to create OpenH264 encoder: {err:?}"))?;
1324 Ok(Self { encoder })
1325 }
1326
1327 fn request_keyframe(&mut self) {
1328 self.encoder.force_intra_frame();
1329 }
1330
1331 fn encode(&mut self, rgba: &[u8], width: u32, height: u32) -> Option<(Vec<u8>, bool)> {
1332 let yuv = rgba_to_yuv420(rgba, width as usize, height as usize);
1333 let yuv_buf = YUVBuffer::from_vec(yuv, width as usize, height as usize);
1334 self.encode_yuv(&yuv_buf, width, height)
1335 }
1336
1337 fn encode_yuv(
1339 &mut self,
1340 yuv_buf: &YUVBuffer,
1341 width: u32,
1342 height: u32,
1343 ) -> Option<(Vec<u8>, bool)> {
1344 let bitstream = match self.encoder.encode(yuv_buf) {
1345 Ok(bs) => bs,
1346 Err(e) => {
1347 eprintln!("[surface-encoder] openh264 encode failed {width}x{height}: {e:?}");
1348 return None;
1349 }
1350 };
1351 let nal_data = bitstream.to_vec();
1352 if nal_data.is_empty() {
1353 eprintln!("[surface-encoder] openh264 produced empty NAL {width}x{height}");
1354 return None;
1355 }
1356 let is_keyframe = h264_stream_contains_idr(&nal_data);
1357 Some((nal_data, is_keyframe))
1358 }
1359}
1360
1361struct SoftwareAV1Encoder {
1366 ctx: rav1e::Context<u8>,
1367 width: usize,
1368 height: usize,
1369 force_keyframe: bool,
1370}
1371
1372impl SoftwareAV1Encoder {
1373 fn new(width: u32, height: u32, quality: SurfaceQuality) -> Result<Self, String> {
1374 use rav1e::prelude::*;
1375
1376 let mut speed = SpeedSettings::from_preset(quality.av1_speed());
1377 speed.rdo_lookahead_frames = 1;
1378 let enc = EncoderConfig {
1379 width: width as usize,
1380 height: height as usize,
1381 chroma_sampling: ChromaSampling::Cs420,
1382 chroma_sample_position: ChromaSamplePosition::Unknown,
1383 speed_settings: speed,
1384 low_latency: true,
1385 min_key_frame_interval: 0,
1386 max_key_frame_interval: 60,
1387 quantizer: quality.av1_quantizer(),
1388 min_quantizer: quality.av1_min_quantizer(),
1389 bitrate: 0,
1390 ..Default::default()
1391 };
1392 let cfg = Config::new().with_encoder_config(enc);
1393 let ctx = cfg
1394 .new_context()
1395 .map_err(|e| format!("rav1e context creation failed: {e}"))?;
1396 Ok(Self {
1397 ctx,
1398 width: width as usize,
1399 height: height as usize,
1400 force_keyframe: false,
1401 })
1402 }
1403
1404 fn request_keyframe(&mut self) {
1405 self.force_keyframe = true;
1406 }
1407
1408 fn encode(&mut self, rgba: &[u8]) -> Option<(Vec<u8>, bool)> {
1409 let yuv = rgba_to_yuv420(rgba, self.width, self.height);
1410 self.encode_yuv_planes(&yuv)
1411 }
1412
1413 fn encode_nv12(
1414 &mut self,
1415 data: &[u8],
1416 y_stride: usize,
1417 uv_stride: usize,
1418 width: usize,
1419 height: usize,
1420 ) -> Option<(Vec<u8>, bool)> {
1421 let yuv = nv12_to_yuv420(data, y_stride, uv_stride, width, height);
1422 self.encode_yuv_planes(&yuv)
1423 }
1424
1425 fn encode_yuv_planes(&mut self, yuv: &[u8]) -> Option<(Vec<u8>, bool)> {
1427 let width = self.width;
1428 let height = self.height;
1429 let y_size = width * height;
1430 let uv_w = width.div_ceil(2);
1431 let uv_h = height.div_ceil(2);
1432 let uv_size = uv_w * uv_h;
1433
1434 let y_plane = &yuv[..y_size];
1435 let u_plane = &yuv[y_size..y_size + uv_size];
1436 let v_plane = &yuv[y_size + uv_size..];
1437
1438 let mut frame = self.ctx.new_frame();
1439 frame.planes[0].copy_from_raw_u8(y_plane, width, 1);
1440 frame.planes[1].copy_from_raw_u8(u_plane, uv_w, 1);
1441 frame.planes[2].copy_from_raw_u8(v_plane, uv_w, 1);
1442
1443 self.send_and_receive(frame)
1444 }
1445
1446 fn send_and_receive(&mut self, frame: rav1e::Frame<u8>) -> Option<(Vec<u8>, bool)> {
1447 use rav1e::prelude::*;
1448
1449 if self.force_keyframe {
1450 let params = FrameParameters {
1451 frame_type_override: FrameTypeOverride::Key,
1452 ..Default::default()
1453 };
1454 if self.ctx.send_frame((frame, params)).is_ok() {
1455 self.force_keyframe = false;
1456 }
1457 } else {
1458 let _ = self.ctx.send_frame(frame);
1459 }
1460
1461 match self.ctx.receive_packet() {
1462 Ok(packet) => {
1463 let is_key = packet.frame_type == rav1e::prelude::FrameType::KEY;
1464 Some((packet.data, is_key))
1465 }
1466 Err(rav1e::EncoderStatus::Encoded) | Err(rav1e::EncoderStatus::NeedMoreData) => None,
1467 Err(_) => None,
1468 }
1469 }
1470}
1471
1472#[cfg(test)]
1473mod tests {
1474 use super::*;
1475
1476 fn make_obu(obu_type: u8, payload: &[u8]) -> Vec<u8> {
1478 let header = (obu_type & 0xF) << 3 | 0b10; let mut obu = vec![header];
1481 let mut size = payload.len();
1483 loop {
1484 let mut byte = (size & 0x7F) as u8;
1485 size >>= 7;
1486 if size > 0 {
1487 byte |= 0x80;
1488 }
1489 obu.push(byte);
1490 if size == 0 {
1491 break;
1492 }
1493 }
1494 obu.extend_from_slice(payload);
1495 obu
1496 }
1497
1498 #[test]
1499 fn av1_keyframe_with_sequence_header_only() {
1500 let data = make_obu(1, &[0xAA; 10]);
1502 assert!(av1_stream_contains_keyframe(&data));
1503 }
1504
1505 #[test]
1506 fn av1_keyframe_with_temporal_delimiter_prefix() {
1507 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));
1513 }
1514
1515 #[test]
1516 fn av1_non_keyframe_with_temporal_delimiter() {
1517 let mut data = make_obu(2, &[]);
1519 data.extend(make_obu(6, &[0xDD; 15]));
1520 assert!(!av1_stream_contains_keyframe(&data));
1521 }
1522
1523 #[test]
1524 fn av1_non_keyframe_frame_header_only() {
1525 let data = make_obu(3, &[0xEE; 5]);
1527 assert!(!av1_stream_contains_keyframe(&data));
1528 }
1529
1530 #[test]
1531 fn av1_empty_stream() {
1532 assert!(!av1_stream_contains_keyframe(&[]));
1533 }
1534
1535 #[test]
1536 fn av1_keyframe_large_leb128_size() {
1537 let mut data = make_obu(2, &[0x00; 200]);
1540 data.extend(make_obu(1, &[0xFF; 4]));
1541 assert!(av1_stream_contains_keyframe(&data));
1542 }
1543}