1use std::{error::Error, ffi::CStr, fmt, ptr, slice};
7
8#[cfg(all(target_os = "linux", feature = "hwdevice"))]
9use std::ffi::CString;
10
11#[cfg(all(
12 any(target_os = "linux", target_os = "macos", target_os = "windows"),
13 feature = "hwdevice"
14))]
15use std::sync::Mutex;
16#[cfg(all(target_os = "linux", feature = "hwdevice"))]
17use std::sync::MutexGuard;
18
19pub use alpha2_media::GpuFault;
20use rsmpeg::{
21 avcodec::{AVCodecContext, AVCodecParameters},
22 avutil::AVFrame,
23 error::RsmpegError,
24 ffi,
25 swscale::SwsContext,
26};
27
28#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
29use rsmpeg::avutil::AVHWDeviceContext;
30
31mod container;
32mod log;
33
34pub use container::{
35 CreateOutputError, DemuxInput, OpenInputError, OutputFile, PathError, file_url, open_input,
36};
37pub use log::{LogLine, LogMark, install_log_capture, log_detail, log_mark, log_since};
38
39#[allow(
41 dead_code,
42 non_camel_case_types,
43 unreachable_pub,
44 clippy::all,
45 clippy::pedantic,
46 clippy::undocumented_unsafe_blocks
47)]
48mod interop {
49 include!(concat!(env!("OUT_DIR"), "/interop.rs"));
50}
51
52const _: () = {
55 use interop::{Alpha2D3D12FrameInfo as D3D12, Alpha2VideoToolboxFrameInfo as VideoToolbox};
56 use std::mem::{align_of, offset_of, size_of};
57 assert!(size_of::<D3D12>() == 64 && align_of::<D3D12>() == 8);
58 assert!(offset_of!(D3D12, resource) == 0);
59 assert!(offset_of!(D3D12, fence) == 8);
60 assert!(offset_of!(D3D12, fence_value) == 16);
61 assert!(offset_of!(D3D12, device) == 24);
62 assert!(offset_of!(D3D12, dxgi_format) == 32);
63 assert!(offset_of!(D3D12, width) == 36);
64 assert!(offset_of!(D3D12, height) == 40);
65 assert!(offset_of!(D3D12, hardware_format) == 44);
66 assert!(offset_of!(D3D12, software_format) == 48);
67 assert!(offset_of!(D3D12, surface_width) == 52);
68 assert!(offset_of!(D3D12, surface_height) == 56);
69 assert!(size_of::<VideoToolbox>() == 24 && align_of::<VideoToolbox>() == 8);
70 assert!(offset_of!(VideoToolbox, pixel_buffer) == 0);
71 assert!(offset_of!(VideoToolbox, width) == 8);
72 assert!(offset_of!(VideoToolbox, height) == 12);
73 assert!(offset_of!(VideoToolbox, hardware_format) == 16);
74 assert!(offset_of!(VideoToolbox, software_format) == 20);
75};
76
77#[cfg(all(
80 any(target_os = "linux", target_os = "macos", target_os = "windows"),
81 feature = "hwdevice"
82))]
83#[allow(clippy::cast_ptr_alignment)]
85fn download_hardware_frame(frame: &AVFrame) -> Result<AVFrame, BridgeError> {
86 if frame.hw_frames_ctx.is_null() {
87 return Err(BridgeError::InvalidInput(
88 "hardware frame has no frames context",
89 ));
90 }
91 let written = unsafe {
94 let frames = (*frame.hw_frames_ctx).data.cast::<ffi::AVHWFramesContext>();
95 ((*frames).width, (*frames).height)
96 };
97 let mut software = AVFrame::new();
98 software
99 .hwframe_transfer_data(frame)
100 .map_err(BridgeError::Ffmpeg)?;
101 zero_row_padding(&mut software, written.0, written.1)?;
104 let copied = unsafe { ffi::av_frame_copy_props(software.as_mut_ptr(), frame.as_ptr()) };
107 if copied < 0 {
108 return Err(BridgeError::FfmpegCode(copied));
109 }
110 Ok(software)
111}
112
113#[cfg_attr(not(feature = "hwdevice"), allow(dead_code))]
121fn zero_row_padding(frame: &mut AVFrame, width: i32, height: i32) -> Result<(), BridgeError> {
122 let format = frame.format;
123 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
125 .ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
126 if width < 0 || height < 0 {
127 return Err(BridgeError::InvalidInput("negative frame size"));
128 }
129 let planes = unsafe { ffi::av_pix_fmt_count_planes(format) };
131 for plane in 0..usize::try_from(planes).unwrap_or(0).min(4) {
132 let plane_index = i32::try_from(plane).unwrap_or(0);
133 let written = unsafe { ffi::av_image_get_linesize(format, width, plane_index) };
135 let (Ok(written), Ok(stride)) = (
136 usize::try_from(written),
137 usize::try_from(frame.linesize[plane]),
138 ) else {
139 continue;
140 };
141 let data = frame.data[plane];
142 if data.is_null() || stride <= written {
143 continue;
144 }
145 let shift = if plane == 1 || plane == 2 {
146 descriptor.log2_chroma_h
147 } else {
148 0
149 };
150 let rows = usize::try_from(-((-height) >> shift)).unwrap_or(0);
151 let start = data as usize;
152 let end = stride
153 .checked_mul(rows)
154 .and_then(|len| start.checked_add(len))
155 .ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
156 let inside = frame.buf.iter().any(|buffer| {
157 unsafe { buffer.as_ref() }.is_some_and(|buffer| {
159 let buffer_start = buffer.data as usize;
160 buffer_start <= start && buffer_start.saturating_add(buffer.size) >= end
161 })
162 });
163 if !inside {
164 return Err(BridgeError::InvalidInput(
165 "frame plane lies outside its buffers",
166 ));
167 }
168 for row in 0..rows {
169 unsafe { ptr::write_bytes(data.add(row * stride + written), 0, stride - written) };
171 }
172 }
173 Ok(())
174}
175
176#[cfg(all(target_os = "windows", feature = "hwdevice"))]
177use interop::Alpha2D3D12FrameInfo as D3D12FrameDescriptor;
178
179#[cfg(all(target_os = "windows", feature = "hwdevice"))]
180unsafe extern "C" {
181 fn alpha2_d3d12_device_init(
182 context: *mut ffi::AVHWDeviceContext,
183 device: *mut std::ffi::c_void,
184 ) -> i32;
185 fn alpha2_d3d12_get_format(
186 context: *mut ffi::AVCodecContext,
187 formats: *const ffi::AVPixelFormat,
188 ) -> ffi::AVPixelFormat;
189 fn alpha2_d3d12_frame_info(
190 frame: *const ffi::AVFrame,
191 output: *mut D3D12FrameDescriptor,
192 ) -> i32;
193 fn alpha2_d3d12_frame_set_fence_value(frame: *mut ffi::AVFrame, value: u64) -> i32;
194 fn alpha2_d3d12_frame_fence_value(frame: *const ffi::AVFrame, output: *mut u64) -> i32;
195 fn alpha2_d3d12_frames_configure(
196 frames: *mut ffi::AVBufferRef,
197 dxgi_format: u32,
198 resource_flags: u32,
199 ) -> i32;
200}
201
202#[cfg(all(target_os = "windows", feature = "hwdevice"))]
204pub const DXGI_FORMAT_NV12: u32 = 103;
205
206#[cfg(all(target_os = "windows", feature = "hwdevice"))]
215pub fn d3d12_frames_configure(
216 frames: &mut rsmpeg::avutil::AVHWFramesContext,
217 dxgi_format: u32,
218 resource_flags: u32,
219) -> Result<(), BridgeError> {
220 let status =
222 unsafe { alpha2_d3d12_frames_configure(frames.as_mut_ptr(), dxgi_format, resource_flags) };
223 if status < 0 {
224 return Err(BridgeError::FfmpegCode(status));
225 }
226 Ok(())
227}
228
229#[derive(Clone, Copy, Debug, Eq, PartialEq)]
233pub struct D3D12FrameInfo {
234 pub resource: u64,
236 pub fence: u64,
238 pub fence_value: u64,
240 pub device: u64,
242 pub dxgi_format: u32,
244 pub width: i32,
246 pub height: i32,
248 pub software_format: i32,
250 pub surface_width: u32,
252 pub surface_height: u32,
254}
255
256impl D3D12FrameInfo {
257 #[cfg_attr(
258 not(all(target_os = "windows", feature = "hwdevice")),
259 allow(dead_code)
260 )]
261 const fn from_descriptor(descriptor: &interop::Alpha2D3D12FrameInfo) -> Self {
262 Self {
263 resource: descriptor.resource,
264 fence: descriptor.fence,
265 fence_value: descriptor.fence_value,
266 device: descriptor.device,
267 dxgi_format: descriptor.dxgi_format,
268 width: descriptor.width,
269 height: descriptor.height,
270 software_format: descriptor.software_format,
271 surface_width: descriptor.surface_width,
272 surface_height: descriptor.surface_height,
273 }
274 }
275}
276
277#[cfg(all(target_os = "windows", feature = "hwdevice"))]
279pub struct D3D12FrameLease {
280 frame: Mutex<AVFrame>,
281}
282
283#[cfg(all(target_os = "windows", feature = "hwdevice"))]
284impl fmt::Debug for D3D12FrameLease {
285 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
286 formatter
287 .debug_struct("D3D12FrameLease")
288 .finish_non_exhaustive()
289 }
290}
291
292#[cfg(all(target_os = "windows", feature = "hwdevice"))]
293impl D3D12FrameLease {
294 #[must_use]
296 pub fn new(frame: AVFrame) -> Self {
297 Self {
298 frame: Mutex::new(frame),
299 }
300 }
301
302 pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
307 let frame = self
308 .frame
309 .lock()
310 .unwrap_or_else(std::sync::PoisonError::into_inner);
311 download_hardware_frame(&frame).map(|frame| DecodedFrame {
312 frame,
313 written: true,
314 })
315 }
316
317 pub fn info(&self) -> Result<D3D12FrameInfo, BridgeError> {
322 let frame = self
323 .frame
324 .lock()
325 .unwrap_or_else(std::sync::PoisonError::into_inner);
326 d3d12_frame_info(&frame)
327 }
328}
329
330#[cfg(all(target_os = "windows", feature = "hwdevice"))]
338#[allow(clippy::cast_ptr_alignment)]
339pub unsafe fn create_d3d12_device_context(device: u64) -> Result<AVHWDeviceContext, BridgeError> {
340 let device = usize::try_from(device)
341 .map_err(|_| BridgeError::InvalidInput("invalid D3D12 device pointer"))?
342 as *mut std::ffi::c_void;
343 if device.is_null() {
344 return Err(BridgeError::InvalidInput("D3D12 device is null"));
345 }
346 let mut context = alloc_hwdevice(ffi::AV_HWDEVICE_TYPE_D3D12VA, "D3D12")?;
347 let device_context = context.data.cast::<ffi::AVHWDeviceContext>();
348 let result = unsafe { alpha2_d3d12_device_init(device_context, device) };
351 if result < 0 {
352 return Err(BridgeError::FfmpegCode(result));
353 }
354 context.init().map_err(BridgeError::Ffmpeg)?;
355 Ok(context)
356}
357
358#[cfg(all(target_os = "windows", feature = "hwdevice"))]
360#[must_use]
361pub fn d3d12_get_format_callback()
362-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
363 alpha2_d3d12_get_format
364}
365
366#[cfg(all(target_os = "windows", feature = "hwdevice"))]
371pub fn d3d12_frame_info(frame: &AVFrame) -> Result<D3D12FrameInfo, BridgeError> {
372 let mut descriptor = D3D12FrameDescriptor::default();
373 let result = unsafe { alpha2_d3d12_frame_info(frame.as_ptr(), &raw mut descriptor) };
375 if result != 0 {
376 return Err(BridgeError::FfmpegCode(result));
377 }
378 Ok(D3D12FrameInfo::from_descriptor(&descriptor))
379}
380
381#[cfg(all(target_os = "windows", feature = "hwdevice"))]
386pub fn d3d12_frame_fence_value(frame: &AVFrame) -> Result<u64, BridgeError> {
387 let mut value = 0;
388 let result = unsafe { alpha2_d3d12_frame_fence_value(frame.as_ptr(), &raw mut value) };
390 if result != 0 {
391 return Err(BridgeError::FfmpegCode(result));
392 }
393 Ok(value)
394}
395
396#[cfg(all(target_os = "windows", feature = "hwdevice"))]
402pub struct D3D12EncodeFrameAccess<'a> {
403 frame: &'a mut AVFrame,
404 info: D3D12FrameInfo,
405}
406
407#[cfg(all(target_os = "windows", feature = "hwdevice"))]
408impl fmt::Debug for D3D12EncodeFrameAccess<'_> {
409 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
410 formatter
411 .debug_struct("D3D12EncodeFrameAccess")
412 .field("info", &self.info)
413 .finish_non_exhaustive()
414 }
415}
416
417#[cfg(all(target_os = "windows", feature = "hwdevice"))]
418impl<'a> D3D12EncodeFrameAccess<'a> {
419 pub fn begin(frame: &'a mut AVFrame) -> Result<Self, BridgeError> {
424 let info = d3d12_frame_info(frame)?;
425 Ok(Self { frame, info })
426 }
427
428 #[must_use]
430 pub const fn info(&self) -> &D3D12FrameInfo {
431 &self.info
432 }
433
434 pub fn retain_frame(&self) -> Result<AVFrame, BridgeError> {
439 let raw = unsafe { ffi::av_frame_clone(self.frame.as_ptr()) };
441 let raw = std::ptr::NonNull::new(raw)
442 .ok_or(BridgeError::InvalidInput("cannot retain encode frame"))?;
443 Ok(unsafe { AVFrame::from_raw(raw) })
445 }
446
447 pub fn commit(&mut self, fence_value: u64) -> Result<(), BridgeError> {
452 let result =
454 unsafe { alpha2_d3d12_frame_set_fence_value(self.frame.as_mut_ptr(), fence_value) };
455 if result != 0 {
456 return Err(BridgeError::FfmpegCode(result));
457 }
458 self.info.fence_value = fence_value;
459 Ok(())
460 }
461}
462
463#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
464use interop::Alpha2VideoToolboxFrameInfo as VideoToolboxFrameDescriptor;
465
466#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
468#[cfg_attr(target_os = "linux", allow(dead_code))]
469unsafe extern "C" {
470 fn alpha2_videotoolbox_get_format(
471 context: *mut ffi::AVCodecContext,
472 formats: *const ffi::AVPixelFormat,
473 ) -> ffi::AVPixelFormat;
474 fn alpha2_videotoolbox_frame_info(
475 frame: *const ffi::AVFrame,
476 output: *mut VideoToolboxFrameDescriptor,
477 ) -> i32;
478}
479
480#[derive(Clone, Copy, Debug, Eq, PartialEq)]
484pub struct VideoToolboxFrameInfo {
485 pub pixel_buffer: u64,
487 pub width: i32,
489 pub height: i32,
491 pub software_format: i32,
493}
494
495impl VideoToolboxFrameInfo {
496 #[cfg_attr(
497 not(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice")),
498 allow(dead_code)
499 )]
500 const fn from_descriptor(descriptor: &interop::Alpha2VideoToolboxFrameInfo) -> Self {
501 Self {
502 pixel_buffer: descriptor.pixel_buffer,
503 width: descriptor.width,
504 height: descriptor.height,
505 software_format: descriptor.software_format,
506 }
507 }
508}
509
510#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
512#[cfg_attr(target_os = "linux", allow(dead_code))]
513fn read_videotoolbox_frame_info(frame: &AVFrame) -> Result<VideoToolboxFrameInfo, BridgeError> {
514 let mut descriptor = VideoToolboxFrameDescriptor::default();
515 let result = unsafe { alpha2_videotoolbox_frame_info(frame.as_ptr(), &raw mut descriptor) };
517 if result != 0 {
518 return Err(BridgeError::InvalidInput(
519 "frame is not a VideoToolbox frame with a pixel buffer",
520 ));
521 }
522 Ok(VideoToolboxFrameInfo::from_descriptor(&descriptor))
523}
524
525#[cfg(all(target_os = "macos", feature = "hwdevice"))]
527pub struct VideoToolboxFrameLease {
528 frame: Mutex<AVFrame>,
529}
530
531#[cfg(all(target_os = "macos", feature = "hwdevice"))]
532impl fmt::Debug for VideoToolboxFrameLease {
533 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
534 formatter
535 .debug_struct("VideoToolboxFrameLease")
536 .finish_non_exhaustive()
537 }
538}
539
540#[cfg(all(target_os = "macos", feature = "hwdevice"))]
541impl VideoToolboxFrameLease {
542 #[must_use]
544 pub fn new(frame: AVFrame) -> Self {
545 Self {
546 frame: Mutex::new(frame),
547 }
548 }
549
550 pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
555 let frame = self
556 .frame
557 .lock()
558 .unwrap_or_else(std::sync::PoisonError::into_inner);
559 download_hardware_frame(&frame).map(|frame| DecodedFrame {
560 frame,
561 written: true,
562 })
563 }
564
565 pub fn info(&self) -> Result<VideoToolboxFrameInfo, BridgeError> {
570 let frame = self
571 .frame
572 .lock()
573 .unwrap_or_else(std::sync::PoisonError::into_inner);
574 read_videotoolbox_frame_info(&frame)
575 }
576}
577
578#[cfg(all(target_os = "macos", feature = "hwdevice"))]
580#[must_use]
581pub fn videotoolbox_get_format_callback()
582-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
583 alpha2_videotoolbox_get_format
584}
585
586#[cfg(all(target_os = "macos", feature = "hwdevice"))]
591pub fn videotoolbox_frame_info(frame: &AVFrame) -> Result<VideoToolboxFrameInfo, BridgeError> {
592 read_videotoolbox_frame_info(frame)
593}
594
595#[cfg(all(target_os = "linux", feature = "hwdevice"))]
597pub const MAX_VULKAN_PLANES: usize = interop::ALPHA2_MAX_VULKAN_PLANES as usize;
598
599#[cfg(all(target_os = "linux", feature = "hwdevice"))]
601#[derive(Clone, Debug)]
602pub struct VulkanDeviceInfo {
603 pub instance: u64,
605 pub physical_device: u64,
607 pub device: u64,
609 pub graphics_queue_family: u32,
611 pub graphics_queue_count: u32,
613 pub decode_queue_family: u32,
615 pub decode_queue_count: u32,
617 pub decode_video_caps: u32,
619 pub encode_queue_family: u32,
621 pub encode_queue_count: u32,
623 pub encode_video_caps: u32,
625 pub core_features: [u32; 4],
627 pub synchronization2: u32,
629 pub sampler_ycbcr_conversion: u32,
631 pub video_maintenance1: u32,
633 pub video_decode_vp9: u32,
635 pub instance_extensions: Vec<String>,
637 pub device_extensions: Vec<String>,
639}
640
641#[cfg(all(target_os = "linux", feature = "hwdevice"))]
642use interop::Alpha2VulkanDeviceInfo as VulkanDeviceDescriptor;
643
644#[cfg(all(target_os = "linux", feature = "hwdevice"))]
649pub struct VulkanQueueLock {
650 device: AVHWDeviceContext,
651 queue_family: u32,
652}
653
654#[cfg(all(target_os = "linux", feature = "hwdevice"))]
657unsafe impl Sync for VulkanQueueLock {}
658
659#[cfg(all(target_os = "linux", feature = "hwdevice"))]
660impl fmt::Debug for VulkanQueueLock {
661 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
662 formatter
663 .debug_struct("VulkanQueueLock")
664 .field("queue_family", &self.queue_family)
665 .finish_non_exhaustive()
666 }
667}
668
669#[cfg(all(target_os = "linux", feature = "hwdevice"))]
670impl VulkanQueueLock {
671 #[allow(clippy::cast_ptr_alignment)]
677 pub fn new(device: AVHWDeviceContext, queue_family: u32) -> Result<Self, BridgeError> {
678 let lockable = unsafe {
681 alpha2_vulkan_queue_lockable(
682 device.data.cast::<ffi::AVHWDeviceContext>().cast_const(),
683 queue_family,
684 )
685 };
686 if lockable == 0 {
687 return Err(BridgeError::InvalidInput(
688 "Vulkan queue lock needs a family of a device the bridge initialized",
689 ));
690 }
691 Ok(Self {
692 device,
693 queue_family,
694 })
695 }
696
697 #[must_use]
699 pub fn lock(&self) -> VulkanQueueGuard<'_> {
700 unsafe { self.raw_lock() };
702 VulkanQueueGuard {
703 lock: self,
704 _owning_thread: std::marker::PhantomData,
705 }
706 }
707
708 #[allow(clippy::cast_ptr_alignment)]
714 pub unsafe fn raw_lock(&self) {
715 unsafe {
718 alpha2_vulkan_queue_lock(
719 self.device.data.cast::<ffi::AVHWDeviceContext>(),
720 self.queue_family,
721 );
722 }
723 }
724
725 #[allow(clippy::cast_ptr_alignment)]
731 pub unsafe fn raw_unlock(&self) {
732 unsafe {
734 alpha2_vulkan_queue_unlock(
735 self.device.data.cast::<ffi::AVHWDeviceContext>(),
736 self.queue_family,
737 );
738 }
739 }
740}
741
742#[cfg(all(target_os = "linux", feature = "hwdevice"))]
744#[derive(Debug)]
745pub struct VulkanQueueGuard<'a> {
746 lock: &'a VulkanQueueLock,
747 _owning_thread: std::marker::PhantomData<*const ()>,
748}
749
750#[cfg(all(target_os = "linux", feature = "hwdevice"))]
751impl Drop for VulkanQueueGuard<'_> {
752 fn drop(&mut self) {
753 unsafe { self.lock.raw_unlock() };
756 }
757}
758
759#[cfg(all(target_os = "linux", feature = "hwdevice"))]
760unsafe extern "C" {
761 fn alpha2_vulkan_queue_lockable(
762 context: *const ffi::AVHWDeviceContext,
763 queue_family: u32,
764 ) -> i32;
765 fn alpha2_vulkan_queue_lock(context: *mut ffi::AVHWDeviceContext, queue_family: u32);
766 fn alpha2_vulkan_queue_unlock(context: *mut ffi::AVHWDeviceContext, queue_family: u32);
767 fn alpha2_vulkan_device_init(
768 context: *mut ffi::AVHWDeviceContext,
769 info: *const VulkanDeviceDescriptor,
770 ) -> i32;
771}
772
773#[cfg(all(target_os = "linux", feature = "hwdevice"))]
783#[allow(clippy::cast_ptr_alignment)]
784pub unsafe fn create_vulkan_device_context(
785 info: &VulkanDeviceInfo,
786) -> Result<AVHWDeviceContext, BridgeError> {
787 let instance = info
788 .instance_extensions
789 .iter()
790 .map(|s| CString::new(s.as_str()))
791 .collect::<Result<Vec<_>, _>>()
792 .map_err(|_| BridgeError::InvalidInput("Vulkan extension contains NUL"))?;
793 let device = info
794 .device_extensions
795 .iter()
796 .map(|s| CString::new(s.as_str()))
797 .collect::<Result<Vec<_>, _>>()
798 .map_err(|_| BridgeError::InvalidInput("Vulkan extension contains NUL"))?;
799 let instance_ptrs = instance.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
800 let device_ptrs = device.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
801 let raw = VulkanDeviceDescriptor {
802 instance: info.instance,
803 physical_device: info.physical_device,
804 device: info.device,
805 graphics_queue_family: info.graphics_queue_family,
806 graphics_queue_count: info.graphics_queue_count,
807 decode_queue_family: info.decode_queue_family,
808 decode_queue_count: info.decode_queue_count,
809 decode_video_caps: info.decode_video_caps,
810 encode_queue_family: info.encode_queue_family,
811 encode_queue_count: info.encode_queue_count,
812 encode_video_caps: info.encode_video_caps,
813 shader_image_gather_extended: info.core_features[0],
814 fragment_stores_and_atomics: info.core_features[1],
815 shader_int64: info.core_features[2],
816 vertex_pipeline_stores_and_atomics: info.core_features[3],
817 synchronization2: info.synchronization2,
818 sampler_ycbcr_conversion: info.sampler_ycbcr_conversion,
819 video_maintenance1: info.video_maintenance1,
820 video_decode_vp9: info.video_decode_vp9,
821 instance_extensions: instance_ptrs.as_ptr(),
822 instance_extension_count: u32::try_from(instance_ptrs.len())
823 .map_err(|_| BridgeError::InvalidInput("too many Vulkan extensions"))?,
824 device_extensions: device_ptrs.as_ptr(),
825 device_extension_count: u32::try_from(device_ptrs.len())
826 .map_err(|_| BridgeError::InvalidInput("too many Vulkan extensions"))?,
827 };
828 let mut context = alloc_hwdevice(ffi::AV_HWDEVICE_TYPE_VULKAN, "Vulkan")?;
829 let device_context = context.data.cast::<ffi::AVHWDeviceContext>();
830 let result = unsafe { alpha2_vulkan_device_init(device_context, &raw const raw) };
833 if result < 0 {
834 return Err(BridgeError::FfmpegCode(result));
835 }
836 context.init().map_err(BridgeError::Ffmpeg)?;
837 Ok(context)
838}
839
840#[cfg(all(target_os = "linux", feature = "hwdevice"))]
844#[derive(Clone, Debug, Eq, PartialEq)]
845pub struct VulkanFrameInfo {
846 pub hardware_format: i32,
848 pub software_format: i32,
850 pub width: i32,
852 pub height: i32,
854 pub coded_width: i32,
856 pub coded_height: i32,
858 pub plane_count: u32,
860 pub sampled: bool,
862 pub instance: u64,
864 pub physical_device: u64,
866 pub device: u64,
868 pub images: [u64; MAX_VULKAN_PLANES],
870 pub formats: [u32; MAX_VULKAN_PLANES],
872 pub layouts: [u32; MAX_VULKAN_PLANES],
874 pub access: [u32; MAX_VULKAN_PLANES],
876 pub semaphores: [u64; MAX_VULKAN_PLANES],
878 pub semaphore_values: [u64; MAX_VULKAN_PLANES],
880 pub queue_families: [u32; MAX_VULKAN_PLANES],
882}
883
884#[cfg(all(target_os = "linux", feature = "hwdevice"))]
886pub type VulkanFrameDescriptor = interop::Alpha2VulkanFrameInfo;
887
888#[cfg(all(target_os = "linux", feature = "hwdevice"))]
893#[repr(transparent)]
894#[derive(Default)]
895pub struct VulkanFrameAccess(interop::Alpha2VulkanFrameAccess);
896
897#[cfg(all(target_os = "linux", feature = "hwdevice"))]
898impl VulkanFrameAccess {
899 #[must_use]
901 pub const fn frame(&self) -> &VulkanFrameDescriptor {
902 &self.0.frame
903 }
904
905 pub const fn frame_mut(&mut self) -> &mut VulkanFrameDescriptor {
907 &mut self.0.frame
908 }
909
910 #[must_use]
912 pub const fn allocator(&self) -> u64 {
913 self.0.allocator
914 }
915
916 #[must_use]
918 pub const fn queue_family(&self) -> u32 {
919 self.0.queue_family
920 }
921
922 #[must_use]
924 pub const fn layer_count(&self) -> u32 {
925 self.0.layer_count
926 }
927}
928
929#[cfg(all(target_os = "linux", feature = "hwdevice"))]
930unsafe extern "C" {
931 fn alpha2_vulkan_get_format(
932 context: *mut ffi::AVCodecContext,
933 formats: *const ffi::AVPixelFormat,
934 ) -> ffi::AVPixelFormat;
935 fn alpha2_vulkan_frame_info(
936 frame: *const ffi::AVFrame,
937 output: *mut VulkanFrameDescriptor,
938 ) -> i32;
939 fn alpha2_vulkan_frames_add_usage(frames: *mut ffi::AVBufferRef, usage: u32) -> i32;
940 fn alpha2_vulkan_frames_usage(frames: *const ffi::AVBufferRef) -> u32;
941 fn alpha2_vulkan_frame_access_begin(
942 frame: *mut ffi::AVFrame,
943 output: *mut VulkanFrameAccess,
944 ) -> i32;
945 fn alpha2_vulkan_frame_access_end(
946 begun: *const interop::Alpha2VulkanFrameAccess,
947 updated: *const VulkanFrameAccess,
948 );
949}
950
951#[cfg(all(target_os = "linux", feature = "hwdevice"))]
953const fn vulkan_frame_refusal(status: i32) -> BridgeError {
954 BridgeError::InvalidInput(match status {
955 -1 => "frame is not a Vulkan hardware frame",
956 -2 => "Vulkan frame has no hardware frames or device context",
957 -3 => "Vulkan frame has no images",
958 -4 => "Vulkan frames context has no frame or queue locks, or no layers",
959 -5 => "Vulkan device has no graphics or compute queue",
960 _ => "Vulkan frame could not be read",
961 })
962}
963
964#[cfg(all(target_os = "linux", feature = "hwdevice"))]
966pub struct VulkanFrameLease {
967 frame: Mutex<AVFrame>,
968}
969
970#[cfg(all(target_os = "linux", feature = "hwdevice"))]
971impl fmt::Debug for VulkanFrameLease {
972 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
973 formatter
974 .debug_struct("VulkanFrameLease")
975 .finish_non_exhaustive()
976 }
977}
978
979#[cfg(all(target_os = "linux", feature = "hwdevice"))]
980impl VulkanFrameLease {
981 #[must_use]
983 pub fn new(frame: AVFrame) -> Self {
984 Self {
985 frame: Mutex::new(frame),
986 }
987 }
988
989 pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
994 let frame = self
995 .frame
996 .lock()
997 .unwrap_or_else(std::sync::PoisonError::into_inner);
998 download_hardware_frame(&frame).map(|frame| DecodedFrame {
999 frame,
1000 written: true,
1001 })
1002 }
1003
1004 pub fn info(&self) -> Result<VulkanFrameInfo, BridgeError> {
1009 let frame = self
1010 .frame
1011 .lock()
1012 .unwrap_or_else(std::sync::PoisonError::into_inner);
1013 vulkan_frame_info(&frame)
1014 }
1015
1016 pub fn lock_access(&self) -> Result<VulkanFrameAccessGuard<'_>, BridgeError> {
1021 let mut frame = self
1022 .frame
1023 .lock()
1024 .unwrap_or_else(std::sync::PoisonError::into_inner);
1025 let mut access = VulkanFrameAccess::default();
1026 let result =
1028 unsafe { alpha2_vulkan_frame_access_begin(frame.as_mut_ptr(), &raw mut access) };
1029 if result != 0 {
1030 return Err(vulkan_frame_refusal(result));
1031 }
1032 Ok(VulkanFrameAccessGuard {
1033 _frame: frame,
1034 begun: access.0,
1035 access,
1036 committed: false,
1037 })
1038 }
1039}
1040
1041#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1054pub struct VulkanFrameAccessGuard<'a> {
1055 _frame: MutexGuard<'a, AVFrame>,
1056 begun: interop::Alpha2VulkanFrameAccess,
1058 access: VulkanFrameAccess,
1059 committed: bool,
1060}
1061
1062#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1063impl VulkanFrameAccessGuard<'_> {
1064 #[must_use]
1066 pub const fn access(&self) -> &VulkanFrameAccess {
1067 &self.access
1068 }
1069
1070 #[must_use]
1077 pub const unsafe fn access_mut(&mut self) -> &mut VulkanFrameAccess {
1078 &mut self.access
1079 }
1080
1081 pub fn commit(&mut self) {
1083 self.committed = true;
1084 }
1085}
1086
1087#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1088impl Drop for VulkanFrameAccessGuard<'_> {
1089 fn drop(&mut self) {
1090 let updated = if self.committed {
1091 &raw const self.access
1092 } else {
1093 ptr::null()
1094 };
1095 unsafe { alpha2_vulkan_frame_access_end(&raw const self.begun, updated) };
1098 }
1099}
1100
1101#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1106pub struct VulkanEncodeFrameAccess<'a> {
1107 frame: &'a mut AVFrame,
1108 begun: interop::Alpha2VulkanFrameAccess,
1110 access: VulkanFrameAccess,
1111 committed: bool,
1112 _owning_thread: std::marker::PhantomData<*const ()>,
1113}
1114
1115#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1116impl<'a> VulkanEncodeFrameAccess<'a> {
1117 pub fn retain_frame(&self) -> Result<AVFrame, BridgeError> {
1122 let raw = unsafe { ffi::av_frame_clone(self.frame.as_ptr()) };
1124 let raw = std::ptr::NonNull::new(raw)
1125 .ok_or(BridgeError::InvalidInput("cannot retain encode frame"))?;
1126 Ok(unsafe { AVFrame::from_raw(raw) })
1128 }
1129
1130 pub fn begin(frame: &'a mut AVFrame) -> Result<Self, BridgeError> {
1135 let mut access = VulkanFrameAccess::default();
1136 let result =
1138 unsafe { alpha2_vulkan_frame_access_begin(frame.as_mut_ptr(), &raw mut access) };
1139 if result != 0 {
1140 return Err(vulkan_frame_refusal(result));
1141 }
1142 Ok(Self {
1143 frame,
1144 begun: access.0,
1145 access,
1146 committed: false,
1147 _owning_thread: std::marker::PhantomData,
1148 })
1149 }
1150
1151 #[must_use]
1153 pub const fn access(&self) -> &VulkanFrameAccess {
1154 &self.access
1155 }
1156
1157 #[must_use]
1163 pub const unsafe fn access_mut(&mut self) -> &mut VulkanFrameAccess {
1164 &mut self.access
1165 }
1166
1167 pub fn commit(&mut self) {
1169 self.committed = true;
1170 }
1171}
1172
1173#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1174impl Drop for VulkanEncodeFrameAccess<'_> {
1175 fn drop(&mut self) {
1176 let updated = if self.committed {
1177 &raw const self.access
1178 } else {
1179 ptr::null()
1180 };
1181 unsafe { alpha2_vulkan_frame_access_end(&raw const self.begun, updated) };
1184 }
1185}
1186
1187#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1189#[must_use]
1190pub fn vulkan_get_format_callback()
1191-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
1192 alpha2_vulkan_get_format
1193}
1194
1195#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1202pub fn vulkan_frame_info(frame: &AVFrame) -> Result<VulkanFrameInfo, BridgeError> {
1203 let mut raw = VulkanFrameDescriptor::default();
1204 let result = unsafe { alpha2_vulkan_frame_info(frame.as_ptr(), &raw mut raw) };
1207 if result != 0 {
1208 return Err(vulkan_frame_refusal(result));
1209 }
1210 Ok(VulkanFrameInfo {
1211 hardware_format: raw.hardware_format,
1212 software_format: raw.software_format,
1213 width: raw.width,
1214 height: raw.height,
1215 coded_width: raw.coded_width,
1216 coded_height: raw.coded_height,
1217 plane_count: raw.plane_count,
1218 sampled: raw.sampled != 0,
1219 instance: raw.instance,
1220 physical_device: raw.physical_device,
1221 device: raw.device,
1222 images: raw.images,
1223 formats: raw.formats,
1224 layouts: raw.layouts,
1225 access: raw.access,
1226 semaphores: raw.semaphores,
1227 semaphore_values: raw.semaphore_values,
1228 queue_families: raw.queue_families,
1229 })
1230}
1231
1232#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
1234fn hwdevice_available(kind: ffi::AVHWDeviceType) -> bool {
1235 let mut each = ffi::AV_HWDEVICE_TYPE_NONE;
1236 loop {
1237 each = unsafe { ffi::av_hwdevice_iterate_types(each) };
1239 if each == ffi::AV_HWDEVICE_TYPE_NONE {
1240 return false;
1241 }
1242 if each == kind {
1243 return true;
1244 }
1245 }
1246}
1247
1248#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
1250fn alloc_hwdevice(
1251 kind: ffi::AVHWDeviceType,
1252 name: &'static str,
1253) -> Result<AVHWDeviceContext, BridgeError> {
1254 if !hwdevice_available(kind) {
1255 return Err(BridgeError::MissingHwDevice(name));
1256 }
1257 Ok(AVHWDeviceContext::alloc(kind))
1258}
1259
1260#[derive(Debug)]
1262#[non_exhaustive]
1263pub enum BridgeError {
1264 InvalidInput(&'static str),
1266 Ffmpeg(RsmpegError),
1268 FfmpegCode(i32),
1271 NativeGpu(NativeGpuFailure),
1273 MissingHwDevice(&'static str),
1275 UnsupportedSurface(String),
1277}
1278
1279#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1281pub struct YuvPlaneLayout {
1282 pub chroma_shift_x: u8,
1284 pub chroma_shift_y: u8,
1286 pub bit_depth: u8,
1288 pub interleaved_chroma: bool,
1290 pub msb_aligned: bool,
1292}
1293
1294impl YuvPlaneLayout {
1295 #[must_use]
1297 pub const fn bytes_per_sample(self) -> u32 {
1298 if self.bit_depth > 8 { 2 } else { 1 }
1299 }
1300}
1301
1302#[derive(Clone, Debug, Eq, PartialEq)]
1304pub struct FramePlane {
1305 pub bytes: bytes::Bytes,
1307 pub stride: u32,
1309 pub width: u32,
1311 pub height: u32,
1313}
1314
1315#[derive(Clone, Debug, Eq, PartialEq)]
1317pub struct YuvPlanes {
1318 pub layout: YuvPlaneLayout,
1320 pub width: u32,
1322 pub height: u32,
1324 pub planes: Vec<FramePlane>,
1326}
1327
1328impl fmt::Display for BridgeError {
1329 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1330 match self {
1331 Self::InvalidInput(context) => {
1332 write!(formatter, "invalid media buffer input: {context}")
1333 }
1334 Self::Ffmpeg(error) => write!(formatter, "{error}"),
1335 Self::FfmpegCode(code) => write!(formatter, "{}", RsmpegError::AVError(*code)),
1336 Self::NativeGpu(failure) => write!(formatter, "{failure}"),
1337 Self::MissingHwDevice(kind) => {
1338 write!(formatter, "the loaded FFmpeg has no {kind} hardware device")
1339 }
1340 Self::UnsupportedSurface(description) => formatter.write_str(description),
1341 }
1342 }
1343}
1344
1345pub fn configure_colorspace(
1353 context: &mut SwsContext,
1354 source_coefficients: i32,
1355 source_full_range: bool,
1356 destination_coefficients: i32,
1357 destination_full_range: bool,
1358) -> Result<(), BridgeError> {
1359 let result = unsafe {
1362 ffi::sws_setColorspaceDetails(
1363 context.as_mut_ptr(),
1364 ffi::sws_getCoefficients(source_coefficients),
1365 i32::from(source_full_range),
1366 ffi::sws_getCoefficients(destination_coefficients),
1367 i32::from(destination_full_range),
1368 0,
1369 1 << 16,
1370 1 << 16,
1371 )
1372 };
1373 if result < 0 {
1374 Err(BridgeError::FfmpegCode(result))
1375 } else {
1376 Ok(())
1377 }
1378}
1379
1380pub fn scale_frame_packed(
1386 context: &mut SwsContext,
1387 frame: &AVFrame,
1388 target: ffi::AVPixelFormat,
1389 output: &mut [u8],
1390) -> Result<(), BridgeError> {
1391 let size = unsafe { ffi::av_image_get_buffer_size(target, frame.width, frame.height, 1) };
1393 if size < 0 {
1394 return Err(BridgeError::FfmpegCode(size));
1395 }
1396 if usize::try_from(size).ok() != Some(output.len()) {
1397 return Err(BridgeError::InvalidInput(
1398 "packed output is not the size of the converted frame",
1399 ));
1400 }
1401 let mut data = [ptr::null_mut::<u8>(); 4];
1402 let mut linesize = [0; 4];
1403 let filled = unsafe {
1405 ffi::av_image_fill_arrays(
1406 data.as_mut_ptr(),
1407 linesize.as_mut_ptr(),
1408 output.as_mut_ptr(),
1409 target,
1410 frame.width,
1411 frame.height,
1412 1,
1413 )
1414 };
1415 if filled < 0 {
1416 return Err(BridgeError::FfmpegCode(filled));
1417 }
1418 let rows = unsafe {
1421 ffi::sws_scale(
1422 context.as_mut_ptr(),
1423 frame.data.as_ptr().cast(),
1424 frame.linesize.as_ptr(),
1425 0,
1426 frame.height,
1427 data.as_ptr(),
1428 linesize.as_ptr(),
1429 )
1430 };
1431 if rows < 0 {
1432 Err(BridgeError::FfmpegCode(rows))
1433 } else {
1434 Ok(())
1435 }
1436}
1437
1438impl Error for BridgeError {}
1439
1440impl BridgeError {
1441 #[must_use]
1443 pub const fn gpu_fault(&self) -> Option<GpuFault> {
1444 match self {
1445 Self::NativeGpu(failure) => failure.fault,
1446 _ => None,
1447 }
1448 }
1449}
1450
1451#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1453pub enum NativeGpuBackend {
1454 Vulkan,
1456 D3D12,
1458 Metal,
1460}
1461
1462impl NativeGpuBackend {
1463 #[must_use]
1465 pub const fn as_str(self) -> &'static str {
1466 match self {
1467 Self::Vulkan => "Vulkan",
1468 Self::D3D12 => "D3D12",
1469 Self::Metal => "Metal",
1470 }
1471 }
1472}
1473
1474#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1478pub struct NativeGpuFailure {
1479 pub backend: NativeGpuBackend,
1481 pub operation: &'static str,
1483 pub step: &'static str,
1485 pub status: i32,
1487 pub fault: Option<GpuFault>,
1489}
1490
1491impl fmt::Display for NativeGpuFailure {
1492 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1493 let Self {
1494 backend,
1495 operation,
1496 step,
1497 status,
1498 ..
1499 } = *self;
1500 let backend = backend.as_str();
1501 if self.fault == Some(GpuFault::Timeout) {
1503 write!(
1504 formatter,
1505 "{backend} {operation} ({step}): {}",
1506 alpha2_media::GPU_TIMEOUT_MESSAGE
1507 )?;
1508 } else {
1509 write!(formatter, "{backend} {operation} failed at {step}")?;
1510 }
1511 if status == 0 {
1512 return Ok(());
1513 }
1514 match self.backend {
1515 NativeGpuBackend::Vulkan => write!(formatter, " (VkResult {status})"),
1516 NativeGpuBackend::D3D12 => write!(formatter, " (HRESULT {status:#010X})"),
1517 NativeGpuBackend::Metal => write!(formatter, " (CVReturn {status})"),
1518 }
1519 }
1520}
1521
1522impl From<RsmpegError> for BridgeError {
1523 fn from(error: RsmpegError) -> Self {
1524 Self::Ffmpeg(error)
1525 }
1526}
1527
1528#[must_use]
1530pub fn runtime_version() -> String {
1531 unsafe { CStr::from_ptr(ffi::av_version_info()) }
1533 .to_string_lossy()
1534 .into_owned()
1535}
1536
1537#[must_use]
1539pub fn runtime_configuration() -> String {
1540 unsafe { CStr::from_ptr(ffi::avcodec_configuration()) }
1542 .to_string_lossy()
1543 .into_owned()
1544}
1545
1546#[must_use]
1548pub fn muxer_accepts_codec(format: &CStr, codec: ffi::AVCodecID) -> Option<bool> {
1549 let muxer = unsafe { ffi::av_guess_format(format.as_ptr(), ptr::null(), ptr::null()) };
1552 if muxer.is_null() {
1553 return None;
1554 }
1555 let accepted = unsafe {
1557 ffi::avformat_query_codec(
1558 muxer,
1559 codec,
1560 i32::try_from(ffi::FF_COMPLIANCE_NORMAL).unwrap_or(0),
1561 )
1562 };
1563 Some(accepted == 1)
1564}
1565
1566#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1568pub struct SampleProfile {
1569 pub is_yuv_420: bool,
1571 pub bit_depth: u8,
1573 pub has_alpha: bool,
1575 pub is_rgb: bool,
1577}
1578
1579#[must_use]
1583pub fn sample_profile(format: ffi::AVPixelFormat) -> Option<SampleProfile> {
1584 if format == ffi::AV_PIX_FMT_NONE {
1585 return None;
1586 }
1587 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
1589 let components = usize::from(descriptor.nb_components.min(4));
1590 Some(SampleProfile {
1591 is_yuv_420: descriptor.log2_chroma_w == 1
1592 && descriptor.log2_chroma_h == 1
1593 && descriptor.nb_components >= 3
1594 && descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB) == 0,
1595 bit_depth: descriptor.comp[..components]
1596 .iter()
1597 .map(|component| u8::try_from(component.depth).unwrap_or(u8::MAX))
1598 .max()
1599 .unwrap_or(8),
1600 has_alpha: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_ALPHA) != 0,
1601 is_rgb: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB | ffi::AV_PIX_FMT_FLAG_PAL)
1602 != 0,
1603 })
1604}
1605
1606#[must_use]
1608pub fn pixel_format_name(format: ffi::AVPixelFormat) -> Option<String> {
1609 let name = unsafe { ffi::av_get_pix_fmt_name(format) };
1611 if name.is_null() {
1612 None
1613 } else {
1614 Some(
1616 unsafe { CStr::from_ptr(name) }
1617 .to_string_lossy()
1618 .into_owned(),
1619 )
1620 }
1621}
1622
1623pub fn open_codec_context(
1632 context: &mut rsmpeg::avcodec::AVCodecContext,
1633 options: rsmpeg::avutil::AVDictionary,
1634) -> Result<Option<rsmpeg::avutil::AVDictionary>, RsmpegError> {
1635 let _media_foundation = media_foundation_lock(context);
1636 let mut dictionary = options.into_raw().as_ptr();
1637 let status = unsafe {
1640 ffi::avcodec_open2(
1641 context.as_mut_ptr(),
1642 ptr::null_mut(),
1643 std::ptr::addr_of_mut!(dictionary),
1644 )
1645 };
1646 let unused = ptr::NonNull::new(dictionary)
1648 .map(|dictionary| unsafe { rsmpeg::avutil::AVDictionary::from_raw(dictionary) });
1649 if status < 0 {
1650 return Err(RsmpegError::from(status));
1651 }
1652 Ok(unused)
1653}
1654
1655fn media_foundation_lock(
1660 context: &rsmpeg::avcodec::AVCodecContext,
1661) -> Option<std::sync::MutexGuard<'static, ()>> {
1662 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1663 if !cfg!(target_os = "windows") || context.codec.is_null() {
1664 return None;
1665 }
1666 let name = unsafe { CStr::from_ptr((*context.codec).name) };
1668 name.to_bytes().ends_with(b"_mf").then(|| {
1669 LOCK.lock()
1670 .unwrap_or_else(std::sync::PoisonError::into_inner)
1671 })
1672}
1673
1674pub struct EncoderContext(Option<rsmpeg::avcodec::AVCodecContext>);
1678
1679impl From<rsmpeg::avcodec::AVCodecContext> for EncoderContext {
1680 fn from(context: rsmpeg::avcodec::AVCodecContext) -> Self {
1681 Self(Some(context))
1682 }
1683}
1684
1685impl std::ops::Deref for EncoderContext {
1686 type Target = rsmpeg::avcodec::AVCodecContext;
1687
1688 fn deref(&self) -> &Self::Target {
1689 self.0.as_ref().expect("taken only when dropped")
1690 }
1691}
1692
1693impl std::ops::DerefMut for EncoderContext {
1694 fn deref_mut(&mut self) -> &mut Self::Target {
1695 self.0.as_mut().expect("taken only when dropped")
1696 }
1697}
1698
1699impl Drop for EncoderContext {
1700 fn drop(&mut self) {
1701 if let Some(context) = self.0.take() {
1702 let _media_foundation = media_foundation_lock(&context);
1703 drop(context);
1704 }
1705 }
1706}
1707
1708#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1713pub const VULKAN_ENCODE_INPUT_USAGE: u32 = 0x0000_0001 | 0x0000_0002 | 0x0000_0004 | 0x0000_4000;
1714
1715#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1725pub fn vulkan_frames_add_usage(
1726 frames: &mut rsmpeg::avutil::AVHWFramesContext,
1727 usage: u32,
1728) -> Result<(), BridgeError> {
1729 let status = unsafe { alpha2_vulkan_frames_add_usage(frames.as_mut_ptr(), usage) };
1731 if status < 0 {
1732 return Err(BridgeError::FfmpegCode(status));
1733 }
1734 Ok(())
1735}
1736
1737#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1739#[must_use]
1740pub fn vulkan_frames_usage(frames: &rsmpeg::avutil::AVHWFramesContext) -> u32 {
1741 unsafe { alpha2_vulkan_frames_usage(frames.as_ptr()) }
1743}
1744
1745#[must_use]
1747pub fn packet_bytes(packet: &rsmpeg::avcodec::AVPacket) -> Vec<u8> {
1748 let size = usize::try_from(packet.size).unwrap_or(0);
1749 if packet.data.is_null() || size == 0 {
1750 return Vec::new();
1751 }
1752 unsafe { slice::from_raw_parts(packet.data.cast_const(), size) }.to_vec()
1754}
1755
1756#[must_use]
1767pub fn codec_parameters_extradata(parameters: &rsmpeg::avcodec::AVCodecParameters) -> Vec<u8> {
1768 let size = usize::try_from(parameters.extradata_size).unwrap_or(0);
1769 if parameters.extradata.is_null() || size == 0 {
1770 return Vec::new();
1771 }
1772 unsafe { slice::from_raw_parts(parameters.extradata.cast_const(), size) }.to_vec()
1774}
1775
1776#[must_use]
1778pub fn packet_has_side_data(
1779 packet: &rsmpeg::avcodec::AVPacket,
1780 kind: ffi::AVPacketSideDataType,
1781) -> bool {
1782 let mut size = 0_usize;
1783 !unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) }.is_null()
1785}
1786
1787#[must_use]
1792pub fn take_packet_side_data(
1793 packet: &mut rsmpeg::avcodec::AVPacket,
1794 kind: ffi::AVPacketSideDataType,
1795) -> Option<Vec<u8>> {
1796 let mut size = 0_usize;
1797 let data = unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) };
1799 if data.is_null() {
1800 return None;
1801 }
1802 let bytes = unsafe { slice::from_raw_parts(data, size) }.to_vec();
1804 let packet = packet.as_mut_ptr();
1805 unsafe {
1808 ffi::av_packet_side_data_remove(
1809 (*packet).side_data,
1810 &raw mut (*packet).side_data_elems,
1811 kind,
1812 );
1813 }
1814 Some(bytes)
1815}
1816
1817pub fn set_packet_side_data(
1822 packet: &mut rsmpeg::avcodec::AVPacket,
1823 kind: ffi::AVPacketSideDataType,
1824 data: &[u8],
1825) -> Result<(), BridgeError> {
1826 let _ = take_packet_side_data(packet, kind);
1827 let entry = unsafe { ffi::av_packet_new_side_data(packet.as_mut_ptr(), kind, data.len()) };
1830 if entry.is_null() {
1831 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
1832 }
1833 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), entry, data.len()) };
1835 Ok(())
1836}
1837
1838pub fn set_output_stream_extradata(
1846 output: &mut rsmpeg::avformat::AVFormatContextOutput,
1847 index: usize,
1848 data: &[u8],
1849) -> Result<(), BridgeError> {
1850 let parameters = output
1851 .streams()
1852 .get(index)
1853 .ok_or(BridgeError::InvalidInput("the output has no such stream"))?
1854 .codecpar;
1855 let size = i32::try_from(data.len())
1856 .map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
1857 let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
1858 let buffer = unsafe { ffi::av_mallocz(data.len() + padding) }.cast::<u8>();
1860 if buffer.is_null() {
1861 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
1862 }
1863 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), buffer, data.len()) };
1865 unsafe {
1868 ffi::av_freep((&raw mut (*parameters).extradata).cast());
1869 (*parameters).extradata = buffer;
1870 (*parameters).extradata_size = size;
1871 }
1872 Ok(())
1873}
1874
1875pub fn packet_from_bytes(data: &[u8]) -> Result<rsmpeg::avcodec::AVPacket, BridgeError> {
1880 let size = i32::try_from(data.len())
1881 .map_err(|_| BridgeError::InvalidInput("a packet is larger than FFmpeg holds"))?;
1882 let mut packet = rsmpeg::avcodec::AVPacket::new();
1883 let status = unsafe { ffi::av_new_packet(packet.as_mut_ptr(), size) };
1885 if status < 0 {
1886 return Err(BridgeError::FfmpegCode(status));
1887 }
1888 if !data.is_empty() {
1889 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), packet.data, data.len()) };
1891 }
1892 Ok(packet)
1893}
1894
1895pub fn shrink_packet_to(
1900 packet: &mut rsmpeg::avcodec::AVPacket,
1901 data: &[u8],
1902) -> Result<(), BridgeError> {
1903 let size = i32::try_from(data.len())
1904 .ok()
1905 .filter(|size| *size <= packet.size)
1906 .ok_or(BridgeError::InvalidInput("a packet cannot grow in place"))?;
1907 let status = unsafe { ffi::av_packet_make_writable(packet.as_mut_ptr()) };
1909 if status < 0 {
1910 return Err(BridgeError::FfmpegCode(status));
1911 }
1912 if !data.is_empty() {
1913 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), packet.data, data.len()) };
1915 }
1916 unsafe { ffi::av_shrink_packet(packet.as_mut_ptr(), size) };
1918 Ok(())
1919}
1920
1921#[derive(Clone, Copy, Debug)]
1923pub struct VideoStreamDescription<'a> {
1924 pub codec_id: ffi::AVCodecID,
1926 pub width: i32,
1928 pub height: i32,
1930 pub pixel_format: ffi::AVPixelFormat,
1932 pub profile: i32,
1934 pub level: i32,
1936 pub color_primaries: ffi::AVColorPrimaries,
1938 pub color_trc: ffi::AVColorTransferCharacteristic,
1940 pub color_space: ffi::AVColorSpace,
1942 pub color_range: ffi::AVColorRange,
1944 pub sample_aspect_ratio: ffi::AVRational,
1946 pub frame_rate: ffi::AVRational,
1948 pub bit_rate: i64,
1950 pub extradata: &'a [u8],
1952}
1953
1954pub fn video_codec_parameters(
1959 description: &VideoStreamDescription<'_>,
1960) -> Result<rsmpeg::avcodec::AVCodecParameters, BridgeError> {
1961 let extradata_size = i32::try_from(description.extradata.len())
1962 .map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
1963 let mut parameters = rsmpeg::avcodec::AVCodecParameters::new();
1964 let raw = parameters.as_mut_ptr();
1965 unsafe {
1967 (*raw).codec_type = ffi::AVMEDIA_TYPE_VIDEO;
1968 (*raw).codec_id = description.codec_id;
1969 (*raw).width = description.width;
1970 (*raw).height = description.height;
1971 (*raw).format = description.pixel_format;
1972 (*raw).profile = description.profile;
1973 (*raw).level = description.level;
1974 (*raw).color_primaries = description.color_primaries;
1975 (*raw).color_trc = description.color_trc;
1976 (*raw).color_space = description.color_space;
1977 (*raw).color_range = description.color_range;
1978 (*raw).sample_aspect_ratio = description.sample_aspect_ratio;
1979 (*raw).framerate = description.frame_rate;
1980 (*raw).bit_rate = description.bit_rate;
1981 (*raw).field_order = ffi::AV_FIELD_PROGRESSIVE;
1982 }
1983 if !description.extradata.is_empty() {
1984 let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
1985 let buffer = unsafe { ffi::av_mallocz(description.extradata.len() + padding) }.cast::<u8>();
1987 if buffer.is_null() {
1988 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
1989 }
1990 unsafe {
1993 ptr::copy_nonoverlapping(
1994 description.extradata.as_ptr(),
1995 buffer,
1996 description.extradata.len(),
1997 );
1998 (*raw).extradata = buffer;
1999 (*raw).extradata_size = extradata_size;
2000 }
2001 }
2002 Ok(parameters)
2003}
2004
2005pub fn bitstream_filter_list(
2014 spec: &CStr,
2015 parameters: &AVCodecParameters,
2016 time_base: rsmpeg::avutil::AVRational,
2017) -> Result<rsmpeg::avcodec::AVBSFContext, BridgeError> {
2018 let mut raw = ptr::null_mut();
2019 let status = unsafe { ffi::av_bsf_list_parse_str(spec.as_ptr(), &raw mut raw) };
2021 if status < 0 {
2022 return Err(BridgeError::FfmpegCode(status));
2023 }
2024 let raw = ptr::NonNull::new(raw).ok_or(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)))?;
2025 let mut context = unsafe { rsmpeg::avcodec::AVBSFContextUninit::from_raw(raw) };
2027 let status = unsafe { ffi::avcodec_parameters_copy(context.par_in, parameters.as_ptr()) };
2031 if status < 0 {
2032 return Err(BridgeError::FfmpegCode(status));
2033 }
2034 context.set_time_base_in(time_base);
2035 context.init().map_err(BridgeError::Ffmpeg)
2036}
2037
2038pub fn set_stream_codec_tag(stream: &mut rsmpeg::avformat::AVStreamMut<'_>, tag: u32) {
2040 let mut parameters = stream.codecpar_mut();
2041 unsafe { (*parameters.as_mut_ptr()).codec_tag = tag };
2043}
2044
2045pub fn set_stream_display_matrix(
2051 stream: &mut rsmpeg::avformat::AVStreamMut<'_>,
2052 display: DisplayRotation,
2053) -> Result<(), BridgeError> {
2054 let mut parameters = stream.codecpar_mut();
2055 let parameters = parameters.as_mut_ptr();
2056 let entry = unsafe {
2059 ffi::av_packet_side_data_new(
2060 &raw mut (*parameters).coded_side_data,
2061 &raw mut (*parameters).nb_coded_side_data,
2062 ffi::AV_PKT_DATA_DISPLAYMATRIX,
2063 size_of::<[i32; 9]>(),
2064 0,
2065 )
2066 };
2067 if entry.is_null() {
2068 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2069 }
2070 let mut matrix = [0_i32; 9];
2071 unsafe {
2074 ffi::av_display_rotation_set(matrix.as_mut_ptr(), -display.counter_clockwise_degrees);
2075 if display.flipped {
2076 ffi::av_display_matrix_flip(matrix.as_mut_ptr(), 1, 0);
2077 }
2078 }
2079 unsafe {
2082 ptr::copy_nonoverlapping(
2083 matrix.as_ptr().cast::<u8>(),
2084 (*entry).data,
2085 size_of::<[i32; 9]>(),
2086 );
2087 }
2088 Ok(())
2089}
2090
2091#[must_use]
2093pub fn hardware_device_type_name(device_type: ffi::AVHWDeviceType) -> Option<String> {
2094 let name = unsafe { ffi::av_hwdevice_get_type_name(device_type) };
2096 if name.is_null() {
2097 None
2098 } else {
2099 Some(
2101 unsafe { CStr::from_ptr(name) }
2102 .to_string_lossy()
2103 .into_owned(),
2104 )
2105 }
2106}
2107
2108#[must_use]
2115pub fn yuv_layout(format: ffi::AVPixelFormat) -> Option<YuvPlaneLayout> {
2116 if format == ffi::AV_PIX_FMT_NONE {
2117 return None;
2118 }
2119 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
2121 let foreign_endian = if cfg!(target_endian = "little") {
2122 ffi::AV_PIX_FMT_FLAG_BE
2123 } else {
2124 0
2125 };
2126 let rejected = foreign_endian
2127 | ffi::AV_PIX_FMT_FLAG_PAL
2128 | ffi::AV_PIX_FMT_FLAG_BITSTREAM
2129 | ffi::AV_PIX_FMT_FLAG_HWACCEL
2130 | ffi::AV_PIX_FMT_FLAG_RGB
2131 | ffi::AV_PIX_FMT_FLAG_ALPHA
2132 | ffi::AV_PIX_FMT_FLAG_BAYER
2133 | ffi::AV_PIX_FMT_FLAG_FLOAT
2134 | ffi::AV_PIX_FMT_FLAG_XYZ;
2135 if descriptor.flags & u64::from(rejected) != 0 || descriptor.nb_components != 3 {
2136 return None;
2137 }
2138 let [luma, u, v, _] = descriptor.comp;
2139 let depth = luma.depth;
2140 if !(8..=16).contains(&depth) || u.depth != depth || v.depth != depth {
2141 return None;
2142 }
2143 if descriptor.log2_chroma_w > 2 || descriptor.log2_chroma_h > 2 {
2144 return None;
2145 }
2146 let bytes = if depth > 8 { 2 } else { 1 };
2147 let shift = luma.shift;
2148 let msb_aligned = match shift {
2149 0 => false,
2150 _ if depth > 8 && shift == 16 - depth => true,
2151 _ => return None,
2152 };
2153 let planar = u.plane == 1
2154 && v.plane == 2
2155 && u.step == bytes
2156 && v.step == bytes
2157 && u.offset == 0
2158 && v.offset == 0;
2159 let interleaved = u.plane == 1
2160 && v.plane == 1
2161 && u.step == 2 * bytes
2162 && v.step == 2 * bytes
2163 && u.offset == 0
2164 && v.offset == bytes;
2165 if luma.plane != 0
2166 || luma.step != bytes
2167 || luma.offset != 0
2168 || u.shift != shift
2169 || v.shift != shift
2170 || !(planar || interleaved)
2171 {
2172 return None;
2173 }
2174 Some(YuvPlaneLayout {
2175 chroma_shift_x: descriptor.log2_chroma_w,
2176 chroma_shift_y: descriptor.log2_chroma_h,
2177 bit_depth: u8::try_from(depth).ok()?,
2178 interleaved_chroma: interleaved,
2179 msb_aligned,
2180 })
2181}
2182
2183pub fn yuv_planes(frame: &AVFrame) -> Result<Option<YuvPlanes>, BridgeError> {
2192 frame_yuv_planes(frame, false)
2193}
2194
2195pub struct DecodedFrame {
2199 frame: AVFrame,
2200 written: bool,
2201}
2202
2203impl DecodedFrame {
2204 pub fn receive(decoder: &mut AVCodecContext) -> Result<Self, RsmpegError> {
2209 let written = decoder_frames_are_initialized(decoder);
2210 let frame = decoder.receive_frame()?;
2211 Ok(Self { frame, written })
2212 }
2213
2214 pub fn yuv_planes(&self) -> Result<Option<YuvPlanes>, BridgeError> {
2221 frame_yuv_planes(&self.frame, self.written)
2222 }
2223
2224 pub fn set_pts(&mut self, pts: i64) {
2226 self.frame.set_pts(pts);
2227 }
2228
2229 #[must_use]
2231 pub fn into_frame(self) -> AVFrame {
2232 self.frame
2233 }
2234}
2235
2236impl std::ops::Deref for DecodedFrame {
2237 type Target = AVFrame;
2238
2239 fn deref(&self) -> &AVFrame {
2240 &self.frame
2241 }
2242}
2243
2244impl fmt::Debug for DecodedFrame {
2245 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2246 formatter
2247 .debug_struct("DecodedFrame")
2248 .field("format", &self.frame.format)
2249 .field("width", &self.frame.width)
2250 .field("height", &self.frame.height)
2251 .field("pts", &self.frame.pts)
2252 .field("written", &self.written)
2253 .finish_non_exhaustive()
2254 }
2255}
2256
2257fn decoder_frames_are_initialized(decoder: &AVCodecContext) -> bool {
2262 static DEFAULT_GET_BUFFER2: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2263 let default = *DEFAULT_GET_BUFFER2.get_or_init(|| {
2265 unsafe {
2267 let mut context = ffi::avcodec_alloc_context3(ptr::null());
2268 let get_buffer2 = context
2269 .as_ref()
2270 .and_then(|context| context.get_buffer2)
2271 .map_or(0, |function| function as usize);
2272 ffi::avcodec_free_context(&raw mut context);
2273 get_buffer2
2274 }
2275 });
2276 let wrapped =
2278 unsafe { decoder.codec.as_ref() }.is_none_or(|codec| !codec.wrapper_name.is_null());
2279 let get_buffer2 = decoder.get_buffer2.map_or(0, |function| function as usize);
2280 !wrapped && default != 0 && get_buffer2 == default
2281}
2282
2283fn frame_yuv_planes(frame: &AVFrame, share: bool) -> Result<Option<YuvPlanes>, BridgeError> {
2285 let Some(layout) = yuv_layout(frame.format) else {
2286 return Ok(None);
2287 };
2288 let width =
2289 u32::try_from(frame.width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2290 let height = u32::try_from(frame.height)
2291 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2292 if width == 0 || height == 0 {
2293 return Err(BridgeError::InvalidInput("empty YUV frame"));
2294 }
2295 let bytes = layout.bytes_per_sample();
2296 let chroma_width = width.div_ceil(1 << layout.chroma_shift_x);
2297 let chroma_height = height.div_ceil(1 << layout.chroma_shift_y);
2298 let shared =
2300 (share && !frame.buf[0].is_null()).then(|| std::sync::Arc::new(SharedFrame(frame.clone())));
2301 let copy = |index: usize,
2302 samples: u32,
2303 pairs: u32,
2304 rows: u32,
2305 context: &'static str|
2306 -> Result<FramePlane, BridgeError> {
2307 let row_bytes = samples
2308 .checked_mul(bytes)
2309 .and_then(|row| row.checked_mul(pairs))
2310 .ok_or(BridgeError::InvalidInput(context))?;
2311 if let Some(view) = shared
2312 .as_ref()
2313 .and_then(|frame| PlaneView::new(frame, index, row_bytes, rows))
2314 {
2315 let stride =
2316 u32::try_from(view.stride).map_err(|_| BridgeError::InvalidInput(context))?;
2317 return Ok(FramePlane {
2318 bytes: bytes::Bytes::from_owner(view),
2319 stride,
2320 width: samples,
2321 height: rows,
2322 });
2323 }
2324 Ok(FramePlane {
2325 bytes: copy_video_plane(
2326 frame.data[index],
2327 frame.linesize[index],
2328 row_bytes,
2329 rows,
2330 context,
2331 )?
2332 .into(),
2333 stride: row_bytes,
2334 width: samples,
2335 height: rows,
2336 })
2337 };
2338 let mut planes = vec![copy(0, width, 1, height, "invalid Y plane")?];
2339 if layout.interleaved_chroma {
2340 planes.push(copy(1, chroma_width, 2, chroma_height, "invalid UV plane")?);
2341 } else {
2342 planes.push(copy(1, chroma_width, 1, chroma_height, "invalid U plane")?);
2343 planes.push(copy(2, chroma_width, 1, chroma_height, "invalid V plane")?);
2344 }
2345 Ok(Some(YuvPlanes {
2346 layout,
2347 width,
2348 height,
2349 planes,
2350 }))
2351}
2352
2353struct SharedFrame(AVFrame);
2355
2356unsafe impl Sync for SharedFrame {}
2359
2360struct PlaneView {
2362 frame: std::sync::Arc<SharedFrame>,
2363 data: *const u8,
2364 len: usize,
2365 stride: usize,
2366}
2367
2368unsafe impl Send for PlaneView {}
2371
2372impl PlaneView {
2373 fn new(
2375 frame: &std::sync::Arc<SharedFrame>,
2376 index: usize,
2377 row_bytes: u32,
2378 rows: u32,
2379 ) -> Option<Self> {
2380 let inner = &frame.0;
2381 let data = inner.data[index].cast_const();
2382 let stride = usize::try_from(inner.linesize[index]).ok()?;
2383 let row_bytes = usize::try_from(row_bytes).ok()?;
2384 if data.is_null() || stride < row_bytes || rows == 0 {
2385 return None;
2386 }
2387 let len = stride.checked_mul(usize::try_from(rows).ok()?)?;
2388 let start = data as usize;
2389 let end = start.checked_add(len)?;
2390 let inside = inner.buf.iter().any(|buffer| {
2391 let Some(buffer) = (unsafe { buffer.as_ref() }) else {
2393 return false;
2394 };
2395 let buffer_start = buffer.data as usize;
2396 buffer_start
2397 .checked_add(buffer.size)
2398 .is_some_and(|buffer_end| buffer_start <= start && end <= buffer_end)
2399 });
2400 inside.then(|| Self {
2401 frame: std::sync::Arc::clone(frame),
2402 data,
2403 len,
2404 stride,
2405 })
2406 }
2407}
2408
2409impl AsRef<[u8]> for PlaneView {
2410 fn as_ref(&self) -> &[u8] {
2411 let _ = &self.frame;
2412 unsafe { slice::from_raw_parts(self.data, self.len) }
2414 }
2415}
2416
2417fn copy_video_plane(
2418 data: *const u8,
2419 linesize: i32,
2420 width: u32,
2421 height: u32,
2422 context: &'static str,
2423) -> Result<Vec<u8>, BridgeError> {
2424 let row_bytes = usize::try_from(width).map_err(|_| BridgeError::InvalidInput(context))?;
2425 let rows = usize::try_from(height).map_err(|_| BridgeError::InvalidInput(context))?;
2426 let stride =
2427 usize::try_from(linesize.unsigned_abs()).map_err(|_| BridgeError::InvalidInput(context))?;
2428 if data.is_null() || linesize == 0 || stride < row_bytes {
2429 return Err(BridgeError::InvalidInput(context));
2430 }
2431 let byte_len = row_bytes
2432 .checked_mul(rows)
2433 .ok_or(BridgeError::InvalidInput(context))?;
2434 let mut output = Vec::with_capacity(byte_len);
2436 let signed_stride =
2437 isize::try_from(linesize).map_err(|_| BridgeError::InvalidInput(context))?;
2438 for row in 0..rows {
2439 let row = isize::try_from(row).map_err(|_| BridgeError::InvalidInput(context))?;
2440 let offset = row
2441 .checked_mul(signed_stride)
2442 .ok_or(BridgeError::InvalidInput(context))?;
2443 let source = unsafe { slice::from_raw_parts(data.offset(offset), row_bytes) };
2446 output.extend_from_slice(source);
2447 }
2448 debug_assert_eq!(output.len(), byte_len);
2449 Ok(output)
2450}
2451
2452pub fn rgba_frame(bytes: &[u8], width: i32, height: i32) -> Result<AVFrame, BridgeError> {
2457 let row_bytes = usize::try_from(width)
2458 .ok()
2459 .and_then(|value| value.checked_mul(4))
2460 .ok_or(BridgeError::InvalidInput("RGBA row width overflow"))?;
2461 let rows =
2462 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("negative RGBA height"))?;
2463 let expected = row_bytes
2464 .checked_mul(rows)
2465 .ok_or(BridgeError::InvalidInput("RGBA frame size overflow"))?;
2466 if bytes.len() != expected {
2467 return Err(BridgeError::InvalidInput("RGBA byte length mismatch"));
2468 }
2469
2470 let mut frame = AVFrame::new();
2471 frame.set_format(ffi::AV_PIX_FMT_RGBA);
2472 frame.set_width(width);
2473 frame.set_height(height);
2474 frame.get_buffer(1)?;
2475 frame.make_writable()?;
2476 let linesize = usize::try_from(frame.linesize[0])
2477 .map_err(|_| BridgeError::InvalidInput("negative RGBA linesize"))?;
2478 if frame.data[0].is_null() || linesize < row_bytes {
2479 return Err(BridgeError::InvalidInput(
2480 "RGBA destination plane is too small",
2481 ));
2482 }
2483
2484 unsafe {
2487 for row in 0..rows {
2488 let destination_offset = row.checked_mul(linesize).ok_or(BridgeError::InvalidInput(
2489 "RGBA destination offset overflow",
2490 ))?;
2491 let source_offset = row
2492 .checked_mul(row_bytes)
2493 .ok_or(BridgeError::InvalidInput("RGBA source offset overflow"))?;
2494 let destination =
2495 slice::from_raw_parts_mut(frame.data[0].add(destination_offset), row_bytes);
2496 destination.copy_from_slice(&bytes[source_offset..source_offset + row_bytes]);
2497 }
2498 }
2499 Ok(frame)
2500}
2501
2502#[allow(clippy::too_many_arguments)]
2507pub fn yuv420p_frame(
2508 y: &[u8],
2509 y_stride: u32,
2510 u: &[u8],
2511 u_stride: u32,
2512 v: &[u8],
2513 v_stride: u32,
2514 width: i32,
2515 height: i32,
2516) -> Result<AVFrame, BridgeError> {
2517 let width =
2518 u32::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2519 let height =
2520 u32::try_from(height).map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2521 if width == 0 || height == 0 {
2522 return Err(BridgeError::InvalidInput("empty YUV frame"));
2523 }
2524 let chroma_width = width.div_ceil(2);
2525 let chroma_height = height.div_ceil(2);
2526 validate_source_plane(y, y_stride, width, height, "invalid Y source plane")?;
2527 validate_source_plane(
2528 u,
2529 u_stride,
2530 chroma_width,
2531 chroma_height,
2532 "invalid U source plane",
2533 )?;
2534 validate_source_plane(
2535 v,
2536 v_stride,
2537 chroma_width,
2538 chroma_height,
2539 "invalid V source plane",
2540 )?;
2541
2542 let mut frame = AVFrame::new();
2543 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
2544 frame.set_width(i32::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV width"))?);
2545 frame.set_height(i32::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV height"))?);
2546 frame.get_buffer(1)?;
2547 frame.make_writable()?;
2548 copy_plane_to_frame(y, y_stride, width, height, frame.data[0], frame.linesize[0])?;
2549 copy_plane_to_frame(
2550 u,
2551 u_stride,
2552 chroma_width,
2553 chroma_height,
2554 frame.data[1],
2555 frame.linesize[1],
2556 )?;
2557 copy_plane_to_frame(
2558 v,
2559 v_stride,
2560 chroma_width,
2561 chroma_height,
2562 frame.data[2],
2563 frame.linesize[2],
2564 )?;
2565 Ok(frame)
2566}
2567
2568#[must_use]
2571pub fn yuv_pixel_format(layout: YuvPlaneLayout) -> Option<ffi::AVPixelFormat> {
2572 let mut descriptor = ptr::null();
2573 loop {
2574 descriptor = unsafe { ffi::av_pix_fmt_desc_next(descriptor) };
2576 if descriptor.is_null() {
2577 return None;
2578 }
2579 let format = unsafe { ffi::av_pix_fmt_desc_get_id(descriptor) };
2581 if yuv_layout(format) == Some(layout) {
2582 return Some(format);
2583 }
2584 }
2585}
2586
2587pub fn planar_frame(
2592 format: ffi::AVPixelFormat,
2593 planes: &[(&[u8], u32)],
2594 width: u32,
2595 height: u32,
2596) -> Result<AVFrame, BridgeError> {
2597 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
2599 .ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
2600 let count = unsafe { ffi::av_pix_fmt_count_planes(format) };
2602 if usize::try_from(count).ok() != Some(planes.len()) || width == 0 || height == 0 {
2603 return Err(BridgeError::InvalidInput(
2604 "planes do not match the pixel format",
2605 ));
2606 }
2607 let signed = |value: u32| {
2608 i32::try_from(value).map_err(|_| BridgeError::InvalidInput("frame size overflow"))
2609 };
2610 let mut frame = AVFrame::new();
2611 frame.set_format(format);
2612 frame.set_width(signed(width)?);
2613 frame.set_height(signed(height)?);
2614 frame.get_buffer(1)?;
2615 frame.make_writable()?;
2616 for (index, (bytes, stride)) in planes.iter().enumerate() {
2617 let plane = i32::try_from(index).unwrap_or(i32::MAX);
2618 let row_bytes = unsafe { ffi::av_image_get_linesize(format, signed(width)?, plane) };
2620 let row_bytes = u32::try_from(row_bytes).map_err(|_| BridgeError::FfmpegCode(row_bytes))?;
2621 let rows = if index == 0 || index == 3 {
2622 height
2623 } else {
2624 height.div_ceil(1 << descriptor.log2_chroma_h)
2625 };
2626 validate_source_plane(bytes, *stride, row_bytes, rows, "invalid source plane")?;
2627 copy_plane_to_frame(
2628 bytes,
2629 *stride,
2630 row_bytes,
2631 rows,
2632 frame.data[index],
2633 frame.linesize[index],
2634 )?;
2635 }
2636 Ok(frame)
2637}
2638
2639fn validate_source_plane(
2640 bytes: &[u8],
2641 stride: u32,
2642 width: u32,
2643 height: u32,
2644 context: &'static str,
2645) -> Result<(), BridgeError> {
2646 let required = usize::try_from(stride).ok().and_then(|stride| {
2647 usize::try_from(height)
2648 .ok()
2649 .and_then(|height| stride.checked_mul(height))
2650 });
2651 if stride < width || required != Some(bytes.len()) {
2652 Err(BridgeError::InvalidInput(context))
2653 } else {
2654 Ok(())
2655 }
2656}
2657
2658fn copy_plane_to_frame(
2659 source: &[u8],
2660 source_stride: u32,
2661 width: u32,
2662 height: u32,
2663 destination: *mut u8,
2664 destination_stride: i32,
2665) -> Result<(), BridgeError> {
2666 let row_bytes =
2667 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV row width overflow"))?;
2668 let rows =
2669 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV row count overflow"))?;
2670 let source_stride = usize::try_from(source_stride)
2671 .map_err(|_| BridgeError::InvalidInput("YUV source stride overflow"))?;
2672 let destination_stride = usize::try_from(destination_stride)
2673 .map_err(|_| BridgeError::InvalidInput("negative YUV destination stride"))?;
2674 if destination.is_null() || destination_stride < row_bytes {
2675 return Err(BridgeError::InvalidInput(
2676 "YUV destination plane is too small",
2677 ));
2678 }
2679 for row in 0..rows {
2680 let source_offset = row
2681 .checked_mul(source_stride)
2682 .ok_or(BridgeError::InvalidInput("YUV source offset overflow"))?;
2683 let destination_offset = row
2684 .checked_mul(destination_stride)
2685 .ok_or(BridgeError::InvalidInput("YUV destination offset overflow"))?;
2686 unsafe {
2689 ptr::copy_nonoverlapping(
2690 source.as_ptr().add(source_offset),
2691 destination.add(destination_offset),
2692 row_bytes,
2693 );
2694 }
2695 }
2696 Ok(())
2697}
2698
2699pub fn audio_frame(
2706 channels: &[Vec<f32>],
2707 format: ffi::AVSampleFormat,
2708 sample_rate: i32,
2709 pts: i64,
2710) -> Result<AVFrame, BridgeError> {
2711 let Some(samples) = channels.first().map(Vec::len) else {
2712 return Err(BridgeError::InvalidInput("invalid planar audio shape"));
2713 };
2714 if sample_rate <= 0
2715 || samples == 0
2716 || channels.len() > ffi::AV_NUM_DATA_POINTERS as usize
2717 || channels.iter().any(|channel| channel.len() != samples)
2718 {
2719 return Err(BridgeError::InvalidInput(
2720 "planar audio channel lengths differ",
2721 ));
2722 }
2723 let sample_bytes = match format {
2724 ffi::AV_SAMPLE_FMT_FLTP | ffi::AV_SAMPLE_FMT_FLT => size_of::<f32>(),
2725 ffi::AV_SAMPLE_FMT_S16 => size_of::<i16>(),
2726 _ => return Err(BridgeError::InvalidInput("unsupported audio sample format")),
2727 };
2728 let planar = format == ffi::AV_SAMPLE_FMT_FLTP;
2729 let channel_count = i32::try_from(channels.len())
2730 .map_err(|_| BridgeError::InvalidInput("audio channel count overflow"))?;
2731 let sample_count = i32::try_from(samples)
2732 .map_err(|_| BridgeError::InvalidInput("audio sample count overflow"))?;
2733 let mut frame = AVFrame::new();
2734 frame.set_format(format);
2735 frame.set_ch_layout(
2736 rsmpeg::avutil::AVChannelLayout::from_nb_channels(channel_count).into_inner(),
2737 );
2738 frame.set_sample_rate(sample_rate);
2739 frame.set_nb_samples(sample_count);
2740 frame.set_pts(pts);
2741 frame.set_time_base(ffi::AVRational {
2742 num: 1,
2743 den: sample_rate,
2744 });
2745 frame.get_buffer(0)?;
2746 frame.make_writable()?;
2747 let plane_bytes = samples
2748 .checked_mul(sample_bytes)
2749 .and_then(|bytes| bytes.checked_mul(if planar { 1 } else { channels.len() }))
2750 .ok_or(BridgeError::InvalidInput("audio plane byte count overflow"))?;
2751 let plane_capacity = usize::try_from(frame.linesize[0]).unwrap_or(0);
2752 if plane_capacity < plane_bytes {
2753 return Err(BridgeError::InvalidInput(
2754 "audio plane capacity is too small",
2755 ));
2756 }
2757 let planes = if planar { channels.len() } else { 1 };
2758 if frame.data[..planes].iter().any(|plane| plane.is_null()) {
2759 return Err(BridgeError::InvalidInput("audio plane pointer is null"));
2760 }
2761 if planar {
2762 for (index, source) in channels.iter().enumerate() {
2763 unsafe {
2766 ptr::copy_nonoverlapping(
2767 source.as_ptr().cast::<u8>(),
2768 frame.data[index],
2769 plane_bytes,
2770 );
2771 }
2772 }
2773 return Ok(frame);
2774 }
2775 let plane = unsafe { std::slice::from_raw_parts_mut(frame.data[0], plane_bytes) };
2777 let interleaved = (0..samples).flat_map(|sample| channels.iter().map(move |c| c[sample]));
2778 if format == ffi::AV_SAMPLE_FMT_FLT {
2779 for (bytes, value) in plane.as_chunks_mut::<4>().0.iter_mut().zip(interleaved) {
2780 *bytes = value.to_ne_bytes();
2781 }
2782 } else {
2783 for (bytes, value) in plane.as_chunks_mut::<2>().0.iter_mut().zip(interleaved) {
2784 #[allow(clippy::cast_possible_truncation)]
2786 let value = (value.clamp(-1.0, 1.0) * 32_767.0).round() as i16;
2787 *bytes = value.to_ne_bytes();
2788 }
2789 }
2790 Ok(frame)
2791}
2792
2793#[must_use]
2808pub fn channel_layout_channels(layout: &rsmpeg::avutil::AVChannelLayout) -> Vec<ffi::AVChannel> {
2809 if layout.order == ffi::AV_CHANNEL_ORDER_UNSPEC {
2810 return Vec::new();
2811 }
2812 let channels = u32::try_from(layout.nb_channels).unwrap_or(0);
2813 (0..channels)
2814 .map(|index| {
2815 layout
2816 .channel_from_index(index)
2817 .unwrap_or(ffi::AV_CHAN_NONE)
2818 })
2819 .collect()
2820}
2821
2822pub fn channel_layout_of(
2827 channels: &[ffi::AVChannel],
2828) -> Result<rsmpeg::avutil::AVChannelLayout, BridgeError> {
2829 let count = i32::try_from(channels.len())
2830 .ok()
2831 .filter(|count| *count > 0)
2832 .ok_or(BridgeError::InvalidInput("a channel layout needs channels"))?;
2833 let mut raw = std::mem::MaybeUninit::<ffi::AVChannelLayout>::zeroed();
2834 let initialized = unsafe { ffi::av_channel_layout_custom_init(raw.as_mut_ptr(), count) };
2836 if initialized < 0 {
2837 return Err(BridgeError::FfmpegCode(initialized));
2838 }
2839 let mut layout = unsafe { rsmpeg::avutil::AVChannelLayout::new(raw.assume_init()) };
2841 for (index, channel) in channels.iter().enumerate() {
2842 unsafe { (*layout.u.map.add(index)).id = *channel };
2844 }
2845 let flags = i32::try_from(ffi::AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL).unwrap_or(0);
2846 let retyped = unsafe {
2848 ffi::av_channel_layout_retype(layout.as_mut_ptr(), ffi::AV_CHANNEL_ORDER_UNSPEC, flags)
2849 };
2850 if retyped < 0 {
2851 return Err(BridgeError::FfmpegCode(retyped));
2852 }
2853 Ok(layout)
2854}
2855
2856pub fn audio_f32_interleaved(frame: &AVFrame) -> Result<Vec<f32>, BridgeError> {
2861 if frame.format != ffi::AV_SAMPLE_FMT_FLT || frame.nb_samples < 0 {
2862 return Err(BridgeError::InvalidInput(
2863 "packed audio format or sample count is invalid",
2864 ));
2865 }
2866 let channels = usize::try_from(frame.ch_layout().nb_channels)
2867 .map_err(|_| BridgeError::InvalidInput("packed audio channel count is negative"))?;
2868 let frames = usize::try_from(frame.nb_samples)
2869 .map_err(|_| BridgeError::InvalidInput("packed audio sample count is negative"))?;
2870 let samples = frames
2871 .checked_mul(channels)
2872 .ok_or(BridgeError::InvalidInput(
2873 "packed audio sample count overflow",
2874 ))?;
2875 let bytes = samples
2876 .checked_mul(size_of::<f32>())
2877 .ok_or(BridgeError::InvalidInput(
2878 "packed audio byte count overflow",
2879 ))?;
2880 if channels == 0
2881 || frame.data[0].is_null()
2882 || usize::try_from(frame.linesize[0]).unwrap_or(0) < bytes
2883 {
2884 return Err(BridgeError::InvalidInput(
2885 "packed audio plane is missing or too small",
2886 ));
2887 }
2888 let mut output = vec![0.0_f32; samples];
2889 unsafe {
2891 ptr::copy_nonoverlapping(frame.data[0], output.as_mut_ptr().cast::<u8>(), bytes);
2892 }
2893 Ok(output)
2894}
2895
2896#[derive(Copy, Clone, Debug, PartialEq)]
2898pub struct DisplayRotation {
2899 pub counter_clockwise_degrees: f64,
2901 pub flipped: bool,
2903}
2904
2905#[must_use]
2907pub fn display_rotation(parameters: &AVCodecParameters) -> Option<DisplayRotation> {
2908 let count = usize::try_from(parameters.nb_coded_side_data).ok()?;
2909 if count == 0 || count > 64 || parameters.coded_side_data.is_null() {
2910 return None;
2911 }
2912 let entries = unsafe { slice::from_raw_parts(parameters.coded_side_data, count) };
2914 let matrix = entries.iter().find(|entry| {
2915 entry.type_ == ffi::AV_PKT_DATA_DISPLAYMATRIX
2916 && entry.size >= 9 * size_of::<i32>()
2917 && !entry.data.is_null()
2918 })?;
2919 unsafe { matrix_rotation(matrix.data) }
2921}
2922
2923#[must_use]
2925pub fn frame_display_rotation(frame: &AVFrame) -> Option<DisplayRotation> {
2926 let entry =
2928 unsafe { ffi::av_frame_get_side_data(frame.as_ptr(), ffi::AV_FRAME_DATA_DISPLAYMATRIX) };
2929 if entry.is_null() {
2930 return None;
2931 }
2932 let entry = unsafe { &*entry };
2934 if entry.size < 9 * size_of::<i32>() || entry.data.is_null() {
2935 return None;
2936 }
2937 unsafe { matrix_rotation(entry.data) }
2939}
2940
2941unsafe fn matrix_rotation(data: *const u8) -> Option<DisplayRotation> {
2944 let mut aligned = [0_i32; 9];
2945 unsafe {
2947 ptr::copy_nonoverlapping(
2948 data,
2949 aligned.as_mut_ptr().cast::<u8>(),
2950 size_of::<[i32; 9]>(),
2951 );
2952 }
2953 let determinant = i64::from(aligned[0]) * i64::from(aligned[4])
2956 - i64::from(aligned[1]) * i64::from(aligned[3]);
2957 let flipped = determinant < 0;
2958 if flipped {
2959 unsafe { ffi::av_display_matrix_flip(aligned.as_mut_ptr(), 1, 0) };
2961 }
2962 let angle = unsafe { ffi::av_display_rotation_get(aligned.as_ptr()) };
2964 angle.is_finite().then_some(DisplayRotation {
2965 counter_clockwise_degrees: angle,
2966 flipped,
2967 })
2968}
2969
2970const fn size_of<T>() -> usize {
2971 std::mem::size_of::<T>()
2972}
2973
2974pub struct Yuv420pFrameBuffer {
2979 frame: AVFrame,
2980 width: usize,
2981 height: usize,
2982 planes: Vec<(usize, usize)>,
2984}
2985
2986pub struct Yuv420pFramePlanes<'a> {
2988 pub y: &'a mut [u8],
2990 pub y_stride: usize,
2992 pub u: &'a mut [u8],
2994 pub v: &'a mut [u8],
2996 pub chroma_stride: usize,
2998}
2999
3000impl Yuv420pFrameBuffer {
3001 pub fn new(width: i32, height: i32) -> Result<Self, BridgeError> {
3006 let coded_width =
3007 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
3008 let coded_height = usize::try_from(height)
3009 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
3010 if coded_width == 0 || coded_height == 0 {
3011 return Err(BridgeError::InvalidInput("empty YUV frame"));
3012 }
3013 Ok(Self {
3014 frame: zeroed_frame(width, height, ffi::AV_PIX_FMT_YUV420P)?,
3015 width: coded_width,
3016 height: coded_height,
3017 planes: Vec::new(),
3018 })
3019 }
3020
3021 pub fn with_format(
3027 width: i32,
3028 height: i32,
3029 format: ffi::AVPixelFormat,
3030 planes: &[(usize, usize)],
3031 ) -> Result<Self, BridgeError> {
3032 let coded_width = usize::try_from(width)
3033 .map_err(|_| BridgeError::InvalidInput("negative frame width"))?;
3034 let coded_height = usize::try_from(height)
3035 .map_err(|_| BridgeError::InvalidInput("negative frame height"))?;
3036 if coded_width == 0 || coded_height == 0 || planes.is_empty() || planes.len() > 4 {
3037 return Err(BridgeError::InvalidInput("empty frame"));
3038 }
3039 let frame = zeroed_frame(width, height, format)?;
3040 for (index, &(row_bytes, _)) in planes.iter().enumerate() {
3041 if frame.data[index].is_null()
3042 || plane_stride(frame.linesize[index], row_bytes).is_err()
3043 {
3044 return Err(BridgeError::InvalidInput(
3045 "the frame's planes are not the ones described",
3046 ));
3047 }
3048 }
3049 Ok(Self {
3050 frame,
3051 width: coded_width,
3052 height: coded_height,
3053 planes: planes.to_vec(),
3054 })
3055 }
3056
3057 pub fn begin_write_planes(&mut self) -> Result<Vec<(&mut [u8], usize)>, BridgeError> {
3062 if self.planes.is_empty() {
3063 let planes = self.begin_write()?;
3064 let chroma = planes.chroma_stride;
3065 return Ok(vec![
3066 (planes.y, planes.y_stride),
3067 (planes.u, chroma),
3068 (planes.v, chroma),
3069 ]);
3070 }
3071 self.make_writable()?;
3072 let mut written = Vec::with_capacity(self.planes.len());
3073 for (index, &(row_bytes, rows)) in self.planes.iter().enumerate() {
3074 let stride = plane_stride(self.frame.linesize[index], row_bytes)?;
3075 let plane = unsafe { writable_plane(self.frame.data[index], stride, row_bytes, rows)? };
3078 written.push((plane, stride));
3079 }
3080 Ok(written)
3081 }
3082
3083 fn make_writable(&mut self) -> Result<(), BridgeError> {
3084 if !self.frame.is_writable().map_err(BridgeError::Ffmpeg)? {
3087 let mut copy = zeroed_frame(self.frame.width, self.frame.height, self.frame.format)?;
3088 let status = unsafe {
3091 let copied = ffi::av_frame_copy(copy.as_mut_ptr(), self.frame.as_ptr());
3092 if copied < 0 {
3093 copied
3094 } else {
3095 ffi::av_frame_copy_props(copy.as_mut_ptr(), self.frame.as_ptr())
3096 }
3097 };
3098 if status < 0 {
3099 return Err(BridgeError::FfmpegCode(status));
3100 }
3101 self.frame = copy;
3102 }
3103 Ok(())
3104 }
3105
3106 #[must_use]
3108 pub const fn dimensions(&self) -> (usize, usize) {
3109 (self.width, self.height)
3110 }
3111
3112 pub fn begin_write(&mut self) -> Result<Yuv420pFramePlanes<'_>, BridgeError> {
3117 if !self.planes.is_empty() {
3118 return Err(BridgeError::InvalidInput("the frame is not YUV420P"));
3119 }
3120 self.make_writable()?;
3121 let chroma_width = self.width.div_ceil(2);
3122 let chroma_height = self.height.div_ceil(2);
3123 let y_stride = plane_stride(self.frame.linesize[0], self.width)?;
3124 let u_stride = plane_stride(self.frame.linesize[1], chroma_width)?;
3125 let v_stride = plane_stride(self.frame.linesize[2], chroma_width)?;
3126 if u_stride != v_stride {
3127 return Err(BridgeError::InvalidInput("mismatched chroma strides"));
3128 }
3129 let y = unsafe { writable_plane(self.frame.data[0], y_stride, self.width, self.height)? };
3132 let u =
3134 unsafe { writable_plane(self.frame.data[1], u_stride, chroma_width, chroma_height)? };
3135 let v =
3137 unsafe { writable_plane(self.frame.data[2], v_stride, chroma_width, chroma_height)? };
3138 Ok(Yuv420pFramePlanes {
3139 y,
3140 y_stride,
3141 u,
3142 v,
3143 chroma_stride: u_stride,
3144 })
3145 }
3146
3147 pub fn set_presentation(&mut self, pts: i64, time_base: ffi::AVRational) {
3149 self.frame.set_pts(pts);
3150 self.frame.set_time_base(time_base);
3151 }
3152
3153 #[must_use]
3155 pub const fn frame(&self) -> &AVFrame {
3156 &self.frame
3157 }
3158
3159 #[must_use]
3161 pub const fn size(&self) -> (usize, usize) {
3162 (self.width, self.height)
3163 }
3164}
3165
3166fn zeroed_frame(
3168 width: i32,
3169 height: i32,
3170 format: ffi::AVPixelFormat,
3171) -> Result<AVFrame, BridgeError> {
3172 let mut frame = AVFrame::new();
3173 frame.set_format(format);
3174 frame.set_width(width);
3175 frame.set_height(height);
3176 frame.get_buffer(1)?;
3177 for buffer in frame.buf {
3178 if let Some(buffer) = unsafe { buffer.as_ref() } {
3180 unsafe { ptr::write_bytes(buffer.data, 0, buffer.size) };
3182 }
3183 }
3184 Ok(frame)
3185}
3186
3187impl fmt::Debug for Yuv420pFrameBuffer {
3188 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3189 formatter
3190 .debug_struct("Yuv420pFrameBuffer")
3191 .field("width", &self.width)
3192 .field("height", &self.height)
3193 .finish_non_exhaustive()
3194 }
3195}
3196
3197fn plane_stride(linesize: i32, width: usize) -> Result<usize, BridgeError> {
3198 let stride = usize::try_from(linesize)
3199 .map_err(|_| BridgeError::InvalidInput("negative frame linesize"))?;
3200 if stride < width {
3201 return Err(BridgeError::InvalidInput(
3202 "frame linesize is below its width",
3203 ));
3204 }
3205 Ok(stride)
3206}
3207
3208unsafe fn writable_plane<'a>(
3211 data: *mut u8,
3212 stride: usize,
3213 width: usize,
3214 height: usize,
3215) -> Result<&'a mut [u8], BridgeError> {
3216 if data.is_null() {
3217 return Err(BridgeError::InvalidInput("frame plane is null"));
3218 }
3219 let len = stride
3220 .checked_mul(height.saturating_sub(1))
3221 .and_then(|rows| rows.checked_add(width))
3222 .ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
3223 Ok(unsafe { slice::from_raw_parts_mut(data, len) })
3225}
3226
3227#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3228mod vulkan_shim_tests;
3229
3230#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3231mod vulkan_access_tests;
3232
3233#[cfg(test)]
3234mod tests;