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#[must_use]
1682pub fn muxer_accepts_codec(format: &CStr, codec: ffi::AVCodecID) -> Option<bool> {
1683 let muxer = unsafe { ffi::av_guess_format(format.as_ptr(), ptr::null(), ptr::null()) };
1686 if muxer.is_null() {
1687 return None;
1688 }
1689 let accepted = unsafe {
1691 ffi::avformat_query_codec(
1692 muxer,
1693 codec,
1694 i32::try_from(ffi::FF_COMPLIANCE_NORMAL).unwrap_or(0),
1695 )
1696 };
1697 Some(accepted == 1)
1698}
1699
1700#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1702pub struct SampleProfile {
1703 pub is_yuv_420: bool,
1705 pub bit_depth: u8,
1707 pub has_alpha: bool,
1709 pub is_rgb: bool,
1712}
1713
1714#[must_use]
1720pub fn sample_profile(format: ffi::AVPixelFormat) -> Option<SampleProfile> {
1721 if format == ffi::AV_PIX_FMT_NONE {
1722 return None;
1723 }
1724 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
1727 let components = usize::from(descriptor.nb_components.min(4));
1728 Some(SampleProfile {
1729 is_yuv_420: descriptor.log2_chroma_w == 1
1730 && descriptor.log2_chroma_h == 1
1731 && descriptor.nb_components >= 3
1732 && descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB) == 0,
1733 bit_depth: descriptor.comp[..components]
1734 .iter()
1735 .map(|component| u8::try_from(component.depth).unwrap_or(u8::MAX))
1736 .max()
1737 .unwrap_or(8),
1738 has_alpha: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_ALPHA) != 0,
1739 is_rgb: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB | ffi::AV_PIX_FMT_FLAG_PAL)
1740 != 0,
1741 })
1742}
1743
1744#[must_use]
1746pub fn pixel_format_name(format: ffi::AVPixelFormat) -> Option<String> {
1747 let name = unsafe { ffi::av_get_pix_fmt_name(format) };
1750 if name.is_null() {
1751 None
1752 } else {
1753 Some(
1755 unsafe { CStr::from_ptr(name) }
1756 .to_string_lossy()
1757 .into_owned(),
1758 )
1759 }
1760}
1761
1762pub fn open_codec_context(
1778 context: &mut rsmpeg::avcodec::AVCodecContext,
1779 options: rsmpeg::avutil::AVDictionary,
1780) -> Result<Option<rsmpeg::avutil::AVDictionary>, RsmpegError> {
1781 let _media_foundation = media_foundation_lock(context);
1782 let mut dictionary = options.into_raw().as_ptr();
1784 let status = unsafe {
1788 ffi::avcodec_open2(
1789 context.as_mut_ptr(),
1790 ptr::null_mut(),
1791 std::ptr::addr_of_mut!(dictionary),
1792 )
1793 };
1794 let unused = ptr::NonNull::new(dictionary)
1797 .map(|dictionary| unsafe { rsmpeg::avutil::AVDictionary::from_raw(dictionary) });
1798 if status < 0 {
1799 return Err(RsmpegError::from(status));
1800 }
1801 Ok(unused)
1802}
1803
1804fn media_foundation_lock(
1817 context: &rsmpeg::avcodec::AVCodecContext,
1818) -> Option<std::sync::MutexGuard<'static, ()>> {
1819 static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1820 if !cfg!(target_os = "windows") || context.codec.is_null() {
1821 return None;
1822 }
1823 let name = unsafe { CStr::from_ptr((*context.codec).name) };
1826 name.to_bytes().ends_with(b"_mf").then(|| {
1827 LOCK.lock()
1828 .unwrap_or_else(std::sync::PoisonError::into_inner)
1829 })
1830}
1831
1832pub struct EncoderContext(Option<rsmpeg::avcodec::AVCodecContext>);
1838
1839impl From<rsmpeg::avcodec::AVCodecContext> for EncoderContext {
1840 fn from(context: rsmpeg::avcodec::AVCodecContext) -> Self {
1841 Self(Some(context))
1842 }
1843}
1844
1845impl std::ops::Deref for EncoderContext {
1846 type Target = rsmpeg::avcodec::AVCodecContext;
1847
1848 fn deref(&self) -> &Self::Target {
1849 self.0.as_ref().expect("taken only when dropped")
1850 }
1851}
1852
1853impl std::ops::DerefMut for EncoderContext {
1854 fn deref_mut(&mut self) -> &mut Self::Target {
1855 self.0.as_mut().expect("taken only when dropped")
1856 }
1857}
1858
1859impl Drop for EncoderContext {
1860 fn drop(&mut self) {
1861 if let Some(context) = self.0.take() {
1862 let _media_foundation = media_foundation_lock(&context);
1863 drop(context);
1864 }
1865 }
1866}
1867
1868#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1874pub const VULKAN_ENCODE_INPUT_USAGE: u32 = 0x0000_0001 | 0x0000_0002 | 0x0000_0004 | 0x0000_4000;
1875
1876#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1894pub fn vulkan_frames_add_usage(
1895 frames: &mut rsmpeg::avutil::AVHWFramesContext,
1896 usage: u32,
1897) -> Result<(), BridgeError> {
1898 let status = unsafe { alpha2_vulkan_frames_add_usage(frames.as_mut_ptr(), usage) };
1901 if status < 0 {
1902 return Err(BridgeError::FfmpegCode(status));
1903 }
1904 Ok(())
1905}
1906
1907#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1914#[must_use]
1915pub fn vulkan_frames_usage(frames: &rsmpeg::avutil::AVHWFramesContext) -> u32 {
1916 unsafe { alpha2_vulkan_frames_usage(frames.as_ptr()) }
1919}
1920
1921#[must_use]
1926pub fn packet_bytes(packet: &rsmpeg::avcodec::AVPacket) -> Vec<u8> {
1927 let size = usize::try_from(packet.size).unwrap_or(0);
1928 if packet.data.is_null() || size == 0 {
1929 return Vec::new();
1930 }
1931 unsafe { slice::from_raw_parts(packet.data.cast_const(), size) }.to_vec()
1934}
1935
1936#[must_use]
1950pub fn codec_parameters_extradata(parameters: &rsmpeg::avcodec::AVCodecParameters) -> Vec<u8> {
1951 let size = usize::try_from(parameters.extradata_size).unwrap_or(0);
1952 if parameters.extradata.is_null() || size == 0 {
1953 return Vec::new();
1954 }
1955 unsafe { slice::from_raw_parts(parameters.extradata.cast_const(), size) }.to_vec()
1958}
1959
1960#[must_use]
1964pub fn packet_has_side_data(
1965 packet: &rsmpeg::avcodec::AVPacket,
1966 kind: ffi::AVPacketSideDataType,
1967) -> bool {
1968 let mut size = 0_usize;
1969 !unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) }.is_null()
1972}
1973
1974#[must_use]
1983pub fn take_packet_side_data(
1984 packet: &mut rsmpeg::avcodec::AVPacket,
1985 kind: ffi::AVPacketSideDataType,
1986) -> Option<Vec<u8>> {
1987 let mut size = 0_usize;
1988 let data = unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) };
1991 if data.is_null() {
1992 return None;
1993 }
1994 let bytes = unsafe { slice::from_raw_parts(data, size) }.to_vec();
1996 let packet = packet.as_mut_ptr();
1997 unsafe {
2001 ffi::av_packet_side_data_remove(
2002 (*packet).side_data,
2003 &raw mut (*packet).side_data_elems,
2004 kind,
2005 );
2006 }
2007 Some(bytes)
2008}
2009
2010pub fn set_packet_side_data(
2016 packet: &mut rsmpeg::avcodec::AVPacket,
2017 kind: ffi::AVPacketSideDataType,
2018 data: &[u8],
2019) -> Result<(), BridgeError> {
2020 let _ = take_packet_side_data(packet, kind);
2021 let entry = unsafe { ffi::av_packet_new_side_data(packet.as_mut_ptr(), kind, data.len()) };
2024 if entry.is_null() {
2025 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2026 }
2027 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), entry, data.len()) };
2029 Ok(())
2030}
2031
2032pub fn set_output_stream_extradata(
2047 output: &mut rsmpeg::avformat::AVFormatContextOutput,
2048 index: usize,
2049 data: &[u8],
2050) -> Result<(), BridgeError> {
2051 let parameters = output
2052 .streams()
2053 .get(index)
2054 .ok_or(BridgeError::InvalidInput("the output has no such stream"))?
2055 .codecpar;
2056 let size = i32::try_from(data.len())
2057 .map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
2058 let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
2059 let buffer = unsafe { ffi::av_mallocz(data.len() + padding) }.cast::<u8>();
2061 if buffer.is_null() {
2062 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2063 }
2064 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), buffer, data.len()) };
2066 unsafe {
2070 ffi::av_freep((&raw mut (*parameters).extradata).cast());
2071 (*parameters).extradata = buffer;
2072 (*parameters).extradata_size = size;
2073 }
2074 Ok(())
2075}
2076
2077pub fn packet_from_bytes(data: &[u8]) -> Result<rsmpeg::avcodec::AVPacket, BridgeError> {
2087 let size = i32::try_from(data.len())
2088 .map_err(|_| BridgeError::InvalidInput("a packet is larger than FFmpeg holds"))?;
2089 let mut packet = rsmpeg::avcodec::AVPacket::new();
2090 let status = unsafe { ffi::av_new_packet(packet.as_mut_ptr(), size) };
2093 if status < 0 {
2094 return Err(BridgeError::FfmpegCode(status));
2095 }
2096 if !data.is_empty() {
2097 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), packet.data, data.len()) };
2099 }
2100 Ok(packet)
2101}
2102
2103#[derive(Clone, Copy, Debug)]
2107pub struct VideoStreamDescription<'a> {
2108 pub codec_id: ffi::AVCodecID,
2110 pub width: i32,
2112 pub height: i32,
2114 pub pixel_format: ffi::AVPixelFormat,
2116 pub profile: i32,
2118 pub level: i32,
2120 pub color_primaries: ffi::AVColorPrimaries,
2122 pub color_trc: ffi::AVColorTransferCharacteristic,
2124 pub color_space: ffi::AVColorSpace,
2126 pub color_range: ffi::AVColorRange,
2128 pub sample_aspect_ratio: ffi::AVRational,
2130 pub frame_rate: ffi::AVRational,
2132 pub bit_rate: i64,
2134 pub extradata: &'a [u8],
2136}
2137
2138pub fn video_codec_parameters(
2143 description: &VideoStreamDescription<'_>,
2144) -> Result<rsmpeg::avcodec::AVCodecParameters, BridgeError> {
2145 let extradata_size = i32::try_from(description.extradata.len())
2146 .map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
2147 let mut parameters = rsmpeg::avcodec::AVCodecParameters::new();
2148 let raw = parameters.as_mut_ptr();
2149 unsafe {
2152 (*raw).codec_type = ffi::AVMEDIA_TYPE_VIDEO;
2153 (*raw).codec_id = description.codec_id;
2154 (*raw).width = description.width;
2155 (*raw).height = description.height;
2156 (*raw).format = description.pixel_format;
2157 (*raw).profile = description.profile;
2158 (*raw).level = description.level;
2159 (*raw).color_primaries = description.color_primaries;
2160 (*raw).color_trc = description.color_trc;
2161 (*raw).color_space = description.color_space;
2162 (*raw).color_range = description.color_range;
2163 (*raw).sample_aspect_ratio = description.sample_aspect_ratio;
2164 (*raw).framerate = description.frame_rate;
2165 (*raw).bit_rate = description.bit_rate;
2166 (*raw).field_order = ffi::AV_FIELD_PROGRESSIVE;
2167 }
2168 if !description.extradata.is_empty() {
2169 let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
2170 let buffer = unsafe { ffi::av_mallocz(description.extradata.len() + padding) }.cast::<u8>();
2172 if buffer.is_null() {
2173 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2174 }
2175 unsafe {
2179 ptr::copy_nonoverlapping(
2180 description.extradata.as_ptr(),
2181 buffer,
2182 description.extradata.len(),
2183 );
2184 (*raw).extradata = buffer;
2185 (*raw).extradata_size = extradata_size;
2186 }
2187 }
2188 Ok(parameters)
2189}
2190
2191pub fn bitstream_filter_list(
2205 spec: &CStr,
2206 parameters: &AVCodecParameters,
2207 time_base: rsmpeg::avutil::AVRational,
2208) -> Result<rsmpeg::avcodec::AVBSFContext, BridgeError> {
2209 let mut raw = ptr::null_mut();
2210 let status = unsafe { ffi::av_bsf_list_parse_str(spec.as_ptr(), &raw mut raw) };
2213 if status < 0 {
2214 return Err(BridgeError::FfmpegCode(status));
2215 }
2216 let raw = ptr::NonNull::new(raw).ok_or(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)))?;
2217 let mut context = unsafe { rsmpeg::avcodec::AVBSFContextUninit::from_raw(raw) };
2220 let status = unsafe { ffi::avcodec_parameters_copy(context.par_in, parameters.as_ptr()) };
2224 if status < 0 {
2225 return Err(BridgeError::FfmpegCode(status));
2226 }
2227 context.set_time_base_in(time_base);
2228 context.init().map_err(BridgeError::Ffmpeg)
2229}
2230
2231pub fn set_stream_codec_tag(stream: &mut rsmpeg::avformat::AVStreamMut<'_>, tag: u32) {
2234 let mut parameters = stream.codecpar_mut();
2235 unsafe { (*parameters.as_mut_ptr()).codec_tag = tag };
2238}
2239
2240pub fn set_stream_display_matrix(
2248 stream: &mut rsmpeg::avformat::AVStreamMut<'_>,
2249 display: DisplayRotation,
2250) -> Result<(), BridgeError> {
2251 let mut parameters = stream.codecpar_mut();
2252 let parameters = parameters.as_mut_ptr();
2253 let entry = unsafe {
2257 ffi::av_packet_side_data_new(
2258 &raw mut (*parameters).coded_side_data,
2259 &raw mut (*parameters).nb_coded_side_data,
2260 ffi::AV_PKT_DATA_DISPLAYMATRIX,
2261 size_of::<[i32; 9]>(),
2262 0,
2263 )
2264 };
2265 if entry.is_null() {
2266 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2267 }
2268 let mut matrix = [0_i32; 9];
2269 unsafe {
2273 ffi::av_display_rotation_set(matrix.as_mut_ptr(), -display.counter_clockwise_degrees);
2274 if display.flipped {
2275 ffi::av_display_matrix_flip(matrix.as_mut_ptr(), 1, 0);
2276 }
2277 }
2278 unsafe {
2281 ptr::copy_nonoverlapping(
2282 matrix.as_ptr().cast::<u8>(),
2283 (*entry).data,
2284 size_of::<[i32; 9]>(),
2285 );
2286 }
2287 Ok(())
2288}
2289
2290#[must_use]
2292pub fn hardware_device_type_name(device_type: ffi::AVHWDeviceType) -> Option<String> {
2293 let name = unsafe { ffi::av_hwdevice_get_type_name(device_type) };
2296 if name.is_null() {
2297 None
2298 } else {
2299 Some(
2301 unsafe { CStr::from_ptr(name) }
2302 .to_string_lossy()
2303 .into_owned(),
2304 )
2305 }
2306}
2307
2308#[must_use]
2318pub fn yuv_layout(format: ffi::AVPixelFormat) -> Option<YuvPlaneLayout> {
2319 if format == ffi::AV_PIX_FMT_NONE {
2320 return None;
2321 }
2322 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
2325 let foreign_endian = if cfg!(target_endian = "little") {
2326 ffi::AV_PIX_FMT_FLAG_BE
2327 } else {
2328 0
2329 };
2330 let rejected = foreign_endian
2331 | ffi::AV_PIX_FMT_FLAG_PAL
2332 | ffi::AV_PIX_FMT_FLAG_BITSTREAM
2333 | ffi::AV_PIX_FMT_FLAG_HWACCEL
2334 | ffi::AV_PIX_FMT_FLAG_RGB
2335 | ffi::AV_PIX_FMT_FLAG_ALPHA
2336 | ffi::AV_PIX_FMT_FLAG_BAYER
2337 | ffi::AV_PIX_FMT_FLAG_FLOAT
2338 | ffi::AV_PIX_FMT_FLAG_XYZ;
2339 if descriptor.flags & u64::from(rejected) != 0 || descriptor.nb_components != 3 {
2340 return None;
2341 }
2342 let [luma, u, v, _] = descriptor.comp;
2343 let depth = luma.depth;
2344 if !(8..=16).contains(&depth) || u.depth != depth || v.depth != depth {
2345 return None;
2346 }
2347 if descriptor.log2_chroma_w > 2 || descriptor.log2_chroma_h > 2 {
2348 return None;
2349 }
2350 let bytes = if depth > 8 { 2 } else { 1 };
2351 let shift = luma.shift;
2352 let msb_aligned = match shift {
2353 0 => false,
2354 _ if depth > 8 && shift == 16 - depth => true,
2355 _ => return None,
2356 };
2357 let planar = u.plane == 1
2358 && v.plane == 2
2359 && u.step == bytes
2360 && v.step == bytes
2361 && u.offset == 0
2362 && v.offset == 0;
2363 let interleaved = u.plane == 1
2364 && v.plane == 1
2365 && u.step == 2 * bytes
2366 && v.step == 2 * bytes
2367 && u.offset == 0
2368 && v.offset == bytes;
2369 if luma.plane != 0
2370 || luma.step != bytes
2371 || luma.offset != 0
2372 || u.shift != shift
2373 || v.shift != shift
2374 || !(planar || interleaved)
2375 {
2376 return None;
2377 }
2378 Some(YuvPlaneLayout {
2379 chroma_shift_x: descriptor.log2_chroma_w,
2380 chroma_shift_y: descriptor.log2_chroma_h,
2381 bit_depth: u8::try_from(depth).ok()?,
2382 interleaved_chroma: interleaved,
2383 msb_aligned,
2384 })
2385}
2386
2387pub fn yuv_planes(frame: &AVFrame) -> Result<Option<YuvPlanes>, BridgeError> {
2401 frame_yuv_planes(frame, false)
2402}
2403
2404pub struct DecodedFrame {
2412 frame: AVFrame,
2413 written: bool,
2415}
2416
2417impl DecodedFrame {
2418 pub fn receive(decoder: &mut AVCodecContext) -> Result<Self, RsmpegError> {
2425 let written = decoder_frames_are_initialized(decoder);
2426 let frame = decoder.receive_frame()?;
2427 Ok(Self { frame, written })
2428 }
2429
2430 pub fn yuv_planes(&self) -> Result<Option<YuvPlanes>, BridgeError> {
2441 frame_yuv_planes(&self.frame, self.written)
2442 }
2443
2444 pub fn set_pts(&mut self, pts: i64) {
2446 self.frame.set_pts(pts);
2447 }
2448
2449 #[must_use]
2451 pub fn into_frame(self) -> AVFrame {
2452 self.frame
2453 }
2454}
2455
2456impl std::ops::Deref for DecodedFrame {
2457 type Target = AVFrame;
2458
2459 fn deref(&self) -> &AVFrame {
2460 &self.frame
2461 }
2462}
2463
2464impl fmt::Debug for DecodedFrame {
2465 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2466 formatter
2467 .debug_struct("DecodedFrame")
2468 .field("format", &self.frame.format)
2469 .field("width", &self.frame.width)
2470 .field("height", &self.frame.height)
2471 .field("pts", &self.frame.pts)
2472 .field("written", &self.written)
2473 .finish_non_exhaustive()
2474 }
2475}
2476
2477fn decoder_frames_are_initialized(decoder: &AVCodecContext) -> bool {
2487 static DEFAULT_GET_BUFFER2: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2488 let default = *DEFAULT_GET_BUFFER2.get_or_init(|| {
2491 unsafe {
2493 let mut context = ffi::avcodec_alloc_context3(ptr::null());
2494 let get_buffer2 = context
2495 .as_ref()
2496 .and_then(|context| context.get_buffer2)
2497 .map_or(0, |function| function as usize);
2498 ffi::avcodec_free_context(&raw mut context);
2499 get_buffer2
2500 }
2501 });
2502 let wrapped =
2504 unsafe { decoder.codec.as_ref() }.is_none_or(|codec| !codec.wrapper_name.is_null());
2505 let get_buffer2 = decoder.get_buffer2.map_or(0, |function| function as usize);
2506 !wrapped && default != 0 && get_buffer2 == default
2507}
2508
2509fn frame_yuv_planes(frame: &AVFrame, share: bool) -> Result<Option<YuvPlanes>, BridgeError> {
2512 let Some(layout) = yuv_layout(frame.format) else {
2513 return Ok(None);
2514 };
2515 let width =
2516 u32::try_from(frame.width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2517 let height = u32::try_from(frame.height)
2518 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2519 if width == 0 || height == 0 {
2520 return Err(BridgeError::InvalidInput("empty YUV frame"));
2521 }
2522 let bytes = layout.bytes_per_sample();
2523 let chroma_width = width.div_ceil(1 << layout.chroma_shift_x);
2524 let chroma_height = height.div_ceil(1 << layout.chroma_shift_y);
2525 let shared =
2528 (share && !frame.buf[0].is_null()).then(|| std::sync::Arc::new(SharedFrame(frame.clone())));
2529 let copy = |index: usize,
2530 samples: u32,
2531 pairs: u32,
2532 rows: u32,
2533 context: &'static str|
2534 -> Result<FramePlane, BridgeError> {
2535 let row_bytes = samples
2536 .checked_mul(bytes)
2537 .and_then(|row| row.checked_mul(pairs))
2538 .ok_or(BridgeError::InvalidInput(context))?;
2539 if let Some(view) = shared
2540 .as_ref()
2541 .and_then(|frame| PlaneView::new(frame, index, row_bytes, rows))
2542 {
2543 let stride =
2544 u32::try_from(view.stride).map_err(|_| BridgeError::InvalidInput(context))?;
2545 return Ok(FramePlane {
2546 bytes: bytes::Bytes::from_owner(view),
2547 stride,
2548 width: samples,
2549 height: rows,
2550 });
2551 }
2552 Ok(FramePlane {
2553 bytes: copy_video_plane(
2554 frame.data[index],
2555 frame.linesize[index],
2556 row_bytes,
2557 rows,
2558 context,
2559 )?
2560 .into(),
2561 stride: row_bytes,
2562 width: samples,
2563 height: rows,
2564 })
2565 };
2566 let mut planes = vec![copy(0, width, 1, height, "invalid Y plane")?];
2567 if layout.interleaved_chroma {
2568 planes.push(copy(1, chroma_width, 2, chroma_height, "invalid UV plane")?);
2569 } else {
2570 planes.push(copy(1, chroma_width, 1, chroma_height, "invalid U plane")?);
2571 planes.push(copy(2, chroma_width, 1, chroma_height, "invalid V plane")?);
2572 }
2573 Ok(Some(YuvPlanes {
2574 layout,
2575 width,
2576 height,
2577 planes,
2578 }))
2579}
2580
2581struct SharedFrame(AVFrame);
2583
2584unsafe impl Sync for SharedFrame {}
2588
2589struct PlaneView {
2592 frame: std::sync::Arc<SharedFrame>,
2593 data: *const u8,
2594 len: usize,
2595 stride: usize,
2596}
2597
2598unsafe impl Send for PlaneView {}
2601
2602impl PlaneView {
2603 fn new(
2607 frame: &std::sync::Arc<SharedFrame>,
2608 index: usize,
2609 row_bytes: u32,
2610 rows: u32,
2611 ) -> Option<Self> {
2612 let inner = &frame.0;
2613 let data = inner.data[index].cast_const();
2614 let stride = usize::try_from(inner.linesize[index]).ok()?;
2615 let row_bytes = usize::try_from(row_bytes).ok()?;
2616 if data.is_null() || stride < row_bytes || rows == 0 {
2617 return None;
2618 }
2619 let len = stride.checked_mul(usize::try_from(rows).ok()?)?;
2620 let start = data as usize;
2621 let end = start.checked_add(len)?;
2622 let inside = inner.buf.iter().any(|buffer| {
2623 let Some(buffer) = (unsafe { buffer.as_ref() }) else {
2626 return false;
2627 };
2628 let buffer_start = buffer.data as usize;
2629 buffer_start
2630 .checked_add(buffer.size)
2631 .is_some_and(|buffer_end| buffer_start <= start && end <= buffer_end)
2632 });
2633 inside.then(|| Self {
2634 frame: std::sync::Arc::clone(frame),
2635 data,
2636 len,
2637 stride,
2638 })
2639 }
2640}
2641
2642impl AsRef<[u8]> for PlaneView {
2643 fn as_ref(&self) -> &[u8] {
2644 let _ = &self.frame;
2646 unsafe { slice::from_raw_parts(self.data, self.len) }
2649 }
2650}
2651
2652fn copy_video_plane(
2653 data: *const u8,
2654 linesize: i32,
2655 width: u32,
2656 height: u32,
2657 context: &'static str,
2658) -> Result<Vec<u8>, BridgeError> {
2659 let row_bytes = usize::try_from(width).map_err(|_| BridgeError::InvalidInput(context))?;
2660 let rows = usize::try_from(height).map_err(|_| BridgeError::InvalidInput(context))?;
2661 let stride =
2662 usize::try_from(linesize.unsigned_abs()).map_err(|_| BridgeError::InvalidInput(context))?;
2663 if data.is_null() || linesize == 0 || stride < row_bytes {
2664 return Err(BridgeError::InvalidInput(context));
2665 }
2666 let byte_len = row_bytes
2667 .checked_mul(rows)
2668 .ok_or(BridgeError::InvalidInput(context))?;
2669 let mut output = Vec::with_capacity(byte_len);
2672 let signed_stride =
2673 isize::try_from(linesize).map_err(|_| BridgeError::InvalidInput(context))?;
2674 for row in 0..rows {
2675 let row = isize::try_from(row).map_err(|_| BridgeError::InvalidInput(context))?;
2676 let offset = row
2677 .checked_mul(signed_stride)
2678 .ok_or(BridgeError::InvalidInput(context))?;
2679 let source = unsafe { slice::from_raw_parts(data.offset(offset), row_bytes) };
2683 output.extend_from_slice(source);
2684 }
2685 debug_assert_eq!(output.len(), byte_len);
2686 Ok(output)
2687}
2688
2689pub fn rgba_frame(bytes: &[u8], width: i32, height: i32) -> Result<AVFrame, BridgeError> {
2694 let row_bytes = usize::try_from(width)
2695 .ok()
2696 .and_then(|value| value.checked_mul(4))
2697 .ok_or(BridgeError::InvalidInput("RGBA row width overflow"))?;
2698 let rows =
2699 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("negative RGBA height"))?;
2700 let expected = row_bytes
2701 .checked_mul(rows)
2702 .ok_or(BridgeError::InvalidInput("RGBA frame size overflow"))?;
2703 if bytes.len() != expected {
2704 return Err(BridgeError::InvalidInput("RGBA byte length mismatch"));
2705 }
2706
2707 let mut frame = AVFrame::new();
2708 frame.set_format(ffi::AV_PIX_FMT_RGBA);
2709 frame.set_width(width);
2710 frame.set_height(height);
2711 frame.get_buffer(1)?;
2712 frame.make_writable()?;
2713 let linesize = usize::try_from(frame.linesize[0])
2714 .map_err(|_| BridgeError::InvalidInput("negative RGBA linesize"))?;
2715 if frame.data[0].is_null() || linesize < row_bytes {
2716 return Err(BridgeError::InvalidInput(
2717 "RGBA destination plane is too small",
2718 ));
2719 }
2720
2721 unsafe {
2724 for row in 0..rows {
2725 let destination_offset = row.checked_mul(linesize).ok_or(BridgeError::InvalidInput(
2726 "RGBA destination offset overflow",
2727 ))?;
2728 let source_offset = row
2729 .checked_mul(row_bytes)
2730 .ok_or(BridgeError::InvalidInput("RGBA source offset overflow"))?;
2731 let destination =
2732 slice::from_raw_parts_mut(frame.data[0].add(destination_offset), row_bytes);
2733 destination.copy_from_slice(&bytes[source_offset..source_offset + row_bytes]);
2734 }
2735 }
2736 Ok(frame)
2737}
2738
2739#[allow(clippy::too_many_arguments)]
2745pub fn yuv420p_frame(
2746 y: &[u8],
2747 y_stride: u32,
2748 u: &[u8],
2749 u_stride: u32,
2750 v: &[u8],
2751 v_stride: u32,
2752 width: i32,
2753 height: i32,
2754) -> Result<AVFrame, BridgeError> {
2755 let width =
2756 u32::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2757 let height =
2758 u32::try_from(height).map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2759 if width == 0 || height == 0 {
2760 return Err(BridgeError::InvalidInput("empty YUV frame"));
2761 }
2762 let chroma_width = width.div_ceil(2);
2763 let chroma_height = height.div_ceil(2);
2764 validate_source_plane(y, y_stride, width, height, "invalid Y source plane")?;
2765 validate_source_plane(
2766 u,
2767 u_stride,
2768 chroma_width,
2769 chroma_height,
2770 "invalid U source plane",
2771 )?;
2772 validate_source_plane(
2773 v,
2774 v_stride,
2775 chroma_width,
2776 chroma_height,
2777 "invalid V source plane",
2778 )?;
2779
2780 let mut frame = AVFrame::new();
2781 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
2782 frame.set_width(i32::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV width"))?);
2783 frame.set_height(i32::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV height"))?);
2784 frame.get_buffer(1)?;
2785 frame.make_writable()?;
2786 copy_plane_to_frame(y, y_stride, width, height, frame.data[0], frame.linesize[0])?;
2787 copy_plane_to_frame(
2788 u,
2789 u_stride,
2790 chroma_width,
2791 chroma_height,
2792 frame.data[1],
2793 frame.linesize[1],
2794 )?;
2795 copy_plane_to_frame(
2796 v,
2797 v_stride,
2798 chroma_width,
2799 chroma_height,
2800 frame.data[2],
2801 frame.linesize[2],
2802 )?;
2803 Ok(frame)
2804}
2805
2806#[must_use]
2810pub fn yuv_pixel_format(layout: YuvPlaneLayout) -> Option<ffi::AVPixelFormat> {
2811 let mut descriptor = ptr::null();
2812 loop {
2813 descriptor = unsafe { ffi::av_pix_fmt_desc_next(descriptor) };
2816 if descriptor.is_null() {
2817 return None;
2818 }
2819 let format = unsafe { ffi::av_pix_fmt_desc_get_id(descriptor) };
2821 if yuv_layout(format) == Some(layout) {
2822 return Some(format);
2823 }
2824 }
2825}
2826
2827pub fn planar_frame(
2834 format: ffi::AVPixelFormat,
2835 planes: &[(&[u8], u32)],
2836 width: u32,
2837 height: u32,
2838) -> Result<AVFrame, BridgeError> {
2839 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
2842 .ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
2843 let count = unsafe { ffi::av_pix_fmt_count_planes(format) };
2845 if usize::try_from(count).ok() != Some(planes.len()) || width == 0 || height == 0 {
2846 return Err(BridgeError::InvalidInput(
2847 "planes do not match the pixel format",
2848 ));
2849 }
2850 let signed = |value: u32| {
2851 i32::try_from(value).map_err(|_| BridgeError::InvalidInput("frame size overflow"))
2852 };
2853 let mut frame = AVFrame::new();
2854 frame.set_format(format);
2855 frame.set_width(signed(width)?);
2856 frame.set_height(signed(height)?);
2857 frame.get_buffer(1)?;
2858 frame.make_writable()?;
2859 for (index, (bytes, stride)) in planes.iter().enumerate() {
2860 let plane = i32::try_from(index).unwrap_or(i32::MAX);
2861 let row_bytes = unsafe { ffi::av_image_get_linesize(format, signed(width)?, plane) };
2863 let row_bytes = u32::try_from(row_bytes).map_err(|_| BridgeError::FfmpegCode(row_bytes))?;
2864 let rows = if index == 0 || index == 3 {
2865 height
2866 } else {
2867 height.div_ceil(1 << descriptor.log2_chroma_h)
2868 };
2869 validate_source_plane(bytes, *stride, row_bytes, rows, "invalid source plane")?;
2870 copy_plane_to_frame(
2871 bytes,
2872 *stride,
2873 row_bytes,
2874 rows,
2875 frame.data[index],
2876 frame.linesize[index],
2877 )?;
2878 }
2879 Ok(frame)
2880}
2881
2882fn validate_source_plane(
2883 bytes: &[u8],
2884 stride: u32,
2885 width: u32,
2886 height: u32,
2887 context: &'static str,
2888) -> Result<(), BridgeError> {
2889 let required = usize::try_from(stride).ok().and_then(|stride| {
2890 usize::try_from(height)
2891 .ok()
2892 .and_then(|height| stride.checked_mul(height))
2893 });
2894 if stride < width || required != Some(bytes.len()) {
2895 Err(BridgeError::InvalidInput(context))
2896 } else {
2897 Ok(())
2898 }
2899}
2900
2901fn copy_plane_to_frame(
2902 source: &[u8],
2903 source_stride: u32,
2904 width: u32,
2905 height: u32,
2906 destination: *mut u8,
2907 destination_stride: i32,
2908) -> Result<(), BridgeError> {
2909 let row_bytes =
2910 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV row width overflow"))?;
2911 let rows =
2912 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV row count overflow"))?;
2913 let source_stride = usize::try_from(source_stride)
2914 .map_err(|_| BridgeError::InvalidInput("YUV source stride overflow"))?;
2915 let destination_stride = usize::try_from(destination_stride)
2916 .map_err(|_| BridgeError::InvalidInput("negative YUV destination stride"))?;
2917 if destination.is_null() || destination_stride < row_bytes {
2918 return Err(BridgeError::InvalidInput(
2919 "YUV destination plane is too small",
2920 ));
2921 }
2922 for row in 0..rows {
2923 let source_offset = row
2924 .checked_mul(source_stride)
2925 .ok_or(BridgeError::InvalidInput("YUV source offset overflow"))?;
2926 let destination_offset = row
2927 .checked_mul(destination_stride)
2928 .ok_or(BridgeError::InvalidInput("YUV destination offset overflow"))?;
2929 unsafe {
2933 ptr::copy_nonoverlapping(
2934 source.as_ptr().add(source_offset),
2935 destination.add(destination_offset),
2936 row_bytes,
2937 );
2938 }
2939 }
2940 Ok(())
2941}
2942
2943pub fn audio_frame(
2952 channels: &[Vec<f32>],
2953 format: ffi::AVSampleFormat,
2954 sample_rate: i32,
2955 pts: i64,
2956) -> Result<AVFrame, BridgeError> {
2957 let Some(samples) = channels.first().map(Vec::len) else {
2958 return Err(BridgeError::InvalidInput("invalid planar audio shape"));
2959 };
2960 if sample_rate <= 0
2961 || samples == 0
2962 || channels.len() > ffi::AV_NUM_DATA_POINTERS as usize
2963 || channels.iter().any(|channel| channel.len() != samples)
2964 {
2965 return Err(BridgeError::InvalidInput(
2966 "planar audio channel lengths differ",
2967 ));
2968 }
2969 let sample_bytes = match format {
2970 ffi::AV_SAMPLE_FMT_FLTP | ffi::AV_SAMPLE_FMT_FLT => size_of::<f32>(),
2971 ffi::AV_SAMPLE_FMT_S16 => size_of::<i16>(),
2972 _ => return Err(BridgeError::InvalidInput("unsupported audio sample format")),
2973 };
2974 let planar = format == ffi::AV_SAMPLE_FMT_FLTP;
2975 let channel_count = i32::try_from(channels.len())
2976 .map_err(|_| BridgeError::InvalidInput("audio channel count overflow"))?;
2977 let sample_count = i32::try_from(samples)
2978 .map_err(|_| BridgeError::InvalidInput("audio sample count overflow"))?;
2979 let mut frame = AVFrame::new();
2980 frame.set_format(format);
2981 frame.set_ch_layout(
2982 rsmpeg::avutil::AVChannelLayout::from_nb_channels(channel_count).into_inner(),
2983 );
2984 frame.set_sample_rate(sample_rate);
2985 frame.set_nb_samples(sample_count);
2986 frame.set_pts(pts);
2987 frame.set_time_base(ffi::AVRational {
2988 num: 1,
2989 den: sample_rate,
2990 });
2991 frame.get_buffer(0)?;
2992 frame.make_writable()?;
2993 let plane_bytes = samples
2994 .checked_mul(sample_bytes)
2995 .and_then(|bytes| bytes.checked_mul(if planar { 1 } else { channels.len() }))
2996 .ok_or(BridgeError::InvalidInput("audio plane byte count overflow"))?;
2997 let plane_capacity = usize::try_from(frame.linesize[0]).unwrap_or(0);
2998 if plane_capacity < plane_bytes {
2999 return Err(BridgeError::InvalidInput(
3000 "audio plane capacity is too small",
3001 ));
3002 }
3003 let planes = if planar { channels.len() } else { 1 };
3004 if frame.data[..planes].iter().any(|plane| plane.is_null()) {
3005 return Err(BridgeError::InvalidInput("audio plane pointer is null"));
3006 }
3007 if planar {
3008 for (index, source) in channels.iter().enumerate() {
3009 unsafe {
3013 ptr::copy_nonoverlapping(
3014 source.as_ptr().cast::<u8>(),
3015 frame.data[index],
3016 plane_bytes,
3017 );
3018 }
3019 }
3020 return Ok(frame);
3021 }
3022 let plane = unsafe { std::slice::from_raw_parts_mut(frame.data[0], plane_bytes) };
3025 let interleaved = (0..samples).flat_map(|sample| channels.iter().map(move |c| c[sample]));
3026 if format == ffi::AV_SAMPLE_FMT_FLT {
3027 for (bytes, value) in plane.as_chunks_mut::<4>().0.iter_mut().zip(interleaved) {
3028 *bytes = value.to_ne_bytes();
3029 }
3030 } else {
3031 for (bytes, value) in plane.as_chunks_mut::<2>().0.iter_mut().zip(interleaved) {
3032 #[allow(clippy::cast_possible_truncation)]
3034 let value = (value.clamp(-1.0, 1.0) * 32_767.0).round() as i16;
3035 *bytes = value.to_ne_bytes();
3036 }
3037 }
3038 Ok(frame)
3039}
3040
3041#[must_use]
3060pub fn channel_layout_channels(layout: &rsmpeg::avutil::AVChannelLayout) -> Vec<ffi::AVChannel> {
3061 if layout.order == ffi::AV_CHANNEL_ORDER_UNSPEC {
3062 return Vec::new();
3063 }
3064 let channels = u32::try_from(layout.nb_channels).unwrap_or(0);
3065 (0..channels)
3066 .map(|index| {
3067 layout
3068 .channel_from_index(index)
3069 .unwrap_or(ffi::AV_CHAN_NONE)
3070 })
3071 .collect()
3072}
3073
3074pub fn channel_layout_of(
3081 channels: &[ffi::AVChannel],
3082) -> Result<rsmpeg::avutil::AVChannelLayout, BridgeError> {
3083 let count = i32::try_from(channels.len())
3084 .ok()
3085 .filter(|count| *count > 0)
3086 .ok_or(BridgeError::InvalidInput("a channel layout needs channels"))?;
3087 let mut raw = std::mem::MaybeUninit::<ffi::AVChannelLayout>::zeroed();
3088 let initialized = unsafe { ffi::av_channel_layout_custom_init(raw.as_mut_ptr(), count) };
3091 if initialized < 0 {
3092 return Err(BridgeError::FfmpegCode(initialized));
3093 }
3094 let mut layout = unsafe { rsmpeg::avutil::AVChannelLayout::new(raw.assume_init()) };
3096 for (index, channel) in channels.iter().enumerate() {
3097 unsafe { (*layout.u.map.add(index)).id = *channel };
3099 }
3100 let flags = i32::try_from(ffi::AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL).unwrap_or(0);
3101 let retyped = unsafe {
3103 ffi::av_channel_layout_retype(layout.as_mut_ptr(), ffi::AV_CHANNEL_ORDER_UNSPEC, flags)
3104 };
3105 if retyped < 0 {
3106 return Err(BridgeError::FfmpegCode(retyped));
3107 }
3108 Ok(layout)
3109}
3110
3111pub fn audio_f32_interleaved(frame: &AVFrame) -> Result<Vec<f32>, BridgeError> {
3116 if frame.format != ffi::AV_SAMPLE_FMT_FLT || frame.nb_samples < 0 {
3117 return Err(BridgeError::InvalidInput(
3118 "packed audio format or sample count is invalid",
3119 ));
3120 }
3121 let channels = usize::try_from(frame.ch_layout().nb_channels)
3122 .map_err(|_| BridgeError::InvalidInput("packed audio channel count is negative"))?;
3123 let frames = usize::try_from(frame.nb_samples)
3124 .map_err(|_| BridgeError::InvalidInput("packed audio sample count is negative"))?;
3125 let samples = frames
3126 .checked_mul(channels)
3127 .ok_or(BridgeError::InvalidInput(
3128 "packed audio sample count overflow",
3129 ))?;
3130 let bytes = samples
3131 .checked_mul(size_of::<f32>())
3132 .ok_or(BridgeError::InvalidInput(
3133 "packed audio byte count overflow",
3134 ))?;
3135 if channels == 0
3136 || frame.data[0].is_null()
3137 || usize::try_from(frame.linesize[0]).unwrap_or(0) < bytes
3138 {
3139 return Err(BridgeError::InvalidInput(
3140 "packed audio plane is missing or too small",
3141 ));
3142 }
3143 let mut output = vec![0.0_f32; samples];
3144 unsafe {
3147 ptr::copy_nonoverlapping(frame.data[0], output.as_mut_ptr().cast::<u8>(), bytes);
3148 }
3149 Ok(output)
3150}
3151
3152#[derive(Copy, Clone, Debug, PartialEq)]
3154pub struct DisplayRotation {
3155 pub counter_clockwise_degrees: f64,
3158 pub flipped: bool,
3162}
3163
3164#[must_use]
3166pub fn display_rotation(parameters: &AVCodecParameters) -> Option<DisplayRotation> {
3167 let count = usize::try_from(parameters.nb_coded_side_data).ok()?;
3168 if count == 0 || count > 64 || parameters.coded_side_data.is_null() {
3169 return None;
3170 }
3171 let entries = unsafe { slice::from_raw_parts(parameters.coded_side_data, count) };
3174 let matrix = entries.iter().find(|entry| {
3175 entry.type_ == ffi::AV_PKT_DATA_DISPLAYMATRIX
3176 && entry.size >= 9 * size_of::<i32>()
3177 && !entry.data.is_null()
3178 })?;
3179 unsafe { matrix_rotation(matrix.data) }
3181}
3182
3183#[must_use]
3186pub fn frame_display_rotation(frame: &AVFrame) -> Option<DisplayRotation> {
3187 let entry =
3189 unsafe { ffi::av_frame_get_side_data(frame.as_ptr(), ffi::AV_FRAME_DATA_DISPLAYMATRIX) };
3190 if entry.is_null() {
3191 return None;
3192 }
3193 let entry = unsafe { &*entry };
3195 if entry.size < 9 * size_of::<i32>() || entry.data.is_null() {
3196 return None;
3197 }
3198 unsafe { matrix_rotation(entry.data) }
3200}
3201
3202unsafe fn matrix_rotation(data: *const u8) -> Option<DisplayRotation> {
3205 let mut aligned = [0_i32; 9];
3206 unsafe {
3209 ptr::copy_nonoverlapping(
3210 data,
3211 aligned.as_mut_ptr().cast::<u8>(),
3212 size_of::<[i32; 9]>(),
3213 );
3214 }
3215 let determinant = i64::from(aligned[0]) * i64::from(aligned[4])
3219 - i64::from(aligned[1]) * i64::from(aligned[3]);
3220 let flipped = determinant < 0;
3221 if flipped {
3222 unsafe { ffi::av_display_matrix_flip(aligned.as_mut_ptr(), 1, 0) };
3224 }
3225 let angle = unsafe { ffi::av_display_rotation_get(aligned.as_ptr()) };
3227 angle.is_finite().then_some(DisplayRotation {
3228 counter_clockwise_degrees: angle,
3229 flipped,
3230 })
3231}
3232
3233const fn size_of<T>() -> usize {
3234 std::mem::size_of::<T>()
3235}
3236
3237pub struct Yuv420pFrameBuffer {
3251 frame: AVFrame,
3252 width: usize,
3253 height: usize,
3254}
3255
3256pub struct Yuv420pFramePlanes<'a> {
3258 pub y: &'a mut [u8],
3260 pub y_stride: usize,
3262 pub u: &'a mut [u8],
3264 pub v: &'a mut [u8],
3266 pub chroma_stride: usize,
3268}
3269
3270impl Yuv420pFrameBuffer {
3271 pub fn new(width: i32, height: i32) -> Result<Self, BridgeError> {
3276 let coded_width =
3277 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
3278 let coded_height = usize::try_from(height)
3279 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
3280 if coded_width == 0 || coded_height == 0 {
3281 return Err(BridgeError::InvalidInput("empty YUV frame"));
3282 }
3283 Ok(Self {
3284 frame: zeroed_yuv420p_frame(width, height)?,
3285 width: coded_width,
3286 height: coded_height,
3287 })
3288 }
3289
3290 #[must_use]
3292 pub const fn dimensions(&self) -> (usize, usize) {
3293 (self.width, self.height)
3294 }
3295
3296 pub fn begin_write(&mut self) -> Result<Yuv420pFramePlanes<'_>, BridgeError> {
3302 if !self.frame.is_writable().map_err(BridgeError::Ffmpeg)? {
3306 let mut copy = zeroed_yuv420p_frame(self.frame.width, self.frame.height)?;
3307 let status = unsafe {
3310 let copied = ffi::av_frame_copy(copy.as_mut_ptr(), self.frame.as_ptr());
3311 if copied < 0 {
3312 copied
3313 } else {
3314 ffi::av_frame_copy_props(copy.as_mut_ptr(), self.frame.as_ptr())
3315 }
3316 };
3317 if status < 0 {
3318 return Err(BridgeError::FfmpegCode(status));
3319 }
3320 self.frame = copy;
3321 }
3322 let chroma_width = self.width.div_ceil(2);
3323 let chroma_height = self.height.div_ceil(2);
3324 let y_stride = plane_stride(self.frame.linesize[0], self.width)?;
3325 let u_stride = plane_stride(self.frame.linesize[1], chroma_width)?;
3326 let v_stride = plane_stride(self.frame.linesize[2], chroma_width)?;
3327 if u_stride != v_stride {
3328 return Err(BridgeError::InvalidInput("mismatched chroma strides"));
3329 }
3330 let y = unsafe { writable_plane(self.frame.data[0], y_stride, self.width, self.height)? };
3335 let u =
3337 unsafe { writable_plane(self.frame.data[1], u_stride, chroma_width, chroma_height)? };
3338 let v =
3340 unsafe { writable_plane(self.frame.data[2], v_stride, chroma_width, chroma_height)? };
3341 Ok(Yuv420pFramePlanes {
3342 y,
3343 y_stride,
3344 u,
3345 v,
3346 chroma_stride: u_stride,
3347 })
3348 }
3349
3350 pub fn set_presentation(&mut self, pts: i64, time_base: ffi::AVRational) {
3352 self.frame.set_pts(pts);
3353 self.frame.set_time_base(time_base);
3354 }
3355
3356 #[must_use]
3358 pub const fn frame(&self) -> &AVFrame {
3359 &self.frame
3360 }
3361
3362 #[must_use]
3364 pub const fn size(&self) -> (usize, usize) {
3365 (self.width, self.height)
3366 }
3367}
3368
3369fn zeroed_yuv420p_frame(width: i32, height: i32) -> Result<AVFrame, BridgeError> {
3371 let mut frame = AVFrame::new();
3372 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
3373 frame.set_width(width);
3374 frame.set_height(height);
3375 frame.get_buffer(1)?;
3376 for buffer in frame.buf {
3377 if let Some(buffer) = unsafe { buffer.as_ref() } {
3380 unsafe { ptr::write_bytes(buffer.data, 0, buffer.size) };
3382 }
3383 }
3384 Ok(frame)
3385}
3386
3387impl fmt::Debug for Yuv420pFrameBuffer {
3388 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3389 formatter
3390 .debug_struct("Yuv420pFrameBuffer")
3391 .field("width", &self.width)
3392 .field("height", &self.height)
3393 .finish_non_exhaustive()
3394 }
3395}
3396
3397fn plane_stride(linesize: i32, width: usize) -> Result<usize, BridgeError> {
3398 let stride = usize::try_from(linesize)
3399 .map_err(|_| BridgeError::InvalidInput("negative frame linesize"))?;
3400 if stride < width {
3401 return Err(BridgeError::InvalidInput(
3402 "frame linesize is below its width",
3403 ));
3404 }
3405 Ok(stride)
3406}
3407
3408unsafe fn writable_plane<'a>(
3412 data: *mut u8,
3413 stride: usize,
3414 width: usize,
3415 height: usize,
3416) -> Result<&'a mut [u8], BridgeError> {
3417 if data.is_null() {
3418 return Err(BridgeError::InvalidInput("frame plane is null"));
3419 }
3420 let len = stride
3421 .checked_mul(height.saturating_sub(1))
3422 .and_then(|rows| rows.checked_add(width))
3423 .ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
3424 Ok(unsafe { slice::from_raw_parts_mut(data, len) })
3426}
3427
3428pub fn exit_without_handlers(code: u8) -> ! {
3440 #[cfg(unix)]
3441 {
3442 unsafe extern "C" {
3443 fn _exit(status: std::ffi::c_int) -> !;
3444 }
3445 unsafe { _exit(std::ffi::c_int::from(code)) }
3447 }
3448 #[cfg(windows)]
3449 {
3450 #[link(name = "kernel32")]
3451 unsafe extern "system" {
3452 fn GetCurrentProcess() -> *mut std::ffi::c_void;
3453 fn TerminateProcess(process: *mut std::ffi::c_void, exit_code: u32) -> i32;
3454 }
3455 if unsafe { TerminateProcess(GetCurrentProcess(), u32::from(code)) } != 0 {
3458 loop {
3460 std::thread::park();
3461 }
3462 }
3463 std::process::exit(i32::from(code))
3465 }
3466 #[cfg(not(any(unix, windows)))]
3467 std::process::exit(i32::from(code))
3468}
3469
3470#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3471mod vulkan_shim_tests;
3472
3473#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3474mod vulkan_access_tests;
3475
3476#[cfg(test)]
3477mod tests;