1use std::{error::Error, ffi::CStr, fmt, ptr, slice};
10
11#[cfg(all(target_os = "linux", feature = "hwdevice"))]
12use std::ffi::CString;
13
14#[cfg(all(
15 any(target_os = "linux", target_os = "macos", target_os = "windows"),
16 feature = "hwdevice"
17))]
18use std::sync::Mutex;
19#[cfg(all(target_os = "linux", feature = "hwdevice"))]
20use std::sync::MutexGuard;
21
22pub use alpha2_media::GpuFault;
23use rsmpeg::{
24 avcodec::{AVCodecContext, AVCodecParameters},
25 avutil::AVFrame,
26 error::RsmpegError,
27 ffi,
28 swscale::SwsContext,
29};
30
31#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
32use rsmpeg::avutil::AVHWDeviceContext;
33
34mod container;
35
36pub use container::{DemuxInput, OpenInputError, OutputFile, open_input};
37
38#[allow(
48 dead_code,
49 non_camel_case_types,
50 unreachable_pub,
51 clippy::all,
52 clippy::pedantic,
53 clippy::undocumented_unsafe_blocks
54)]
55mod interop {
56 include!(concat!(env!("OUT_DIR"), "/interop.rs"));
57}
58
59const _: () = {
64 use interop::{Alpha2D3D12FrameInfo as D3D12, Alpha2VideoToolboxFrameInfo as VideoToolbox};
65 use std::mem::{align_of, offset_of, size_of};
66 assert!(size_of::<D3D12>() == 64 && align_of::<D3D12>() == 8);
67 assert!(offset_of!(D3D12, resource) == 0);
68 assert!(offset_of!(D3D12, fence) == 8);
69 assert!(offset_of!(D3D12, fence_value) == 16);
70 assert!(offset_of!(D3D12, device) == 24);
71 assert!(offset_of!(D3D12, dxgi_format) == 32);
72 assert!(offset_of!(D3D12, width) == 36);
73 assert!(offset_of!(D3D12, height) == 40);
74 assert!(offset_of!(D3D12, hardware_format) == 44);
75 assert!(offset_of!(D3D12, software_format) == 48);
76 assert!(offset_of!(D3D12, surface_width) == 52);
77 assert!(offset_of!(D3D12, surface_height) == 56);
78 assert!(size_of::<VideoToolbox>() == 24 && align_of::<VideoToolbox>() == 8);
79 assert!(offset_of!(VideoToolbox, pixel_buffer) == 0);
80 assert!(offset_of!(VideoToolbox, width) == 8);
81 assert!(offset_of!(VideoToolbox, height) == 12);
82 assert!(offset_of!(VideoToolbox, hardware_format) == 16);
83 assert!(offset_of!(VideoToolbox, software_format) == 20);
84};
85
86#[cfg(all(
93 any(target_os = "linux", target_os = "macos", target_os = "windows"),
94 feature = "hwdevice"
95))]
96#[allow(clippy::cast_ptr_alignment)]
98fn download_hardware_frame(frame: &AVFrame) -> Result<AVFrame, BridgeError> {
99 if frame.hw_frames_ctx.is_null() {
100 return Err(BridgeError::InvalidInput(
101 "hardware frame has no frames context",
102 ));
103 }
104 let written = unsafe {
107 let frames = (*frame.hw_frames_ctx).data.cast::<ffi::AVHWFramesContext>();
108 ((*frames).width, (*frames).height)
109 };
110 let mut software = AVFrame::new();
111 software
112 .hwframe_transfer_data(frame)
113 .map_err(BridgeError::Ffmpeg)?;
114 zero_row_padding(&mut software, written.0, written.1)?;
119 let copied = unsafe { ffi::av_frame_copy_props(software.as_mut_ptr(), frame.as_ptr()) };
123 if copied < 0 {
124 return Err(BridgeError::FfmpegCode(copied));
125 }
126 Ok(software)
127}
128
129#[cfg_attr(not(feature = "hwdevice"), allow(dead_code))]
140fn zero_row_padding(frame: &mut AVFrame, width: i32, height: i32) -> Result<(), BridgeError> {
141 let format = frame.format;
142 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
145 .ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
146 if width < 0 || height < 0 {
147 return Err(BridgeError::InvalidInput("negative frame size"));
148 }
149 let planes = unsafe { ffi::av_pix_fmt_count_planes(format) };
151 for plane in 0..usize::try_from(planes).unwrap_or(0).min(4) {
152 let plane_index = i32::try_from(plane).unwrap_or(0);
153 let written = unsafe { ffi::av_image_get_linesize(format, width, plane_index) };
155 let (Ok(written), Ok(stride)) = (
156 usize::try_from(written),
157 usize::try_from(frame.linesize[plane]),
158 ) else {
159 continue;
160 };
161 let data = frame.data[plane];
162 if data.is_null() || stride <= written {
163 continue;
164 }
165 let shift = if plane == 1 || plane == 2 {
166 descriptor.log2_chroma_h
167 } else {
168 0
169 };
170 let rows = usize::try_from(-((-height) >> shift)).unwrap_or(0);
171 let start = data as usize;
172 let end = stride
173 .checked_mul(rows)
174 .and_then(|len| start.checked_add(len))
175 .ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
176 let inside = frame.buf.iter().any(|buffer| {
177 unsafe { buffer.as_ref() }.is_some_and(|buffer| {
180 let buffer_start = buffer.data as usize;
181 buffer_start <= start && buffer_start.saturating_add(buffer.size) >= end
182 })
183 });
184 if !inside {
185 return Err(BridgeError::InvalidInput(
186 "frame plane lies outside its buffers",
187 ));
188 }
189 for row in 0..rows {
190 unsafe { ptr::write_bytes(data.add(row * stride + written), 0, stride - written) };
193 }
194 }
195 Ok(())
196}
197
198#[cfg(all(target_os = "windows", feature = "hwdevice"))]
199use interop::Alpha2D3D12FrameInfo as D3D12FrameDescriptor;
200
201#[cfg(all(target_os = "windows", feature = "hwdevice"))]
202unsafe extern "C" {
203 fn alpha2_d3d12_device_init(
204 context: *mut ffi::AVHWDeviceContext,
205 device: *mut std::ffi::c_void,
206 ) -> i32;
207 fn alpha2_d3d12_get_format(
208 context: *mut ffi::AVCodecContext,
209 formats: *const ffi::AVPixelFormat,
210 ) -> ffi::AVPixelFormat;
211 fn alpha2_d3d12_frame_info(
212 frame: *const ffi::AVFrame,
213 output: *mut D3D12FrameDescriptor,
214 ) -> i32;
215 fn alpha2_d3d12_frame_set_fence_value(frame: *mut ffi::AVFrame, value: u64) -> i32;
216 fn alpha2_d3d12_frame_fence_value(frame: *const ffi::AVFrame, output: *mut u64) -> i32;
217 fn alpha2_d3d12_frames_configure(
218 frames: *mut ffi::AVBufferRef,
219 dxgi_format: u32,
220 resource_flags: u32,
221 ) -> i32;
222}
223
224#[cfg(all(target_os = "windows", feature = "hwdevice"))]
226pub const DXGI_FORMAT_NV12: u32 = 103;
227
228#[cfg(all(target_os = "windows", feature = "hwdevice"))]
245pub fn d3d12_frames_configure(
246 frames: &mut rsmpeg::avutil::AVHWFramesContext,
247 dxgi_format: u32,
248 resource_flags: u32,
249) -> Result<(), BridgeError> {
250 let status =
253 unsafe { alpha2_d3d12_frames_configure(frames.as_mut_ptr(), dxgi_format, resource_flags) };
254 if status < 0 {
255 return Err(BridgeError::FfmpegCode(status));
256 }
257 Ok(())
258}
259
260#[derive(Clone, Copy, Debug, Eq, PartialEq)]
266pub struct D3D12FrameInfo {
267 pub resource: u64,
269 pub fence: u64,
271 pub fence_value: u64,
273 pub device: u64,
275 pub dxgi_format: u32,
277 pub width: i32,
280 pub height: i32,
283 pub software_format: i32,
285 pub surface_width: u32,
287 pub surface_height: u32,
289}
290
291impl D3D12FrameInfo {
292 #[cfg_attr(
293 not(all(target_os = "windows", feature = "hwdevice")),
294 allow(dead_code)
295 )]
296 const fn from_descriptor(descriptor: &interop::Alpha2D3D12FrameInfo) -> Self {
297 Self {
298 resource: descriptor.resource,
299 fence: descriptor.fence,
300 fence_value: descriptor.fence_value,
301 device: descriptor.device,
302 dxgi_format: descriptor.dxgi_format,
303 width: descriptor.width,
304 height: descriptor.height,
305 software_format: descriptor.software_format,
306 surface_width: descriptor.surface_width,
307 surface_height: descriptor.surface_height,
308 }
309 }
310}
311
312#[cfg(all(target_os = "windows", feature = "hwdevice"))]
314pub struct D3D12FrameLease {
315 frame: Mutex<AVFrame>,
316}
317
318#[cfg(all(target_os = "windows", feature = "hwdevice"))]
319impl fmt::Debug for D3D12FrameLease {
320 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
321 formatter
322 .debug_struct("D3D12FrameLease")
323 .finish_non_exhaustive()
324 }
325}
326
327#[cfg(all(target_os = "windows", feature = "hwdevice"))]
328impl D3D12FrameLease {
329 #[must_use]
331 pub fn new(frame: AVFrame) -> Self {
332 Self {
333 frame: Mutex::new(frame),
334 }
335 }
336
337 pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
346 let frame = self
347 .frame
348 .lock()
349 .unwrap_or_else(std::sync::PoisonError::into_inner);
350 download_hardware_frame(&frame).map(|frame| DecodedFrame {
351 frame,
352 written: true,
353 })
354 }
355
356 pub fn info(&self) -> Result<D3D12FrameInfo, BridgeError> {
361 let frame = self
362 .frame
363 .lock()
364 .unwrap_or_else(std::sync::PoisonError::into_inner);
365 d3d12_frame_info(&frame)
366 }
367}
368
369#[cfg(all(target_os = "windows", feature = "hwdevice"))]
379#[allow(clippy::cast_ptr_alignment)]
380pub unsafe fn create_d3d12_device_context(device: u64) -> Result<AVHWDeviceContext, BridgeError> {
381 let device = usize::try_from(device)
382 .map_err(|_| BridgeError::InvalidInput("invalid D3D12 device pointer"))?
383 as *mut std::ffi::c_void;
384 if device.is_null() {
385 return Err(BridgeError::InvalidInput("D3D12 device is null"));
386 }
387 let mut context = alloc_hwdevice(ffi::AV_HWDEVICE_TYPE_D3D12VA, "D3D12")?;
388 let device_context = context.data.cast::<ffi::AVHWDeviceContext>();
389 let result = unsafe { alpha2_d3d12_device_init(device_context, device) };
392 if result < 0 {
393 return Err(BridgeError::FfmpegCode(result));
394 }
395 context.init().map_err(BridgeError::Ffmpeg)?;
396 Ok(context)
397}
398
399#[cfg(all(target_os = "windows", feature = "hwdevice"))]
401#[must_use]
402pub fn d3d12_get_format_callback()
403-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
404 alpha2_d3d12_get_format
405}
406
407#[cfg(all(target_os = "windows", feature = "hwdevice"))]
412pub fn d3d12_frame_info(frame: &AVFrame) -> Result<D3D12FrameInfo, BridgeError> {
413 let mut descriptor = D3D12FrameDescriptor::default();
414 let result = unsafe { alpha2_d3d12_frame_info(frame.as_ptr(), &raw mut descriptor) };
416 if result != 0 {
417 return Err(BridgeError::FfmpegCode(result));
418 }
419 Ok(D3D12FrameInfo::from_descriptor(&descriptor))
420}
421
422#[cfg(all(target_os = "windows", feature = "hwdevice"))]
428pub fn d3d12_frame_fence_value(frame: &AVFrame) -> Result<u64, BridgeError> {
429 let mut value = 0;
430 let result = unsafe { alpha2_d3d12_frame_fence_value(frame.as_ptr(), &raw mut value) };
432 if result != 0 {
433 return Err(BridgeError::FfmpegCode(result));
434 }
435 Ok(value)
436}
437
438#[cfg(all(target_os = "windows", feature = "hwdevice"))]
451pub struct D3D12EncodeFrameAccess<'a> {
452 frame: &'a mut AVFrame,
453 info: D3D12FrameInfo,
454}
455
456#[cfg(all(target_os = "windows", feature = "hwdevice"))]
457impl fmt::Debug for D3D12EncodeFrameAccess<'_> {
458 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
459 formatter
460 .debug_struct("D3D12EncodeFrameAccess")
461 .field("info", &self.info)
462 .finish_non_exhaustive()
463 }
464}
465
466#[cfg(all(target_os = "windows", feature = "hwdevice"))]
467impl<'a> D3D12EncodeFrameAccess<'a> {
468 pub fn begin(frame: &'a mut AVFrame) -> Result<Self, BridgeError> {
473 let info = d3d12_frame_info(frame)?;
474 Ok(Self { frame, info })
475 }
476
477 #[must_use]
480 pub const fn info(&self) -> &D3D12FrameInfo {
481 &self.info
482 }
483
484 pub fn retain_frame(&self) -> Result<AVFrame, BridgeError> {
489 let raw = unsafe { ffi::av_frame_clone(self.frame.as_ptr()) };
492 let raw = std::ptr::NonNull::new(raw)
493 .ok_or(BridgeError::InvalidInput("cannot retain encode frame"))?;
494 Ok(unsafe { AVFrame::from_raw(raw) })
496 }
497
498 pub fn commit(&mut self, fence_value: u64) -> Result<(), BridgeError> {
505 let result =
508 unsafe { alpha2_d3d12_frame_set_fence_value(self.frame.as_mut_ptr(), fence_value) };
509 if result != 0 {
510 return Err(BridgeError::FfmpegCode(result));
511 }
512 self.info.fence_value = fence_value;
513 Ok(())
514 }
515}
516
517#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
518use interop::Alpha2VideoToolboxFrameInfo as VideoToolboxFrameDescriptor;
519
520#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
523#[cfg_attr(target_os = "linux", allow(dead_code))]
524unsafe extern "C" {
525 fn alpha2_videotoolbox_get_format(
526 context: *mut ffi::AVCodecContext,
527 formats: *const ffi::AVPixelFormat,
528 ) -> ffi::AVPixelFormat;
529 fn alpha2_videotoolbox_frame_info(
530 frame: *const ffi::AVFrame,
531 output: *mut VideoToolboxFrameDescriptor,
532 ) -> i32;
533}
534
535#[derive(Clone, Copy, Debug, Eq, PartialEq)]
540pub struct VideoToolboxFrameInfo {
541 pub pixel_buffer: u64,
543 pub width: i32,
545 pub height: i32,
547 pub software_format: i32,
549}
550
551impl VideoToolboxFrameInfo {
552 #[cfg_attr(
553 not(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice")),
554 allow(dead_code)
555 )]
556 const fn from_descriptor(descriptor: &interop::Alpha2VideoToolboxFrameInfo) -> Self {
557 Self {
558 pixel_buffer: descriptor.pixel_buffer,
559 width: descriptor.width,
560 height: descriptor.height,
561 software_format: descriptor.software_format,
562 }
563 }
564}
565
566#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
570#[cfg_attr(target_os = "linux", allow(dead_code))]
571fn read_videotoolbox_frame_info(frame: &AVFrame) -> Result<VideoToolboxFrameInfo, BridgeError> {
572 let mut descriptor = VideoToolboxFrameDescriptor::default();
573 let result = unsafe { alpha2_videotoolbox_frame_info(frame.as_ptr(), &raw mut descriptor) };
576 if result != 0 {
577 return Err(BridgeError::InvalidInput(
578 "frame is not a VideoToolbox frame with a pixel buffer",
579 ));
580 }
581 Ok(VideoToolboxFrameInfo::from_descriptor(&descriptor))
582}
583
584#[cfg(all(target_os = "macos", feature = "hwdevice"))]
586pub struct VideoToolboxFrameLease {
587 frame: Mutex<AVFrame>,
588}
589
590#[cfg(all(target_os = "macos", feature = "hwdevice"))]
591impl fmt::Debug for VideoToolboxFrameLease {
592 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
593 formatter
594 .debug_struct("VideoToolboxFrameLease")
595 .finish_non_exhaustive()
596 }
597}
598
599#[cfg(all(target_os = "macos", feature = "hwdevice"))]
600impl VideoToolboxFrameLease {
601 #[must_use]
603 pub fn new(frame: AVFrame) -> Self {
604 Self {
605 frame: Mutex::new(frame),
606 }
607 }
608
609 pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
618 let frame = self
619 .frame
620 .lock()
621 .unwrap_or_else(std::sync::PoisonError::into_inner);
622 download_hardware_frame(&frame).map(|frame| DecodedFrame {
623 frame,
624 written: true,
625 })
626 }
627
628 pub fn info(&self) -> Result<VideoToolboxFrameInfo, BridgeError> {
633 let frame = self
634 .frame
635 .lock()
636 .unwrap_or_else(std::sync::PoisonError::into_inner);
637 read_videotoolbox_frame_info(&frame)
638 }
639}
640
641#[cfg(all(target_os = "macos", feature = "hwdevice"))]
643#[must_use]
644pub fn videotoolbox_get_format_callback()
645-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
646 alpha2_videotoolbox_get_format
647}
648
649#[cfg(all(target_os = "macos", feature = "hwdevice"))]
654pub fn videotoolbox_frame_info(frame: &AVFrame) -> Result<VideoToolboxFrameInfo, BridgeError> {
655 read_videotoolbox_frame_info(frame)
656}
657
658#[cfg(all(target_os = "linux", feature = "hwdevice"))]
660pub const MAX_VULKAN_PLANES: usize = interop::ALPHA2_MAX_VULKAN_PLANES as usize;
661
662#[cfg(all(target_os = "linux", feature = "hwdevice"))]
664#[derive(Clone, Debug)]
665pub struct VulkanDeviceInfo {
666 pub instance: u64,
668 pub physical_device: u64,
670 pub device: u64,
672 pub graphics_queue_family: u32,
674 pub graphics_queue_count: u32,
676 pub decode_queue_family: u32,
678 pub decode_queue_count: u32,
680 pub decode_video_caps: u32,
682 pub encode_queue_family: u32,
684 pub encode_queue_count: u32,
686 pub encode_video_caps: u32,
688 pub core_features: [u32; 4],
690 pub synchronization2: u32,
692 pub sampler_ycbcr_conversion: u32,
694 pub video_maintenance1: u32,
696 pub instance_extensions: Vec<String>,
698 pub device_extensions: Vec<String>,
700}
701
702#[cfg(all(target_os = "linux", feature = "hwdevice"))]
703use interop::Alpha2VulkanDeviceInfo as VulkanDeviceDescriptor;
704
705#[cfg(all(target_os = "linux", feature = "hwdevice"))]
713pub struct VulkanQueueLock {
714 device: AVHWDeviceContext,
715 queue_family: u32,
716}
717
718#[cfg(all(target_os = "linux", feature = "hwdevice"))]
723unsafe impl Sync for VulkanQueueLock {}
724
725#[cfg(all(target_os = "linux", feature = "hwdevice"))]
726impl fmt::Debug for VulkanQueueLock {
727 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
728 formatter
729 .debug_struct("VulkanQueueLock")
730 .field("queue_family", &self.queue_family)
731 .finish_non_exhaustive()
732 }
733}
734
735#[cfg(all(target_os = "linux", feature = "hwdevice"))]
736impl VulkanQueueLock {
737 #[allow(clippy::cast_ptr_alignment)]
744 pub fn new(device: AVHWDeviceContext, queue_family: u32) -> Result<Self, BridgeError> {
745 let lockable = unsafe {
748 alpha2_vulkan_queue_lockable(
749 device.data.cast::<ffi::AVHWDeviceContext>().cast_const(),
750 queue_family,
751 )
752 };
753 if lockable == 0 {
754 return Err(BridgeError::InvalidInput(
755 "Vulkan queue lock needs a family of a device the bridge initialized",
756 ));
757 }
758 Ok(Self {
759 device,
760 queue_family,
761 })
762 }
763
764 #[must_use]
767 pub fn lock(&self) -> VulkanQueueGuard<'_> {
768 unsafe { self.raw_lock() };
771 VulkanQueueGuard {
772 lock: self,
773 _owning_thread: std::marker::PhantomData,
774 }
775 }
776
777 #[allow(clippy::cast_ptr_alignment)]
785 pub unsafe fn raw_lock(&self) {
786 unsafe {
789 alpha2_vulkan_queue_lock(
790 self.device.data.cast::<ffi::AVHWDeviceContext>(),
791 self.queue_family,
792 );
793 }
794 }
795
796 #[allow(clippy::cast_ptr_alignment)]
804 pub unsafe fn raw_unlock(&self) {
805 unsafe {
807 alpha2_vulkan_queue_unlock(
808 self.device.data.cast::<ffi::AVHWDeviceContext>(),
809 self.queue_family,
810 );
811 }
812 }
813}
814
815#[cfg(all(target_os = "linux", feature = "hwdevice"))]
820#[derive(Debug)]
821pub struct VulkanQueueGuard<'a> {
822 lock: &'a VulkanQueueLock,
823 _owning_thread: std::marker::PhantomData<*const ()>,
824}
825
826#[cfg(all(target_os = "linux", feature = "hwdevice"))]
827impl Drop for VulkanQueueGuard<'_> {
828 fn drop(&mut self) {
829 unsafe { self.lock.raw_unlock() };
832 }
833}
834
835#[cfg(all(target_os = "linux", feature = "hwdevice"))]
836unsafe extern "C" {
837 fn alpha2_vulkan_queue_lockable(
838 context: *const ffi::AVHWDeviceContext,
839 queue_family: u32,
840 ) -> i32;
841 fn alpha2_vulkan_queue_lock(context: *mut ffi::AVHWDeviceContext, queue_family: u32);
842 fn alpha2_vulkan_queue_unlock(context: *mut ffi::AVHWDeviceContext, queue_family: u32);
843 fn alpha2_vulkan_device_init(
844 context: *mut ffi::AVHWDeviceContext,
845 info: *const VulkanDeviceDescriptor,
846 ) -> i32;
847}
848
849#[cfg(all(target_os = "linux", feature = "hwdevice"))]
862#[allow(clippy::cast_ptr_alignment)]
863pub unsafe fn create_vulkan_device_context(
864 info: &VulkanDeviceInfo,
865) -> Result<AVHWDeviceContext, BridgeError> {
866 let instance = info
867 .instance_extensions
868 .iter()
869 .map(|s| CString::new(s.as_str()))
870 .collect::<Result<Vec<_>, _>>()
871 .map_err(|_| BridgeError::InvalidInput("Vulkan extension contains NUL"))?;
872 let device = info
873 .device_extensions
874 .iter()
875 .map(|s| CString::new(s.as_str()))
876 .collect::<Result<Vec<_>, _>>()
877 .map_err(|_| BridgeError::InvalidInput("Vulkan extension contains NUL"))?;
878 let instance_ptrs = instance.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
879 let device_ptrs = device.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
880 let raw = VulkanDeviceDescriptor {
881 instance: info.instance,
882 physical_device: info.physical_device,
883 device: info.device,
884 graphics_queue_family: info.graphics_queue_family,
885 graphics_queue_count: info.graphics_queue_count,
886 decode_queue_family: info.decode_queue_family,
887 decode_queue_count: info.decode_queue_count,
888 decode_video_caps: info.decode_video_caps,
889 encode_queue_family: info.encode_queue_family,
890 encode_queue_count: info.encode_queue_count,
891 encode_video_caps: info.encode_video_caps,
892 shader_image_gather_extended: info.core_features[0],
893 fragment_stores_and_atomics: info.core_features[1],
894 shader_int64: info.core_features[2],
895 vertex_pipeline_stores_and_atomics: info.core_features[3],
896 synchronization2: info.synchronization2,
897 sampler_ycbcr_conversion: info.sampler_ycbcr_conversion,
898 video_maintenance1: info.video_maintenance1,
899 instance_extensions: instance_ptrs.as_ptr(),
900 instance_extension_count: u32::try_from(instance_ptrs.len())
901 .map_err(|_| BridgeError::InvalidInput("too many Vulkan extensions"))?,
902 device_extensions: device_ptrs.as_ptr(),
903 device_extension_count: u32::try_from(device_ptrs.len())
904 .map_err(|_| BridgeError::InvalidInput("too many Vulkan extensions"))?,
905 };
906 let mut context = alloc_hwdevice(ffi::AV_HWDEVICE_TYPE_VULKAN, "Vulkan")?;
907 let device_context = context.data.cast::<ffi::AVHWDeviceContext>();
908 let result = unsafe { alpha2_vulkan_device_init(device_context, &raw const raw) };
912 if result < 0 {
913 return Err(BridgeError::FfmpegCode(result));
914 }
915 context.init().map_err(BridgeError::Ffmpeg)?;
916 Ok(context)
917}
918
919#[cfg(all(target_os = "linux", feature = "hwdevice"))]
924#[derive(Clone, Debug, Eq, PartialEq)]
925pub struct VulkanFrameInfo {
926 pub hardware_format: i32,
928 pub software_format: i32,
930 pub width: i32,
932 pub height: i32,
934 pub coded_width: i32,
936 pub coded_height: i32,
938 pub plane_count: u32,
940 pub sampled: bool,
942 pub instance: u64,
944 pub physical_device: u64,
946 pub device: u64,
948 pub images: [u64; MAX_VULKAN_PLANES],
950 pub formats: [u32; MAX_VULKAN_PLANES],
952 pub layouts: [u32; MAX_VULKAN_PLANES],
954 pub access: [u32; MAX_VULKAN_PLANES],
956 pub semaphores: [u64; MAX_VULKAN_PLANES],
958 pub semaphore_values: [u64; MAX_VULKAN_PLANES],
960 pub queue_families: [u32; MAX_VULKAN_PLANES],
962}
963
964#[cfg(all(target_os = "linux", feature = "hwdevice"))]
967pub type VulkanFrameDescriptor = interop::Alpha2VulkanFrameInfo;
968
969#[cfg(all(target_os = "linux", feature = "hwdevice"))]
978#[repr(transparent)]
979#[derive(Default)]
980pub struct VulkanFrameAccess(interop::Alpha2VulkanFrameAccess);
981
982#[cfg(all(target_os = "linux", feature = "hwdevice"))]
983impl VulkanFrameAccess {
984 #[must_use]
986 pub const fn frame(&self) -> &VulkanFrameDescriptor {
987 &self.0.frame
988 }
989
990 pub const fn frame_mut(&mut self) -> &mut VulkanFrameDescriptor {
994 &mut self.0.frame
995 }
996
997 #[must_use]
999 pub const fn allocator(&self) -> u64 {
1000 self.0.allocator
1001 }
1002
1003 #[must_use]
1005 pub const fn queue_family(&self) -> u32 {
1006 self.0.queue_family
1007 }
1008
1009 #[must_use]
1011 pub const fn layer_count(&self) -> u32 {
1012 self.0.layer_count
1013 }
1014}
1015
1016#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1017unsafe extern "C" {
1018 fn alpha2_vulkan_get_format(
1019 context: *mut ffi::AVCodecContext,
1020 formats: *const ffi::AVPixelFormat,
1021 ) -> ffi::AVPixelFormat;
1022 fn alpha2_vulkan_frame_info(
1023 frame: *const ffi::AVFrame,
1024 output: *mut VulkanFrameDescriptor,
1025 ) -> i32;
1026 fn alpha2_vulkan_frames_add_usage(frames: *mut ffi::AVBufferRef, usage: u32) -> i32;
1027 fn alpha2_vulkan_frames_usage(frames: *const ffi::AVBufferRef) -> u32;
1028 fn alpha2_vulkan_frame_access_begin(
1029 frame: *mut ffi::AVFrame,
1030 output: *mut VulkanFrameAccess,
1031 ) -> i32;
1032 fn alpha2_vulkan_frame_access_end(
1033 begun: *const interop::Alpha2VulkanFrameAccess,
1034 updated: *const VulkanFrameAccess,
1035 );
1036}
1037
1038#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1041const fn vulkan_frame_refusal(status: i32) -> BridgeError {
1042 BridgeError::InvalidInput(match status {
1043 -1 => "frame is not a Vulkan hardware frame",
1044 -2 => "Vulkan frame has no hardware frames or device context",
1045 -3 => "Vulkan frame has no images",
1046 -4 => "Vulkan frames context has no frame or queue locks, or no layers",
1047 -5 => "Vulkan device has no graphics or compute queue",
1048 _ => "Vulkan frame could not be read",
1049 })
1050}
1051
1052#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1054pub struct VulkanFrameLease {
1055 frame: Mutex<AVFrame>,
1056}
1057
1058#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1059impl fmt::Debug for VulkanFrameLease {
1060 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1061 formatter
1062 .debug_struct("VulkanFrameLease")
1063 .finish_non_exhaustive()
1064 }
1065}
1066
1067#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1068impl VulkanFrameLease {
1069 #[must_use]
1071 pub fn new(frame: AVFrame) -> Self {
1072 Self {
1073 frame: Mutex::new(frame),
1074 }
1075 }
1076
1077 pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
1086 let frame = self
1087 .frame
1088 .lock()
1089 .unwrap_or_else(std::sync::PoisonError::into_inner);
1090 download_hardware_frame(&frame).map(|frame| DecodedFrame {
1091 frame,
1092 written: true,
1093 })
1094 }
1095
1096 pub fn info(&self) -> Result<VulkanFrameInfo, BridgeError> {
1101 let frame = self
1102 .frame
1103 .lock()
1104 .unwrap_or_else(std::sync::PoisonError::into_inner);
1105 vulkan_frame_info(&frame)
1106 }
1107
1108 pub fn lock_access(&self) -> Result<VulkanFrameAccessGuard<'_>, BridgeError> {
1113 let mut frame = self
1114 .frame
1115 .lock()
1116 .unwrap_or_else(std::sync::PoisonError::into_inner);
1117 let mut access = VulkanFrameAccess::default();
1118 let result =
1121 unsafe { alpha2_vulkan_frame_access_begin(frame.as_mut_ptr(), &raw mut access) };
1122 if result != 0 {
1123 return Err(vulkan_frame_refusal(result));
1124 }
1125 Ok(VulkanFrameAccessGuard {
1126 _frame: frame,
1127 begun: access.0,
1128 access,
1129 committed: false,
1130 })
1131 }
1132}
1133
1134#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1148pub struct VulkanFrameAccessGuard<'a> {
1149 _frame: MutexGuard<'a, AVFrame>,
1151 begun: interop::Alpha2VulkanFrameAccess,
1153 access: VulkanFrameAccess,
1154 committed: bool,
1155}
1156
1157#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1158impl VulkanFrameAccessGuard<'_> {
1159 #[must_use]
1161 pub const fn access(&self) -> &VulkanFrameAccess {
1162 &self.access
1163 }
1164
1165 #[must_use]
1178 pub const unsafe fn access_mut(&mut self) -> &mut VulkanFrameAccess {
1179 &mut self.access
1180 }
1181
1182 pub fn commit(&mut self) {
1184 self.committed = true;
1185 }
1186}
1187
1188#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1189impl Drop for VulkanFrameAccessGuard<'_> {
1190 fn drop(&mut self) {
1191 let updated = if self.committed {
1192 &raw const self.access
1193 } else {
1194 ptr::null()
1195 };
1196 unsafe { alpha2_vulkan_frame_access_end(&raw const self.begun, updated) };
1200 }
1201}
1202
1203#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1215pub struct VulkanEncodeFrameAccess<'a> {
1216 frame: &'a mut AVFrame,
1217 begun: interop::Alpha2VulkanFrameAccess,
1219 access: VulkanFrameAccess,
1220 committed: bool,
1221 _owning_thread: std::marker::PhantomData<*const ()>,
1222}
1223
1224#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1225impl<'a> VulkanEncodeFrameAccess<'a> {
1226 pub fn retain_frame(&self) -> Result<AVFrame, BridgeError> {
1231 let raw = unsafe { ffi::av_frame_clone(self.frame.as_ptr()) };
1234 let raw = std::ptr::NonNull::new(raw)
1235 .ok_or(BridgeError::InvalidInput("cannot retain encode frame"))?;
1236 Ok(unsafe { AVFrame::from_raw(raw) })
1238 }
1239
1240 pub fn begin(frame: &'a mut AVFrame) -> Result<Self, BridgeError> {
1245 let mut access = VulkanFrameAccess::default();
1246 let result =
1249 unsafe { alpha2_vulkan_frame_access_begin(frame.as_mut_ptr(), &raw mut access) };
1250 if result != 0 {
1251 return Err(vulkan_frame_refusal(result));
1252 }
1253 Ok(Self {
1254 frame,
1255 begun: access.0,
1256 access,
1257 committed: false,
1258 _owning_thread: std::marker::PhantomData,
1259 })
1260 }
1261
1262 #[must_use]
1264 pub const fn access(&self) -> &VulkanFrameAccess {
1265 &self.access
1266 }
1267
1268 #[must_use]
1275 pub const unsafe fn access_mut(&mut self) -> &mut VulkanFrameAccess {
1276 &mut self.access
1277 }
1278
1279 pub fn commit(&mut self) {
1281 self.committed = true;
1282 }
1283}
1284
1285#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1286impl Drop for VulkanEncodeFrameAccess<'_> {
1287 fn drop(&mut self) {
1288 let updated = if self.committed {
1289 &raw const self.access
1290 } else {
1291 ptr::null()
1292 };
1293 unsafe { alpha2_vulkan_frame_access_end(&raw const self.begun, updated) };
1297 }
1298}
1299
1300#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1302#[must_use]
1303pub fn vulkan_get_format_callback()
1304-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
1305 alpha2_vulkan_get_format
1306}
1307
1308#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1316pub fn vulkan_frame_info(frame: &AVFrame) -> Result<VulkanFrameInfo, BridgeError> {
1317 let mut raw = VulkanFrameDescriptor::default();
1318 let result = unsafe { alpha2_vulkan_frame_info(frame.as_ptr(), &raw mut raw) };
1322 if result != 0 {
1323 return Err(vulkan_frame_refusal(result));
1324 }
1325 Ok(VulkanFrameInfo {
1326 hardware_format: raw.hardware_format,
1327 software_format: raw.software_format,
1328 width: raw.width,
1329 height: raw.height,
1330 coded_width: raw.coded_width,
1331 coded_height: raw.coded_height,
1332 plane_count: raw.plane_count,
1333 sampled: raw.sampled != 0,
1334 instance: raw.instance,
1335 physical_device: raw.physical_device,
1336 device: raw.device,
1337 images: raw.images,
1338 formats: raw.formats,
1339 layouts: raw.layouts,
1340 access: raw.access,
1341 semaphores: raw.semaphores,
1342 semaphore_values: raw.semaphore_values,
1343 queue_families: raw.queue_families,
1344 })
1345}
1346
1347#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
1349fn hwdevice_available(kind: ffi::AVHWDeviceType) -> bool {
1350 let mut each = ffi::AV_HWDEVICE_TYPE_NONE;
1351 loop {
1352 each = unsafe { ffi::av_hwdevice_iterate_types(each) };
1354 if each == ffi::AV_HWDEVICE_TYPE_NONE {
1355 return false;
1356 }
1357 if each == kind {
1358 return true;
1359 }
1360 }
1361}
1362
1363#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
1367fn alloc_hwdevice(
1368 kind: ffi::AVHWDeviceType,
1369 name: &'static str,
1370) -> Result<AVHWDeviceContext, BridgeError> {
1371 if !hwdevice_available(kind) {
1372 return Err(BridgeError::MissingHwDevice(name));
1373 }
1374 Ok(AVHWDeviceContext::alloc(kind))
1375}
1376
1377#[derive(Debug)]
1379#[non_exhaustive]
1380pub enum BridgeError {
1381 InvalidInput(&'static str),
1383 Ffmpeg(RsmpegError),
1385 FfmpegCode(i32),
1388 NativeGpu(NativeGpuFailure),
1391 MissingHwDevice(&'static str),
1394}
1395
1396#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1399pub struct YuvPlaneLayout {
1400 pub chroma_shift_x: u8,
1402 pub chroma_shift_y: u8,
1404 pub bit_depth: u8,
1406 pub interleaved_chroma: bool,
1408 pub msb_aligned: bool,
1410}
1411
1412impl YuvPlaneLayout {
1413 #[must_use]
1415 pub const fn bytes_per_sample(self) -> u32 {
1416 if self.bit_depth > 8 { 2 } else { 1 }
1417 }
1418}
1419
1420#[derive(Clone, Debug, Eq, PartialEq)]
1422pub struct FramePlane {
1423 pub bytes: bytes::Bytes,
1427 pub stride: u32,
1429 pub width: u32,
1432 pub height: u32,
1434}
1435
1436#[derive(Clone, Debug, Eq, PartialEq)]
1438pub struct YuvPlanes {
1439 pub layout: YuvPlaneLayout,
1441 pub width: u32,
1443 pub height: u32,
1445 pub planes: Vec<FramePlane>,
1447}
1448
1449impl fmt::Display for BridgeError {
1450 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1451 match self {
1452 Self::InvalidInput(context) => {
1453 write!(formatter, "invalid media buffer input: {context}")
1454 }
1455 Self::Ffmpeg(error) => write!(formatter, "{error}"),
1456 Self::FfmpegCode(code) => write!(formatter, "{}", RsmpegError::AVError(*code)),
1458 Self::NativeGpu(failure) => write!(formatter, "{failure}"),
1459 Self::MissingHwDevice(kind) => {
1460 write!(formatter, "the loaded FFmpeg has no {kind} hardware device")
1461 }
1462 }
1463 }
1464}
1465
1466pub fn configure_colorspace(
1474 context: &mut SwsContext,
1475 source_coefficients: i32,
1476 source_full_range: bool,
1477 destination_coefficients: i32,
1478 destination_full_range: bool,
1479) -> Result<(), BridgeError> {
1480 let result = unsafe {
1483 ffi::sws_setColorspaceDetails(
1484 context.as_mut_ptr(),
1485 ffi::sws_getCoefficients(source_coefficients),
1486 i32::from(source_full_range),
1487 ffi::sws_getCoefficients(destination_coefficients),
1488 i32::from(destination_full_range),
1489 0,
1490 1 << 16,
1491 1 << 16,
1492 )
1493 };
1494 if result < 0 {
1495 Err(BridgeError::FfmpegCode(result))
1496 } else {
1497 Ok(())
1498 }
1499}
1500
1501pub fn scale_frame_packed(
1510 context: &mut SwsContext,
1511 frame: &AVFrame,
1512 target: ffi::AVPixelFormat,
1513 output: &mut [u8],
1514) -> Result<(), BridgeError> {
1515 let size = unsafe { ffi::av_image_get_buffer_size(target, frame.width, frame.height, 1) };
1517 if size < 0 {
1518 return Err(BridgeError::FfmpegCode(size));
1519 }
1520 if usize::try_from(size).ok() != Some(output.len()) {
1521 return Err(BridgeError::InvalidInput(
1522 "packed output is not the size of the converted frame",
1523 ));
1524 }
1525 let mut data = [ptr::null_mut::<u8>(); 4];
1526 let mut linesize = [0; 4];
1527 let filled = unsafe {
1530 ffi::av_image_fill_arrays(
1531 data.as_mut_ptr(),
1532 linesize.as_mut_ptr(),
1533 output.as_mut_ptr(),
1534 target,
1535 frame.width,
1536 frame.height,
1537 1,
1538 )
1539 };
1540 if filled < 0 {
1541 return Err(BridgeError::FfmpegCode(filled));
1542 }
1543 let rows = unsafe {
1547 ffi::sws_scale(
1548 context.as_mut_ptr(),
1549 frame.data.as_ptr().cast(),
1550 frame.linesize.as_ptr(),
1551 0,
1552 frame.height,
1553 data.as_ptr(),
1554 linesize.as_ptr(),
1555 )
1556 };
1557 if rows < 0 {
1558 Err(BridgeError::FfmpegCode(rows))
1559 } else {
1560 Ok(())
1561 }
1562}
1563
1564impl Error for BridgeError {}
1565
1566impl BridgeError {
1567 #[must_use]
1570 pub const fn gpu_fault(&self) -> Option<GpuFault> {
1571 match self {
1572 Self::NativeGpu(failure) => failure.fault,
1573 _ => None,
1574 }
1575 }
1576}
1577
1578#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1580pub enum NativeGpuBackend {
1581 Vulkan,
1583 D3D12,
1585 Metal,
1587}
1588
1589impl NativeGpuBackend {
1590 #[must_use]
1592 pub const fn as_str(self) -> &'static str {
1593 match self {
1594 Self::Vulkan => "Vulkan",
1595 Self::D3D12 => "D3D12",
1596 Self::Metal => "Metal",
1597 }
1598 }
1599}
1600
1601#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1608pub struct NativeGpuFailure {
1609 pub backend: NativeGpuBackend,
1611 pub operation: &'static str,
1613 pub step: &'static str,
1615 pub status: i32,
1618 pub fault: Option<GpuFault>,
1620}
1621
1622impl fmt::Display for NativeGpuFailure {
1623 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1624 let Self {
1625 backend,
1626 operation,
1627 step,
1628 status,
1629 ..
1630 } = *self;
1631 let backend = backend.as_str();
1632 if self.fault == Some(GpuFault::Timeout) {
1635 write!(
1636 formatter,
1637 "{backend} {operation} ({step}): {}",
1638 alpha2_media::GPU_TIMEOUT_MESSAGE
1639 )?;
1640 } else {
1641 write!(formatter, "{backend} {operation} failed at {step}")?;
1642 }
1643 if status == 0 {
1644 return Ok(());
1645 }
1646 match self.backend {
1647 NativeGpuBackend::Vulkan => write!(formatter, " (VkResult {status})"),
1648 NativeGpuBackend::D3D12 => write!(formatter, " (HRESULT {status:#010X})"),
1649 NativeGpuBackend::Metal => write!(formatter, " (CVReturn {status})"),
1650 }
1651 }
1652}
1653
1654impl From<RsmpegError> for BridgeError {
1655 fn from(error: RsmpegError) -> Self {
1656 Self::Ffmpeg(error)
1657 }
1658}
1659
1660#[must_use]
1662pub fn runtime_version() -> String {
1663 unsafe { CStr::from_ptr(ffi::av_version_info()) }
1665 .to_string_lossy()
1666 .into_owned()
1667}
1668
1669#[must_use]
1671pub fn runtime_configuration() -> String {
1672 unsafe { CStr::from_ptr(ffi::avcodec_configuration()) }
1674 .to_string_lossy()
1675 .into_owned()
1676}
1677
1678#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1680pub struct SampleProfile {
1681 pub is_yuv_420: bool,
1683 pub bit_depth: u8,
1685 pub has_alpha: bool,
1687 pub is_rgb: bool,
1690}
1691
1692#[must_use]
1698pub fn sample_profile(format: ffi::AVPixelFormat) -> Option<SampleProfile> {
1699 if format == ffi::AV_PIX_FMT_NONE {
1700 return None;
1701 }
1702 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
1705 let components = usize::from(descriptor.nb_components.min(4));
1706 Some(SampleProfile {
1707 is_yuv_420: descriptor.log2_chroma_w == 1
1708 && descriptor.log2_chroma_h == 1
1709 && descriptor.nb_components >= 3
1710 && descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB) == 0,
1711 bit_depth: descriptor.comp[..components]
1712 .iter()
1713 .map(|component| u8::try_from(component.depth).unwrap_or(u8::MAX))
1714 .max()
1715 .unwrap_or(8),
1716 has_alpha: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_ALPHA) != 0,
1717 is_rgb: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB | ffi::AV_PIX_FMT_FLAG_PAL)
1718 != 0,
1719 })
1720}
1721
1722#[must_use]
1724pub fn pixel_format_name(format: ffi::AVPixelFormat) -> Option<String> {
1725 let name = unsafe { ffi::av_get_pix_fmt_name(format) };
1728 if name.is_null() {
1729 None
1730 } else {
1731 Some(
1733 unsafe { CStr::from_ptr(name) }
1734 .to_string_lossy()
1735 .into_owned(),
1736 )
1737 }
1738}
1739
1740pub fn open_codec_context(
1756 context: &mut rsmpeg::avcodec::AVCodecContext,
1757 options: rsmpeg::avutil::AVDictionary,
1758) -> Result<Option<rsmpeg::avutil::AVDictionary>, RsmpegError> {
1759 let mut dictionary = options.into_raw().as_ptr();
1761 let status = unsafe {
1765 ffi::avcodec_open2(
1766 context.as_mut_ptr(),
1767 ptr::null_mut(),
1768 std::ptr::addr_of_mut!(dictionary),
1769 )
1770 };
1771 let unused = ptr::NonNull::new(dictionary)
1774 .map(|dictionary| unsafe { rsmpeg::avutil::AVDictionary::from_raw(dictionary) });
1775 if status < 0 {
1776 return Err(RsmpegError::from(status));
1777 }
1778 Ok(unused)
1779}
1780
1781#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1787pub const VULKAN_ENCODE_INPUT_USAGE: u32 = 0x0000_0001 | 0x0000_0002 | 0x0000_0004 | 0x0000_4000;
1788
1789#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1807pub fn vulkan_frames_add_usage(
1808 frames: &mut rsmpeg::avutil::AVHWFramesContext,
1809 usage: u32,
1810) -> Result<(), BridgeError> {
1811 let status = unsafe { alpha2_vulkan_frames_add_usage(frames.as_mut_ptr(), usage) };
1814 if status < 0 {
1815 return Err(BridgeError::FfmpegCode(status));
1816 }
1817 Ok(())
1818}
1819
1820#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1827#[must_use]
1828pub fn vulkan_frames_usage(frames: &rsmpeg::avutil::AVHWFramesContext) -> u32 {
1829 unsafe { alpha2_vulkan_frames_usage(frames.as_ptr()) }
1832}
1833
1834#[must_use]
1839pub fn packet_bytes(packet: &rsmpeg::avcodec::AVPacket) -> Vec<u8> {
1840 let size = usize::try_from(packet.size).unwrap_or(0);
1841 if packet.data.is_null() || size == 0 {
1842 return Vec::new();
1843 }
1844 unsafe { slice::from_raw_parts(packet.data.cast_const(), size) }.to_vec()
1847}
1848
1849#[must_use]
1863pub fn codec_parameters_extradata(parameters: &rsmpeg::avcodec::AVCodecParameters) -> Vec<u8> {
1864 let size = usize::try_from(parameters.extradata_size).unwrap_or(0);
1865 if parameters.extradata.is_null() || size == 0 {
1866 return Vec::new();
1867 }
1868 unsafe { slice::from_raw_parts(parameters.extradata.cast_const(), size) }.to_vec()
1871}
1872
1873#[must_use]
1877pub fn packet_has_side_data(
1878 packet: &rsmpeg::avcodec::AVPacket,
1879 kind: ffi::AVPacketSideDataType,
1880) -> bool {
1881 let mut size = 0_usize;
1882 !unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) }.is_null()
1885}
1886
1887#[must_use]
1896pub fn take_packet_side_data(
1897 packet: &mut rsmpeg::avcodec::AVPacket,
1898 kind: ffi::AVPacketSideDataType,
1899) -> Option<Vec<u8>> {
1900 let mut size = 0_usize;
1901 let data = unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) };
1904 if data.is_null() {
1905 return None;
1906 }
1907 let bytes = unsafe { slice::from_raw_parts(data, size) }.to_vec();
1909 let packet = packet.as_mut_ptr();
1910 unsafe {
1914 ffi::av_packet_side_data_remove(
1915 (*packet).side_data,
1916 &raw mut (*packet).side_data_elems,
1917 kind,
1918 );
1919 }
1920 Some(bytes)
1921}
1922
1923pub fn set_packet_side_data(
1929 packet: &mut rsmpeg::avcodec::AVPacket,
1930 kind: ffi::AVPacketSideDataType,
1931 data: &[u8],
1932) -> Result<(), BridgeError> {
1933 let _ = take_packet_side_data(packet, kind);
1934 let entry = unsafe { ffi::av_packet_new_side_data(packet.as_mut_ptr(), kind, data.len()) };
1937 if entry.is_null() {
1938 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
1939 }
1940 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), entry, data.len()) };
1942 Ok(())
1943}
1944
1945pub fn set_output_stream_extradata(
1960 output: &mut rsmpeg::avformat::AVFormatContextOutput,
1961 index: usize,
1962 data: &[u8],
1963) -> Result<(), BridgeError> {
1964 let parameters = output
1965 .streams()
1966 .get(index)
1967 .ok_or(BridgeError::InvalidInput("the output has no such stream"))?
1968 .codecpar;
1969 let size = i32::try_from(data.len())
1970 .map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
1971 let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
1972 let buffer = unsafe { ffi::av_mallocz(data.len() + padding) }.cast::<u8>();
1974 if buffer.is_null() {
1975 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
1976 }
1977 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), buffer, data.len()) };
1979 unsafe {
1983 ffi::av_freep((&raw mut (*parameters).extradata).cast());
1984 (*parameters).extradata = buffer;
1985 (*parameters).extradata_size = size;
1986 }
1987 Ok(())
1988}
1989
1990pub fn bitstream_filter_list(
2004 spec: &CStr,
2005 parameters: &AVCodecParameters,
2006 time_base: rsmpeg::avutil::AVRational,
2007) -> Result<rsmpeg::avcodec::AVBSFContext, BridgeError> {
2008 let mut raw = ptr::null_mut();
2009 let status = unsafe { ffi::av_bsf_list_parse_str(spec.as_ptr(), &raw mut raw) };
2012 if status < 0 {
2013 return Err(BridgeError::FfmpegCode(status));
2014 }
2015 let raw = ptr::NonNull::new(raw).ok_or(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)))?;
2016 let mut context = unsafe { rsmpeg::avcodec::AVBSFContextUninit::from_raw(raw) };
2019 let status = unsafe { ffi::avcodec_parameters_copy(context.par_in, parameters.as_ptr()) };
2023 if status < 0 {
2024 return Err(BridgeError::FfmpegCode(status));
2025 }
2026 context.set_time_base_in(time_base);
2027 context.init().map_err(BridgeError::Ffmpeg)
2028}
2029
2030pub fn set_stream_codec_tag(stream: &mut rsmpeg::avformat::AVStreamMut<'_>, tag: u32) {
2033 let mut parameters = stream.codecpar_mut();
2034 unsafe { (*parameters.as_mut_ptr()).codec_tag = tag };
2037}
2038
2039pub fn set_stream_display_matrix(
2047 stream: &mut rsmpeg::avformat::AVStreamMut<'_>,
2048 display: DisplayRotation,
2049) -> Result<(), BridgeError> {
2050 let mut parameters = stream.codecpar_mut();
2051 let parameters = parameters.as_mut_ptr();
2052 let entry = unsafe {
2056 ffi::av_packet_side_data_new(
2057 &raw mut (*parameters).coded_side_data,
2058 &raw mut (*parameters).nb_coded_side_data,
2059 ffi::AV_PKT_DATA_DISPLAYMATRIX,
2060 size_of::<[i32; 9]>(),
2061 0,
2062 )
2063 };
2064 if entry.is_null() {
2065 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2066 }
2067 let mut matrix = [0_i32; 9];
2068 unsafe {
2072 ffi::av_display_rotation_set(matrix.as_mut_ptr(), -display.counter_clockwise_degrees);
2073 if display.flipped {
2074 ffi::av_display_matrix_flip(matrix.as_mut_ptr(), 1, 0);
2075 }
2076 }
2077 unsafe {
2080 ptr::copy_nonoverlapping(
2081 matrix.as_ptr().cast::<u8>(),
2082 (*entry).data,
2083 size_of::<[i32; 9]>(),
2084 );
2085 }
2086 Ok(())
2087}
2088
2089#[must_use]
2091pub fn hardware_device_type_name(device_type: ffi::AVHWDeviceType) -> Option<String> {
2092 let name = unsafe { ffi::av_hwdevice_get_type_name(device_type) };
2095 if name.is_null() {
2096 None
2097 } else {
2098 Some(
2100 unsafe { CStr::from_ptr(name) }
2101 .to_string_lossy()
2102 .into_owned(),
2103 )
2104 }
2105}
2106
2107#[must_use]
2117pub fn yuv_layout(format: ffi::AVPixelFormat) -> Option<YuvPlaneLayout> {
2118 if format == ffi::AV_PIX_FMT_NONE {
2119 return None;
2120 }
2121 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
2124 let foreign_endian = if cfg!(target_endian = "little") {
2125 ffi::AV_PIX_FMT_FLAG_BE
2126 } else {
2127 0
2128 };
2129 let rejected = foreign_endian
2130 | ffi::AV_PIX_FMT_FLAG_PAL
2131 | ffi::AV_PIX_FMT_FLAG_BITSTREAM
2132 | ffi::AV_PIX_FMT_FLAG_HWACCEL
2133 | ffi::AV_PIX_FMT_FLAG_RGB
2134 | ffi::AV_PIX_FMT_FLAG_ALPHA
2135 | ffi::AV_PIX_FMT_FLAG_BAYER
2136 | ffi::AV_PIX_FMT_FLAG_FLOAT
2137 | ffi::AV_PIX_FMT_FLAG_XYZ;
2138 if descriptor.flags & u64::from(rejected) != 0 || descriptor.nb_components != 3 {
2139 return None;
2140 }
2141 let [luma, u, v, _] = descriptor.comp;
2142 let depth = luma.depth;
2143 if !(8..=16).contains(&depth) || u.depth != depth || v.depth != depth {
2144 return None;
2145 }
2146 if descriptor.log2_chroma_w > 2 || descriptor.log2_chroma_h > 2 {
2147 return None;
2148 }
2149 let bytes = if depth > 8 { 2 } else { 1 };
2150 let shift = luma.shift;
2151 let msb_aligned = match shift {
2152 0 => false,
2153 _ if depth > 8 && shift == 16 - depth => true,
2154 _ => return None,
2155 };
2156 let planar = u.plane == 1
2157 && v.plane == 2
2158 && u.step == bytes
2159 && v.step == bytes
2160 && u.offset == 0
2161 && v.offset == 0;
2162 let interleaved = u.plane == 1
2163 && v.plane == 1
2164 && u.step == 2 * bytes
2165 && v.step == 2 * bytes
2166 && u.offset == 0
2167 && v.offset == bytes;
2168 if luma.plane != 0
2169 || luma.step != bytes
2170 || luma.offset != 0
2171 || u.shift != shift
2172 || v.shift != shift
2173 || !(planar || interleaved)
2174 {
2175 return None;
2176 }
2177 Some(YuvPlaneLayout {
2178 chroma_shift_x: descriptor.log2_chroma_w,
2179 chroma_shift_y: descriptor.log2_chroma_h,
2180 bit_depth: u8::try_from(depth).ok()?,
2181 interleaved_chroma: interleaved,
2182 msb_aligned,
2183 })
2184}
2185
2186pub fn yuv_planes(frame: &AVFrame) -> Result<Option<YuvPlanes>, BridgeError> {
2200 frame_yuv_planes(frame, false)
2201}
2202
2203pub struct DecodedFrame {
2211 frame: AVFrame,
2212 written: bool,
2214}
2215
2216impl DecodedFrame {
2217 pub fn receive(decoder: &mut AVCodecContext) -> Result<Self, RsmpegError> {
2224 let written = decoder_frames_are_initialized(decoder);
2225 let frame = decoder.receive_frame()?;
2226 Ok(Self { frame, written })
2227 }
2228
2229 pub fn yuv_planes(&self) -> Result<Option<YuvPlanes>, BridgeError> {
2240 frame_yuv_planes(&self.frame, self.written)
2241 }
2242
2243 pub fn set_pts(&mut self, pts: i64) {
2245 self.frame.set_pts(pts);
2246 }
2247
2248 #[must_use]
2250 pub fn into_frame(self) -> AVFrame {
2251 self.frame
2252 }
2253}
2254
2255impl std::ops::Deref for DecodedFrame {
2256 type Target = AVFrame;
2257
2258 fn deref(&self) -> &AVFrame {
2259 &self.frame
2260 }
2261}
2262
2263impl fmt::Debug for DecodedFrame {
2264 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2265 formatter
2266 .debug_struct("DecodedFrame")
2267 .field("format", &self.frame.format)
2268 .field("width", &self.frame.width)
2269 .field("height", &self.frame.height)
2270 .field("pts", &self.frame.pts)
2271 .field("written", &self.written)
2272 .finish_non_exhaustive()
2273 }
2274}
2275
2276fn decoder_frames_are_initialized(decoder: &AVCodecContext) -> bool {
2286 static DEFAULT_GET_BUFFER2: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2287 let default = *DEFAULT_GET_BUFFER2.get_or_init(|| {
2290 unsafe {
2292 let mut context = ffi::avcodec_alloc_context3(ptr::null());
2293 let get_buffer2 = context
2294 .as_ref()
2295 .and_then(|context| context.get_buffer2)
2296 .map_or(0, |function| function as usize);
2297 ffi::avcodec_free_context(&raw mut context);
2298 get_buffer2
2299 }
2300 });
2301 let wrapped =
2303 unsafe { decoder.codec.as_ref() }.is_none_or(|codec| !codec.wrapper_name.is_null());
2304 let get_buffer2 = decoder.get_buffer2.map_or(0, |function| function as usize);
2305 !wrapped && default != 0 && get_buffer2 == default
2306}
2307
2308fn frame_yuv_planes(frame: &AVFrame, share: bool) -> Result<Option<YuvPlanes>, BridgeError> {
2311 let Some(layout) = yuv_layout(frame.format) else {
2312 return Ok(None);
2313 };
2314 let width =
2315 u32::try_from(frame.width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2316 let height = u32::try_from(frame.height)
2317 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2318 if width == 0 || height == 0 {
2319 return Err(BridgeError::InvalidInput("empty YUV frame"));
2320 }
2321 let bytes = layout.bytes_per_sample();
2322 let chroma_width = width.div_ceil(1 << layout.chroma_shift_x);
2323 let chroma_height = height.div_ceil(1 << layout.chroma_shift_y);
2324 let shared =
2327 (share && !frame.buf[0].is_null()).then(|| std::sync::Arc::new(SharedFrame(frame.clone())));
2328 let copy = |index: usize,
2329 samples: u32,
2330 pairs: u32,
2331 rows: u32,
2332 context: &'static str|
2333 -> Result<FramePlane, BridgeError> {
2334 let row_bytes = samples
2335 .checked_mul(bytes)
2336 .and_then(|row| row.checked_mul(pairs))
2337 .ok_or(BridgeError::InvalidInput(context))?;
2338 if let Some(view) = shared
2339 .as_ref()
2340 .and_then(|frame| PlaneView::new(frame, index, row_bytes, rows))
2341 {
2342 let stride =
2343 u32::try_from(view.stride).map_err(|_| BridgeError::InvalidInput(context))?;
2344 return Ok(FramePlane {
2345 bytes: bytes::Bytes::from_owner(view),
2346 stride,
2347 width: samples,
2348 height: rows,
2349 });
2350 }
2351 Ok(FramePlane {
2352 bytes: copy_video_plane(
2353 frame.data[index],
2354 frame.linesize[index],
2355 row_bytes,
2356 rows,
2357 context,
2358 )?
2359 .into(),
2360 stride: row_bytes,
2361 width: samples,
2362 height: rows,
2363 })
2364 };
2365 let mut planes = vec![copy(0, width, 1, height, "invalid Y plane")?];
2366 if layout.interleaved_chroma {
2367 planes.push(copy(1, chroma_width, 2, chroma_height, "invalid UV plane")?);
2368 } else {
2369 planes.push(copy(1, chroma_width, 1, chroma_height, "invalid U plane")?);
2370 planes.push(copy(2, chroma_width, 1, chroma_height, "invalid V plane")?);
2371 }
2372 Ok(Some(YuvPlanes {
2373 layout,
2374 width,
2375 height,
2376 planes,
2377 }))
2378}
2379
2380struct SharedFrame(AVFrame);
2382
2383unsafe impl Sync for SharedFrame {}
2387
2388struct PlaneView {
2391 frame: std::sync::Arc<SharedFrame>,
2392 data: *const u8,
2393 len: usize,
2394 stride: usize,
2395}
2396
2397unsafe impl Send for PlaneView {}
2400
2401impl PlaneView {
2402 fn new(
2406 frame: &std::sync::Arc<SharedFrame>,
2407 index: usize,
2408 row_bytes: u32,
2409 rows: u32,
2410 ) -> Option<Self> {
2411 let inner = &frame.0;
2412 let data = inner.data[index].cast_const();
2413 let stride = usize::try_from(inner.linesize[index]).ok()?;
2414 let row_bytes = usize::try_from(row_bytes).ok()?;
2415 if data.is_null() || stride < row_bytes || rows == 0 {
2416 return None;
2417 }
2418 let len = stride.checked_mul(usize::try_from(rows).ok()?)?;
2419 let start = data as usize;
2420 let end = start.checked_add(len)?;
2421 let inside = inner.buf.iter().any(|buffer| {
2422 let Some(buffer) = (unsafe { buffer.as_ref() }) else {
2425 return false;
2426 };
2427 let buffer_start = buffer.data as usize;
2428 buffer_start
2429 .checked_add(buffer.size)
2430 .is_some_and(|buffer_end| buffer_start <= start && end <= buffer_end)
2431 });
2432 inside.then(|| Self {
2433 frame: std::sync::Arc::clone(frame),
2434 data,
2435 len,
2436 stride,
2437 })
2438 }
2439}
2440
2441impl AsRef<[u8]> for PlaneView {
2442 fn as_ref(&self) -> &[u8] {
2443 let _ = &self.frame;
2445 unsafe { slice::from_raw_parts(self.data, self.len) }
2448 }
2449}
2450
2451fn copy_video_plane(
2452 data: *const u8,
2453 linesize: i32,
2454 width: u32,
2455 height: u32,
2456 context: &'static str,
2457) -> Result<Vec<u8>, BridgeError> {
2458 let row_bytes = usize::try_from(width).map_err(|_| BridgeError::InvalidInput(context))?;
2459 let rows = usize::try_from(height).map_err(|_| BridgeError::InvalidInput(context))?;
2460 let stride =
2461 usize::try_from(linesize.unsigned_abs()).map_err(|_| BridgeError::InvalidInput(context))?;
2462 if data.is_null() || linesize == 0 || stride < row_bytes {
2463 return Err(BridgeError::InvalidInput(context));
2464 }
2465 let byte_len = row_bytes
2466 .checked_mul(rows)
2467 .ok_or(BridgeError::InvalidInput(context))?;
2468 let mut output = Vec::with_capacity(byte_len);
2471 let signed_stride =
2472 isize::try_from(linesize).map_err(|_| BridgeError::InvalidInput(context))?;
2473 for row in 0..rows {
2474 let row = isize::try_from(row).map_err(|_| BridgeError::InvalidInput(context))?;
2475 let offset = row
2476 .checked_mul(signed_stride)
2477 .ok_or(BridgeError::InvalidInput(context))?;
2478 let source = unsafe { slice::from_raw_parts(data.offset(offset), row_bytes) };
2482 output.extend_from_slice(source);
2483 }
2484 debug_assert_eq!(output.len(), byte_len);
2485 Ok(output)
2486}
2487
2488pub fn rgba_frame(bytes: &[u8], width: i32, height: i32) -> Result<AVFrame, BridgeError> {
2493 let row_bytes = usize::try_from(width)
2494 .ok()
2495 .and_then(|value| value.checked_mul(4))
2496 .ok_or(BridgeError::InvalidInput("RGBA row width overflow"))?;
2497 let rows =
2498 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("negative RGBA height"))?;
2499 let expected = row_bytes
2500 .checked_mul(rows)
2501 .ok_or(BridgeError::InvalidInput("RGBA frame size overflow"))?;
2502 if bytes.len() != expected {
2503 return Err(BridgeError::InvalidInput("RGBA byte length mismatch"));
2504 }
2505
2506 let mut frame = AVFrame::new();
2507 frame.set_format(ffi::AV_PIX_FMT_RGBA);
2508 frame.set_width(width);
2509 frame.set_height(height);
2510 frame.get_buffer(1)?;
2511 frame.make_writable()?;
2512 let linesize = usize::try_from(frame.linesize[0])
2513 .map_err(|_| BridgeError::InvalidInput("negative RGBA linesize"))?;
2514 if frame.data[0].is_null() || linesize < row_bytes {
2515 return Err(BridgeError::InvalidInput(
2516 "RGBA destination plane is too small",
2517 ));
2518 }
2519
2520 unsafe {
2523 for row in 0..rows {
2524 let destination_offset = row.checked_mul(linesize).ok_or(BridgeError::InvalidInput(
2525 "RGBA destination offset overflow",
2526 ))?;
2527 let source_offset = row
2528 .checked_mul(row_bytes)
2529 .ok_or(BridgeError::InvalidInput("RGBA source offset overflow"))?;
2530 let destination =
2531 slice::from_raw_parts_mut(frame.data[0].add(destination_offset), row_bytes);
2532 destination.copy_from_slice(&bytes[source_offset..source_offset + row_bytes]);
2533 }
2534 }
2535 Ok(frame)
2536}
2537
2538#[allow(clippy::too_many_arguments)]
2544pub fn yuv420p_frame(
2545 y: &[u8],
2546 y_stride: u32,
2547 u: &[u8],
2548 u_stride: u32,
2549 v: &[u8],
2550 v_stride: u32,
2551 width: i32,
2552 height: i32,
2553) -> Result<AVFrame, BridgeError> {
2554 let width =
2555 u32::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2556 let height =
2557 u32::try_from(height).map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2558 if width == 0 || height == 0 {
2559 return Err(BridgeError::InvalidInput("empty YUV frame"));
2560 }
2561 let chroma_width = width.div_ceil(2);
2562 let chroma_height = height.div_ceil(2);
2563 validate_source_plane(y, y_stride, width, height, "invalid Y source plane")?;
2564 validate_source_plane(
2565 u,
2566 u_stride,
2567 chroma_width,
2568 chroma_height,
2569 "invalid U source plane",
2570 )?;
2571 validate_source_plane(
2572 v,
2573 v_stride,
2574 chroma_width,
2575 chroma_height,
2576 "invalid V source plane",
2577 )?;
2578
2579 let mut frame = AVFrame::new();
2580 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
2581 frame.set_width(i32::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV width"))?);
2582 frame.set_height(i32::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV height"))?);
2583 frame.get_buffer(1)?;
2584 frame.make_writable()?;
2585 copy_plane_to_frame(y, y_stride, width, height, frame.data[0], frame.linesize[0])?;
2586 copy_plane_to_frame(
2587 u,
2588 u_stride,
2589 chroma_width,
2590 chroma_height,
2591 frame.data[1],
2592 frame.linesize[1],
2593 )?;
2594 copy_plane_to_frame(
2595 v,
2596 v_stride,
2597 chroma_width,
2598 chroma_height,
2599 frame.data[2],
2600 frame.linesize[2],
2601 )?;
2602 Ok(frame)
2603}
2604
2605#[must_use]
2609pub fn yuv_pixel_format(layout: YuvPlaneLayout) -> Option<ffi::AVPixelFormat> {
2610 let mut descriptor = ptr::null();
2611 loop {
2612 descriptor = unsafe { ffi::av_pix_fmt_desc_next(descriptor) };
2615 if descriptor.is_null() {
2616 return None;
2617 }
2618 let format = unsafe { ffi::av_pix_fmt_desc_get_id(descriptor) };
2620 if yuv_layout(format) == Some(layout) {
2621 return Some(format);
2622 }
2623 }
2624}
2625
2626pub fn planar_frame(
2633 format: ffi::AVPixelFormat,
2634 planes: &[(&[u8], u32)],
2635 width: u32,
2636 height: u32,
2637) -> Result<AVFrame, BridgeError> {
2638 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
2641 .ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
2642 let count = unsafe { ffi::av_pix_fmt_count_planes(format) };
2644 if usize::try_from(count).ok() != Some(planes.len()) || width == 0 || height == 0 {
2645 return Err(BridgeError::InvalidInput(
2646 "planes do not match the pixel format",
2647 ));
2648 }
2649 let signed = |value: u32| {
2650 i32::try_from(value).map_err(|_| BridgeError::InvalidInput("frame size overflow"))
2651 };
2652 let mut frame = AVFrame::new();
2653 frame.set_format(format);
2654 frame.set_width(signed(width)?);
2655 frame.set_height(signed(height)?);
2656 frame.get_buffer(1)?;
2657 frame.make_writable()?;
2658 for (index, (bytes, stride)) in planes.iter().enumerate() {
2659 let plane = i32::try_from(index).unwrap_or(i32::MAX);
2660 let row_bytes = unsafe { ffi::av_image_get_linesize(format, signed(width)?, plane) };
2662 let row_bytes = u32::try_from(row_bytes).map_err(|_| BridgeError::FfmpegCode(row_bytes))?;
2663 let rows = if index == 0 || index == 3 {
2664 height
2665 } else {
2666 height.div_ceil(1 << descriptor.log2_chroma_h)
2667 };
2668 validate_source_plane(bytes, *stride, row_bytes, rows, "invalid source plane")?;
2669 copy_plane_to_frame(
2670 bytes,
2671 *stride,
2672 row_bytes,
2673 rows,
2674 frame.data[index],
2675 frame.linesize[index],
2676 )?;
2677 }
2678 Ok(frame)
2679}
2680
2681fn validate_source_plane(
2682 bytes: &[u8],
2683 stride: u32,
2684 width: u32,
2685 height: u32,
2686 context: &'static str,
2687) -> Result<(), BridgeError> {
2688 let required = usize::try_from(stride).ok().and_then(|stride| {
2689 usize::try_from(height)
2690 .ok()
2691 .and_then(|height| stride.checked_mul(height))
2692 });
2693 if stride < width || required != Some(bytes.len()) {
2694 Err(BridgeError::InvalidInput(context))
2695 } else {
2696 Ok(())
2697 }
2698}
2699
2700fn copy_plane_to_frame(
2701 source: &[u8],
2702 source_stride: u32,
2703 width: u32,
2704 height: u32,
2705 destination: *mut u8,
2706 destination_stride: i32,
2707) -> Result<(), BridgeError> {
2708 let row_bytes =
2709 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV row width overflow"))?;
2710 let rows =
2711 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV row count overflow"))?;
2712 let source_stride = usize::try_from(source_stride)
2713 .map_err(|_| BridgeError::InvalidInput("YUV source stride overflow"))?;
2714 let destination_stride = usize::try_from(destination_stride)
2715 .map_err(|_| BridgeError::InvalidInput("negative YUV destination stride"))?;
2716 if destination.is_null() || destination_stride < row_bytes {
2717 return Err(BridgeError::InvalidInput(
2718 "YUV destination plane is too small",
2719 ));
2720 }
2721 for row in 0..rows {
2722 let source_offset = row
2723 .checked_mul(source_stride)
2724 .ok_or(BridgeError::InvalidInput("YUV source offset overflow"))?;
2725 let destination_offset = row
2726 .checked_mul(destination_stride)
2727 .ok_or(BridgeError::InvalidInput("YUV destination offset overflow"))?;
2728 unsafe {
2732 ptr::copy_nonoverlapping(
2733 source.as_ptr().add(source_offset),
2734 destination.add(destination_offset),
2735 row_bytes,
2736 );
2737 }
2738 }
2739 Ok(())
2740}
2741
2742pub fn audio_frame_f32p(
2748 channels: &[Vec<f32>],
2749 sample_rate: i32,
2750 pts: i64,
2751) -> Result<AVFrame, BridgeError> {
2752 let Some(samples) = channels.first().map(Vec::len) else {
2753 return Err(BridgeError::InvalidInput("invalid planar audio shape"));
2754 };
2755 if sample_rate <= 0
2756 || samples == 0
2757 || channels.len() > ffi::AV_NUM_DATA_POINTERS as usize
2758 || channels.iter().any(|channel| channel.len() != samples)
2759 {
2760 return Err(BridgeError::InvalidInput(
2761 "planar audio channel lengths differ",
2762 ));
2763 }
2764 let channel_count = i32::try_from(channels.len())
2765 .map_err(|_| BridgeError::InvalidInput("audio channel count overflow"))?;
2766 let sample_count = i32::try_from(samples)
2767 .map_err(|_| BridgeError::InvalidInput("audio sample count overflow"))?;
2768 let mut frame = AVFrame::new();
2769 frame.set_format(ffi::AV_SAMPLE_FMT_FLTP);
2770 frame.set_ch_layout(
2771 rsmpeg::avutil::AVChannelLayout::from_nb_channels(channel_count).into_inner(),
2772 );
2773 frame.set_sample_rate(sample_rate);
2774 frame.set_nb_samples(sample_count);
2775 frame.set_pts(pts);
2776 frame.set_time_base(ffi::AVRational {
2777 num: 1,
2778 den: sample_rate,
2779 });
2780 frame.get_buffer(0)?;
2781 frame.make_writable()?;
2782 let plane_bytes = samples
2783 .checked_mul(size_of::<f32>())
2784 .ok_or(BridgeError::InvalidInput("audio plane byte count overflow"))?;
2785 let plane_capacity = usize::try_from(frame.linesize[0]).unwrap_or(0);
2786 if plane_capacity < plane_bytes {
2787 return Err(BridgeError::InvalidInput(
2788 "audio plane capacity is too small",
2789 ));
2790 }
2791
2792 for (index, source) in channels.iter().enumerate() {
2793 if frame.data[index].is_null() {
2794 return Err(BridgeError::InvalidInput("audio plane pointer is null"));
2795 }
2796 unsafe {
2799 ptr::copy_nonoverlapping(source.as_ptr().cast::<u8>(), frame.data[index], plane_bytes);
2800 }
2801 }
2802 Ok(frame)
2803}
2804
2805#[must_use]
2824pub fn channel_layout_channels(layout: &rsmpeg::avutil::AVChannelLayout) -> Vec<ffi::AVChannel> {
2825 if layout.order == ffi::AV_CHANNEL_ORDER_UNSPEC {
2826 return Vec::new();
2827 }
2828 let channels = u32::try_from(layout.nb_channels).unwrap_or(0);
2829 (0..channels)
2830 .map(|index| {
2831 layout
2832 .channel_from_index(index)
2833 .unwrap_or(ffi::AV_CHAN_NONE)
2834 })
2835 .collect()
2836}
2837
2838pub fn channel_layout_of(
2845 channels: &[ffi::AVChannel],
2846) -> Result<rsmpeg::avutil::AVChannelLayout, BridgeError> {
2847 let count = i32::try_from(channels.len())
2848 .ok()
2849 .filter(|count| *count > 0)
2850 .ok_or(BridgeError::InvalidInput("a channel layout needs channels"))?;
2851 let mut raw = std::mem::MaybeUninit::<ffi::AVChannelLayout>::zeroed();
2852 let initialized = unsafe { ffi::av_channel_layout_custom_init(raw.as_mut_ptr(), count) };
2855 if initialized < 0 {
2856 return Err(BridgeError::FfmpegCode(initialized));
2857 }
2858 let mut layout = unsafe { rsmpeg::avutil::AVChannelLayout::new(raw.assume_init()) };
2860 for (index, channel) in channels.iter().enumerate() {
2861 unsafe { (*layout.u.map.add(index)).id = *channel };
2863 }
2864 let flags = i32::try_from(ffi::AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL).unwrap_or(0);
2865 let retyped = unsafe {
2867 ffi::av_channel_layout_retype(layout.as_mut_ptr(), ffi::AV_CHANNEL_ORDER_UNSPEC, flags)
2868 };
2869 if retyped < 0 {
2870 return Err(BridgeError::FfmpegCode(retyped));
2871 }
2872 Ok(layout)
2873}
2874
2875pub fn audio_f32_interleaved(frame: &AVFrame) -> Result<Vec<f32>, BridgeError> {
2880 if frame.format != ffi::AV_SAMPLE_FMT_FLT || frame.nb_samples < 0 {
2881 return Err(BridgeError::InvalidInput(
2882 "packed audio format or sample count is invalid",
2883 ));
2884 }
2885 let channels = usize::try_from(frame.ch_layout().nb_channels)
2886 .map_err(|_| BridgeError::InvalidInput("packed audio channel count is negative"))?;
2887 let frames = usize::try_from(frame.nb_samples)
2888 .map_err(|_| BridgeError::InvalidInput("packed audio sample count is negative"))?;
2889 let samples = frames
2890 .checked_mul(channels)
2891 .ok_or(BridgeError::InvalidInput(
2892 "packed audio sample count overflow",
2893 ))?;
2894 let bytes = samples
2895 .checked_mul(size_of::<f32>())
2896 .ok_or(BridgeError::InvalidInput(
2897 "packed audio byte count overflow",
2898 ))?;
2899 if channels == 0
2900 || frame.data[0].is_null()
2901 || usize::try_from(frame.linesize[0]).unwrap_or(0) < bytes
2902 {
2903 return Err(BridgeError::InvalidInput(
2904 "packed audio plane is missing or too small",
2905 ));
2906 }
2907 let mut output = vec![0.0_f32; samples];
2908 unsafe {
2911 ptr::copy_nonoverlapping(frame.data[0], output.as_mut_ptr().cast::<u8>(), bytes);
2912 }
2913 Ok(output)
2914}
2915
2916#[derive(Copy, Clone, Debug, PartialEq)]
2918pub struct DisplayRotation {
2919 pub counter_clockwise_degrees: f64,
2922 pub flipped: bool,
2926}
2927
2928#[must_use]
2930pub fn display_rotation(parameters: &AVCodecParameters) -> Option<DisplayRotation> {
2931 let count = usize::try_from(parameters.nb_coded_side_data).ok()?;
2932 if count == 0 || count > 64 || parameters.coded_side_data.is_null() {
2933 return None;
2934 }
2935 let entries = unsafe { slice::from_raw_parts(parameters.coded_side_data, count) };
2938 let matrix = entries.iter().find(|entry| {
2939 entry.type_ == ffi::AV_PKT_DATA_DISPLAYMATRIX
2940 && entry.size >= 9 * size_of::<i32>()
2941 && !entry.data.is_null()
2942 })?;
2943 unsafe { matrix_rotation(matrix.data) }
2945}
2946
2947#[must_use]
2950pub fn frame_display_rotation(frame: &AVFrame) -> Option<DisplayRotation> {
2951 let entry =
2953 unsafe { ffi::av_frame_get_side_data(frame.as_ptr(), ffi::AV_FRAME_DATA_DISPLAYMATRIX) };
2954 if entry.is_null() {
2955 return None;
2956 }
2957 let entry = unsafe { &*entry };
2959 if entry.size < 9 * size_of::<i32>() || entry.data.is_null() {
2960 return None;
2961 }
2962 unsafe { matrix_rotation(entry.data) }
2964}
2965
2966unsafe fn matrix_rotation(data: *const u8) -> Option<DisplayRotation> {
2969 let mut aligned = [0_i32; 9];
2970 unsafe {
2973 ptr::copy_nonoverlapping(
2974 data,
2975 aligned.as_mut_ptr().cast::<u8>(),
2976 size_of::<[i32; 9]>(),
2977 );
2978 }
2979 let determinant = i64::from(aligned[0]) * i64::from(aligned[4])
2983 - i64::from(aligned[1]) * i64::from(aligned[3]);
2984 let flipped = determinant < 0;
2985 if flipped {
2986 unsafe { ffi::av_display_matrix_flip(aligned.as_mut_ptr(), 1, 0) };
2988 }
2989 let angle = unsafe { ffi::av_display_rotation_get(aligned.as_ptr()) };
2991 angle.is_finite().then_some(DisplayRotation {
2992 counter_clockwise_degrees: angle,
2993 flipped,
2994 })
2995}
2996
2997const fn size_of<T>() -> usize {
2998 std::mem::size_of::<T>()
2999}
3000
3001pub struct Yuv420pFrameBuffer {
3015 frame: AVFrame,
3016 width: usize,
3017 height: usize,
3018}
3019
3020pub struct Yuv420pFramePlanes<'a> {
3022 pub y: &'a mut [u8],
3024 pub y_stride: usize,
3026 pub u: &'a mut [u8],
3028 pub v: &'a mut [u8],
3030 pub chroma_stride: usize,
3032}
3033
3034impl Yuv420pFrameBuffer {
3035 pub fn new(width: i32, height: i32) -> Result<Self, BridgeError> {
3040 let coded_width =
3041 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
3042 let coded_height = usize::try_from(height)
3043 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
3044 if coded_width == 0 || coded_height == 0 {
3045 return Err(BridgeError::InvalidInput("empty YUV frame"));
3046 }
3047 Ok(Self {
3048 frame: zeroed_yuv420p_frame(width, height)?,
3049 width: coded_width,
3050 height: coded_height,
3051 })
3052 }
3053
3054 #[must_use]
3056 pub const fn dimensions(&self) -> (usize, usize) {
3057 (self.width, self.height)
3058 }
3059
3060 pub fn begin_write(&mut self) -> Result<Yuv420pFramePlanes<'_>, BridgeError> {
3066 if !self.frame.is_writable().map_err(BridgeError::Ffmpeg)? {
3070 let mut copy = zeroed_yuv420p_frame(self.frame.width, self.frame.height)?;
3071 let status = unsafe {
3074 let copied = ffi::av_frame_copy(copy.as_mut_ptr(), self.frame.as_ptr());
3075 if copied < 0 {
3076 copied
3077 } else {
3078 ffi::av_frame_copy_props(copy.as_mut_ptr(), self.frame.as_ptr())
3079 }
3080 };
3081 if status < 0 {
3082 return Err(BridgeError::FfmpegCode(status));
3083 }
3084 self.frame = copy;
3085 }
3086 let chroma_width = self.width.div_ceil(2);
3087 let chroma_height = self.height.div_ceil(2);
3088 let y_stride = plane_stride(self.frame.linesize[0], self.width)?;
3089 let u_stride = plane_stride(self.frame.linesize[1], chroma_width)?;
3090 let v_stride = plane_stride(self.frame.linesize[2], chroma_width)?;
3091 if u_stride != v_stride {
3092 return Err(BridgeError::InvalidInput("mismatched chroma strides"));
3093 }
3094 let y = unsafe { writable_plane(self.frame.data[0], y_stride, self.width, self.height)? };
3099 let u =
3101 unsafe { writable_plane(self.frame.data[1], u_stride, chroma_width, chroma_height)? };
3102 let v =
3104 unsafe { writable_plane(self.frame.data[2], v_stride, chroma_width, chroma_height)? };
3105 Ok(Yuv420pFramePlanes {
3106 y,
3107 y_stride,
3108 u,
3109 v,
3110 chroma_stride: u_stride,
3111 })
3112 }
3113
3114 pub fn set_presentation(&mut self, pts: i64, time_base: ffi::AVRational) {
3116 self.frame.set_pts(pts);
3117 self.frame.set_time_base(time_base);
3118 }
3119
3120 #[must_use]
3122 pub const fn frame(&self) -> &AVFrame {
3123 &self.frame
3124 }
3125
3126 #[must_use]
3128 pub const fn size(&self) -> (usize, usize) {
3129 (self.width, self.height)
3130 }
3131}
3132
3133fn zeroed_yuv420p_frame(width: i32, height: i32) -> Result<AVFrame, BridgeError> {
3135 let mut frame = AVFrame::new();
3136 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
3137 frame.set_width(width);
3138 frame.set_height(height);
3139 frame.get_buffer(1)?;
3140 for buffer in frame.buf {
3141 if let Some(buffer) = unsafe { buffer.as_ref() } {
3144 unsafe { ptr::write_bytes(buffer.data, 0, buffer.size) };
3146 }
3147 }
3148 Ok(frame)
3149}
3150
3151impl fmt::Debug for Yuv420pFrameBuffer {
3152 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3153 formatter
3154 .debug_struct("Yuv420pFrameBuffer")
3155 .field("width", &self.width)
3156 .field("height", &self.height)
3157 .finish_non_exhaustive()
3158 }
3159}
3160
3161fn plane_stride(linesize: i32, width: usize) -> Result<usize, BridgeError> {
3162 let stride = usize::try_from(linesize)
3163 .map_err(|_| BridgeError::InvalidInput("negative frame linesize"))?;
3164 if stride < width {
3165 return Err(BridgeError::InvalidInput(
3166 "frame linesize is below its width",
3167 ));
3168 }
3169 Ok(stride)
3170}
3171
3172unsafe fn writable_plane<'a>(
3176 data: *mut u8,
3177 stride: usize,
3178 width: usize,
3179 height: usize,
3180) -> Result<&'a mut [u8], BridgeError> {
3181 if data.is_null() {
3182 return Err(BridgeError::InvalidInput("frame plane is null"));
3183 }
3184 let len = stride
3185 .checked_mul(height.saturating_sub(1))
3186 .and_then(|rows| rows.checked_add(width))
3187 .ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
3188 Ok(unsafe { slice::from_raw_parts_mut(data, len) })
3190}
3191
3192pub fn exit_without_handlers(code: u8) -> ! {
3204 #[cfg(unix)]
3205 {
3206 unsafe extern "C" {
3207 fn _exit(status: std::ffi::c_int) -> !;
3208 }
3209 unsafe { _exit(std::ffi::c_int::from(code)) }
3211 }
3212 #[cfg(windows)]
3213 {
3214 #[link(name = "kernel32")]
3215 unsafe extern "system" {
3216 fn GetCurrentProcess() -> *mut std::ffi::c_void;
3217 fn TerminateProcess(process: *mut std::ffi::c_void, exit_code: u32) -> i32;
3218 }
3219 if unsafe { TerminateProcess(GetCurrentProcess(), u32::from(code)) } != 0 {
3222 loop {
3224 std::thread::park();
3225 }
3226 }
3227 std::process::exit(i32::from(code))
3229 }
3230 #[cfg(not(any(unix, windows)))]
3231 std::process::exit(i32::from(code))
3232}
3233
3234#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3235mod vulkan_shim_tests;
3236
3237#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3238mod vulkan_access_tests;
3239
3240#[cfg(test)]
3241mod tests;