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 bitstream_filter_list(
2026 spec: &CStr,
2027 parameters: &AVCodecParameters,
2028 time_base: rsmpeg::avutil::AVRational,
2029) -> Result<rsmpeg::avcodec::AVBSFContext, BridgeError> {
2030 let mut raw = ptr::null_mut();
2031 let status = unsafe { ffi::av_bsf_list_parse_str(spec.as_ptr(), &raw mut raw) };
2034 if status < 0 {
2035 return Err(BridgeError::FfmpegCode(status));
2036 }
2037 let raw = ptr::NonNull::new(raw).ok_or(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)))?;
2038 let mut context = unsafe { rsmpeg::avcodec::AVBSFContextUninit::from_raw(raw) };
2041 let status = unsafe { ffi::avcodec_parameters_copy(context.par_in, parameters.as_ptr()) };
2045 if status < 0 {
2046 return Err(BridgeError::FfmpegCode(status));
2047 }
2048 context.set_time_base_in(time_base);
2049 context.init().map_err(BridgeError::Ffmpeg)
2050}
2051
2052pub fn set_stream_codec_tag(stream: &mut rsmpeg::avformat::AVStreamMut<'_>, tag: u32) {
2055 let mut parameters = stream.codecpar_mut();
2056 unsafe { (*parameters.as_mut_ptr()).codec_tag = tag };
2059}
2060
2061pub fn set_stream_display_matrix(
2069 stream: &mut rsmpeg::avformat::AVStreamMut<'_>,
2070 display: DisplayRotation,
2071) -> Result<(), BridgeError> {
2072 let mut parameters = stream.codecpar_mut();
2073 let parameters = parameters.as_mut_ptr();
2074 let entry = unsafe {
2078 ffi::av_packet_side_data_new(
2079 &raw mut (*parameters).coded_side_data,
2080 &raw mut (*parameters).nb_coded_side_data,
2081 ffi::AV_PKT_DATA_DISPLAYMATRIX,
2082 size_of::<[i32; 9]>(),
2083 0,
2084 )
2085 };
2086 if entry.is_null() {
2087 return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
2088 }
2089 let mut matrix = [0_i32; 9];
2090 unsafe {
2094 ffi::av_display_rotation_set(matrix.as_mut_ptr(), -display.counter_clockwise_degrees);
2095 if display.flipped {
2096 ffi::av_display_matrix_flip(matrix.as_mut_ptr(), 1, 0);
2097 }
2098 }
2099 unsafe {
2102 ptr::copy_nonoverlapping(
2103 matrix.as_ptr().cast::<u8>(),
2104 (*entry).data,
2105 size_of::<[i32; 9]>(),
2106 );
2107 }
2108 Ok(())
2109}
2110
2111#[must_use]
2113pub fn hardware_device_type_name(device_type: ffi::AVHWDeviceType) -> Option<String> {
2114 let name = unsafe { ffi::av_hwdevice_get_type_name(device_type) };
2117 if name.is_null() {
2118 None
2119 } else {
2120 Some(
2122 unsafe { CStr::from_ptr(name) }
2123 .to_string_lossy()
2124 .into_owned(),
2125 )
2126 }
2127}
2128
2129#[must_use]
2139pub fn yuv_layout(format: ffi::AVPixelFormat) -> Option<YuvPlaneLayout> {
2140 if format == ffi::AV_PIX_FMT_NONE {
2141 return None;
2142 }
2143 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
2146 let foreign_endian = if cfg!(target_endian = "little") {
2147 ffi::AV_PIX_FMT_FLAG_BE
2148 } else {
2149 0
2150 };
2151 let rejected = foreign_endian
2152 | ffi::AV_PIX_FMT_FLAG_PAL
2153 | ffi::AV_PIX_FMT_FLAG_BITSTREAM
2154 | ffi::AV_PIX_FMT_FLAG_HWACCEL
2155 | ffi::AV_PIX_FMT_FLAG_RGB
2156 | ffi::AV_PIX_FMT_FLAG_ALPHA
2157 | ffi::AV_PIX_FMT_FLAG_BAYER
2158 | ffi::AV_PIX_FMT_FLAG_FLOAT
2159 | ffi::AV_PIX_FMT_FLAG_XYZ;
2160 if descriptor.flags & u64::from(rejected) != 0 || descriptor.nb_components != 3 {
2161 return None;
2162 }
2163 let [luma, u, v, _] = descriptor.comp;
2164 let depth = luma.depth;
2165 if !(8..=16).contains(&depth) || u.depth != depth || v.depth != depth {
2166 return None;
2167 }
2168 if descriptor.log2_chroma_w > 2 || descriptor.log2_chroma_h > 2 {
2169 return None;
2170 }
2171 let bytes = if depth > 8 { 2 } else { 1 };
2172 let shift = luma.shift;
2173 let msb_aligned = match shift {
2174 0 => false,
2175 _ if depth > 8 && shift == 16 - depth => true,
2176 _ => return None,
2177 };
2178 let planar = u.plane == 1
2179 && v.plane == 2
2180 && u.step == bytes
2181 && v.step == bytes
2182 && u.offset == 0
2183 && v.offset == 0;
2184 let interleaved = u.plane == 1
2185 && v.plane == 1
2186 && u.step == 2 * bytes
2187 && v.step == 2 * bytes
2188 && u.offset == 0
2189 && v.offset == bytes;
2190 if luma.plane != 0
2191 || luma.step != bytes
2192 || luma.offset != 0
2193 || u.shift != shift
2194 || v.shift != shift
2195 || !(planar || interleaved)
2196 {
2197 return None;
2198 }
2199 Some(YuvPlaneLayout {
2200 chroma_shift_x: descriptor.log2_chroma_w,
2201 chroma_shift_y: descriptor.log2_chroma_h,
2202 bit_depth: u8::try_from(depth).ok()?,
2203 interleaved_chroma: interleaved,
2204 msb_aligned,
2205 })
2206}
2207
2208pub fn yuv_planes(frame: &AVFrame) -> Result<Option<YuvPlanes>, BridgeError> {
2222 frame_yuv_planes(frame, false)
2223}
2224
2225pub struct DecodedFrame {
2233 frame: AVFrame,
2234 written: bool,
2236}
2237
2238impl DecodedFrame {
2239 pub fn receive(decoder: &mut AVCodecContext) -> Result<Self, RsmpegError> {
2246 let written = decoder_frames_are_initialized(decoder);
2247 let frame = decoder.receive_frame()?;
2248 Ok(Self { frame, written })
2249 }
2250
2251 pub fn yuv_planes(&self) -> Result<Option<YuvPlanes>, BridgeError> {
2262 frame_yuv_planes(&self.frame, self.written)
2263 }
2264
2265 pub fn set_pts(&mut self, pts: i64) {
2267 self.frame.set_pts(pts);
2268 }
2269
2270 #[must_use]
2272 pub fn into_frame(self) -> AVFrame {
2273 self.frame
2274 }
2275}
2276
2277impl std::ops::Deref for DecodedFrame {
2278 type Target = AVFrame;
2279
2280 fn deref(&self) -> &AVFrame {
2281 &self.frame
2282 }
2283}
2284
2285impl fmt::Debug for DecodedFrame {
2286 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
2287 formatter
2288 .debug_struct("DecodedFrame")
2289 .field("format", &self.frame.format)
2290 .field("width", &self.frame.width)
2291 .field("height", &self.frame.height)
2292 .field("pts", &self.frame.pts)
2293 .field("written", &self.written)
2294 .finish_non_exhaustive()
2295 }
2296}
2297
2298fn decoder_frames_are_initialized(decoder: &AVCodecContext) -> bool {
2308 static DEFAULT_GET_BUFFER2: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
2309 let default = *DEFAULT_GET_BUFFER2.get_or_init(|| {
2312 unsafe {
2314 let mut context = ffi::avcodec_alloc_context3(ptr::null());
2315 let get_buffer2 = context
2316 .as_ref()
2317 .and_then(|context| context.get_buffer2)
2318 .map_or(0, |function| function as usize);
2319 ffi::avcodec_free_context(&raw mut context);
2320 get_buffer2
2321 }
2322 });
2323 let wrapped =
2325 unsafe { decoder.codec.as_ref() }.is_none_or(|codec| !codec.wrapper_name.is_null());
2326 let get_buffer2 = decoder.get_buffer2.map_or(0, |function| function as usize);
2327 !wrapped && default != 0 && get_buffer2 == default
2328}
2329
2330fn frame_yuv_planes(frame: &AVFrame, share: bool) -> Result<Option<YuvPlanes>, BridgeError> {
2333 let Some(layout) = yuv_layout(frame.format) else {
2334 return Ok(None);
2335 };
2336 let width =
2337 u32::try_from(frame.width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2338 let height = u32::try_from(frame.height)
2339 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2340 if width == 0 || height == 0 {
2341 return Err(BridgeError::InvalidInput("empty YUV frame"));
2342 }
2343 let bytes = layout.bytes_per_sample();
2344 let chroma_width = width.div_ceil(1 << layout.chroma_shift_x);
2345 let chroma_height = height.div_ceil(1 << layout.chroma_shift_y);
2346 let shared =
2349 (share && !frame.buf[0].is_null()).then(|| std::sync::Arc::new(SharedFrame(frame.clone())));
2350 let copy = |index: usize,
2351 samples: u32,
2352 pairs: u32,
2353 rows: u32,
2354 context: &'static str|
2355 -> Result<FramePlane, BridgeError> {
2356 let row_bytes = samples
2357 .checked_mul(bytes)
2358 .and_then(|row| row.checked_mul(pairs))
2359 .ok_or(BridgeError::InvalidInput(context))?;
2360 if let Some(view) = shared
2361 .as_ref()
2362 .and_then(|frame| PlaneView::new(frame, index, row_bytes, rows))
2363 {
2364 let stride =
2365 u32::try_from(view.stride).map_err(|_| BridgeError::InvalidInput(context))?;
2366 return Ok(FramePlane {
2367 bytes: bytes::Bytes::from_owner(view),
2368 stride,
2369 width: samples,
2370 height: rows,
2371 });
2372 }
2373 Ok(FramePlane {
2374 bytes: copy_video_plane(
2375 frame.data[index],
2376 frame.linesize[index],
2377 row_bytes,
2378 rows,
2379 context,
2380 )?
2381 .into(),
2382 stride: row_bytes,
2383 width: samples,
2384 height: rows,
2385 })
2386 };
2387 let mut planes = vec![copy(0, width, 1, height, "invalid Y plane")?];
2388 if layout.interleaved_chroma {
2389 planes.push(copy(1, chroma_width, 2, chroma_height, "invalid UV plane")?);
2390 } else {
2391 planes.push(copy(1, chroma_width, 1, chroma_height, "invalid U plane")?);
2392 planes.push(copy(2, chroma_width, 1, chroma_height, "invalid V plane")?);
2393 }
2394 Ok(Some(YuvPlanes {
2395 layout,
2396 width,
2397 height,
2398 planes,
2399 }))
2400}
2401
2402struct SharedFrame(AVFrame);
2404
2405unsafe impl Sync for SharedFrame {}
2409
2410struct PlaneView {
2413 frame: std::sync::Arc<SharedFrame>,
2414 data: *const u8,
2415 len: usize,
2416 stride: usize,
2417}
2418
2419unsafe impl Send for PlaneView {}
2422
2423impl PlaneView {
2424 fn new(
2428 frame: &std::sync::Arc<SharedFrame>,
2429 index: usize,
2430 row_bytes: u32,
2431 rows: u32,
2432 ) -> Option<Self> {
2433 let inner = &frame.0;
2434 let data = inner.data[index].cast_const();
2435 let stride = usize::try_from(inner.linesize[index]).ok()?;
2436 let row_bytes = usize::try_from(row_bytes).ok()?;
2437 if data.is_null() || stride < row_bytes || rows == 0 {
2438 return None;
2439 }
2440 let len = stride.checked_mul(usize::try_from(rows).ok()?)?;
2441 let start = data as usize;
2442 let end = start.checked_add(len)?;
2443 let inside = inner.buf.iter().any(|buffer| {
2444 let Some(buffer) = (unsafe { buffer.as_ref() }) else {
2447 return false;
2448 };
2449 let buffer_start = buffer.data as usize;
2450 buffer_start
2451 .checked_add(buffer.size)
2452 .is_some_and(|buffer_end| buffer_start <= start && end <= buffer_end)
2453 });
2454 inside.then(|| Self {
2455 frame: std::sync::Arc::clone(frame),
2456 data,
2457 len,
2458 stride,
2459 })
2460 }
2461}
2462
2463impl AsRef<[u8]> for PlaneView {
2464 fn as_ref(&self) -> &[u8] {
2465 let _ = &self.frame;
2467 unsafe { slice::from_raw_parts(self.data, self.len) }
2470 }
2471}
2472
2473fn copy_video_plane(
2474 data: *const u8,
2475 linesize: i32,
2476 width: u32,
2477 height: u32,
2478 context: &'static str,
2479) -> Result<Vec<u8>, BridgeError> {
2480 let row_bytes = usize::try_from(width).map_err(|_| BridgeError::InvalidInput(context))?;
2481 let rows = usize::try_from(height).map_err(|_| BridgeError::InvalidInput(context))?;
2482 let stride =
2483 usize::try_from(linesize.unsigned_abs()).map_err(|_| BridgeError::InvalidInput(context))?;
2484 if data.is_null() || linesize == 0 || stride < row_bytes {
2485 return Err(BridgeError::InvalidInput(context));
2486 }
2487 let byte_len = row_bytes
2488 .checked_mul(rows)
2489 .ok_or(BridgeError::InvalidInput(context))?;
2490 let mut output = Vec::with_capacity(byte_len);
2493 let signed_stride =
2494 isize::try_from(linesize).map_err(|_| BridgeError::InvalidInput(context))?;
2495 for row in 0..rows {
2496 let row = isize::try_from(row).map_err(|_| BridgeError::InvalidInput(context))?;
2497 let offset = row
2498 .checked_mul(signed_stride)
2499 .ok_or(BridgeError::InvalidInput(context))?;
2500 let source = unsafe { slice::from_raw_parts(data.offset(offset), row_bytes) };
2504 output.extend_from_slice(source);
2505 }
2506 debug_assert_eq!(output.len(), byte_len);
2507 Ok(output)
2508}
2509
2510pub fn rgba_frame(bytes: &[u8], width: i32, height: i32) -> Result<AVFrame, BridgeError> {
2515 let row_bytes = usize::try_from(width)
2516 .ok()
2517 .and_then(|value| value.checked_mul(4))
2518 .ok_or(BridgeError::InvalidInput("RGBA row width overflow"))?;
2519 let rows =
2520 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("negative RGBA height"))?;
2521 let expected = row_bytes
2522 .checked_mul(rows)
2523 .ok_or(BridgeError::InvalidInput("RGBA frame size overflow"))?;
2524 if bytes.len() != expected {
2525 return Err(BridgeError::InvalidInput("RGBA byte length mismatch"));
2526 }
2527
2528 let mut frame = AVFrame::new();
2529 frame.set_format(ffi::AV_PIX_FMT_RGBA);
2530 frame.set_width(width);
2531 frame.set_height(height);
2532 frame.get_buffer(1)?;
2533 frame.make_writable()?;
2534 let linesize = usize::try_from(frame.linesize[0])
2535 .map_err(|_| BridgeError::InvalidInput("negative RGBA linesize"))?;
2536 if frame.data[0].is_null() || linesize < row_bytes {
2537 return Err(BridgeError::InvalidInput(
2538 "RGBA destination plane is too small",
2539 ));
2540 }
2541
2542 unsafe {
2545 for row in 0..rows {
2546 let destination_offset = row.checked_mul(linesize).ok_or(BridgeError::InvalidInput(
2547 "RGBA destination offset overflow",
2548 ))?;
2549 let source_offset = row
2550 .checked_mul(row_bytes)
2551 .ok_or(BridgeError::InvalidInput("RGBA source offset overflow"))?;
2552 let destination =
2553 slice::from_raw_parts_mut(frame.data[0].add(destination_offset), row_bytes);
2554 destination.copy_from_slice(&bytes[source_offset..source_offset + row_bytes]);
2555 }
2556 }
2557 Ok(frame)
2558}
2559
2560#[allow(clippy::too_many_arguments)]
2566pub fn yuv420p_frame(
2567 y: &[u8],
2568 y_stride: u32,
2569 u: &[u8],
2570 u_stride: u32,
2571 v: &[u8],
2572 v_stride: u32,
2573 width: i32,
2574 height: i32,
2575) -> Result<AVFrame, BridgeError> {
2576 let width =
2577 u32::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
2578 let height =
2579 u32::try_from(height).map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
2580 if width == 0 || height == 0 {
2581 return Err(BridgeError::InvalidInput("empty YUV frame"));
2582 }
2583 let chroma_width = width.div_ceil(2);
2584 let chroma_height = height.div_ceil(2);
2585 validate_source_plane(y, y_stride, width, height, "invalid Y source plane")?;
2586 validate_source_plane(
2587 u,
2588 u_stride,
2589 chroma_width,
2590 chroma_height,
2591 "invalid U source plane",
2592 )?;
2593 validate_source_plane(
2594 v,
2595 v_stride,
2596 chroma_width,
2597 chroma_height,
2598 "invalid V source plane",
2599 )?;
2600
2601 let mut frame = AVFrame::new();
2602 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
2603 frame.set_width(i32::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV width"))?);
2604 frame.set_height(i32::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV height"))?);
2605 frame.get_buffer(1)?;
2606 frame.make_writable()?;
2607 copy_plane_to_frame(y, y_stride, width, height, frame.data[0], frame.linesize[0])?;
2608 copy_plane_to_frame(
2609 u,
2610 u_stride,
2611 chroma_width,
2612 chroma_height,
2613 frame.data[1],
2614 frame.linesize[1],
2615 )?;
2616 copy_plane_to_frame(
2617 v,
2618 v_stride,
2619 chroma_width,
2620 chroma_height,
2621 frame.data[2],
2622 frame.linesize[2],
2623 )?;
2624 Ok(frame)
2625}
2626
2627#[must_use]
2631pub fn yuv_pixel_format(layout: YuvPlaneLayout) -> Option<ffi::AVPixelFormat> {
2632 let mut descriptor = ptr::null();
2633 loop {
2634 descriptor = unsafe { ffi::av_pix_fmt_desc_next(descriptor) };
2637 if descriptor.is_null() {
2638 return None;
2639 }
2640 let format = unsafe { ffi::av_pix_fmt_desc_get_id(descriptor) };
2642 if yuv_layout(format) == Some(layout) {
2643 return Some(format);
2644 }
2645 }
2646}
2647
2648pub fn planar_frame(
2655 format: ffi::AVPixelFormat,
2656 planes: &[(&[u8], u32)],
2657 width: u32,
2658 height: u32,
2659) -> Result<AVFrame, BridgeError> {
2660 let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
2663 .ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
2664 let count = unsafe { ffi::av_pix_fmt_count_planes(format) };
2666 if usize::try_from(count).ok() != Some(planes.len()) || width == 0 || height == 0 {
2667 return Err(BridgeError::InvalidInput(
2668 "planes do not match the pixel format",
2669 ));
2670 }
2671 let signed = |value: u32| {
2672 i32::try_from(value).map_err(|_| BridgeError::InvalidInput("frame size overflow"))
2673 };
2674 let mut frame = AVFrame::new();
2675 frame.set_format(format);
2676 frame.set_width(signed(width)?);
2677 frame.set_height(signed(height)?);
2678 frame.get_buffer(1)?;
2679 frame.make_writable()?;
2680 for (index, (bytes, stride)) in planes.iter().enumerate() {
2681 let plane = i32::try_from(index).unwrap_or(i32::MAX);
2682 let row_bytes = unsafe { ffi::av_image_get_linesize(format, signed(width)?, plane) };
2684 let row_bytes = u32::try_from(row_bytes).map_err(|_| BridgeError::FfmpegCode(row_bytes))?;
2685 let rows = if index == 0 || index == 3 {
2686 height
2687 } else {
2688 height.div_ceil(1 << descriptor.log2_chroma_h)
2689 };
2690 validate_source_plane(bytes, *stride, row_bytes, rows, "invalid source plane")?;
2691 copy_plane_to_frame(
2692 bytes,
2693 *stride,
2694 row_bytes,
2695 rows,
2696 frame.data[index],
2697 frame.linesize[index],
2698 )?;
2699 }
2700 Ok(frame)
2701}
2702
2703fn validate_source_plane(
2704 bytes: &[u8],
2705 stride: u32,
2706 width: u32,
2707 height: u32,
2708 context: &'static str,
2709) -> Result<(), BridgeError> {
2710 let required = usize::try_from(stride).ok().and_then(|stride| {
2711 usize::try_from(height)
2712 .ok()
2713 .and_then(|height| stride.checked_mul(height))
2714 });
2715 if stride < width || required != Some(bytes.len()) {
2716 Err(BridgeError::InvalidInput(context))
2717 } else {
2718 Ok(())
2719 }
2720}
2721
2722fn copy_plane_to_frame(
2723 source: &[u8],
2724 source_stride: u32,
2725 width: u32,
2726 height: u32,
2727 destination: *mut u8,
2728 destination_stride: i32,
2729) -> Result<(), BridgeError> {
2730 let row_bytes =
2731 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV row width overflow"))?;
2732 let rows =
2733 usize::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV row count overflow"))?;
2734 let source_stride = usize::try_from(source_stride)
2735 .map_err(|_| BridgeError::InvalidInput("YUV source stride overflow"))?;
2736 let destination_stride = usize::try_from(destination_stride)
2737 .map_err(|_| BridgeError::InvalidInput("negative YUV destination stride"))?;
2738 if destination.is_null() || destination_stride < row_bytes {
2739 return Err(BridgeError::InvalidInput(
2740 "YUV destination plane is too small",
2741 ));
2742 }
2743 for row in 0..rows {
2744 let source_offset = row
2745 .checked_mul(source_stride)
2746 .ok_or(BridgeError::InvalidInput("YUV source offset overflow"))?;
2747 let destination_offset = row
2748 .checked_mul(destination_stride)
2749 .ok_or(BridgeError::InvalidInput("YUV destination offset overflow"))?;
2750 unsafe {
2754 ptr::copy_nonoverlapping(
2755 source.as_ptr().add(source_offset),
2756 destination.add(destination_offset),
2757 row_bytes,
2758 );
2759 }
2760 }
2761 Ok(())
2762}
2763
2764pub fn audio_frame(
2773 channels: &[Vec<f32>],
2774 format: ffi::AVSampleFormat,
2775 sample_rate: i32,
2776 pts: i64,
2777) -> Result<AVFrame, BridgeError> {
2778 let Some(samples) = channels.first().map(Vec::len) else {
2779 return Err(BridgeError::InvalidInput("invalid planar audio shape"));
2780 };
2781 if sample_rate <= 0
2782 || samples == 0
2783 || channels.len() > ffi::AV_NUM_DATA_POINTERS as usize
2784 || channels.iter().any(|channel| channel.len() != samples)
2785 {
2786 return Err(BridgeError::InvalidInput(
2787 "planar audio channel lengths differ",
2788 ));
2789 }
2790 let sample_bytes = match format {
2791 ffi::AV_SAMPLE_FMT_FLTP | ffi::AV_SAMPLE_FMT_FLT => size_of::<f32>(),
2792 ffi::AV_SAMPLE_FMT_S16 => size_of::<i16>(),
2793 _ => return Err(BridgeError::InvalidInput("unsupported audio sample format")),
2794 };
2795 let planar = format == ffi::AV_SAMPLE_FMT_FLTP;
2796 let channel_count = i32::try_from(channels.len())
2797 .map_err(|_| BridgeError::InvalidInput("audio channel count overflow"))?;
2798 let sample_count = i32::try_from(samples)
2799 .map_err(|_| BridgeError::InvalidInput("audio sample count overflow"))?;
2800 let mut frame = AVFrame::new();
2801 frame.set_format(format);
2802 frame.set_ch_layout(
2803 rsmpeg::avutil::AVChannelLayout::from_nb_channels(channel_count).into_inner(),
2804 );
2805 frame.set_sample_rate(sample_rate);
2806 frame.set_nb_samples(sample_count);
2807 frame.set_pts(pts);
2808 frame.set_time_base(ffi::AVRational {
2809 num: 1,
2810 den: sample_rate,
2811 });
2812 frame.get_buffer(0)?;
2813 frame.make_writable()?;
2814 let plane_bytes = samples
2815 .checked_mul(sample_bytes)
2816 .and_then(|bytes| bytes.checked_mul(if planar { 1 } else { channels.len() }))
2817 .ok_or(BridgeError::InvalidInput("audio plane byte count overflow"))?;
2818 let plane_capacity = usize::try_from(frame.linesize[0]).unwrap_or(0);
2819 if plane_capacity < plane_bytes {
2820 return Err(BridgeError::InvalidInput(
2821 "audio plane capacity is too small",
2822 ));
2823 }
2824 let planes = if planar { channels.len() } else { 1 };
2825 if frame.data[..planes].iter().any(|plane| plane.is_null()) {
2826 return Err(BridgeError::InvalidInput("audio plane pointer is null"));
2827 }
2828 if planar {
2829 for (index, source) in channels.iter().enumerate() {
2830 unsafe {
2834 ptr::copy_nonoverlapping(
2835 source.as_ptr().cast::<u8>(),
2836 frame.data[index],
2837 plane_bytes,
2838 );
2839 }
2840 }
2841 return Ok(frame);
2842 }
2843 let plane = unsafe { std::slice::from_raw_parts_mut(frame.data[0], plane_bytes) };
2846 let interleaved = (0..samples).flat_map(|sample| channels.iter().map(move |c| c[sample]));
2847 if format == ffi::AV_SAMPLE_FMT_FLT {
2848 for (bytes, value) in plane.as_chunks_mut::<4>().0.iter_mut().zip(interleaved) {
2849 *bytes = value.to_ne_bytes();
2850 }
2851 } else {
2852 for (bytes, value) in plane.as_chunks_mut::<2>().0.iter_mut().zip(interleaved) {
2853 #[allow(clippy::cast_possible_truncation)]
2855 let value = (value.clamp(-1.0, 1.0) * 32_767.0).round() as i16;
2856 *bytes = value.to_ne_bytes();
2857 }
2858 }
2859 Ok(frame)
2860}
2861
2862#[must_use]
2881pub fn channel_layout_channels(layout: &rsmpeg::avutil::AVChannelLayout) -> Vec<ffi::AVChannel> {
2882 if layout.order == ffi::AV_CHANNEL_ORDER_UNSPEC {
2883 return Vec::new();
2884 }
2885 let channels = u32::try_from(layout.nb_channels).unwrap_or(0);
2886 (0..channels)
2887 .map(|index| {
2888 layout
2889 .channel_from_index(index)
2890 .unwrap_or(ffi::AV_CHAN_NONE)
2891 })
2892 .collect()
2893}
2894
2895pub fn channel_layout_of(
2902 channels: &[ffi::AVChannel],
2903) -> Result<rsmpeg::avutil::AVChannelLayout, BridgeError> {
2904 let count = i32::try_from(channels.len())
2905 .ok()
2906 .filter(|count| *count > 0)
2907 .ok_or(BridgeError::InvalidInput("a channel layout needs channels"))?;
2908 let mut raw = std::mem::MaybeUninit::<ffi::AVChannelLayout>::zeroed();
2909 let initialized = unsafe { ffi::av_channel_layout_custom_init(raw.as_mut_ptr(), count) };
2912 if initialized < 0 {
2913 return Err(BridgeError::FfmpegCode(initialized));
2914 }
2915 let mut layout = unsafe { rsmpeg::avutil::AVChannelLayout::new(raw.assume_init()) };
2917 for (index, channel) in channels.iter().enumerate() {
2918 unsafe { (*layout.u.map.add(index)).id = *channel };
2920 }
2921 let flags = i32::try_from(ffi::AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL).unwrap_or(0);
2922 let retyped = unsafe {
2924 ffi::av_channel_layout_retype(layout.as_mut_ptr(), ffi::AV_CHANNEL_ORDER_UNSPEC, flags)
2925 };
2926 if retyped < 0 {
2927 return Err(BridgeError::FfmpegCode(retyped));
2928 }
2929 Ok(layout)
2930}
2931
2932pub fn audio_f32_interleaved(frame: &AVFrame) -> Result<Vec<f32>, BridgeError> {
2937 if frame.format != ffi::AV_SAMPLE_FMT_FLT || frame.nb_samples < 0 {
2938 return Err(BridgeError::InvalidInput(
2939 "packed audio format or sample count is invalid",
2940 ));
2941 }
2942 let channels = usize::try_from(frame.ch_layout().nb_channels)
2943 .map_err(|_| BridgeError::InvalidInput("packed audio channel count is negative"))?;
2944 let frames = usize::try_from(frame.nb_samples)
2945 .map_err(|_| BridgeError::InvalidInput("packed audio sample count is negative"))?;
2946 let samples = frames
2947 .checked_mul(channels)
2948 .ok_or(BridgeError::InvalidInput(
2949 "packed audio sample count overflow",
2950 ))?;
2951 let bytes = samples
2952 .checked_mul(size_of::<f32>())
2953 .ok_or(BridgeError::InvalidInput(
2954 "packed audio byte count overflow",
2955 ))?;
2956 if channels == 0
2957 || frame.data[0].is_null()
2958 || usize::try_from(frame.linesize[0]).unwrap_or(0) < bytes
2959 {
2960 return Err(BridgeError::InvalidInput(
2961 "packed audio plane is missing or too small",
2962 ));
2963 }
2964 let mut output = vec![0.0_f32; samples];
2965 unsafe {
2968 ptr::copy_nonoverlapping(frame.data[0], output.as_mut_ptr().cast::<u8>(), bytes);
2969 }
2970 Ok(output)
2971}
2972
2973#[derive(Copy, Clone, Debug, PartialEq)]
2975pub struct DisplayRotation {
2976 pub counter_clockwise_degrees: f64,
2979 pub flipped: bool,
2983}
2984
2985#[must_use]
2987pub fn display_rotation(parameters: &AVCodecParameters) -> Option<DisplayRotation> {
2988 let count = usize::try_from(parameters.nb_coded_side_data).ok()?;
2989 if count == 0 || count > 64 || parameters.coded_side_data.is_null() {
2990 return None;
2991 }
2992 let entries = unsafe { slice::from_raw_parts(parameters.coded_side_data, count) };
2995 let matrix = entries.iter().find(|entry| {
2996 entry.type_ == ffi::AV_PKT_DATA_DISPLAYMATRIX
2997 && entry.size >= 9 * size_of::<i32>()
2998 && !entry.data.is_null()
2999 })?;
3000 unsafe { matrix_rotation(matrix.data) }
3002}
3003
3004#[must_use]
3007pub fn frame_display_rotation(frame: &AVFrame) -> Option<DisplayRotation> {
3008 let entry =
3010 unsafe { ffi::av_frame_get_side_data(frame.as_ptr(), ffi::AV_FRAME_DATA_DISPLAYMATRIX) };
3011 if entry.is_null() {
3012 return None;
3013 }
3014 let entry = unsafe { &*entry };
3016 if entry.size < 9 * size_of::<i32>() || entry.data.is_null() {
3017 return None;
3018 }
3019 unsafe { matrix_rotation(entry.data) }
3021}
3022
3023unsafe fn matrix_rotation(data: *const u8) -> Option<DisplayRotation> {
3026 let mut aligned = [0_i32; 9];
3027 unsafe {
3030 ptr::copy_nonoverlapping(
3031 data,
3032 aligned.as_mut_ptr().cast::<u8>(),
3033 size_of::<[i32; 9]>(),
3034 );
3035 }
3036 let determinant = i64::from(aligned[0]) * i64::from(aligned[4])
3040 - i64::from(aligned[1]) * i64::from(aligned[3]);
3041 let flipped = determinant < 0;
3042 if flipped {
3043 unsafe { ffi::av_display_matrix_flip(aligned.as_mut_ptr(), 1, 0) };
3045 }
3046 let angle = unsafe { ffi::av_display_rotation_get(aligned.as_ptr()) };
3048 angle.is_finite().then_some(DisplayRotation {
3049 counter_clockwise_degrees: angle,
3050 flipped,
3051 })
3052}
3053
3054const fn size_of<T>() -> usize {
3055 std::mem::size_of::<T>()
3056}
3057
3058pub struct Yuv420pFrameBuffer {
3072 frame: AVFrame,
3073 width: usize,
3074 height: usize,
3075}
3076
3077pub struct Yuv420pFramePlanes<'a> {
3079 pub y: &'a mut [u8],
3081 pub y_stride: usize,
3083 pub u: &'a mut [u8],
3085 pub v: &'a mut [u8],
3087 pub chroma_stride: usize,
3089}
3090
3091impl Yuv420pFrameBuffer {
3092 pub fn new(width: i32, height: i32) -> Result<Self, BridgeError> {
3097 let coded_width =
3098 usize::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
3099 let coded_height = usize::try_from(height)
3100 .map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
3101 if coded_width == 0 || coded_height == 0 {
3102 return Err(BridgeError::InvalidInput("empty YUV frame"));
3103 }
3104 Ok(Self {
3105 frame: zeroed_yuv420p_frame(width, height)?,
3106 width: coded_width,
3107 height: coded_height,
3108 })
3109 }
3110
3111 #[must_use]
3113 pub const fn dimensions(&self) -> (usize, usize) {
3114 (self.width, self.height)
3115 }
3116
3117 pub fn begin_write(&mut self) -> Result<Yuv420pFramePlanes<'_>, BridgeError> {
3123 if !self.frame.is_writable().map_err(BridgeError::Ffmpeg)? {
3127 let mut copy = zeroed_yuv420p_frame(self.frame.width, self.frame.height)?;
3128 let status = unsafe {
3131 let copied = ffi::av_frame_copy(copy.as_mut_ptr(), self.frame.as_ptr());
3132 if copied < 0 {
3133 copied
3134 } else {
3135 ffi::av_frame_copy_props(copy.as_mut_ptr(), self.frame.as_ptr())
3136 }
3137 };
3138 if status < 0 {
3139 return Err(BridgeError::FfmpegCode(status));
3140 }
3141 self.frame = copy;
3142 }
3143 let chroma_width = self.width.div_ceil(2);
3144 let chroma_height = self.height.div_ceil(2);
3145 let y_stride = plane_stride(self.frame.linesize[0], self.width)?;
3146 let u_stride = plane_stride(self.frame.linesize[1], chroma_width)?;
3147 let v_stride = plane_stride(self.frame.linesize[2], chroma_width)?;
3148 if u_stride != v_stride {
3149 return Err(BridgeError::InvalidInput("mismatched chroma strides"));
3150 }
3151 let y = unsafe { writable_plane(self.frame.data[0], y_stride, self.width, self.height)? };
3156 let u =
3158 unsafe { writable_plane(self.frame.data[1], u_stride, chroma_width, chroma_height)? };
3159 let v =
3161 unsafe { writable_plane(self.frame.data[2], v_stride, chroma_width, chroma_height)? };
3162 Ok(Yuv420pFramePlanes {
3163 y,
3164 y_stride,
3165 u,
3166 v,
3167 chroma_stride: u_stride,
3168 })
3169 }
3170
3171 pub fn set_presentation(&mut self, pts: i64, time_base: ffi::AVRational) {
3173 self.frame.set_pts(pts);
3174 self.frame.set_time_base(time_base);
3175 }
3176
3177 #[must_use]
3179 pub const fn frame(&self) -> &AVFrame {
3180 &self.frame
3181 }
3182
3183 #[must_use]
3185 pub const fn size(&self) -> (usize, usize) {
3186 (self.width, self.height)
3187 }
3188}
3189
3190fn zeroed_yuv420p_frame(width: i32, height: i32) -> Result<AVFrame, BridgeError> {
3192 let mut frame = AVFrame::new();
3193 frame.set_format(ffi::AV_PIX_FMT_YUV420P);
3194 frame.set_width(width);
3195 frame.set_height(height);
3196 frame.get_buffer(1)?;
3197 for buffer in frame.buf {
3198 if let Some(buffer) = unsafe { buffer.as_ref() } {
3201 unsafe { ptr::write_bytes(buffer.data, 0, buffer.size) };
3203 }
3204 }
3205 Ok(frame)
3206}
3207
3208impl fmt::Debug for Yuv420pFrameBuffer {
3209 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
3210 formatter
3211 .debug_struct("Yuv420pFrameBuffer")
3212 .field("width", &self.width)
3213 .field("height", &self.height)
3214 .finish_non_exhaustive()
3215 }
3216}
3217
3218fn plane_stride(linesize: i32, width: usize) -> Result<usize, BridgeError> {
3219 let stride = usize::try_from(linesize)
3220 .map_err(|_| BridgeError::InvalidInput("negative frame linesize"))?;
3221 if stride < width {
3222 return Err(BridgeError::InvalidInput(
3223 "frame linesize is below its width",
3224 ));
3225 }
3226 Ok(stride)
3227}
3228
3229unsafe fn writable_plane<'a>(
3233 data: *mut u8,
3234 stride: usize,
3235 width: usize,
3236 height: usize,
3237) -> Result<&'a mut [u8], BridgeError> {
3238 if data.is_null() {
3239 return Err(BridgeError::InvalidInput("frame plane is null"));
3240 }
3241 let len = stride
3242 .checked_mul(height.saturating_sub(1))
3243 .and_then(|rows| rows.checked_add(width))
3244 .ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
3245 Ok(unsafe { slice::from_raw_parts_mut(data, len) })
3247}
3248
3249pub fn exit_without_handlers(code: u8) -> ! {
3261 #[cfg(unix)]
3262 {
3263 unsafe extern "C" {
3264 fn _exit(status: std::ffi::c_int) -> !;
3265 }
3266 unsafe { _exit(std::ffi::c_int::from(code)) }
3268 }
3269 #[cfg(windows)]
3270 {
3271 #[link(name = "kernel32")]
3272 unsafe extern "system" {
3273 fn GetCurrentProcess() -> *mut std::ffi::c_void;
3274 fn TerminateProcess(process: *mut std::ffi::c_void, exit_code: u32) -> i32;
3275 }
3276 if unsafe { TerminateProcess(GetCurrentProcess(), u32::from(code)) } != 0 {
3279 loop {
3281 std::thread::park();
3282 }
3283 }
3284 std::process::exit(i32::from(code))
3286 }
3287 #[cfg(not(any(unix, windows)))]
3288 std::process::exit(i32::from(code))
3289}
3290
3291#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3292mod vulkan_shim_tests;
3293
3294#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
3295mod vulkan_access_tests;
3296
3297#[cfg(test)]
3298mod tests;