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 mut dictionary = options.into_raw().as_ptr();
1783 let status = unsafe {
1787 ffi::avcodec_open2(
1788 context.as_mut_ptr(),
1789 ptr::null_mut(),
1790 std::ptr::addr_of_mut!(dictionary),
1791 )
1792 };
1793 let unused = ptr::NonNull::new(dictionary)
1796 .map(|dictionary| unsafe { rsmpeg::avutil::AVDictionary::from_raw(dictionary) });
1797 if status < 0 {
1798 return Err(RsmpegError::from(status));
1799 }
1800 Ok(unused)
1801}
1802
1803#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1809pub const VULKAN_ENCODE_INPUT_USAGE: u32 = 0x0000_0001 | 0x0000_0002 | 0x0000_0004 | 0x0000_4000;
1810
1811#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1829pub fn vulkan_frames_add_usage(
1830 frames: &mut rsmpeg::avutil::AVHWFramesContext,
1831 usage: u32,
1832) -> Result<(), BridgeError> {
1833 let status = unsafe { alpha2_vulkan_frames_add_usage(frames.as_mut_ptr(), usage) };
1836 if status < 0 {
1837 return Err(BridgeError::FfmpegCode(status));
1838 }
1839 Ok(())
1840}
1841
1842#[cfg(all(target_os = "linux", feature = "hwdevice"))]
1849#[must_use]
1850pub fn vulkan_frames_usage(frames: &rsmpeg::avutil::AVHWFramesContext) -> u32 {
1851 unsafe { alpha2_vulkan_frames_usage(frames.as_ptr()) }
1854}
1855
1856#[must_use]
1861pub fn packet_bytes(packet: &rsmpeg::avcodec::AVPacket) -> Vec<u8> {
1862 let size = usize::try_from(packet.size).unwrap_or(0);
1863 if packet.data.is_null() || size == 0 {
1864 return Vec::new();
1865 }
1866 unsafe { slice::from_raw_parts(packet.data.cast_const(), size) }.to_vec()
1869}
1870
1871#[must_use]
1885pub fn codec_parameters_extradata(parameters: &rsmpeg::avcodec::AVCodecParameters) -> Vec<u8> {
1886 let size = usize::try_from(parameters.extradata_size).unwrap_or(0);
1887 if parameters.extradata.is_null() || size == 0 {
1888 return Vec::new();
1889 }
1890 unsafe { slice::from_raw_parts(parameters.extradata.cast_const(), size) }.to_vec()
1893}
1894
1895#[must_use]
1899pub fn packet_has_side_data(
1900 packet: &rsmpeg::avcodec::AVPacket,
1901 kind: ffi::AVPacketSideDataType,
1902) -> bool {
1903 let mut size = 0_usize;
1904 !unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) }.is_null()
1907}
1908
1909#[must_use]
1918pub fn take_packet_side_data(
1919 packet: &mut rsmpeg::avcodec::AVPacket,
1920 kind: ffi::AVPacketSideDataType,
1921) -> Option<Vec<u8>> {
1922 let mut size = 0_usize;
1923 let data = unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) };
1926 if data.is_null() {
1927 return None;
1928 }
1929 let bytes = unsafe { slice::from_raw_parts(data, size) }.to_vec();
1931 let packet = packet.as_mut_ptr();
1932 unsafe {
1936 ffi::av_packet_side_data_remove(
1937 (*packet).side_data,
1938 &raw mut (*packet).side_data_elems,
1939 kind,
1940 );
1941 }
1942 Some(bytes)
1943}
1944
1945pub fn set_packet_side_data(
1951 packet: &mut rsmpeg::avcodec::AVPacket,
1952 kind: ffi::AVPacketSideDataType,
1953 data: &[u8],
1954) -> Result<(), BridgeError> {
1955 let _ = take_packet_side_data(packet, kind);
1956 let entry = unsafe { ffi::av_packet_new_side_data(packet.as_mut_ptr(), kind, data.len()) };
1959 if entry.is_null() {
1960 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
1961 }
1962 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), entry, data.len()) };
1964 Ok(())
1965}
1966
1967pub fn set_output_stream_extradata(
1982 output: &mut rsmpeg::avformat::AVFormatContextOutput,
1983 index: usize,
1984 data: &[u8],
1985) -> Result<(), BridgeError> {
1986 let parameters = output
1987 .streams()
1988 .get(index)
1989 .ok_or(BridgeError::InvalidInput("the output has no such stream"))?
1990 .codecpar;
1991 let size = i32::try_from(data.len())
1992 .map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
1993 let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
1994 let buffer = unsafe { ffi::av_mallocz(data.len() + padding) }.cast::<u8>();
1996 if buffer.is_null() {
1997 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
1998 }
1999 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), buffer, data.len()) };
2001 unsafe {
2005 ffi::av_freep((&raw mut (*parameters).extradata).cast());
2006 (*parameters).extradata = buffer;
2007 (*parameters).extradata_size = size;
2008 }
2009 Ok(())
2010}
2011
2012pub fn packet_from_bytes(data: &[u8]) -> Result<rsmpeg::avcodec::AVPacket, BridgeError> {
2022 let size = i32::try_from(data.len())
2023 .map_err(|_| BridgeError::InvalidInput("a packet is larger than FFmpeg holds"))?;
2024 let mut packet = rsmpeg::avcodec::AVPacket::new();
2025 let status = unsafe { ffi::av_new_packet(packet.as_mut_ptr(), size) };
2028 if status < 0 {
2029 return Err(BridgeError::FfmpegCode(status));
2030 }
2031 if !data.is_empty() {
2032 unsafe { ptr::copy_nonoverlapping(data.as_ptr(), packet.data, data.len()) };
2034 }
2035 Ok(packet)
2036}
2037
2038#[derive(Clone, Copy, Debug)]
2042pub struct VideoStreamDescription<'a> {
2043 pub codec_id: ffi::AVCodecID,
2045 pub width: i32,
2047 pub height: i32,
2049 pub pixel_format: ffi::AVPixelFormat,
2051 pub profile: i32,
2053 pub level: i32,
2055 pub color_primaries: ffi::AVColorPrimaries,
2057 pub color_trc: ffi::AVColorTransferCharacteristic,
2059 pub color_space: ffi::AVColorSpace,
2061 pub color_range: ffi::AVColorRange,
2063 pub sample_aspect_ratio: ffi::AVRational,
2065 pub frame_rate: ffi::AVRational,
2067 pub bit_rate: i64,
2069 pub extradata: &'a [u8],
2071}
2072
2073pub fn video_codec_parameters(
2078 description: &VideoStreamDescription<'_>,
2079) -> Result<rsmpeg::avcodec::AVCodecParameters, BridgeError> {
2080 let extradata_size = i32::try_from(description.extradata.len())
2081 .map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
2082 let mut parameters = rsmpeg::avcodec::AVCodecParameters::new();
2083 let raw = parameters.as_mut_ptr();
2084 unsafe {
2087 (*raw).codec_type = ffi::AVMEDIA_TYPE_VIDEO;
2088 (*raw).codec_id = description.codec_id;
2089 (*raw).width = description.width;
2090 (*raw).height = description.height;
2091 (*raw).format = description.pixel_format;
2092 (*raw).profile = description.profile;
2093 (*raw).level = description.level;
2094 (*raw).color_primaries = description.color_primaries;
2095 (*raw).color_trc = description.color_trc;
2096 (*raw).color_space = description.color_space;
2097 (*raw).color_range = description.color_range;
2098 (*raw).sample_aspect_ratio = description.sample_aspect_ratio;
2099 (*raw).framerate = description.frame_rate;
2100 (*raw).bit_rate = description.bit_rate;
2101 (*raw).field_order = ffi::AV_FIELD_PROGRESSIVE;
2102 }
2103 if !description.extradata.is_empty() {
2104 let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
2105 let buffer = unsafe { ffi::av_mallocz(description.extradata.len() + padding) }.cast::<u8>();
2107 if buffer.is_null() {
2108 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2109 }
2110 unsafe {
2114 ptr::copy_nonoverlapping(
2115 description.extradata.as_ptr(),
2116 buffer,
2117 description.extradata.len(),
2118 );
2119 (*raw).extradata = buffer;
2120 (*raw).extradata_size = extradata_size;
2121 }
2122 }
2123 Ok(parameters)
2124}
2125
2126pub fn bitstream_filter_list(
2140 spec: &CStr,
2141 parameters: &AVCodecParameters,
2142 time_base: rsmpeg::avutil::AVRational,
2143) -> Result<rsmpeg::avcodec::AVBSFContext, BridgeError> {
2144 let mut raw = ptr::null_mut();
2145 let status = unsafe { ffi::av_bsf_list_parse_str(spec.as_ptr(), &raw mut raw) };
2148 if status < 0 {
2149 return Err(BridgeError::FfmpegCode(status));
2150 }
2151 let raw = ptr::NonNull::new(raw).ok_or(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)))?;
2152 let mut context = unsafe { rsmpeg::avcodec::AVBSFContextUninit::from_raw(raw) };
2155 let status = unsafe { ffi::avcodec_parameters_copy(context.par_in, parameters.as_ptr()) };
2159 if status < 0 {
2160 return Err(BridgeError::FfmpegCode(status));
2161 }
2162 context.set_time_base_in(time_base);
2163 context.init().map_err(BridgeError::Ffmpeg)
2164}
2165
2166pub fn set_stream_codec_tag(stream: &mut rsmpeg::avformat::AVStreamMut<'_>, tag: u32) {
2169 let mut parameters = stream.codecpar_mut();
2170 unsafe { (*parameters.as_mut_ptr()).codec_tag = tag };
2173}
2174
2175pub fn set_stream_display_matrix(
2183 stream: &mut rsmpeg::avformat::AVStreamMut<'_>,
2184 display: DisplayRotation,
2185) -> Result<(), BridgeError> {
2186 let mut parameters = stream.codecpar_mut();
2187 let parameters = parameters.as_mut_ptr();
2188 let entry = unsafe {
2192 ffi::av_packet_side_data_new(
2193 &raw mut (*parameters).coded_side_data,
2194 &raw mut (*parameters).nb_coded_side_data,
2195 ffi::AV_PKT_DATA_DISPLAYMATRIX,
2196 size_of::<[i32; 9]>(),
2197 0,
2198 )
2199 };
2200 if entry.is_null() {
2201 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2202 }
2203 let mut matrix = [0_i32; 9];
2204 unsafe {
2208 ffi::av_display_rotation_set(matrix.as_mut_ptr(), -display.counter_clockwise_degrees);
2209 if display.flipped {
2210 ffi::av_display_matrix_flip(matrix.as_mut_ptr(), 1, 0);
2211 }
2212 }
2213 unsafe {
2216 ptr::copy_nonoverlapping(
2217 matrix.as_ptr().cast::<u8>(),
2218 (*entry).data,
2219 size_of::<[i32; 9]>(),
2220 );
2221 }
2222 Ok(())
2223}
2224
2225#[must_use]
2227pub fn hardware_device_type_name(device_type: ffi::AVHWDeviceType) -> Option<String> {
2228 let name = unsafe { ffi::av_hwdevice_get_type_name(device_type) };
2231 if name.is_null() {
2232 None
2233 } else {
2234 Some(
2236 unsafe { CStr::from_ptr(name) }
2237 .to_string_lossy()
2238 .into_owned(),
2239 )
2240 }
2241}
2242
2243#[must_use]
2253pub fn yuv_layout(format: ffi::AVPixelFormat) -> Option<YuvPlaneLayout> {
2254 if format == ffi::AV_PIX_FMT_NONE {
2255 return None;
2256 }
2257 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
2260 let foreign_endian = if cfg!(target_endian = "little") {
2261 ffi::AV_PIX_FMT_FLAG_BE
2262 } else {
2263 0
2264 };
2265 let rejected = foreign_endian
2266 | ffi::AV_PIX_FMT_FLAG_PAL
2267 | ffi::AV_PIX_FMT_FLAG_BITSTREAM
2268 | ffi::AV_PIX_FMT_FLAG_HWACCEL
2269 | ffi::AV_PIX_FMT_FLAG_RGB
2270 | ffi::AV_PIX_FMT_FLAG_ALPHA
2271 | ffi::AV_PIX_FMT_FLAG_BAYER
2272 | ffi::AV_PIX_FMT_FLAG_FLOAT
2273 | ffi::AV_PIX_FMT_FLAG_XYZ;
2274 if descriptor.flags & u64::from(rejected) != 0 || descriptor.nb_components != 3 {
2275 return None;
2276 }
2277 let [luma, u, v, _] = descriptor.comp;
2278 let depth = luma.depth;
2279 if !(8..=16).contains(&depth) || u.depth != depth || v.depth != depth {
2280 return None;
2281 }
2282 if descriptor.log2_chroma_w > 2 || descriptor.log2_chroma_h > 2 {
2283 return None;
2284 }
2285 let bytes = if depth > 8 { 2 } else { 1 };
2286 let shift = luma.shift;
2287 let msb_aligned = match shift {
2288 0 => false,
2289 _ if depth > 8 && shift == 16 - depth => true,
2290 _ => return None,
2291 };
2292 let planar = u.plane == 1
2293 && v.plane == 2
2294 && u.step == bytes
2295 && v.step == bytes
2296 && u.offset == 0
2297 && v.offset == 0;
2298 let interleaved = u.plane == 1
2299 && v.plane == 1
2300 && u.step == 2 * bytes
2301 && v.step == 2 * bytes
2302 && u.offset == 0
2303 && v.offset == bytes;
2304 if luma.plane != 0
2305 || luma.step != bytes
2306 || luma.offset != 0
2307 || u.shift != shift
2308 || v.shift != shift
2309 || !(planar || interleaved)
2310 {
2311 return None;
2312 }
2313 Some(YuvPlaneLayout {
2314 chroma_shift_x: descriptor.log2_chroma_w,
2315 chroma_shift_y: descriptor.log2_chroma_h,
2316 bit_depth: u8::try_from(depth).ok()?,
2317 interleaved_chroma: interleaved,
2318 msb_aligned,
2319 })
2320}
2321
2322pub fn yuv_planes(frame: &AVFrame) -> Result<Option<YuvPlanes>, BridgeError> {
2336 frame_yuv_planes(frame, false)
2337}
2338
2339pub struct DecodedFrame {
2347 frame: AVFrame,
2348 written: bool,
2350}
2351
2352impl DecodedFrame {
2353 pub fn receive(decoder: &mut AVCodecContext) -> Result<Self, RsmpegError> {
2360 let written = decoder_frames_are_initialized(decoder);
2361 let frame = decoder.receive_frame()?;
2362 Ok(Self { frame, written })
2363 }
2364
2365 pub fn yuv_planes(&self) -> Result<Option<YuvPlanes>, BridgeError> {
2376 frame_yuv_planes(&self.frame, self.written)
2377 }
2378
2379 pub fn set_pts(&mut self, pts: i64) {
2381 self.frame.set_pts(pts);
2382 }
2383
2384 #[must_use]
2386 pub fn into_frame(self) -> AVFrame {
2387 self.frame
2388 }
2389}
2390
2391impl std::ops::Deref for DecodedFrame {
2392 type Target = AVFrame;
2393
2394 fn deref(&self) -> &AVFrame {
2395 &self.frame
2396 }
2397}
2398
2399impl fmt::Debug for DecodedFrame {
2400 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2401 formatter
2402 .debug_struct("DecodedFrame")
2403 .field("format", &self.frame.format)
2404 .field("width", &self.frame.width)
2405 .field("height", &self.frame.height)
2406 .field("pts", &self.frame.pts)
2407 .field("written", &self.written)
2408 .finish_non_exhaustive()
2409 }
2410}
2411
2412fn decoder_frames_are_initialized(decoder: &AVCodecContext) -> bool {
2422 static DEFAULT_GET_BUFFER2: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2423 let default = *DEFAULT_GET_BUFFER2.get_or_init(|| {
2426 unsafe {
2428 let mut context = ffi::avcodec_alloc_context3(ptr::null());
2429 let get_buffer2 = context
2430 .as_ref()
2431 .and_then(|context| context.get_buffer2)
2432 .map_or(0, |function| function as usize);
2433 ffi::avcodec_free_context(&raw mut context);
2434 get_buffer2
2435 }
2436 });
2437 let wrapped =
2439 unsafe { decoder.codec.as_ref() }.is_none_or(|codec| !codec.wrapper_name.is_null());
2440 let get_buffer2 = decoder.get_buffer2.map_or(0, |function| function as usize);
2441 !wrapped && default != 0 && get_buffer2 == default
2442}
2443
2444fn frame_yuv_planes(frame: &AVFrame, share: bool) -> Result<Option<YuvPlanes>, BridgeError> {
2447 let Some(layout) = yuv_layout(frame.format) else {
2448 return Ok(None);
2449 };
2450 let width =
2451 u32::try_from(frame.width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2452 let height = u32::try_from(frame.height)
2453 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2454 if width == 0 || height == 0 {
2455 return Err(BridgeError::InvalidInput("empty YUV frame"));
2456 }
2457 let bytes = layout.bytes_per_sample();
2458 let chroma_width = width.div_ceil(1 << layout.chroma_shift_x);
2459 let chroma_height = height.div_ceil(1 << layout.chroma_shift_y);
2460 let shared =
2463 (share && !frame.buf[0].is_null()).then(|| std::sync::Arc::new(SharedFrame(frame.clone())));
2464 let copy = |index: usize,
2465 samples: u32,
2466 pairs: u32,
2467 rows: u32,
2468 context: &'static str|
2469 -> Result<FramePlane, BridgeError> {
2470 let row_bytes = samples
2471 .checked_mul(bytes)
2472 .and_then(|row| row.checked_mul(pairs))
2473 .ok_or(BridgeError::InvalidInput(context))?;
2474 if let Some(view) = shared
2475 .as_ref()
2476 .and_then(|frame| PlaneView::new(frame, index, row_bytes, rows))
2477 {
2478 let stride =
2479 u32::try_from(view.stride).map_err(|_| BridgeError::InvalidInput(context))?;
2480 return Ok(FramePlane {
2481 bytes: bytes::Bytes::from_owner(view),
2482 stride,
2483 width: samples,
2484 height: rows,
2485 });
2486 }
2487 Ok(FramePlane {
2488 bytes: copy_video_plane(
2489 frame.data[index],
2490 frame.linesize[index],
2491 row_bytes,
2492 rows,
2493 context,
2494 )?
2495 .into(),
2496 stride: row_bytes,
2497 width: samples,
2498 height: rows,
2499 })
2500 };
2501 let mut planes = vec![copy(0, width, 1, height, "invalid Y plane")?];
2502 if layout.interleaved_chroma {
2503 planes.push(copy(1, chroma_width, 2, chroma_height, "invalid UV plane")?);
2504 } else {
2505 planes.push(copy(1, chroma_width, 1, chroma_height, "invalid U plane")?);
2506 planes.push(copy(2, chroma_width, 1, chroma_height, "invalid V plane")?);
2507 }
2508 Ok(Some(YuvPlanes {
2509 layout,
2510 width,
2511 height,
2512 planes,
2513 }))
2514}
2515
2516struct SharedFrame(AVFrame);
2518
2519unsafe impl Sync for SharedFrame {}
2523
2524struct PlaneView {
2527 frame: std::sync::Arc<SharedFrame>,
2528 data: *const u8,
2529 len: usize,
2530 stride: usize,
2531}
2532
2533unsafe impl Send for PlaneView {}
2536
2537impl PlaneView {
2538 fn new(
2542 frame: &std::sync::Arc<SharedFrame>,
2543 index: usize,
2544 row_bytes: u32,
2545 rows: u32,
2546 ) -> Option<Self> {
2547 let inner = &frame.0;
2548 let data = inner.data[index].cast_const();
2549 let stride = usize::try_from(inner.linesize[index]).ok()?;
2550 let row_bytes = usize::try_from(row_bytes).ok()?;
2551 if data.is_null() || stride < row_bytes || rows == 0 {
2552 return None;
2553 }
2554 let len = stride.checked_mul(usize::try_from(rows).ok()?)?;
2555 let start = data as usize;
2556 let end = start.checked_add(len)?;
2557 let inside = inner.buf.iter().any(|buffer| {
2558 let Some(buffer) = (unsafe { buffer.as_ref() }) else {
2561 return false;
2562 };
2563 let buffer_start = buffer.data as usize;
2564 buffer_start
2565 .checked_add(buffer.size)
2566 .is_some_and(|buffer_end| buffer_start <= start && end <= buffer_end)
2567 });
2568 inside.then(|| Self {
2569 frame: std::sync::Arc::clone(frame),
2570 data,
2571 len,
2572 stride,
2573 })
2574 }
2575}
2576
2577impl AsRef<[u8]> for PlaneView {
2578 fn as_ref(&self) -> &[u8] {
2579 let _ = &self.frame;
2581 unsafe { slice::from_raw_parts(self.data, self.len) }
2584 }
2585}
2586
2587fn copy_video_plane(
2588 data: *const u8,
2589 linesize: i32,
2590 width: u32,
2591 height: u32,
2592 context: &'static str,
2593) -> Result<Vec<u8>, BridgeError> {
2594 let row_bytes = usize::try_from(width).map_err(|_| BridgeError::InvalidInput(context))?;
2595 let rows = usize::try_from(height).map_err(|_| BridgeError::InvalidInput(context))?;
2596 let stride =
2597 usize::try_from(linesize.unsigned_abs()).map_err(|_| BridgeError::InvalidInput(context))?;
2598 if data.is_null() || linesize == 0 || stride < row_bytes {
2599 return Err(BridgeError::InvalidInput(context));
2600 }
2601 let byte_len = row_bytes
2602 .checked_mul(rows)
2603 .ok_or(BridgeError::InvalidInput(context))?;
2604 let mut output = Vec::with_capacity(byte_len);
2607 let signed_stride =
2608 isize::try_from(linesize).map_err(|_| BridgeError::InvalidInput(context))?;
2609 for row in 0..rows {
2610 let row = isize::try_from(row).map_err(|_| BridgeError::InvalidInput(context))?;
2611 let offset = row
2612 .checked_mul(signed_stride)
2613 .ok_or(BridgeError::InvalidInput(context))?;
2614 let source = unsafe { slice::from_raw_parts(data.offset(offset), row_bytes) };
2618 output.extend_from_slice(source);
2619 }
2620 debug_assert_eq!(output.len(), byte_len);
2621 Ok(output)
2622}
2623
2624pub fn rgba_frame(bytes: &[u8], width: i32, height: i32) -> Result<AVFrame, BridgeError> {
2629 let row_bytes = usize::try_from(width)
2630 .ok()
2631 .and_then(|value| value.checked_mul(4))
2632 .ok_or(BridgeError::InvalidInput("RGBA row width overflow"))?;
2633 let rows =
2634 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("negative RGBA height"))?;
2635 let expected = row_bytes
2636 .checked_mul(rows)
2637 .ok_or(BridgeError::InvalidInput("RGBA frame size overflow"))?;
2638 if bytes.len() != expected {
2639 return Err(BridgeError::InvalidInput("RGBA byte length mismatch"));
2640 }
2641
2642 let mut frame = AVFrame::new();
2643 frame.set_format(ffi::AV_PIX_FMT_RGBA);
2644 frame.set_width(width);
2645 frame.set_height(height);
2646 frame.get_buffer(1)?;
2647 frame.make_writable()?;
2648 let linesize = usize::try_from(frame.linesize[0])
2649 .map_err(|_| BridgeError::InvalidInput("negative RGBA linesize"))?;
2650 if frame.data[0].is_null() || linesize < row_bytes {
2651 return Err(BridgeError::InvalidInput(
2652 "RGBA destination plane is too small",
2653 ));
2654 }
2655
2656 unsafe {
2659 for row in 0..rows {
2660 let destination_offset = row.checked_mul(linesize).ok_or(BridgeError::InvalidInput(
2661 "RGBA destination offset overflow",
2662 ))?;
2663 let source_offset = row
2664 .checked_mul(row_bytes)
2665 .ok_or(BridgeError::InvalidInput("RGBA source offset overflow"))?;
2666 let destination =
2667 slice::from_raw_parts_mut(frame.data[0].add(destination_offset), row_bytes);
2668 destination.copy_from_slice(&bytes[source_offset..source_offset + row_bytes]);
2669 }
2670 }
2671 Ok(frame)
2672}
2673
2674#[allow(clippy::too_many_arguments)]
2680pub fn yuv420p_frame(
2681 y: &[u8],
2682 y_stride: u32,
2683 u: &[u8],
2684 u_stride: u32,
2685 v: &[u8],
2686 v_stride: u32,
2687 width: i32,
2688 height: i32,
2689) -> Result<AVFrame, BridgeError> {
2690 let width =
2691 u32::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2692 let height =
2693 u32::try_from(height).map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2694 if width == 0 || height == 0 {
2695 return Err(BridgeError::InvalidInput("empty YUV frame"));
2696 }
2697 let chroma_width = width.div_ceil(2);
2698 let chroma_height = height.div_ceil(2);
2699 validate_source_plane(y, y_stride, width, height, "invalid Y source plane")?;
2700 validate_source_plane(
2701 u,
2702 u_stride,
2703 chroma_width,
2704 chroma_height,
2705 "invalid U source plane",
2706 )?;
2707 validate_source_plane(
2708 v,
2709 v_stride,
2710 chroma_width,
2711 chroma_height,
2712 "invalid V source plane",
2713 )?;
2714
2715 let mut frame = AVFrame::new();
2716 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
2717 frame.set_width(i32::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV width"))?);
2718 frame.set_height(i32::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV height"))?);
2719 frame.get_buffer(1)?;
2720 frame.make_writable()?;
2721 copy_plane_to_frame(y, y_stride, width, height, frame.data[0], frame.linesize[0])?;
2722 copy_plane_to_frame(
2723 u,
2724 u_stride,
2725 chroma_width,
2726 chroma_height,
2727 frame.data[1],
2728 frame.linesize[1],
2729 )?;
2730 copy_plane_to_frame(
2731 v,
2732 v_stride,
2733 chroma_width,
2734 chroma_height,
2735 frame.data[2],
2736 frame.linesize[2],
2737 )?;
2738 Ok(frame)
2739}
2740
2741#[must_use]
2745pub fn yuv_pixel_format(layout: YuvPlaneLayout) -> Option<ffi::AVPixelFormat> {
2746 let mut descriptor = ptr::null();
2747 loop {
2748 descriptor = unsafe { ffi::av_pix_fmt_desc_next(descriptor) };
2751 if descriptor.is_null() {
2752 return None;
2753 }
2754 let format = unsafe { ffi::av_pix_fmt_desc_get_id(descriptor) };
2756 if yuv_layout(format) == Some(layout) {
2757 return Some(format);
2758 }
2759 }
2760}
2761
2762pub fn planar_frame(
2769 format: ffi::AVPixelFormat,
2770 planes: &[(&[u8], u32)],
2771 width: u32,
2772 height: u32,
2773) -> Result<AVFrame, BridgeError> {
2774 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
2777 .ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
2778 let count = unsafe { ffi::av_pix_fmt_count_planes(format) };
2780 if usize::try_from(count).ok() != Some(planes.len()) || width == 0 || height == 0 {
2781 return Err(BridgeError::InvalidInput(
2782 "planes do not match the pixel format",
2783 ));
2784 }
2785 let signed = |value: u32| {
2786 i32::try_from(value).map_err(|_| BridgeError::InvalidInput("frame size overflow"))
2787 };
2788 let mut frame = AVFrame::new();
2789 frame.set_format(format);
2790 frame.set_width(signed(width)?);
2791 frame.set_height(signed(height)?);
2792 frame.get_buffer(1)?;
2793 frame.make_writable()?;
2794 for (index, (bytes, stride)) in planes.iter().enumerate() {
2795 let plane = i32::try_from(index).unwrap_or(i32::MAX);
2796 let row_bytes = unsafe { ffi::av_image_get_linesize(format, signed(width)?, plane) };
2798 let row_bytes = u32::try_from(row_bytes).map_err(|_| BridgeError::FfmpegCode(row_bytes))?;
2799 let rows = if index == 0 || index == 3 {
2800 height
2801 } else {
2802 height.div_ceil(1 << descriptor.log2_chroma_h)
2803 };
2804 validate_source_plane(bytes, *stride, row_bytes, rows, "invalid source plane")?;
2805 copy_plane_to_frame(
2806 bytes,
2807 *stride,
2808 row_bytes,
2809 rows,
2810 frame.data[index],
2811 frame.linesize[index],
2812 )?;
2813 }
2814 Ok(frame)
2815}
2816
2817fn validate_source_plane(
2818 bytes: &[u8],
2819 stride: u32,
2820 width: u32,
2821 height: u32,
2822 context: &'static str,
2823) -> Result<(), BridgeError> {
2824 let required = usize::try_from(stride).ok().and_then(|stride| {
2825 usize::try_from(height)
2826 .ok()
2827 .and_then(|height| stride.checked_mul(height))
2828 });
2829 if stride < width || required != Some(bytes.len()) {
2830 Err(BridgeError::InvalidInput(context))
2831 } else {
2832 Ok(())
2833 }
2834}
2835
2836fn copy_plane_to_frame(
2837 source: &[u8],
2838 source_stride: u32,
2839 width: u32,
2840 height: u32,
2841 destination: *mut u8,
2842 destination_stride: i32,
2843) -> Result<(), BridgeError> {
2844 let row_bytes =
2845 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV row width overflow"))?;
2846 let rows =
2847 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV row count overflow"))?;
2848 let source_stride = usize::try_from(source_stride)
2849 .map_err(|_| BridgeError::InvalidInput("YUV source stride overflow"))?;
2850 let destination_stride = usize::try_from(destination_stride)
2851 .map_err(|_| BridgeError::InvalidInput("negative YUV destination stride"))?;
2852 if destination.is_null() || destination_stride < row_bytes {
2853 return Err(BridgeError::InvalidInput(
2854 "YUV destination plane is too small",
2855 ));
2856 }
2857 for row in 0..rows {
2858 let source_offset = row
2859 .checked_mul(source_stride)
2860 .ok_or(BridgeError::InvalidInput("YUV source offset overflow"))?;
2861 let destination_offset = row
2862 .checked_mul(destination_stride)
2863 .ok_or(BridgeError::InvalidInput("YUV destination offset overflow"))?;
2864 unsafe {
2868 ptr::copy_nonoverlapping(
2869 source.as_ptr().add(source_offset),
2870 destination.add(destination_offset),
2871 row_bytes,
2872 );
2873 }
2874 }
2875 Ok(())
2876}
2877
2878pub fn audio_frame(
2887 channels: &[Vec<f32>],
2888 format: ffi::AVSampleFormat,
2889 sample_rate: i32,
2890 pts: i64,
2891) -> Result<AVFrame, BridgeError> {
2892 let Some(samples) = channels.first().map(Vec::len) else {
2893 return Err(BridgeError::InvalidInput("invalid planar audio shape"));
2894 };
2895 if sample_rate <= 0
2896 || samples == 0
2897 || channels.len() > ffi::AV_NUM_DATA_POINTERS as usize
2898 || channels.iter().any(|channel| channel.len() != samples)
2899 {
2900 return Err(BridgeError::InvalidInput(
2901 "planar audio channel lengths differ",
2902 ));
2903 }
2904 let sample_bytes = match format {
2905 ffi::AV_SAMPLE_FMT_FLTP | ffi::AV_SAMPLE_FMT_FLT => size_of::<f32>(),
2906 ffi::AV_SAMPLE_FMT_S16 => size_of::<i16>(),
2907 _ => return Err(BridgeError::InvalidInput("unsupported audio sample format")),
2908 };
2909 let planar = format == ffi::AV_SAMPLE_FMT_FLTP;
2910 let channel_count = i32::try_from(channels.len())
2911 .map_err(|_| BridgeError::InvalidInput("audio channel count overflow"))?;
2912 let sample_count = i32::try_from(samples)
2913 .map_err(|_| BridgeError::InvalidInput("audio sample count overflow"))?;
2914 let mut frame = AVFrame::new();
2915 frame.set_format(format);
2916 frame.set_ch_layout(
2917 rsmpeg::avutil::AVChannelLayout::from_nb_channels(channel_count).into_inner(),
2918 );
2919 frame.set_sample_rate(sample_rate);
2920 frame.set_nb_samples(sample_count);
2921 frame.set_pts(pts);
2922 frame.set_time_base(ffi::AVRational {
2923 num: 1,
2924 den: sample_rate,
2925 });
2926 frame.get_buffer(0)?;
2927 frame.make_writable()?;
2928 let plane_bytes = samples
2929 .checked_mul(sample_bytes)
2930 .and_then(|bytes| bytes.checked_mul(if planar { 1 } else { channels.len() }))
2931 .ok_or(BridgeError::InvalidInput("audio plane byte count overflow"))?;
2932 let plane_capacity = usize::try_from(frame.linesize[0]).unwrap_or(0);
2933 if plane_capacity < plane_bytes {
2934 return Err(BridgeError::InvalidInput(
2935 "audio plane capacity is too small",
2936 ));
2937 }
2938 let planes = if planar { channels.len() } else { 1 };
2939 if frame.data[..planes].iter().any(|plane| plane.is_null()) {
2940 return Err(BridgeError::InvalidInput("audio plane pointer is null"));
2941 }
2942 if planar {
2943 for (index, source) in channels.iter().enumerate() {
2944 unsafe {
2948 ptr::copy_nonoverlapping(
2949 source.as_ptr().cast::<u8>(),
2950 frame.data[index],
2951 plane_bytes,
2952 );
2953 }
2954 }
2955 return Ok(frame);
2956 }
2957 let plane = unsafe { std::slice::from_raw_parts_mut(frame.data[0], plane_bytes) };
2960 let interleaved = (0..samples).flat_map(|sample| channels.iter().map(move |c| c[sample]));
2961 if format == ffi::AV_SAMPLE_FMT_FLT {
2962 for (bytes, value) in plane.as_chunks_mut::<4>().0.iter_mut().zip(interleaved) {
2963 *bytes = value.to_ne_bytes();
2964 }
2965 } else {
2966 for (bytes, value) in plane.as_chunks_mut::<2>().0.iter_mut().zip(interleaved) {
2967 #[allow(clippy::cast_possible_truncation)]
2969 let value = (value.clamp(-1.0, 1.0) * 32_767.0).round() as i16;
2970 *bytes = value.to_ne_bytes();
2971 }
2972 }
2973 Ok(frame)
2974}
2975
2976#[must_use]
2995pub fn channel_layout_channels(layout: &rsmpeg::avutil::AVChannelLayout) -> Vec<ffi::AVChannel> {
2996 if layout.order == ffi::AV_CHANNEL_ORDER_UNSPEC {
2997 return Vec::new();
2998 }
2999 let channels = u32::try_from(layout.nb_channels).unwrap_or(0);
3000 (0..channels)
3001 .map(|index| {
3002 layout
3003 .channel_from_index(index)
3004 .unwrap_or(ffi::AV_CHAN_NONE)
3005 })
3006 .collect()
3007}
3008
3009pub fn channel_layout_of(
3016 channels: &[ffi::AVChannel],
3017) -> Result<rsmpeg::avutil::AVChannelLayout, BridgeError> {
3018 let count = i32::try_from(channels.len())
3019 .ok()
3020 .filter(|count| *count > 0)
3021 .ok_or(BridgeError::InvalidInput("a channel layout needs channels"))?;
3022 let mut raw = std::mem::MaybeUninit::<ffi::AVChannelLayout>::zeroed();
3023 let initialized = unsafe { ffi::av_channel_layout_custom_init(raw.as_mut_ptr(), count) };
3026 if initialized < 0 {
3027 return Err(BridgeError::FfmpegCode(initialized));
3028 }
3029 let mut layout = unsafe { rsmpeg::avutil::AVChannelLayout::new(raw.assume_init()) };
3031 for (index, channel) in channels.iter().enumerate() {
3032 unsafe { (*layout.u.map.add(index)).id = *channel };
3034 }
3035 let flags = i32::try_from(ffi::AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL).unwrap_or(0);
3036 let retyped = unsafe {
3038 ffi::av_channel_layout_retype(layout.as_mut_ptr(), ffi::AV_CHANNEL_ORDER_UNSPEC, flags)
3039 };
3040 if retyped < 0 {
3041 return Err(BridgeError::FfmpegCode(retyped));
3042 }
3043 Ok(layout)
3044}
3045
3046pub fn audio_f32_interleaved(frame: &AVFrame) -> Result<Vec<f32>, BridgeError> {
3051 if frame.format != ffi::AV_SAMPLE_FMT_FLT || frame.nb_samples < 0 {
3052 return Err(BridgeError::InvalidInput(
3053 "packed audio format or sample count is invalid",
3054 ));
3055 }
3056 let channels = usize::try_from(frame.ch_layout().nb_channels)
3057 .map_err(|_| BridgeError::InvalidInput("packed audio channel count is negative"))?;
3058 let frames = usize::try_from(frame.nb_samples)
3059 .map_err(|_| BridgeError::InvalidInput("packed audio sample count is negative"))?;
3060 let samples = frames
3061 .checked_mul(channels)
3062 .ok_or(BridgeError::InvalidInput(
3063 "packed audio sample count overflow",
3064 ))?;
3065 let bytes = samples
3066 .checked_mul(size_of::<f32>())
3067 .ok_or(BridgeError::InvalidInput(
3068 "packed audio byte count overflow",
3069 ))?;
3070 if channels == 0
3071 || frame.data[0].is_null()
3072 || usize::try_from(frame.linesize[0]).unwrap_or(0) < bytes
3073 {
3074 return Err(BridgeError::InvalidInput(
3075 "packed audio plane is missing or too small",
3076 ));
3077 }
3078 let mut output = vec![0.0_f32; samples];
3079 unsafe {
3082 ptr::copy_nonoverlapping(frame.data[0], output.as_mut_ptr().cast::<u8>(), bytes);
3083 }
3084 Ok(output)
3085}
3086
3087#[derive(Copy, Clone, Debug, PartialEq)]
3089pub struct DisplayRotation {
3090 pub counter_clockwise_degrees: f64,
3093 pub flipped: bool,
3097}
3098
3099#[must_use]
3101pub fn display_rotation(parameters: &AVCodecParameters) -> Option<DisplayRotation> {
3102 let count = usize::try_from(parameters.nb_coded_side_data).ok()?;
3103 if count == 0 || count > 64 || parameters.coded_side_data.is_null() {
3104 return None;
3105 }
3106 let entries = unsafe { slice::from_raw_parts(parameters.coded_side_data, count) };
3109 let matrix = entries.iter().find(|entry| {
3110 entry.type_ == ffi::AV_PKT_DATA_DISPLAYMATRIX
3111 && entry.size >= 9 * size_of::<i32>()
3112 && !entry.data.is_null()
3113 })?;
3114 unsafe { matrix_rotation(matrix.data) }
3116}
3117
3118#[must_use]
3121pub fn frame_display_rotation(frame: &AVFrame) -> Option<DisplayRotation> {
3122 let entry =
3124 unsafe { ffi::av_frame_get_side_data(frame.as_ptr(), ffi::AV_FRAME_DATA_DISPLAYMATRIX) };
3125 if entry.is_null() {
3126 return None;
3127 }
3128 let entry = unsafe { &*entry };
3130 if entry.size < 9 * size_of::<i32>() || entry.data.is_null() {
3131 return None;
3132 }
3133 unsafe { matrix_rotation(entry.data) }
3135}
3136
3137unsafe fn matrix_rotation(data: *const u8) -> Option<DisplayRotation> {
3140 let mut aligned = [0_i32; 9];
3141 unsafe {
3144 ptr::copy_nonoverlapping(
3145 data,
3146 aligned.as_mut_ptr().cast::<u8>(),
3147 size_of::<[i32; 9]>(),
3148 );
3149 }
3150 let determinant = i64::from(aligned[0]) * i64::from(aligned[4])
3154 - i64::from(aligned[1]) * i64::from(aligned[3]);
3155 let flipped = determinant < 0;
3156 if flipped {
3157 unsafe { ffi::av_display_matrix_flip(aligned.as_mut_ptr(), 1, 0) };
3159 }
3160 let angle = unsafe { ffi::av_display_rotation_get(aligned.as_ptr()) };
3162 angle.is_finite().then_some(DisplayRotation {
3163 counter_clockwise_degrees: angle,
3164 flipped,
3165 })
3166}
3167
3168const fn size_of<T>() -> usize {
3169 std::mem::size_of::<T>()
3170}
3171
3172pub struct Yuv420pFrameBuffer {
3186 frame: AVFrame,
3187 width: usize,
3188 height: usize,
3189}
3190
3191pub struct Yuv420pFramePlanes<'a> {
3193 pub y: &'a mut [u8],
3195 pub y_stride: usize,
3197 pub u: &'a mut [u8],
3199 pub v: &'a mut [u8],
3201 pub chroma_stride: usize,
3203}
3204
3205impl Yuv420pFrameBuffer {
3206 pub fn new(width: i32, height: i32) -> Result<Self, BridgeError> {
3211 let coded_width =
3212 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
3213 let coded_height = usize::try_from(height)
3214 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
3215 if coded_width == 0 || coded_height == 0 {
3216 return Err(BridgeError::InvalidInput("empty YUV frame"));
3217 }
3218 Ok(Self {
3219 frame: zeroed_yuv420p_frame(width, height)?,
3220 width: coded_width,
3221 height: coded_height,
3222 })
3223 }
3224
3225 #[must_use]
3227 pub const fn dimensions(&self) -> (usize, usize) {
3228 (self.width, self.height)
3229 }
3230
3231 pub fn begin_write(&mut self) -> Result<Yuv420pFramePlanes<'_>, BridgeError> {
3237 if !self.frame.is_writable().map_err(BridgeError::Ffmpeg)? {
3241 let mut copy = zeroed_yuv420p_frame(self.frame.width, self.frame.height)?;
3242 let status = unsafe {
3245 let copied = ffi::av_frame_copy(copy.as_mut_ptr(), self.frame.as_ptr());
3246 if copied < 0 {
3247 copied
3248 } else {
3249 ffi::av_frame_copy_props(copy.as_mut_ptr(), self.frame.as_ptr())
3250 }
3251 };
3252 if status < 0 {
3253 return Err(BridgeError::FfmpegCode(status));
3254 }
3255 self.frame = copy;
3256 }
3257 let chroma_width = self.width.div_ceil(2);
3258 let chroma_height = self.height.div_ceil(2);
3259 let y_stride = plane_stride(self.frame.linesize[0], self.width)?;
3260 let u_stride = plane_stride(self.frame.linesize[1], chroma_width)?;
3261 let v_stride = plane_stride(self.frame.linesize[2], chroma_width)?;
3262 if u_stride != v_stride {
3263 return Err(BridgeError::InvalidInput("mismatched chroma strides"));
3264 }
3265 let y = unsafe { writable_plane(self.frame.data[0], y_stride, self.width, self.height)? };
3270 let u =
3272 unsafe { writable_plane(self.frame.data[1], u_stride, chroma_width, chroma_height)? };
3273 let v =
3275 unsafe { writable_plane(self.frame.data[2], v_stride, chroma_width, chroma_height)? };
3276 Ok(Yuv420pFramePlanes {
3277 y,
3278 y_stride,
3279 u,
3280 v,
3281 chroma_stride: u_stride,
3282 })
3283 }
3284
3285 pub fn set_presentation(&mut self, pts: i64, time_base: ffi::AVRational) {
3287 self.frame.set_pts(pts);
3288 self.frame.set_time_base(time_base);
3289 }
3290
3291 #[must_use]
3293 pub const fn frame(&self) -> &AVFrame {
3294 &self.frame
3295 }
3296
3297 #[must_use]
3299 pub const fn size(&self) -> (usize, usize) {
3300 (self.width, self.height)
3301 }
3302}
3303
3304fn zeroed_yuv420p_frame(width: i32, height: i32) -> Result<AVFrame, BridgeError> {
3306 let mut frame = AVFrame::new();
3307 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
3308 frame.set_width(width);
3309 frame.set_height(height);
3310 frame.get_buffer(1)?;
3311 for buffer in frame.buf {
3312 if let Some(buffer) = unsafe { buffer.as_ref() } {
3315 unsafe { ptr::write_bytes(buffer.data, 0, buffer.size) };
3317 }
3318 }
3319 Ok(frame)
3320}
3321
3322impl fmt::Debug for Yuv420pFrameBuffer {
3323 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3324 formatter
3325 .debug_struct("Yuv420pFrameBuffer")
3326 .field("width", &self.width)
3327 .field("height", &self.height)
3328 .finish_non_exhaustive()
3329 }
3330}
3331
3332fn plane_stride(linesize: i32, width: usize) -> Result<usize, BridgeError> {
3333 let stride = usize::try_from(linesize)
3334 .map_err(|_| BridgeError::InvalidInput("negative frame linesize"))?;
3335 if stride < width {
3336 return Err(BridgeError::InvalidInput(
3337 "frame linesize is below its width",
3338 ));
3339 }
3340 Ok(stride)
3341}
3342
3343unsafe fn writable_plane<'a>(
3347 data: *mut u8,
3348 stride: usize,
3349 width: usize,
3350 height: usize,
3351) -> Result<&'a mut [u8], BridgeError> {
3352 if data.is_null() {
3353 return Err(BridgeError::InvalidInput("frame plane is null"));
3354 }
3355 let len = stride
3356 .checked_mul(height.saturating_sub(1))
3357 .and_then(|rows| rows.checked_add(width))
3358 .ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
3359 Ok(unsafe { slice::from_raw_parts_mut(data, len) })
3361}
3362
3363pub fn exit_without_handlers(code: u8) -> ! {
3375 #[cfg(unix)]
3376 {
3377 unsafe extern "C" {
3378 fn _exit(status: std::ffi::c_int) -> !;
3379 }
3380 unsafe { _exit(std::ffi::c_int::from(code)) }
3382 }
3383 #[cfg(windows)]
3384 {
3385 #[link(name = "kernel32")]
3386 unsafe extern "system" {
3387 fn GetCurrentProcess() -> *mut std::ffi::c_void;
3388 fn TerminateProcess(process: *mut std::ffi::c_void, exit_code: u32) -> i32;
3389 }
3390 if unsafe { TerminateProcess(GetCurrentProcess(), u32::from(code)) } != 0 {
3393 loop {
3395 std::thread::park();
3396 }
3397 }
3398 std::process::exit(i32::from(code))
3400 }
3401 #[cfg(not(any(unix, windows)))]
3402 std::process::exit(i32::from(code))
3403}
3404
3405#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3406mod vulkan_shim_tests;
3407
3408#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3409mod vulkan_access_tests;
3410
3411#[cfg(test)]
3412mod tests;