1#![allow(unsafe_op_in_unsafe_fn)]
25
26use std::ffi::{c_char, c_int, c_void, CStr, CString};
27use std::mem;
28use std::os::fd::AsRawFd;
29use std::ptr;
30use std::sync::Once;
31
32use ffmpeg_sys_next as ff;
33use libc::{close, dup, lseek, SEEK_END};
34
35use crate::encoders::QP_HYSTERESIS_LIMIT;
36use crate::RustCaptureSettings;
37use smithay::backend::allocator::{dmabuf::Dmabuf, Buffer};
38
39static FF_INIT: Once = Once::new();
42const AV_DRM_MAX_PLANES: usize = 4;
44const FULLCOLOR_SW_FORMATS: [ff::AVPixelFormat; 2] = [
48 ff::AVPixelFormat::AV_PIX_FMT_YUV444P,
49 ff::AVPixelFormat::AV_PIX_FMT_VUYX,
50];
51
52#[repr(C)]
60#[derive(Clone, Copy, Debug)]
61struct AVDRMObjectDescriptor {
62 pub fd: c_int,
63 pub size: usize,
64 pub format_modifier: u64,
65}
66
67#[repr(C)]
70#[derive(Clone, Copy, Debug)]
71struct AVDRMPlaneDescriptor {
72 pub object_index: c_int,
73 pub offset: isize,
74 pub pitch: isize,
75}
76
77#[repr(C)]
79#[derive(Clone, Copy, Debug)]
80struct AVDRMLayerDescriptor {
81 pub format: u32,
82 pub nb_planes: c_int,
83 pub planes: [AVDRMPlaneDescriptor; AV_DRM_MAX_PLANES],
84}
85
86#[repr(C)]
89#[derive(Clone, Copy, Debug)]
90struct AVDRMFrameDescriptor {
91 pub nb_objects: c_int,
92 pub objects: [AVDRMObjectDescriptor; AV_DRM_MAX_PLANES],
93 pub nb_layers: c_int,
94 pub layers: [AVDRMLayerDescriptor; AV_DRM_MAX_PLANES],
95}
96
97struct DmabufResources {
102 fds: Vec<c_int>,
103}
104
105unsafe extern "C" fn release_drm_frame(opaque: *mut c_void, data: *mut u8) {
114 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
115 let resources = Box::from_raw(opaque as *mut DmabufResources);
116 for &fd in &resources.fds {
117 close(fd);
118 }
119 if !data.is_null() {
120 ff::av_free(data as *mut c_void);
121 }
122 }));
123}
124
125fn pix_fmt_name(fmt: ff::AVPixelFormat) -> String {
128 unsafe {
129 let name = ff::av_get_pix_fmt_name(fmt);
130 if name.is_null() {
131 format!("{fmt:?}")
132 } else {
133 CStr::from_ptr(name).to_string_lossy().into_owned()
134 }
135 }
136}
137
138unsafe fn fullcolor_sw_format(device: *mut ff::AVBufferRef) -> Option<ff::AVPixelFormat> {
149 let constraints = ff::av_hwdevice_get_hwframe_constraints(device, ptr::null());
150 if constraints.is_null() {
151 return None;
152 }
153 let mut carried = Vec::new();
154 let mut fmt = (*constraints).valid_sw_formats;
155 if !fmt.is_null() {
156 while *fmt != ff::AVPixelFormat::AV_PIX_FMT_NONE {
157 carried.push(*fmt);
158 fmt = fmt.add(1);
159 }
160 }
161 let mut owned = constraints;
162 ff::av_hwframe_constraints_free(&mut owned);
163 preferred_fullcolor_format(&carried)
164}
165
166fn preferred_fullcolor_format(carried: &[ff::AVPixelFormat]) -> Option<ff::AVPixelFormat> {
169 FULLCOLOR_SW_FORMATS
170 .into_iter()
171 .find(|wanted| carried.contains(wanted))
172}
173
174fn pack_i444_as_vuyx(planar: &[u8], packed: &mut Vec<u8>, plane: usize) {
181 packed.resize(plane * 4, 0);
182 let (y, chroma) = planar.split_at(plane);
183 let (u, v) = chroma.split_at(plane);
184 for (i, px) in packed.chunks_exact_mut(4).enumerate() {
185 px[0] = v[i];
186 px[1] = u[i];
187 px[2] = y[i];
188 px[3] = 0xFF;
189 }
190}
191
192fn ff_err_str(err: i32) -> String {
194 unsafe {
195 let mut errbuf = [0 as c_char; 128];
196 ff::av_strerror(err, errbuf.as_mut_ptr(), 128);
197 CStr::from_ptr(errbuf.as_ptr())
198 .to_string_lossy()
199 .into_owned()
200 }
201}
202
203unsafe fn set_colorimetry(ctx: *mut ff::AVCodecContext) {
213 (*ctx).color_range = ff::AVColorRange::AVCOL_RANGE_MPEG;
214 (*ctx).colorspace = ff::AVColorSpace::AVCOL_SPC_BT709;
215 (*ctx).color_primaries = ff::AVColorPrimaries::AVCOL_PRI_BT709;
216 (*ctx).color_trc = ff::AVColorTransferCharacteristic::AVCOL_TRC_BT709;
217}
218
219pub struct VaapiEncoder {
243 encoder_ctx: *mut ff::AVCodecContext,
244 codec: *const ff::AVCodec,
245
246 #[allow(dead_code)]
247 hw_device_ctx: *mut ff::AVBufferRef,
248 #[allow(dead_code)]
249 drm_device_ctx: *mut ff::AVBufferRef,
250 #[allow(dead_code)]
251 drm_frames_ctx: *mut ff::AVBufferRef,
252
253 enc_frames_ctx: *mut ff::AVBufferRef,
254
255 filter_graph: *mut ff::AVFilterGraph,
256 buffersrc_ctx: *mut ff::AVFilterContext,
257 buffersink_ctx: *mut ff::AVFilterContext,
258
259 video_frame: *mut ff::AVFrame,
260 sw_frame: *mut ff::AVFrame,
261 hw_frame: *mut ff::AVFrame,
262
263 packet: *mut ff::AVPacket,
264
265 width: i32,
266 height: i32,
267 fps: i32,
268
269 sw_format: ff::AVPixelFormat,
270 packed_444: Vec<u8>,
271
272 current_qp: u32,
273 qp_hysteresis_counter: u32,
274
275 cbr_mode: bool,
276 current_bitrate_kbps: i32,
277 current_vbv_mult: f64,
278 current_kf_s: f64,
279
280 omit_stripe_headers: bool,
281}
282
283unsafe impl Send for VaapiEncoder {}
287
288impl Drop for VaapiEncoder {
293 fn drop(&mut self) {
294 unsafe {
295 if !self.packet.is_null() {
296 ff::av_packet_free(&mut self.packet);
297 }
298 if !self.video_frame.is_null() {
299 ff::av_frame_free(&mut self.video_frame);
300 }
301 if !self.sw_frame.is_null() {
302 ff::av_frame_free(&mut self.sw_frame);
303 }
304 if !self.hw_frame.is_null() {
305 ff::av_frame_free(&mut self.hw_frame);
306 }
307
308 if !self.filter_graph.is_null() {
309 ff::avfilter_graph_free(&mut self.filter_graph);
310 }
311 if !self.encoder_ctx.is_null() {
312 ff::avcodec_free_context(&mut self.encoder_ctx);
313 }
314
315 if !self.enc_frames_ctx.is_null() {
316 ff::av_buffer_unref(&mut self.enc_frames_ctx);
317 }
318 if !self.drm_frames_ctx.is_null() {
319 ff::av_buffer_unref(&mut self.drm_frames_ctx);
320 }
321 if !self.hw_device_ctx.is_null() {
322 ff::av_buffer_unref(&mut self.hw_device_ctx);
323 }
324 if !self.drm_device_ctx.is_null() {
325 ff::av_buffer_unref(&mut self.drm_device_ctx);
326 }
327 }
328 }
329}
330
331impl VaapiEncoder {
332 pub fn new(
336 settings: &RustCaptureSettings,
337 ) -> Result<Self, String> {
338 Self::new_impl(settings, false)
339 }
340
341 pub fn new_host(
346 settings: &RustCaptureSettings,
347 ) -> Result<Self, String> {
348 Self::new_impl(settings, true)
349 }
350
351 fn new_impl(
395 settings: &RustCaptureSettings,
396 host_input: bool,
397 ) -> Result<Self, String> {
398 FF_INIT.call_once(|| {});
399
400 let width = settings.width;
401 let height = settings.height;
402 let fps = (settings.target_fps as i32).max(1);
404
405 unsafe {
406 let mut drm_device_ctx: *mut ff::AVBufferRef = ptr::null_mut();
407 let render_node = if settings.encode_node_index >= 0 {
408 format!("/dev/dri/renderD{}", 128 + settings.encode_node_index)
409 } else {
410 "/dev/dri/renderD128".to_string()
411 };
412 let device_url = CString::new(render_node).unwrap();
413
414 let ret = ff::av_hwdevice_ctx_create(
415 &mut drm_device_ctx,
416 ff::AVHWDeviceType::AV_HWDEVICE_TYPE_DRM,
417 device_url.as_ptr(),
418 ptr::null_mut(),
419 0,
420 );
421 if ret < 0 {
422 return Err(format!("Failed to create DRM device: {}", ff_err_str(ret)));
423 }
424
425 let mut hw_device_ctx: *mut ff::AVBufferRef = ptr::null_mut();
426 let ret = ff::av_hwdevice_ctx_create_derived(
427 &mut hw_device_ctx,
428 ff::AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI,
429 drm_device_ctx,
430 0,
431 );
432 if ret < 0 {
433 ff::av_buffer_unref(&mut drm_device_ctx);
434 return Err(format!(
435 "Failed to derive VAAPI device: {}",
436 ff_err_str(ret)
437 ));
438 }
439
440 let enc_sw_format = if settings.video_fullcolor {
441 match fullcolor_sw_format(hw_device_ctx) {
442 Some(fmt) => fmt,
443 None => {
444 ff::av_buffer_unref(&mut hw_device_ctx);
445 ff::av_buffer_unref(&mut drm_device_ctx);
446 return Err(
447 "4:4:4 requested but this VA-API driver carries no 4:4:4 surface format"
448 .into(),
449 );
450 }
451 }
452 } else {
453 ff::AVPixelFormat::AV_PIX_FMT_NV12
454 };
455
456 let mut drm_frames_ref = ff::av_hwframe_ctx_alloc(drm_device_ctx);
457 if drm_frames_ref.is_null() {
458 ff::av_buffer_unref(&mut hw_device_ctx);
459 ff::av_buffer_unref(&mut drm_device_ctx);
460 return Err("Failed to alloc DRM frames ctx".into());
461 }
462
463 let drm_frames = (*drm_frames_ref).data as *mut ff::AVHWFramesContext;
464 (*drm_frames).format = ff::AVPixelFormat::AV_PIX_FMT_DRM_PRIME;
465 (*drm_frames).sw_format = ff::AVPixelFormat::AV_PIX_FMT_BGRA;
466 (*drm_frames).width = width;
467 (*drm_frames).height = height;
468 (*drm_frames).initial_pool_size = 0;
469
470 if ff::av_hwframe_ctx_init(drm_frames_ref) < 0 {
471 ff::av_buffer_unref(&mut drm_frames_ref);
472 ff::av_buffer_unref(&mut hw_device_ctx);
473 ff::av_buffer_unref(&mut drm_device_ctx);
474 return Err("Failed to init DRM frames ctx".into());
475 }
476
477 let codec_name = CString::new("h264_vaapi").unwrap();
478 let codec = ff::avcodec_find_encoder_by_name(codec_name.as_ptr());
479 if codec.is_null() {
480 ff::av_buffer_unref(&mut drm_frames_ref);
481 ff::av_buffer_unref(&mut hw_device_ctx);
482 ff::av_buffer_unref(&mut drm_device_ctx);
483 return Err("h264_vaapi encoder not found".into());
484 }
485
486 let aligned_width = (width + 15) & !15;
487 let aligned_height = (height + 31) & !31;
488
489 let mut enc_frames_ref = ff::av_hwframe_ctx_alloc(hw_device_ctx);
490 if enc_frames_ref.is_null() {
491 ff::av_buffer_unref(&mut drm_frames_ref);
492 ff::av_buffer_unref(&mut hw_device_ctx);
493 ff::av_buffer_unref(&mut drm_device_ctx);
494 return Err("Failed to allocate encoder frames ctx".into());
495 }
496 let enc_frames = (*enc_frames_ref).data as *mut ff::AVHWFramesContext;
497 (*enc_frames).format = ff::AVPixelFormat::AV_PIX_FMT_VAAPI;
498 (*enc_frames).sw_format = enc_sw_format;
499 (*enc_frames).width = aligned_width;
500 (*enc_frames).height = aligned_height;
501 (*enc_frames).initial_pool_size = 20;
502
503 if ff::av_hwframe_ctx_init(enc_frames_ref) < 0 {
504 ff::av_buffer_unref(&mut enc_frames_ref);
505 ff::av_buffer_unref(&mut drm_frames_ref);
506 ff::av_buffer_unref(&mut hw_device_ctx);
507 ff::av_buffer_unref(&mut drm_device_ctx);
508 return Err(format!(
509 "Failed to init encoder frames ctx: this VA-API driver allocates no {} surfaces",
510 pix_fmt_name(enc_sw_format)
511 ));
512 }
513
514 let mut saved_enc_frames_ctx = ff::av_buffer_ref(enc_frames_ref);
515
516 let mut encoder_ctx = ff::avcodec_alloc_context3(codec);
517 if encoder_ctx.is_null() {
518 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
519 ff::av_buffer_unref(&mut enc_frames_ref);
520 ff::av_buffer_unref(&mut drm_frames_ref);
521 ff::av_buffer_unref(&mut hw_device_ctx);
522 ff::av_buffer_unref(&mut drm_device_ctx);
523 return Err("Failed to allocate encoder context".into());
524 }
525 (*encoder_ctx).width = width;
526 (*encoder_ctx).height = height;
527 (*encoder_ctx).time_base = ff::AVRational { num: 1, den: fps };
528 (*encoder_ctx).framerate = ff::AVRational { num: fps, den: 1 };
529 (*encoder_ctx).pix_fmt = ff::AVPixelFormat::AV_PIX_FMT_VAAPI;
530 (*encoder_ctx).hw_device_ctx = ff::av_buffer_ref(hw_device_ctx);
531 (*encoder_ctx).hw_frames_ctx = ff::av_buffer_ref(enc_frames_ref);
532 (*encoder_ctx).max_b_frames = 0;
533 (*encoder_ctx).gop_size = std::ffi::c_int::MAX;
534 (*encoder_ctx).slices = 4;
535 (*encoder_ctx).compression_level = 6;
536 set_colorimetry(encoder_ctx);
537
538 ff::av_buffer_unref(&mut enc_frames_ref);
539
540 let mut opts: *mut ff::AVDictionary = ptr::null_mut();
541 let set_opt = |d: &mut *mut ff::AVDictionary, k: &str, v: &str| {
542 let ck = CString::new(k).unwrap();
543 let cv = CString::new(v).unwrap();
544 ff::av_dict_set(d, ck.as_ptr(), cv.as_ptr(), 0);
545 };
546
547 if settings.video_cbr_mode {
548 let bps = (settings.video_bitrate_kbps.max(0) as i64).saturating_mul(1000);
549 let vbv = crate::encoders::vbv_bits(
550 bps.min(u32::MAX as i64) as u32,
551 settings.target_fps,
552 settings.keyframe_interval_s,
553 settings.video_vbv_multiplier,
554 );
555 (*encoder_ctx).bit_rate = bps;
556 (*encoder_ctx).rc_max_rate = bps;
557 (*encoder_ctx).rc_buffer_size = vbv.min(i32::MAX as u32) as i32;
558 set_opt(&mut opts, "rc_mode", "CBR");
559 } else {
560 set_opt(&mut opts, "rc_mode", "CQP");
561 set_opt(&mut opts, "qp", &settings.video_crf.to_string());
562 }
563 set_opt(&mut opts, "async_depth", "1");
564 if enc_sw_format == ff::AVPixelFormat::AV_PIX_FMT_NV12 {
568 set_opt(&mut opts, "profile", "high");
569 }
570 set_opt(
571 &mut opts,
572 "level",
573 &super::min_h264_level(width as u32, height as u32, fps as u32).to_string(),
574 );
575
576 let ret = ff::avcodec_open2(encoder_ctx, codec, &mut opts);
577 ff::av_dict_free(&mut opts);
578 if ret < 0 {
579 ff::avcodec_free_context(&mut encoder_ctx);
580 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
581 ff::av_buffer_unref(&mut drm_frames_ref);
582 ff::av_buffer_unref(&mut hw_device_ctx);
583 ff::av_buffer_unref(&mut drm_device_ctx);
584 if enc_sw_format != ff::AVPixelFormat::AV_PIX_FMT_NV12 {
585 return Err(format!(
586 "Failed to open encoder for 4:4:4 ({}): {}. This FFmpeg build's h264_vaapi \
587 advertises no 4:4:4 profile.",
588 pix_fmt_name(enc_sw_format),
589 ff_err_str(ret)
590 ));
591 }
592 return Err(format!("Failed to open encoder: {}", ff_err_str(ret)));
593 }
594
595 let mut filter_graph = ff::avfilter_graph_alloc();
596 let buffersrc = ff::avfilter_get_by_name(CString::new("buffer").unwrap().as_ptr());
597 let buffersink =
598 ff::avfilter_get_by_name(CString::new("buffersink").unwrap().as_ptr());
599 let name_in = CString::new("in").unwrap();
600 let name_out = CString::new("out").unwrap();
601
602 let buffersrc_ctx =
603 ff::avfilter_graph_alloc_filter(filter_graph, buffersrc, name_in.as_ptr());
604
605 let par = ff::av_buffersrc_parameters_alloc();
606 if par.is_null() {
607 ff::avfilter_graph_free(&mut filter_graph);
608 ff::avcodec_free_context(&mut encoder_ctx);
609 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
610 ff::av_buffer_unref(&mut drm_frames_ref);
611 ff::av_buffer_unref(&mut hw_device_ctx);
612 ff::av_buffer_unref(&mut drm_device_ctx);
613 return Err("Failed to alloc buffersrc parameters".into());
614 }
615 if host_input {
616 (*par).format = ff::AVPixelFormat::AV_PIX_FMT_BGRA as i32;
617 } else {
618 (*par).format = ff::AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32;
619 (*par).hw_frames_ctx = ff::av_buffer_ref(drm_frames_ref);
620 }
621 (*par).width = width;
622 (*par).height = height;
623 (*par).time_base = ff::AVRational { num: 1, den: fps };
624
625 let ret = ff::av_buffersrc_parameters_set(buffersrc_ctx, par);
626 if !(*par).hw_frames_ctx.is_null() {
627 ff::av_buffer_unref(&mut (*par).hw_frames_ctx);
628 }
629 ff::av_free(par as *mut c_void);
630 if ret < 0 {
631 ff::avfilter_graph_free(&mut filter_graph);
632 ff::avcodec_free_context(&mut encoder_ctx);
633 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
634 ff::av_buffer_unref(&mut drm_frames_ref);
635 ff::av_buffer_unref(&mut hw_device_ctx);
636 ff::av_buffer_unref(&mut drm_device_ctx);
637 return Err(format!(
638 "Failed to set buffersrc parameters: {}",
639 ff_err_str(ret)
640 ));
641 }
642
643 let args_str = format!(
644 "video_size={}x{}:time_base=1/{}:pixel_aspect=1/1",
645 width, height, fps
646 );
647 let args = CString::new(args_str).unwrap();
648 if ff::avfilter_init_str(buffersrc_ctx, args.as_ptr()) < 0 {
649 ff::avfilter_graph_free(&mut filter_graph);
650 ff::avcodec_free_context(&mut encoder_ctx);
651 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
652 ff::av_buffer_unref(&mut drm_frames_ref);
653 ff::av_buffer_unref(&mut hw_device_ctx);
654 ff::av_buffer_unref(&mut drm_device_ctx);
655 return Err("Failed to init buffersrc".into());
656 }
657
658 let mut buffersink_ctx: *mut ff::AVFilterContext = ptr::null_mut();
659 if ff::avfilter_graph_create_filter(
660 &mut buffersink_ctx,
661 buffersink,
662 name_out.as_ptr(),
663 ptr::null(),
664 ptr::null_mut(),
665 filter_graph,
666 ) < 0
667 {
668 ff::avfilter_graph_free(&mut filter_graph);
669 ff::avcodec_free_context(&mut encoder_ctx);
670 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
671 ff::av_buffer_unref(&mut drm_frames_ref);
672 ff::av_buffer_unref(&mut hw_device_ctx);
673 ff::av_buffer_unref(&mut drm_device_ctx);
674 return Err("Failed to create buffersink".into());
675 }
676
677 let stage = if host_input { "hwupload" } else { "hwmap" };
678 let filters_desc = CString::new(format!(
679 "{},scale_vaapi=w={}:h={}:format={}:out_color_matrix=bt709:out_range=tv",
680 stage,
681 width,
682 height,
683 pix_fmt_name(enc_sw_format)
684 ))
685 .unwrap();
686 let mut seg: *mut ff::AVFilterGraphSegment = ptr::null_mut();
687 let mut seg_inputs: *mut ff::AVFilterInOut = ptr::null_mut();
688 let mut seg_outputs: *mut ff::AVFilterInOut = ptr::null_mut();
689 let seg_ok = ff::avfilter_graph_segment_parse(
690 filter_graph,
691 filters_desc.as_ptr(),
692 0,
693 &mut seg,
694 ) >= 0
695 && ff::avfilter_graph_segment_create_filters(seg, 0) >= 0
696 && {
697 for i in 0..(*filter_graph).nb_filters {
698 let f = *(*filter_graph).filters.add(i as usize);
699 if (*f).hw_device_ctx.is_null() {
700 (*f).hw_device_ctx = ff::av_buffer_ref(hw_device_ctx);
701 }
702 }
703 ff::avfilter_graph_segment_apply(seg, 0, &mut seg_inputs, &mut seg_outputs)
704 >= 0
705 }
706 && !seg_inputs.is_null()
707 && !seg_outputs.is_null()
708 && ff::avfilter_link(
709 buffersrc_ctx,
710 0,
711 (*seg_inputs).filter_ctx,
712 (*seg_inputs).pad_idx as u32,
713 ) >= 0
714 && ff::avfilter_link(
715 (*seg_outputs).filter_ctx,
716 (*seg_outputs).pad_idx as u32,
717 buffersink_ctx,
718 0,
719 ) >= 0;
720 ff::avfilter_inout_free(&mut seg_inputs);
721 ff::avfilter_inout_free(&mut seg_outputs);
722 ff::avfilter_graph_segment_free(&mut seg);
723 if !seg_ok {
724 ff::avfilter_graph_free(&mut filter_graph);
725 ff::avcodec_free_context(&mut encoder_ctx);
726 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
727 ff::av_buffer_unref(&mut drm_frames_ref);
728 ff::av_buffer_unref(&mut hw_device_ctx);
729 ff::av_buffer_unref(&mut drm_device_ctx);
730 return Err("Failed to build filter graph".into());
731 }
732
733 if ff::avfilter_graph_config(filter_graph, ptr::null_mut()) < 0 {
734 ff::avfilter_graph_free(&mut filter_graph);
735 ff::avcodec_free_context(&mut encoder_ctx);
736 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
737 ff::av_buffer_unref(&mut drm_frames_ref);
738 ff::av_buffer_unref(&mut hw_device_ctx);
739 ff::av_buffer_unref(&mut drm_device_ctx);
740 return Err("Failed to config filter graph".into());
741 }
742
743 let mut video_frame = ff::av_frame_alloc();
744 let mut sw_frame = ff::av_frame_alloc();
745 let mut hw_frame = ff::av_frame_alloc();
746
747 if ff::av_hwframe_get_buffer((*encoder_ctx).hw_frames_ctx, hw_frame, 0) < 0 {
748 ff::av_frame_free(&mut hw_frame);
749 ff::av_frame_free(&mut sw_frame);
750 ff::av_frame_free(&mut video_frame);
751 ff::avfilter_graph_free(&mut filter_graph);
752 ff::avcodec_free_context(&mut encoder_ctx);
753 ff::av_buffer_unref(&mut saved_enc_frames_ctx);
754 ff::av_buffer_unref(&mut drm_frames_ref);
755 ff::av_buffer_unref(&mut hw_device_ctx);
756 ff::av_buffer_unref(&mut drm_device_ctx);
757 return Err("Failed to allocate HW frame from the encoder pool".into());
758 }
759
760 Ok(Self {
761 encoder_ctx,
762 codec,
763 hw_device_ctx,
764 drm_device_ctx,
765 drm_frames_ctx: drm_frames_ref,
766 enc_frames_ctx: saved_enc_frames_ctx,
767 filter_graph,
768 buffersrc_ctx,
769 buffersink_ctx,
770 video_frame,
771 sw_frame,
772 hw_frame,
773 packet: ff::av_packet_alloc(),
774 width,
775 height,
776 fps,
777 sw_format: enc_sw_format,
778 packed_444: Vec::new(),
779 current_qp: settings.video_crf as u32,
780 qp_hysteresis_counter: 0,
781 cbr_mode: settings.video_cbr_mode,
782 current_bitrate_kbps: settings.video_bitrate_kbps,
783 current_vbv_mult: settings.video_vbv_multiplier,
784 current_kf_s: settings.keyframe_interval_s,
785 omit_stripe_headers: settings.omit_stripe_headers,
786 })
787 }
788 }
789
790 pub fn is_fullcolor(&self) -> bool {
794 self.sw_format != ff::AVPixelFormat::AV_PIX_FMT_NV12
795 }
796
797 unsafe fn reopen_codec(&mut self, qp: u32) -> Result<(), String> {
812 if !self.encoder_ctx.is_null() {
813 ff::avcodec_free_context(&mut self.encoder_ctx);
814 }
815
816 self.encoder_ctx = ff::avcodec_alloc_context3(self.codec);
817 if self.encoder_ctx.is_null() {
818 return Err("Failed to re-alloc encoder context".into());
819 }
820
821 (*self.encoder_ctx).width = self.width;
822 (*self.encoder_ctx).height = self.height;
823 (*self.encoder_ctx).time_base = ff::AVRational { num: 1, den: self.fps };
824 (*self.encoder_ctx).framerate = ff::AVRational { num: self.fps, den: 1 };
825 (*self.encoder_ctx).pix_fmt = ff::AVPixelFormat::AV_PIX_FMT_VAAPI;
826 (*self.encoder_ctx).hw_device_ctx = ff::av_buffer_ref(self.hw_device_ctx);
827 (*self.encoder_ctx).hw_frames_ctx = ff::av_buffer_ref(self.enc_frames_ctx);
828 (*self.encoder_ctx).max_b_frames = 0;
829 (*self.encoder_ctx).gop_size = std::ffi::c_int::MAX;
830 (*self.encoder_ctx).slices = 4;
831 (*self.encoder_ctx).compression_level = 6;
832 set_colorimetry(self.encoder_ctx);
833
834 let mut opts: *mut ff::AVDictionary = ptr::null_mut();
835 let set_opt = |d: &mut *mut ff::AVDictionary, k: &str, v: &str| {
836 let ck = CString::new(k).unwrap();
837 let cv = CString::new(v).unwrap();
838 ff::av_dict_set(d, ck.as_ptr(), cv.as_ptr(), 0);
839 };
840
841 if self.cbr_mode {
842 let bps = (self.current_bitrate_kbps.max(0) as i64).saturating_mul(1000);
843 let vbv = crate::encoders::vbv_bits(
844 bps.min(u32::MAX as i64) as u32,
845 self.fps.max(1) as f64,
846 self.current_kf_s,
847 self.current_vbv_mult,
848 );
849 (*self.encoder_ctx).bit_rate = bps;
850 (*self.encoder_ctx).rc_max_rate = bps;
851 (*self.encoder_ctx).rc_buffer_size = vbv.min(i32::MAX as u32) as i32;
852 set_opt(&mut opts, "rc_mode", "CBR");
853 } else {
854 set_opt(&mut opts, "rc_mode", "CQP");
855 set_opt(&mut opts, "qp", &qp.to_string());
856 }
857 set_opt(&mut opts, "async_depth", "1");
858 if !self.is_fullcolor() {
859 set_opt(&mut opts, "profile", "high");
860 }
861 set_opt(
862 &mut opts,
863 "level",
864 &super::min_h264_level(self.width as u32, self.height as u32, self.fps.max(1) as u32)
865 .to_string(),
866 );
867
868 let ret = ff::avcodec_open2(self.encoder_ctx, self.codec, &mut opts);
869 ff::av_dict_free(&mut opts);
870
871 if ret < 0 {
872 ff::avcodec_free_context(&mut self.encoder_ctx);
876 return Err(format!("Failed to re-open encoder: {}", ff_err_str(ret)));
877 }
878
879 self.current_qp = qp;
880 Ok(())
881 }
882
883 fn require_open_codec(&self) -> Result<(), String> {
886 if self.encoder_ctx.is_null() {
887 return Err("no open codec context after a failed re-open; the session needs a rebuild".into());
888 }
889 Ok(())
890 }
891
892 unsafe fn update_qp(&mut self, target_qp: u32) -> Result<(), String> {
910 if self.cbr_mode {
911 return Ok(());
912 }
913
914 if target_qp == self.current_qp {
915 self.qp_hysteresis_counter = 0;
916 return Ok(());
917 }
918
919 if target_qp < self.current_qp {
920 self.qp_hysteresis_counter = 0;
921 self.reopen_codec(target_qp)?;
922 } else {
923 self.qp_hysteresis_counter += 1;
924 if self.qp_hysteresis_counter > QP_HYSTERESIS_LIMIT {
925 self.qp_hysteresis_counter = 0;
926 self.reopen_codec(target_qp)?;
927 }
928 }
929
930 Ok(())
931 }
932
933 pub fn reconfigure_rate(&mut self, settings: &RustCaptureSettings) -> Result<(), String> {
944 unsafe {
945 let mut changed = false;
946 if self.cbr_mode
947 && (settings.video_bitrate_kbps != self.current_bitrate_kbps
948 || settings.video_vbv_multiplier != self.current_vbv_mult)
949 {
950 changed = true;
951 }
952 let new_fps = settings.target_fps.max(1.0) as i32;
953 if new_fps != self.fps {
954 changed = true;
955 }
956 if !changed {
957 return Ok(());
958 }
959 self.fps = new_fps;
960 self.current_bitrate_kbps = settings.video_bitrate_kbps;
961 self.current_vbv_mult = settings.video_vbv_multiplier;
962 self.current_kf_s = settings.keyframe_interval_s;
963 self.reopen_codec(self.current_qp)
964 }
965 }
966
967 unsafe fn collect_packet(&mut self, frame_number: u64, output: &mut Vec<u8>) {
980 while ff::avcodec_receive_packet(self.encoder_ctx, self.packet) == 0 {
981 let size = (*self.packet).size as usize;
982 let data = (*self.packet).data;
983 let is_key = ((*self.packet).flags & ff::AV_PKT_FLAG_KEY) != 0;
984
985 let header_sz = if self.omit_stripe_headers { 0 } else { 10 };
986 output.reserve(header_sz + size);
987 if !self.omit_stripe_headers {
988 output.push(0x04);
989 output.push(if is_key { 0x01 } else { 0x00 });
990 output.extend_from_slice(&(frame_number as u16).to_be_bytes());
991 output.extend_from_slice(&0u16.to_be_bytes());
992 output.extend_from_slice(&(self.width as u16).to_be_bytes());
993 output.extend_from_slice(&(self.height as u16).to_be_bytes());
994 }
995
996 let slice = std::slice::from_raw_parts(data, size);
997 output.extend_from_slice(slice);
998
999 ff::av_packet_unref(self.packet);
1000 }
1001 }
1002
1003 pub fn encode_dmabuf(
1024 &mut self,
1025 dmabuf: &Dmabuf,
1026 frame_number: u64,
1027 qp: u32,
1028 force_idr: bool,
1029 ) -> Result<Vec<u8>, String> {
1030 unsafe {
1031 self.update_qp(qp)?;
1032 self.require_open_codec()?;
1033
1034 let desc_size = mem::size_of::<AVDRMFrameDescriptor>();
1035 let desc_ptr = ff::av_mallocz(desc_size) as *mut AVDRMFrameDescriptor;
1036 if desc_ptr.is_null() {
1037 return Err("OOM".into());
1038 }
1039
1040 let mut resources = DmabufResources { fds: Vec::new() };
1041
1042 (*desc_ptr).nb_objects = dmabuf.handles().count() as i32;
1043 (*desc_ptr).nb_layers = 1;
1044
1045 for (i, (handle, _)) in dmabuf.handles().zip(dmabuf.offsets()).enumerate() {
1046 let fd = dup(handle.as_raw_fd());
1047 if fd < 0 {
1048 for &dup_fd in &resources.fds {
1049 close(dup_fd);
1050 }
1051 ff::av_free(desc_ptr as *mut c_void);
1052 return Err("Failed to dup fd".into());
1053 }
1054 resources.fds.push(fd);
1055 (*desc_ptr).objects[i].fd = fd;
1056 let object_size = lseek(fd, 0, SEEK_END);
1060 if object_size <= 0 {
1061 for &dup_fd in &resources.fds {
1062 close(dup_fd);
1063 }
1064 ff::av_free(desc_ptr as *mut c_void);
1065 return Err("Failed to query the dmabuf object size".into());
1066 }
1067 (*desc_ptr).objects[i].size = object_size as usize;
1068 (*desc_ptr).objects[i].format_modifier = u64::from(dmabuf.format().modifier);
1069 }
1070
1071 (*desc_ptr).layers[0].format = dmabuf.format().code as u32;
1072 (*desc_ptr).layers[0].nb_planes = dmabuf.num_planes() as i32;
1073
1074 for (i, (stride, offset)) in dmabuf.strides().zip(dmabuf.offsets()).enumerate() {
1075 (*desc_ptr).layers[0].planes[i].object_index = i as i32;
1076 (*desc_ptr).layers[0].planes[i].offset = offset as isize;
1077 (*desc_ptr).layers[0].planes[i].pitch = stride as isize;
1078 }
1079
1080 if dmabuf.handles().count() == 1 && dmabuf.num_planes() > 1 {
1081 for i in 0..dmabuf.num_planes() {
1082 (*desc_ptr).layers[0].planes[i].object_index = 0;
1083 }
1084 }
1085
1086 ff::av_frame_unref(self.video_frame);
1087 (*self.video_frame).width = self.width;
1088 (*self.video_frame).height = self.height;
1089 (*self.video_frame).format = ff::AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32;
1090 (*self.video_frame).data[0] = desc_ptr as *mut u8;
1091
1092 let opaque = Box::into_raw(Box::new(resources));
1093 let buf_ref = ff::av_buffer_create(
1094 desc_ptr as *mut u8,
1095 desc_size,
1096 Some(release_drm_frame),
1097 opaque as *mut c_void,
1098 0,
1099 );
1100
1101 if buf_ref.is_null() {
1102 release_drm_frame(opaque as *mut c_void, ptr::null_mut());
1103 ff::av_free(desc_ptr as *mut c_void);
1104 return Err("Failed to create buffer ref".into());
1105 }
1106 (*self.video_frame).buf[0] = buf_ref;
1107 (*self.video_frame).pts = frame_number as i64;
1108 (*self.video_frame).hw_frames_ctx = ff::av_buffer_ref(self.drm_frames_ctx);
1109
1110 if ff::av_buffersrc_add_frame(self.buffersrc_ctx, self.video_frame) < 0 {
1111 ff::av_frame_unref(self.video_frame);
1112 return Err("Failed to feed filter graph".into());
1113 }
1114
1115 let mut output = Vec::new();
1116 let mut filtered_frame = ff::av_frame_alloc();
1117
1118 while ff::av_buffersink_get_frame(self.buffersink_ctx, filtered_frame) >= 0 {
1119 if force_idr {
1120 (*filtered_frame).pict_type = ff::AVPictureType::AV_PICTURE_TYPE_I;
1121 }
1122
1123 if ff::avcodec_send_frame(self.encoder_ctx, filtered_frame) < 0 {
1124 ff::av_frame_free(&mut filtered_frame);
1125 return Err("Failed to send frame to encoder".into());
1126 }
1127 ff::av_frame_unref(filtered_frame);
1128
1129 self.collect_packet(frame_number, &mut output);
1130 }
1131 ff::av_frame_free(&mut filtered_frame);
1132
1133 Ok(output)
1134 }
1135 }
1136
1137 pub fn encode_host_argb(
1148 &mut self,
1149 bgra: &[u8],
1150 stride: usize,
1151 frame_number: u64,
1152 qp: u32,
1153 force_idr: bool,
1154 ) -> Result<Vec<u8>, String> {
1155 unsafe {
1156 self.update_qp(qp)?;
1157 self.require_open_codec()?;
1158
1159 let h = self.height as usize;
1160 let needed = stride.checked_mul(h).ok_or("stride overflow")?;
1161 if bgra.len() < needed {
1162 return Err("Input buffer too small".into());
1163 }
1164
1165 ff::av_frame_unref(self.video_frame);
1166 (*self.video_frame).width = self.width;
1167 (*self.video_frame).height = self.height;
1168 (*self.video_frame).format = ff::AVPixelFormat::AV_PIX_FMT_BGRA as i32;
1169 if ff::av_frame_get_buffer(self.video_frame, 0) < 0 {
1170 return Err("Failed to allocate host BGRA frame".into());
1171 }
1172 let dst = (*self.video_frame).data[0];
1173 let dst_stride = (*self.video_frame).linesize[0] as usize;
1174 let row_bytes = (self.width as usize) * 4;
1175 let copy_bytes = row_bytes.min(stride).min(dst_stride);
1176 for row in 0..h {
1177 ptr::copy_nonoverlapping(
1178 bgra.as_ptr().add(row * stride),
1179 dst.add(row * dst_stride),
1180 copy_bytes,
1181 );
1182 }
1183 (*self.video_frame).pts = frame_number as i64;
1184
1185 if ff::av_buffersrc_add_frame(self.buffersrc_ctx, self.video_frame) < 0 {
1186 ff::av_frame_unref(self.video_frame);
1187 return Err("Failed to feed filter graph".into());
1188 }
1189
1190 let mut output = Vec::new();
1191 let mut filtered_frame = ff::av_frame_alloc();
1192 while ff::av_buffersink_get_frame(self.buffersink_ctx, filtered_frame) >= 0 {
1193 if force_idr {
1194 (*filtered_frame).pict_type = ff::AVPictureType::AV_PICTURE_TYPE_I;
1195 }
1196 if ff::avcodec_send_frame(self.encoder_ctx, filtered_frame) < 0 {
1197 ff::av_frame_free(&mut filtered_frame);
1198 return Err("Failed to send frame to encoder".into());
1199 }
1200 ff::av_frame_unref(filtered_frame);
1201 self.collect_packet(frame_number, &mut output);
1202 }
1203 ff::av_frame_free(&mut filtered_frame);
1204
1205 Ok(output)
1206 }
1207 }
1208
1209 pub fn encode_raw(
1222 &mut self,
1223 pixels: &[u8],
1224 frame_number: u64,
1225 qp: u32,
1226 force_idr: bool,
1227 ) -> Result<Vec<u8>, String> {
1228 unsafe {
1229 self.update_qp(qp)?;
1230 self.require_open_codec()?;
1231
1232 let width = self.width as usize;
1233 let height = self.height as usize;
1234 let plane = width * height;
1235 let required_size = if self.is_fullcolor() { plane * 3 } else { plane + plane / 2 };
1236
1237 if pixels.len() < required_size {
1238 return Err("Input buffer too small".into());
1239 }
1240
1241 ff::av_frame_unref(self.sw_frame);
1242 (*self.sw_frame).format = self.sw_format as i32;
1243 (*self.sw_frame).width = self.width;
1244 (*self.sw_frame).height = self.height;
1245
1246 match self.sw_format {
1247 ff::AVPixelFormat::AV_PIX_FMT_YUV444P => {
1248 for i in 0..3 {
1249 (*self.sw_frame).data[i] = pixels.as_ptr().add(i * plane) as *mut u8;
1250 (*self.sw_frame).linesize[i] = self.width;
1251 }
1252 }
1253 ff::AVPixelFormat::AV_PIX_FMT_NV12 => {
1254 (*self.sw_frame).data[0] = pixels.as_ptr() as *mut u8;
1255 (*self.sw_frame).linesize[0] = self.width;
1256 (*self.sw_frame).data[1] = pixels.as_ptr().add(plane) as *mut u8;
1257 (*self.sw_frame).linesize[1] = self.width;
1258 }
1259 _ => {
1260 pack_i444_as_vuyx(pixels, &mut self.packed_444, plane);
1261 (*self.sw_frame).data[0] = self.packed_444.as_mut_ptr();
1262 (*self.sw_frame).linesize[0] = self.width * 4;
1263 }
1264 }
1265
1266 ff::av_frame_unref(self.hw_frame);
1267 if ff::av_hwframe_get_buffer((*self.encoder_ctx).hw_frames_ctx, self.hw_frame, 0) < 0 {
1268 return Err("Failed to allocate HW frame from the encoder pool".into());
1269 }
1270 (*self.hw_frame).width = self.width;
1271 (*self.hw_frame).height = self.height;
1272
1273 if ff::av_hwframe_transfer_data(self.hw_frame, self.sw_frame, 0) < 0 {
1274 return Err("Failed to upload frame to GPU".into());
1275 }
1276
1277 ff::av_frame_unref(self.sw_frame);
1278
1279 (*self.hw_frame).pts = frame_number as i64;
1280 if force_idr {
1281 (*self.hw_frame).pict_type = ff::AVPictureType::AV_PICTURE_TYPE_I;
1282 } else {
1283 (*self.hw_frame).pict_type = ff::AVPictureType::AV_PICTURE_TYPE_NONE;
1284 }
1285
1286 if ff::avcodec_send_frame(self.encoder_ctx, self.hw_frame) < 0 {
1287 return Err("Error sending frame to encoder".into());
1288 }
1289
1290 let mut output = Vec::new();
1291 self.collect_packet(frame_number, &mut output);
1292
1293 Ok(output)
1294 }
1295 }
1296}
1297
1298#[cfg(test)]
1299mod fullcolor_tests {
1300 use super::*;
1301
1302 #[test]
1306 fn packs_i444_as_vuyx() {
1307 let plane = 4;
1308 let y: Vec<u8> = (0..plane).map(|i| (i * 3 + 1) as u8).collect();
1309 let u: Vec<u8> = (0..plane).map(|i| (i * 5 + 60) as u8).collect();
1310 let v: Vec<u8> = (0..plane).map(|i| (i * 7 + 130) as u8).collect();
1311 let planar: Vec<u8> = y.iter().chain(&u).chain(&v).copied().collect();
1312
1313 let mut packed = Vec::new();
1314 pack_i444_as_vuyx(&planar, &mut packed, plane);
1315 assert_eq!(
1316 packed,
1317 vec![
1318 130, 60, 1, 255, 137, 65, 4, 255, 144, 70, 7, 255, 151, 75, 10, 255,
1322 ]
1323 );
1324
1325 pack_i444_as_vuyx(&vec![0u8; plane * 3], &mut packed, plane);
1327 assert_eq!(packed, [0, 0, 0, 255].repeat(plane));
1328 }
1329
1330 #[test]
1334 fn prefers_planar_fullcolor_format() {
1335 use ff::AVPixelFormat::*;
1336 assert_eq!(preferred_fullcolor_format(&[AV_PIX_FMT_NV12]), None);
1337 assert_eq!(
1338 preferred_fullcolor_format(&[AV_PIX_FMT_NV12, AV_PIX_FMT_VUYX]),
1339 Some(AV_PIX_FMT_VUYX)
1340 );
1341 assert_eq!(
1342 preferred_fullcolor_format(&[AV_PIX_FMT_VUYX, AV_PIX_FMT_YUV444P]),
1343 Some(AV_PIX_FMT_YUV444P)
1344 );
1345 }
1346
1347 #[test]
1350 fn fullcolor_formats_have_parseable_names() {
1351 for fmt in FULLCOLOR_SW_FORMATS {
1352 let name = pix_fmt_name(fmt);
1353 let round_trip =
1354 unsafe { ff::av_get_pix_fmt(CString::new(name.clone()).unwrap().as_ptr()) };
1355 assert_eq!(round_trip, fmt, "{name} did not parse back");
1356 }
1357 }
1358
1359 #[test]
1366 fn negotiated_chroma_matches_what_was_asked_for() {
1367 let mut settings = RustCaptureSettings {
1368 width: 128,
1369 height: 128,
1370 output_mode: 1,
1371 video_fullcolor: true,
1372 ..Default::default()
1373 };
1374 match VaapiEncoder::new_host(&settings) {
1375 Ok(enc) => assert!(enc.is_fullcolor(), "4:4:4 session reports 4:2:0"),
1376 Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
1377 }
1378 settings.video_fullcolor = false;
1379 match VaapiEncoder::new_host(&settings) {
1380 Ok(enc) => assert!(!enc.is_fullcolor(), "4:2:0 session reports 4:4:4"),
1381 Err(e) => assert!(!e.is_empty(), "refusal must carry a reason"),
1382 }
1383 }
1384
1385 #[test]
1391 #[ignore]
1392 fn gpu_fullcolor_format_follows_device_constraints() {
1393 unsafe {
1394 let mut dev: *mut ff::AVBufferRef = ptr::null_mut();
1395 assert!(
1396 ff::av_hwdevice_ctx_create(
1397 &mut dev,
1398 ff::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA,
1399 ptr::null(),
1400 ptr::null_mut(),
1401 0,
1402 ) >= 0,
1403 "no CUDA device"
1404 );
1405
1406 let constraints = ff::av_hwdevice_get_hwframe_constraints(dev, ptr::null());
1407 assert!(!constraints.is_null());
1408 let mut carried = Vec::new();
1409 let mut fmt = (*constraints).valid_sw_formats;
1410 while !fmt.is_null() && *fmt != ff::AVPixelFormat::AV_PIX_FMT_NONE {
1411 carried.push(*fmt);
1412 fmt = fmt.add(1);
1413 }
1414 let mut owned = constraints;
1415 ff::av_hwframe_constraints_free(&mut owned);
1416
1417 let picked = fullcolor_sw_format(dev);
1418 println!(
1419 "device carries {} formats; 4:4:4 pick: {:?}",
1420 carried.len(),
1421 picked.map(pix_fmt_name)
1422 );
1423 assert_eq!(picked, preferred_fullcolor_format(&carried));
1424 if let Some(fmt) = picked {
1425 assert!(carried.contains(&fmt), "picked a format the device never reported");
1426 }
1427 ff::av_buffer_unref(&mut dev);
1428 }
1429 }
1430}
1431
1432#[cfg(test)]
1433mod graph_ordering_tests {
1434 use super::*;
1435
1436 unsafe fn build_hwupload_graph(staged: bool) -> Result<(), String> {
1447 let mut dev: *mut ff::AVBufferRef = ptr::null_mut();
1448 if ff::av_hwdevice_ctx_create(
1449 &mut dev,
1450 ff::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA,
1451 ptr::null(),
1452 ptr::null_mut(),
1453 0,
1454 ) < 0
1455 {
1456 return Err("no CUDA device".into());
1457 }
1458
1459 let graph = ff::avfilter_graph_alloc();
1460 let mut src: *mut ff::AVFilterContext = ptr::null_mut();
1461 let args = CString::new("video_size=64x64:pix_fmt=bgra:time_base=1/30").unwrap();
1462 let r = ff::avfilter_graph_create_filter(
1463 &mut src,
1464 ff::avfilter_get_by_name(CString::new("buffer").unwrap().as_ptr()),
1465 CString::new("in").unwrap().as_ptr(),
1466 args.as_ptr(),
1467 ptr::null_mut(),
1468 graph,
1469 );
1470 assert!(r >= 0, "buffersrc create");
1471 let mut sink: *mut ff::AVFilterContext = ptr::null_mut();
1472 let r = ff::avfilter_graph_create_filter(
1473 &mut sink,
1474 ff::avfilter_get_by_name(CString::new("buffersink").unwrap().as_ptr()),
1475 CString::new("out").unwrap().as_ptr(),
1476 ptr::null(),
1477 ptr::null_mut(),
1478 graph,
1479 );
1480 assert!(r >= 0, "buffersink create");
1481
1482 let desc = CString::new("hwupload,hwdownload,format=bgra").unwrap();
1483 let result: Result<(), String> = if staged {
1484 let mut seg: *mut ff::AVFilterGraphSegment = ptr::null_mut();
1485 let mut ins: *mut ff::AVFilterInOut = ptr::null_mut();
1486 let mut outs: *mut ff::AVFilterInOut = ptr::null_mut();
1487 let ok = ff::avfilter_graph_segment_parse(graph, desc.as_ptr(), 0, &mut seg) >= 0
1488 && ff::avfilter_graph_segment_create_filters(seg, 0) >= 0
1489 && {
1490 for i in 0..(*graph).nb_filters {
1491 let f = *(*graph).filters.add(i as usize);
1492 if (*f).hw_device_ctx.is_null() {
1493 (*f).hw_device_ctx = ff::av_buffer_ref(dev);
1494 }
1495 }
1496 ff::avfilter_graph_segment_apply(seg, 0, &mut ins, &mut outs) >= 0
1497 }
1498 && !ins.is_null()
1499 && !outs.is_null()
1500 && ff::avfilter_link(src, 0, (*ins).filter_ctx, (*ins).pad_idx as u32) >= 0
1501 && ff::avfilter_link((*outs).filter_ctx, (*outs).pad_idx as u32, sink, 0) >= 0;
1502 ff::avfilter_inout_free(&mut ins);
1503 ff::avfilter_inout_free(&mut outs);
1504 ff::avfilter_graph_segment_free(&mut seg);
1505 if ok { Ok(()) } else { Err("segment build failed".into()) }
1506 } else {
1507 let mut inputs = ff::avfilter_inout_alloc();
1508 let mut outputs = ff::avfilter_inout_alloc();
1509 (*inputs).name = ff::av_strdup(CString::new("in").unwrap().as_ptr());
1510 (*inputs).filter_ctx = src;
1511 (*inputs).pad_idx = 0;
1512 (*inputs).next = ptr::null_mut();
1513 (*outputs).name = ff::av_strdup(CString::new("out").unwrap().as_ptr());
1514 (*outputs).filter_ctx = sink;
1515 (*outputs).pad_idx = 0;
1516 (*outputs).next = ptr::null_mut();
1517 let r = ff::avfilter_graph_parse_ptr(
1518 graph,
1519 desc.as_ptr(),
1520 &mut outputs,
1521 &mut inputs,
1522 ptr::null_mut(),
1523 );
1524 ff::avfilter_inout_free(&mut inputs);
1525 ff::avfilter_inout_free(&mut outputs);
1526 if r < 0 {
1527 Err(format!("parse failed before the attach loop could run: {}", ff_err_str(r)))
1528 } else {
1529 for i in 0..(*graph).nb_filters {
1530 let f = *(*graph).filters.add(i as usize);
1531 if (*f).hw_device_ctx.is_null() {
1532 (*f).hw_device_ctx = ff::av_buffer_ref(dev);
1533 }
1534 }
1535 Ok(())
1536 }
1537 };
1538
1539 let result = result.and_then(|()| {
1540 let r = ff::avfilter_graph_config(graph, ptr::null_mut());
1541 if r < 0 { Err(format!("config failed: {}", ff_err_str(r))) } else { Ok(()) }
1542 });
1543
1544 let result = result.and_then(|()| {
1545 let frame = ff::av_frame_alloc();
1546 (*frame).format = ff::AVPixelFormat::AV_PIX_FMT_BGRA as i32;
1547 (*frame).width = 64;
1548 (*frame).height = 64;
1549 if ff::av_frame_get_buffer(frame, 0) < 0 {
1550 return Err("frame alloc".into());
1551 }
1552 for y in 0..64 {
1553 let row = (*frame).data[0].add(y * (*frame).linesize[0] as usize);
1554 std::ptr::write_bytes(row, 0x80, 64 * 4);
1555 }
1556 let mut fr = frame;
1557 let ok = ff::av_buffersrc_add_frame(src, fr) >= 0 && {
1558 let out = ff::av_frame_alloc();
1559 let got = ff::av_buffersink_get_frame(sink, out) >= 0
1560 && (*out).width == 64
1561 && !(*out).data[0].is_null();
1562 let mut o = out;
1563 ff::av_frame_free(&mut o);
1564 got
1565 };
1566 ff::av_frame_free(&mut fr);
1567 if ok { Ok(()) } else { Err("frame did not flow through the graph".into()) }
1568 });
1569
1570 let mut g = graph;
1571 ff::avfilter_graph_free(&mut g);
1572 ff::av_buffer_unref(&mut dev);
1573 result
1574 }
1575
1576 #[test]
1582 #[ignore]
1583 fn gpu_hwupload_device_attach_ordering() {
1584 unsafe {
1585 let old = build_hwupload_graph(false);
1586 let new = build_hwupload_graph(true);
1587 println!("one-shot parse+attach-after: {old:?}");
1588 println!("staged segment build: {new:?}");
1589 assert!(new.is_ok(), "staged build must work: {new:?}");
1590 assert!(old.is_err(), "expected the one-shot ordering to fail on this FFmpeg");
1591 }
1592 }
1593}