use std::{error::Error, ffi::CStr, fmt, ptr, slice};
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
use std::ffi::CString;
#[cfg(all(
any(target_os = "linux", target_os = "macos", target_os = "windows"),
feature = "hwdevice"
))]
use std::sync::Mutex;
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
use std::sync::MutexGuard;
pub use alpha2_media::GpuFault;
use rsmpeg::{
avcodec::{AVCodecContext, AVCodecParameters},
avutil::AVFrame,
error::RsmpegError,
ffi,
swscale::SwsContext,
};
#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
use rsmpeg::avutil::AVHWDeviceContext;
mod container;
pub use container::{DemuxInput, OpenInputError, OutputFile, open_input};
#[allow(
dead_code,
non_camel_case_types,
unreachable_pub,
clippy::all,
clippy::pedantic,
clippy::undocumented_unsafe_blocks
)]
mod interop {
include!(concat!(env!("OUT_DIR"), "/interop.rs"));
}
const _: () = {
use interop::{Alpha2D3D12FrameInfo as D3D12, Alpha2VideoToolboxFrameInfo as VideoToolbox};
use std::mem::{align_of, offset_of, size_of};
assert!(size_of::<D3D12>() == 64 && align_of::<D3D12>() == 8);
assert!(offset_of!(D3D12, resource) == 0);
assert!(offset_of!(D3D12, fence) == 8);
assert!(offset_of!(D3D12, fence_value) == 16);
assert!(offset_of!(D3D12, device) == 24);
assert!(offset_of!(D3D12, dxgi_format) == 32);
assert!(offset_of!(D3D12, width) == 36);
assert!(offset_of!(D3D12, height) == 40);
assert!(offset_of!(D3D12, hardware_format) == 44);
assert!(offset_of!(D3D12, software_format) == 48);
assert!(offset_of!(D3D12, surface_width) == 52);
assert!(offset_of!(D3D12, surface_height) == 56);
assert!(size_of::<VideoToolbox>() == 24 && align_of::<VideoToolbox>() == 8);
assert!(offset_of!(VideoToolbox, pixel_buffer) == 0);
assert!(offset_of!(VideoToolbox, width) == 8);
assert!(offset_of!(VideoToolbox, height) == 12);
assert!(offset_of!(VideoToolbox, hardware_format) == 16);
assert!(offset_of!(VideoToolbox, software_format) == 20);
};
#[cfg(all(
any(target_os = "linux", target_os = "macos", target_os = "windows"),
feature = "hwdevice"
))]
#[allow(clippy::cast_ptr_alignment)]
fn download_hardware_frame(frame: &AVFrame) -> Result<AVFrame, BridgeError> {
if frame.hw_frames_ctx.is_null() {
return Err(BridgeError::InvalidInput(
"hardware frame has no frames context",
));
}
let written = unsafe {
let frames = (*frame.hw_frames_ctx).data.cast::<ffi::AVHWFramesContext>();
((*frames).width, (*frames).height)
};
let mut software = AVFrame::new();
software
.hwframe_transfer_data(frame)
.map_err(BridgeError::Ffmpeg)?;
zero_row_padding(&mut software, written.0, written.1)?;
let copied = unsafe { ffi::av_frame_copy_props(software.as_mut_ptr(), frame.as_ptr()) };
if copied < 0 {
return Err(BridgeError::FfmpegCode(copied));
}
Ok(software)
}
#[cfg_attr(not(feature = "hwdevice"), allow(dead_code))]
fn zero_row_padding(frame: &mut AVFrame, width: i32, height: i32) -> Result<(), BridgeError> {
let format = frame.format;
let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
.ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
if width < 0 || height < 0 {
return Err(BridgeError::InvalidInput("negative frame size"));
}
let planes = unsafe { ffi::av_pix_fmt_count_planes(format) };
for plane in 0..usize::try_from(planes).unwrap_or(0).min(4) {
let plane_index = i32::try_from(plane).unwrap_or(0);
let written = unsafe { ffi::av_image_get_linesize(format, width, plane_index) };
let (Ok(written), Ok(stride)) = (
usize::try_from(written),
usize::try_from(frame.linesize[plane]),
) else {
continue;
};
let data = frame.data[plane];
if data.is_null() || stride <= written {
continue;
}
let shift = if plane == 1 || plane == 2 {
descriptor.log2_chroma_h
} else {
0
};
let rows = usize::try_from(-((-height) >> shift)).unwrap_or(0);
let start = data as usize;
let end = stride
.checked_mul(rows)
.and_then(|len| start.checked_add(len))
.ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
let inside = frame.buf.iter().any(|buffer| {
unsafe { buffer.as_ref() }.is_some_and(|buffer| {
let buffer_start = buffer.data as usize;
buffer_start <= start && buffer_start.saturating_add(buffer.size) >= end
})
});
if !inside {
return Err(BridgeError::InvalidInput(
"frame plane lies outside its buffers",
));
}
for row in 0..rows {
unsafe { ptr::write_bytes(data.add(row * stride + written), 0, stride - written) };
}
}
Ok(())
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
use interop::Alpha2D3D12FrameInfo as D3D12FrameDescriptor;
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
unsafe extern "C" {
fn alpha2_d3d12_device_init(
context: *mut ffi::AVHWDeviceContext,
device: *mut std::ffi::c_void,
) -> i32;
fn alpha2_d3d12_get_format(
context: *mut ffi::AVCodecContext,
formats: *const ffi::AVPixelFormat,
) -> ffi::AVPixelFormat;
fn alpha2_d3d12_frame_info(
frame: *const ffi::AVFrame,
output: *mut D3D12FrameDescriptor,
) -> i32;
fn alpha2_d3d12_frame_set_fence_value(frame: *mut ffi::AVFrame, value: u64) -> i32;
fn alpha2_d3d12_frame_fence_value(frame: *const ffi::AVFrame, output: *mut u64) -> i32;
fn alpha2_d3d12_frames_configure(
frames: *mut ffi::AVBufferRef,
dxgi_format: u32,
resource_flags: u32,
) -> i32;
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
pub const DXGI_FORMAT_NV12: u32 = 103;
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
pub fn d3d12_frames_configure(
frames: &mut rsmpeg::avutil::AVHWFramesContext,
dxgi_format: u32,
resource_flags: u32,
) -> Result<(), BridgeError> {
let status =
unsafe { alpha2_d3d12_frames_configure(frames.as_mut_ptr(), dxgi_format, resource_flags) };
if status < 0 {
return Err(BridgeError::FfmpegCode(status));
}
Ok(())
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct D3D12FrameInfo {
pub resource: u64,
pub fence: u64,
pub fence_value: u64,
pub device: u64,
pub dxgi_format: u32,
pub width: i32,
pub height: i32,
pub software_format: i32,
pub surface_width: u32,
pub surface_height: u32,
}
impl D3D12FrameInfo {
#[cfg_attr(
not(all(target_os = "windows", feature = "hwdevice")),
allow(dead_code)
)]
const fn from_descriptor(descriptor: &interop::Alpha2D3D12FrameInfo) -> Self {
Self {
resource: descriptor.resource,
fence: descriptor.fence,
fence_value: descriptor.fence_value,
device: descriptor.device,
dxgi_format: descriptor.dxgi_format,
width: descriptor.width,
height: descriptor.height,
software_format: descriptor.software_format,
surface_width: descriptor.surface_width,
surface_height: descriptor.surface_height,
}
}
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
pub struct D3D12FrameLease {
frame: Mutex<AVFrame>,
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
impl fmt::Debug for D3D12FrameLease {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("D3D12FrameLease")
.finish_non_exhaustive()
}
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
impl D3D12FrameLease {
#[must_use]
pub fn new(frame: AVFrame) -> Self {
Self {
frame: Mutex::new(frame),
}
}
pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
let frame = self
.frame
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
download_hardware_frame(&frame).map(|frame| DecodedFrame {
frame,
written: true,
})
}
pub fn info(&self) -> Result<D3D12FrameInfo, BridgeError> {
let frame = self
.frame
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
d3d12_frame_info(&frame)
}
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe fn create_d3d12_device_context(device: u64) -> Result<AVHWDeviceContext, BridgeError> {
let device = usize::try_from(device)
.map_err(|_| BridgeError::InvalidInput("invalid D3D12 device pointer"))?
as *mut std::ffi::c_void;
if device.is_null() {
return Err(BridgeError::InvalidInput("D3D12 device is null"));
}
let mut context = alloc_hwdevice(ffi::AV_HWDEVICE_TYPE_D3D12VA, "D3D12")?;
let device_context = context.data.cast::<ffi::AVHWDeviceContext>();
let result = unsafe { alpha2_d3d12_device_init(device_context, device) };
if result < 0 {
return Err(BridgeError::FfmpegCode(result));
}
context.init().map_err(BridgeError::Ffmpeg)?;
Ok(context)
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
#[must_use]
pub fn d3d12_get_format_callback()
-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
alpha2_d3d12_get_format
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
pub fn d3d12_frame_info(frame: &AVFrame) -> Result<D3D12FrameInfo, BridgeError> {
let mut descriptor = D3D12FrameDescriptor::default();
let result = unsafe { alpha2_d3d12_frame_info(frame.as_ptr(), &raw mut descriptor) };
if result != 0 {
return Err(BridgeError::FfmpegCode(result));
}
Ok(D3D12FrameInfo::from_descriptor(&descriptor))
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
pub fn d3d12_frame_fence_value(frame: &AVFrame) -> Result<u64, BridgeError> {
let mut value = 0;
let result = unsafe { alpha2_d3d12_frame_fence_value(frame.as_ptr(), &raw mut value) };
if result != 0 {
return Err(BridgeError::FfmpegCode(result));
}
Ok(value)
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
pub struct D3D12EncodeFrameAccess<'a> {
frame: &'a mut AVFrame,
info: D3D12FrameInfo,
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
impl fmt::Debug for D3D12EncodeFrameAccess<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("D3D12EncodeFrameAccess")
.field("info", &self.info)
.finish_non_exhaustive()
}
}
#[cfg(all(target_os = "windows", feature = "hwdevice"))]
impl<'a> D3D12EncodeFrameAccess<'a> {
pub fn begin(frame: &'a mut AVFrame) -> Result<Self, BridgeError> {
let info = d3d12_frame_info(frame)?;
Ok(Self { frame, info })
}
#[must_use]
pub const fn info(&self) -> &D3D12FrameInfo {
&self.info
}
pub fn retain_frame(&self) -> Result<AVFrame, BridgeError> {
let raw = unsafe { ffi::av_frame_clone(self.frame.as_ptr()) };
let raw = std::ptr::NonNull::new(raw)
.ok_or(BridgeError::InvalidInput("cannot retain encode frame"))?;
Ok(unsafe { AVFrame::from_raw(raw) })
}
pub fn commit(&mut self, fence_value: u64) -> Result<(), BridgeError> {
let result =
unsafe { alpha2_d3d12_frame_set_fence_value(self.frame.as_mut_ptr(), fence_value) };
if result != 0 {
return Err(BridgeError::FfmpegCode(result));
}
self.info.fence_value = fence_value;
Ok(())
}
}
#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
use interop::Alpha2VideoToolboxFrameInfo as VideoToolboxFrameDescriptor;
#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
#[cfg_attr(target_os = "linux", allow(dead_code))]
unsafe extern "C" {
fn alpha2_videotoolbox_get_format(
context: *mut ffi::AVCodecContext,
formats: *const ffi::AVPixelFormat,
) -> ffi::AVPixelFormat;
fn alpha2_videotoolbox_frame_info(
frame: *const ffi::AVFrame,
output: *mut VideoToolboxFrameDescriptor,
) -> i32;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct VideoToolboxFrameInfo {
pub pixel_buffer: u64,
pub width: i32,
pub height: i32,
pub software_format: i32,
}
impl VideoToolboxFrameInfo {
#[cfg_attr(
not(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice")),
allow(dead_code)
)]
const fn from_descriptor(descriptor: &interop::Alpha2VideoToolboxFrameInfo) -> Self {
Self {
pixel_buffer: descriptor.pixel_buffer,
width: descriptor.width,
height: descriptor.height,
software_format: descriptor.software_format,
}
}
}
#[cfg(all(any(target_os = "linux", target_os = "macos"), feature = "hwdevice"))]
#[cfg_attr(target_os = "linux", allow(dead_code))]
fn read_videotoolbox_frame_info(frame: &AVFrame) -> Result<VideoToolboxFrameInfo, BridgeError> {
let mut descriptor = VideoToolboxFrameDescriptor::default();
let result = unsafe { alpha2_videotoolbox_frame_info(frame.as_ptr(), &raw mut descriptor) };
if result != 0 {
return Err(BridgeError::InvalidInput(
"frame is not a VideoToolbox frame with a pixel buffer",
));
}
Ok(VideoToolboxFrameInfo::from_descriptor(&descriptor))
}
#[cfg(all(target_os = "macos", feature = "hwdevice"))]
pub struct VideoToolboxFrameLease {
frame: Mutex<AVFrame>,
}
#[cfg(all(target_os = "macos", feature = "hwdevice"))]
impl fmt::Debug for VideoToolboxFrameLease {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("VideoToolboxFrameLease")
.finish_non_exhaustive()
}
}
#[cfg(all(target_os = "macos", feature = "hwdevice"))]
impl VideoToolboxFrameLease {
#[must_use]
pub fn new(frame: AVFrame) -> Self {
Self {
frame: Mutex::new(frame),
}
}
pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
let frame = self
.frame
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
download_hardware_frame(&frame).map(|frame| DecodedFrame {
frame,
written: true,
})
}
pub fn info(&self) -> Result<VideoToolboxFrameInfo, BridgeError> {
let frame = self
.frame
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
read_videotoolbox_frame_info(&frame)
}
}
#[cfg(all(target_os = "macos", feature = "hwdevice"))]
#[must_use]
pub fn videotoolbox_get_format_callback()
-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
alpha2_videotoolbox_get_format
}
#[cfg(all(target_os = "macos", feature = "hwdevice"))]
pub fn videotoolbox_frame_info(frame: &AVFrame) -> Result<VideoToolboxFrameInfo, BridgeError> {
read_videotoolbox_frame_info(frame)
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub const MAX_VULKAN_PLANES: usize = interop::ALPHA2_MAX_VULKAN_PLANES as usize;
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
#[derive(Clone, Debug)]
pub struct VulkanDeviceInfo {
pub instance: u64,
pub physical_device: u64,
pub device: u64,
pub graphics_queue_family: u32,
pub graphics_queue_count: u32,
pub decode_queue_family: u32,
pub decode_queue_count: u32,
pub decode_video_caps: u32,
pub encode_queue_family: u32,
pub encode_queue_count: u32,
pub encode_video_caps: u32,
pub core_features: [u32; 4],
pub synchronization2: u32,
pub sampler_ycbcr_conversion: u32,
pub video_maintenance1: u32,
pub instance_extensions: Vec<String>,
pub device_extensions: Vec<String>,
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
use interop::Alpha2VulkanDeviceInfo as VulkanDeviceDescriptor;
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub struct VulkanQueueLock {
device: AVHWDeviceContext,
queue_family: u32,
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
unsafe impl Sync for VulkanQueueLock {}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl fmt::Debug for VulkanQueueLock {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("VulkanQueueLock")
.field("queue_family", &self.queue_family)
.finish_non_exhaustive()
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl VulkanQueueLock {
#[allow(clippy::cast_ptr_alignment)]
pub fn new(device: AVHWDeviceContext, queue_family: u32) -> Result<Self, BridgeError> {
let lockable = unsafe {
alpha2_vulkan_queue_lockable(
device.data.cast::<ffi::AVHWDeviceContext>().cast_const(),
queue_family,
)
};
if lockable == 0 {
return Err(BridgeError::InvalidInput(
"Vulkan queue lock needs a family of a device the bridge initialized",
));
}
Ok(Self {
device,
queue_family,
})
}
#[must_use]
pub fn lock(&self) -> VulkanQueueGuard<'_> {
unsafe { self.raw_lock() };
VulkanQueueGuard {
lock: self,
_owning_thread: std::marker::PhantomData,
}
}
#[allow(clippy::cast_ptr_alignment)]
pub unsafe fn raw_lock(&self) {
unsafe {
alpha2_vulkan_queue_lock(
self.device.data.cast::<ffi::AVHWDeviceContext>(),
self.queue_family,
);
}
}
#[allow(clippy::cast_ptr_alignment)]
pub unsafe fn raw_unlock(&self) {
unsafe {
alpha2_vulkan_queue_unlock(
self.device.data.cast::<ffi::AVHWDeviceContext>(),
self.queue_family,
);
}
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
#[derive(Debug)]
pub struct VulkanQueueGuard<'a> {
lock: &'a VulkanQueueLock,
_owning_thread: std::marker::PhantomData<*const ()>,
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl Drop for VulkanQueueGuard<'_> {
fn drop(&mut self) {
unsafe { self.lock.raw_unlock() };
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
unsafe extern "C" {
fn alpha2_vulkan_queue_lockable(
context: *const ffi::AVHWDeviceContext,
queue_family: u32,
) -> i32;
fn alpha2_vulkan_queue_lock(context: *mut ffi::AVHWDeviceContext, queue_family: u32);
fn alpha2_vulkan_queue_unlock(context: *mut ffi::AVHWDeviceContext, queue_family: u32);
fn alpha2_vulkan_device_init(
context: *mut ffi::AVHWDeviceContext,
info: *const VulkanDeviceDescriptor,
) -> i32;
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
#[allow(clippy::cast_ptr_alignment)]
pub unsafe fn create_vulkan_device_context(
info: &VulkanDeviceInfo,
) -> Result<AVHWDeviceContext, BridgeError> {
let instance = info
.instance_extensions
.iter()
.map(|s| CString::new(s.as_str()))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| BridgeError::InvalidInput("Vulkan extension contains NUL"))?;
let device = info
.device_extensions
.iter()
.map(|s| CString::new(s.as_str()))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| BridgeError::InvalidInput("Vulkan extension contains NUL"))?;
let instance_ptrs = instance.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
let device_ptrs = device.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
let raw = VulkanDeviceDescriptor {
instance: info.instance,
physical_device: info.physical_device,
device: info.device,
graphics_queue_family: info.graphics_queue_family,
graphics_queue_count: info.graphics_queue_count,
decode_queue_family: info.decode_queue_family,
decode_queue_count: info.decode_queue_count,
decode_video_caps: info.decode_video_caps,
encode_queue_family: info.encode_queue_family,
encode_queue_count: info.encode_queue_count,
encode_video_caps: info.encode_video_caps,
shader_image_gather_extended: info.core_features[0],
fragment_stores_and_atomics: info.core_features[1],
shader_int64: info.core_features[2],
vertex_pipeline_stores_and_atomics: info.core_features[3],
synchronization2: info.synchronization2,
sampler_ycbcr_conversion: info.sampler_ycbcr_conversion,
video_maintenance1: info.video_maintenance1,
instance_extensions: instance_ptrs.as_ptr(),
instance_extension_count: u32::try_from(instance_ptrs.len())
.map_err(|_| BridgeError::InvalidInput("too many Vulkan extensions"))?,
device_extensions: device_ptrs.as_ptr(),
device_extension_count: u32::try_from(device_ptrs.len())
.map_err(|_| BridgeError::InvalidInput("too many Vulkan extensions"))?,
};
let mut context = alloc_hwdevice(ffi::AV_HWDEVICE_TYPE_VULKAN, "Vulkan")?;
let device_context = context.data.cast::<ffi::AVHWDeviceContext>();
let result = unsafe { alpha2_vulkan_device_init(device_context, &raw const raw) };
if result < 0 {
return Err(BridgeError::FfmpegCode(result));
}
context.init().map_err(BridgeError::Ffmpeg)?;
Ok(context)
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VulkanFrameInfo {
pub hardware_format: i32,
pub software_format: i32,
pub width: i32,
pub height: i32,
pub coded_width: i32,
pub coded_height: i32,
pub plane_count: u32,
pub sampled: bool,
pub instance: u64,
pub physical_device: u64,
pub device: u64,
pub images: [u64; MAX_VULKAN_PLANES],
pub formats: [u32; MAX_VULKAN_PLANES],
pub layouts: [u32; MAX_VULKAN_PLANES],
pub access: [u32; MAX_VULKAN_PLANES],
pub semaphores: [u64; MAX_VULKAN_PLANES],
pub semaphore_values: [u64; MAX_VULKAN_PLANES],
pub queue_families: [u32; MAX_VULKAN_PLANES],
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub type VulkanFrameDescriptor = interop::Alpha2VulkanFrameInfo;
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
#[repr(transparent)]
#[derive(Default)]
pub struct VulkanFrameAccess(interop::Alpha2VulkanFrameAccess);
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl VulkanFrameAccess {
#[must_use]
pub const fn frame(&self) -> &VulkanFrameDescriptor {
&self.0.frame
}
pub const fn frame_mut(&mut self) -> &mut VulkanFrameDescriptor {
&mut self.0.frame
}
#[must_use]
pub const fn allocator(&self) -> u64 {
self.0.allocator
}
#[must_use]
pub const fn queue_family(&self) -> u32 {
self.0.queue_family
}
#[must_use]
pub const fn layer_count(&self) -> u32 {
self.0.layer_count
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
unsafe extern "C" {
fn alpha2_vulkan_get_format(
context: *mut ffi::AVCodecContext,
formats: *const ffi::AVPixelFormat,
) -> ffi::AVPixelFormat;
fn alpha2_vulkan_frame_info(
frame: *const ffi::AVFrame,
output: *mut VulkanFrameDescriptor,
) -> i32;
fn alpha2_vulkan_frames_add_usage(frames: *mut ffi::AVBufferRef, usage: u32) -> i32;
fn alpha2_vulkan_frames_usage(frames: *const ffi::AVBufferRef) -> u32;
fn alpha2_vulkan_frame_access_begin(
frame: *mut ffi::AVFrame,
output: *mut VulkanFrameAccess,
) -> i32;
fn alpha2_vulkan_frame_access_end(
begun: *const interop::Alpha2VulkanFrameAccess,
updated: *const VulkanFrameAccess,
);
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
const fn vulkan_frame_refusal(status: i32) -> BridgeError {
BridgeError::InvalidInput(match status {
-1 => "frame is not a Vulkan hardware frame",
-2 => "Vulkan frame has no hardware frames or device context",
-3 => "Vulkan frame has no images",
-4 => "Vulkan frames context has no frame or queue locks, or no layers",
-5 => "Vulkan device has no graphics or compute queue",
_ => "Vulkan frame could not be read",
})
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub struct VulkanFrameLease {
frame: Mutex<AVFrame>,
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl fmt::Debug for VulkanFrameLease {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("VulkanFrameLease")
.finish_non_exhaustive()
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl VulkanFrameLease {
#[must_use]
pub fn new(frame: AVFrame) -> Self {
Self {
frame: Mutex::new(frame),
}
}
pub fn download(&self) -> Result<DecodedFrame, BridgeError> {
let frame = self
.frame
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
download_hardware_frame(&frame).map(|frame| DecodedFrame {
frame,
written: true,
})
}
pub fn info(&self) -> Result<VulkanFrameInfo, BridgeError> {
let frame = self
.frame
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
vulkan_frame_info(&frame)
}
pub fn lock_access(&self) -> Result<VulkanFrameAccessGuard<'_>, BridgeError> {
let mut frame = self
.frame
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let mut access = VulkanFrameAccess::default();
let result =
unsafe { alpha2_vulkan_frame_access_begin(frame.as_mut_ptr(), &raw mut access) };
if result != 0 {
return Err(vulkan_frame_refusal(result));
}
Ok(VulkanFrameAccessGuard {
_frame: frame,
begun: access.0,
access,
committed: false,
})
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub struct VulkanFrameAccessGuard<'a> {
_frame: MutexGuard<'a, AVFrame>,
begun: interop::Alpha2VulkanFrameAccess,
access: VulkanFrameAccess,
committed: bool,
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl VulkanFrameAccessGuard<'_> {
#[must_use]
pub const fn access(&self) -> &VulkanFrameAccess {
&self.access
}
#[must_use]
pub const unsafe fn access_mut(&mut self) -> &mut VulkanFrameAccess {
&mut self.access
}
pub fn commit(&mut self) {
self.committed = true;
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl Drop for VulkanFrameAccessGuard<'_> {
fn drop(&mut self) {
let updated = if self.committed {
&raw const self.access
} else {
ptr::null()
};
unsafe { alpha2_vulkan_frame_access_end(&raw const self.begun, updated) };
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub struct VulkanEncodeFrameAccess<'a> {
frame: &'a mut AVFrame,
begun: interop::Alpha2VulkanFrameAccess,
access: VulkanFrameAccess,
committed: bool,
_owning_thread: std::marker::PhantomData<*const ()>,
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl<'a> VulkanEncodeFrameAccess<'a> {
pub fn retain_frame(&self) -> Result<AVFrame, BridgeError> {
let raw = unsafe { ffi::av_frame_clone(self.frame.as_ptr()) };
let raw = std::ptr::NonNull::new(raw)
.ok_or(BridgeError::InvalidInput("cannot retain encode frame"))?;
Ok(unsafe { AVFrame::from_raw(raw) })
}
pub fn begin(frame: &'a mut AVFrame) -> Result<Self, BridgeError> {
let mut access = VulkanFrameAccess::default();
let result =
unsafe { alpha2_vulkan_frame_access_begin(frame.as_mut_ptr(), &raw mut access) };
if result != 0 {
return Err(vulkan_frame_refusal(result));
}
Ok(Self {
frame,
begun: access.0,
access,
committed: false,
_owning_thread: std::marker::PhantomData,
})
}
#[must_use]
pub const fn access(&self) -> &VulkanFrameAccess {
&self.access
}
#[must_use]
pub const unsafe fn access_mut(&mut self) -> &mut VulkanFrameAccess {
&mut self.access
}
pub fn commit(&mut self) {
self.committed = true;
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
impl Drop for VulkanEncodeFrameAccess<'_> {
fn drop(&mut self) {
let updated = if self.committed {
&raw const self.access
} else {
ptr::null()
};
unsafe { alpha2_vulkan_frame_access_end(&raw const self.begun, updated) };
}
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
#[must_use]
pub fn vulkan_get_format_callback()
-> unsafe extern "C" fn(*mut ffi::AVCodecContext, *const ffi::AVPixelFormat) -> ffi::AVPixelFormat {
alpha2_vulkan_get_format
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub fn vulkan_frame_info(frame: &AVFrame) -> Result<VulkanFrameInfo, BridgeError> {
let mut raw = VulkanFrameDescriptor::default();
let result = unsafe { alpha2_vulkan_frame_info(frame.as_ptr(), &raw mut raw) };
if result != 0 {
return Err(vulkan_frame_refusal(result));
}
Ok(VulkanFrameInfo {
hardware_format: raw.hardware_format,
software_format: raw.software_format,
width: raw.width,
height: raw.height,
coded_width: raw.coded_width,
coded_height: raw.coded_height,
plane_count: raw.plane_count,
sampled: raw.sampled != 0,
instance: raw.instance,
physical_device: raw.physical_device,
device: raw.device,
images: raw.images,
formats: raw.formats,
layouts: raw.layouts,
access: raw.access,
semaphores: raw.semaphores,
semaphore_values: raw.semaphore_values,
queue_families: raw.queue_families,
})
}
#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
fn hwdevice_available(kind: ffi::AVHWDeviceType) -> bool {
let mut each = ffi::AV_HWDEVICE_TYPE_NONE;
loop {
each = unsafe { ffi::av_hwdevice_iterate_types(each) };
if each == ffi::AV_HWDEVICE_TYPE_NONE {
return false;
}
if each == kind {
return true;
}
}
}
#[cfg(all(any(target_os = "linux", target_os = "windows"), feature = "hwdevice"))]
fn alloc_hwdevice(
kind: ffi::AVHWDeviceType,
name: &'static str,
) -> Result<AVHWDeviceContext, BridgeError> {
if !hwdevice_available(kind) {
return Err(BridgeError::MissingHwDevice(name));
}
Ok(AVHWDeviceContext::alloc(kind))
}
#[derive(Debug)]
#[non_exhaustive]
pub enum BridgeError {
InvalidInput(&'static str),
Ffmpeg(RsmpegError),
FfmpegCode(i32),
NativeGpu(NativeGpuFailure),
MissingHwDevice(&'static str),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct YuvPlaneLayout {
pub chroma_shift_x: u8,
pub chroma_shift_y: u8,
pub bit_depth: u8,
pub interleaved_chroma: bool,
pub msb_aligned: bool,
}
impl YuvPlaneLayout {
#[must_use]
pub const fn bytes_per_sample(self) -> u32 {
if self.bit_depth > 8 { 2 } else { 1 }
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct FramePlane {
pub bytes: bytes::Bytes,
pub stride: u32,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct YuvPlanes {
pub layout: YuvPlaneLayout,
pub width: u32,
pub height: u32,
pub planes: Vec<FramePlane>,
}
impl fmt::Display for BridgeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidInput(context) => {
write!(formatter, "invalid media buffer input: {context}")
}
Self::Ffmpeg(error) => write!(formatter, "{error}"),
Self::FfmpegCode(code) => write!(formatter, "{}", RsmpegError::AVError(*code)),
Self::NativeGpu(failure) => write!(formatter, "{failure}"),
Self::MissingHwDevice(kind) => {
write!(formatter, "the loaded FFmpeg has no {kind} hardware device")
}
}
}
}
pub fn configure_colorspace(
context: &mut SwsContext,
source_coefficients: i32,
source_full_range: bool,
destination_coefficients: i32,
destination_full_range: bool,
) -> Result<(), BridgeError> {
let result = unsafe {
ffi::sws_setColorspaceDetails(
context.as_mut_ptr(),
ffi::sws_getCoefficients(source_coefficients),
i32::from(source_full_range),
ffi::sws_getCoefficients(destination_coefficients),
i32::from(destination_full_range),
0,
1 << 16,
1 << 16,
)
};
if result < 0 {
Err(BridgeError::FfmpegCode(result))
} else {
Ok(())
}
}
pub fn scale_frame_packed(
context: &mut SwsContext,
frame: &AVFrame,
target: ffi::AVPixelFormat,
output: &mut [u8],
) -> Result<(), BridgeError> {
let size = unsafe { ffi::av_image_get_buffer_size(target, frame.width, frame.height, 1) };
if size < 0 {
return Err(BridgeError::FfmpegCode(size));
}
if usize::try_from(size).ok() != Some(output.len()) {
return Err(BridgeError::InvalidInput(
"packed output is not the size of the converted frame",
));
}
let mut data = [ptr::null_mut::<u8>(); 4];
let mut linesize = [0; 4];
let filled = unsafe {
ffi::av_image_fill_arrays(
data.as_mut_ptr(),
linesize.as_mut_ptr(),
output.as_mut_ptr(),
target,
frame.width,
frame.height,
1,
)
};
if filled < 0 {
return Err(BridgeError::FfmpegCode(filled));
}
let rows = unsafe {
ffi::sws_scale(
context.as_mut_ptr(),
frame.data.as_ptr().cast(),
frame.linesize.as_ptr(),
0,
frame.height,
data.as_ptr(),
linesize.as_ptr(),
)
};
if rows < 0 {
Err(BridgeError::FfmpegCode(rows))
} else {
Ok(())
}
}
impl Error for BridgeError {}
impl BridgeError {
#[must_use]
pub const fn gpu_fault(&self) -> Option<GpuFault> {
match self {
Self::NativeGpu(failure) => failure.fault,
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum NativeGpuBackend {
Vulkan,
D3D12,
Metal,
}
impl NativeGpuBackend {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Vulkan => "Vulkan",
Self::D3D12 => "D3D12",
Self::Metal => "Metal",
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct NativeGpuFailure {
pub backend: NativeGpuBackend,
pub operation: &'static str,
pub step: &'static str,
pub status: i32,
pub fault: Option<GpuFault>,
}
impl fmt::Display for NativeGpuFailure {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
backend,
operation,
step,
status,
..
} = *self;
let backend = backend.as_str();
if self.fault == Some(GpuFault::Timeout) {
write!(
formatter,
"{backend} {operation} ({step}): {}",
alpha2_media::GPU_TIMEOUT_MESSAGE
)?;
} else {
write!(formatter, "{backend} {operation} failed at {step}")?;
}
if status == 0 {
return Ok(());
}
match self.backend {
NativeGpuBackend::Vulkan => write!(formatter, " (VkResult {status})"),
NativeGpuBackend::D3D12 => write!(formatter, " (HRESULT {status:#010X})"),
NativeGpuBackend::Metal => write!(formatter, " (CVReturn {status})"),
}
}
}
impl From<RsmpegError> for BridgeError {
fn from(error: RsmpegError) -> Self {
Self::Ffmpeg(error)
}
}
#[must_use]
pub fn runtime_version() -> String {
unsafe { CStr::from_ptr(ffi::av_version_info()) }
.to_string_lossy()
.into_owned()
}
#[must_use]
pub fn runtime_configuration() -> String {
unsafe { CStr::from_ptr(ffi::avcodec_configuration()) }
.to_string_lossy()
.into_owned()
}
#[must_use]
pub fn muxer_accepts_codec(format: &CStr, codec: ffi::AVCodecID) -> Option<bool> {
let muxer = unsafe { ffi::av_guess_format(format.as_ptr(), ptr::null(), ptr::null()) };
if muxer.is_null() {
return None;
}
let accepted = unsafe {
ffi::avformat_query_codec(
muxer,
codec,
i32::try_from(ffi::FF_COMPLIANCE_NORMAL).unwrap_or(0),
)
};
Some(accepted == 1)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct SampleProfile {
pub is_yuv_420: bool,
pub bit_depth: u8,
pub has_alpha: bool,
pub is_rgb: bool,
}
#[must_use]
pub fn sample_profile(format: ffi::AVPixelFormat) -> Option<SampleProfile> {
if format == ffi::AV_PIX_FMT_NONE {
return None;
}
let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
let components = usize::from(descriptor.nb_components.min(4));
Some(SampleProfile {
is_yuv_420: descriptor.log2_chroma_w == 1
&& descriptor.log2_chroma_h == 1
&& descriptor.nb_components >= 3
&& descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB) == 0,
bit_depth: descriptor.comp[..components]
.iter()
.map(|component| u8::try_from(component.depth).unwrap_or(u8::MAX))
.max()
.unwrap_or(8),
has_alpha: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_ALPHA) != 0,
is_rgb: descriptor.flags & u64::from(ffi::AV_PIX_FMT_FLAG_RGB | ffi::AV_PIX_FMT_FLAG_PAL)
!= 0,
})
}
#[must_use]
pub fn pixel_format_name(format: ffi::AVPixelFormat) -> Option<String> {
let name = unsafe { ffi::av_get_pix_fmt_name(format) };
if name.is_null() {
None
} else {
Some(
unsafe { CStr::from_ptr(name) }
.to_string_lossy()
.into_owned(),
)
}
}
pub fn open_codec_context(
context: &mut rsmpeg::avcodec::AVCodecContext,
options: rsmpeg::avutil::AVDictionary,
) -> Result<Option<rsmpeg::avutil::AVDictionary>, RsmpegError> {
let mut dictionary = options.into_raw().as_ptr();
let status = unsafe {
ffi::avcodec_open2(
context.as_mut_ptr(),
ptr::null_mut(),
std::ptr::addr_of_mut!(dictionary),
)
};
let unused = ptr::NonNull::new(dictionary)
.map(|dictionary| unsafe { rsmpeg::avutil::AVDictionary::from_raw(dictionary) });
if status < 0 {
return Err(RsmpegError::from(status));
}
Ok(unused)
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub const VULKAN_ENCODE_INPUT_USAGE: u32 = 0x0000_0001 | 0x0000_0002 | 0x0000_0004 | 0x0000_4000;
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
pub fn vulkan_frames_add_usage(
frames: &mut rsmpeg::avutil::AVHWFramesContext,
usage: u32,
) -> Result<(), BridgeError> {
let status = unsafe { alpha2_vulkan_frames_add_usage(frames.as_mut_ptr(), usage) };
if status < 0 {
return Err(BridgeError::FfmpegCode(status));
}
Ok(())
}
#[cfg(all(target_os = "linux", feature = "hwdevice"))]
#[must_use]
pub fn vulkan_frames_usage(frames: &rsmpeg::avutil::AVHWFramesContext) -> u32 {
unsafe { alpha2_vulkan_frames_usage(frames.as_ptr()) }
}
#[must_use]
pub fn packet_bytes(packet: &rsmpeg::avcodec::AVPacket) -> Vec<u8> {
let size = usize::try_from(packet.size).unwrap_or(0);
if packet.data.is_null() || size == 0 {
return Vec::new();
}
unsafe { slice::from_raw_parts(packet.data.cast_const(), size) }.to_vec()
}
#[must_use]
pub fn codec_parameters_extradata(parameters: &rsmpeg::avcodec::AVCodecParameters) -> Vec<u8> {
let size = usize::try_from(parameters.extradata_size).unwrap_or(0);
if parameters.extradata.is_null() || size == 0 {
return Vec::new();
}
unsafe { slice::from_raw_parts(parameters.extradata.cast_const(), size) }.to_vec()
}
#[must_use]
pub fn packet_has_side_data(
packet: &rsmpeg::avcodec::AVPacket,
kind: ffi::AVPacketSideDataType,
) -> bool {
let mut size = 0_usize;
!unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) }.is_null()
}
#[must_use]
pub fn take_packet_side_data(
packet: &mut rsmpeg::avcodec::AVPacket,
kind: ffi::AVPacketSideDataType,
) -> Option<Vec<u8>> {
let mut size = 0_usize;
let data = unsafe { ffi::av_packet_get_side_data(packet.as_ptr(), kind, &raw mut size) };
if data.is_null() {
return None;
}
let bytes = unsafe { slice::from_raw_parts(data, size) }.to_vec();
let packet = packet.as_mut_ptr();
unsafe {
ffi::av_packet_side_data_remove(
(*packet).side_data,
&raw mut (*packet).side_data_elems,
kind,
);
}
Some(bytes)
}
pub fn set_packet_side_data(
packet: &mut rsmpeg::avcodec::AVPacket,
kind: ffi::AVPacketSideDataType,
data: &[u8],
) -> Result<(), BridgeError> {
let _ = take_packet_side_data(packet, kind);
let entry = unsafe { ffi::av_packet_new_side_data(packet.as_mut_ptr(), kind, data.len()) };
if entry.is_null() {
return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
}
unsafe { ptr::copy_nonoverlapping(data.as_ptr(), entry, data.len()) };
Ok(())
}
pub fn set_output_stream_extradata(
output: &mut rsmpeg::avformat::AVFormatContextOutput,
index: usize,
data: &[u8],
) -> Result<(), BridgeError> {
let parameters = output
.streams()
.get(index)
.ok_or(BridgeError::InvalidInput("the output has no such stream"))?
.codecpar;
let size = i32::try_from(data.len())
.map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
let buffer = unsafe { ffi::av_mallocz(data.len() + padding) }.cast::<u8>();
if buffer.is_null() {
return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
}
unsafe { ptr::copy_nonoverlapping(data.as_ptr(), buffer, data.len()) };
unsafe {
ffi::av_freep((&raw mut (*parameters).extradata).cast());
(*parameters).extradata = buffer;
(*parameters).extradata_size = size;
}
Ok(())
}
pub fn packet_from_bytes(data: &[u8]) -> Result<rsmpeg::avcodec::AVPacket, BridgeError> {
let size = i32::try_from(data.len())
.map_err(|_| BridgeError::InvalidInput("a packet is larger than FFmpeg holds"))?;
let mut packet = rsmpeg::avcodec::AVPacket::new();
let status = unsafe { ffi::av_new_packet(packet.as_mut_ptr(), size) };
if status < 0 {
return Err(BridgeError::FfmpegCode(status));
}
if !data.is_empty() {
unsafe { ptr::copy_nonoverlapping(data.as_ptr(), packet.data, data.len()) };
}
Ok(packet)
}
#[derive(Clone, Copy, Debug)]
pub struct VideoStreamDescription<'a> {
pub codec_id: ffi::AVCodecID,
pub width: i32,
pub height: i32,
pub pixel_format: ffi::AVPixelFormat,
pub profile: i32,
pub level: i32,
pub color_primaries: ffi::AVColorPrimaries,
pub color_trc: ffi::AVColorTransferCharacteristic,
pub color_space: ffi::AVColorSpace,
pub color_range: ffi::AVColorRange,
pub sample_aspect_ratio: ffi::AVRational,
pub frame_rate: ffi::AVRational,
pub bit_rate: i64,
pub extradata: &'a [u8],
}
pub fn video_codec_parameters(
description: &VideoStreamDescription<'_>,
) -> Result<rsmpeg::avcodec::AVCodecParameters, BridgeError> {
let extradata_size = i32::try_from(description.extradata.len())
.map_err(|_| BridgeError::InvalidInput("extradata is larger than FFmpeg holds"))?;
let mut parameters = rsmpeg::avcodec::AVCodecParameters::new();
let raw = parameters.as_mut_ptr();
unsafe {
(*raw).codec_type = ffi::AVMEDIA_TYPE_VIDEO;
(*raw).codec_id = description.codec_id;
(*raw).width = description.width;
(*raw).height = description.height;
(*raw).format = description.pixel_format;
(*raw).profile = description.profile;
(*raw).level = description.level;
(*raw).color_primaries = description.color_primaries;
(*raw).color_trc = description.color_trc;
(*raw).color_space = description.color_space;
(*raw).color_range = description.color_range;
(*raw).sample_aspect_ratio = description.sample_aspect_ratio;
(*raw).framerate = description.frame_rate;
(*raw).bit_rate = description.bit_rate;
(*raw).field_order = ffi::AV_FIELD_PROGRESSIVE;
}
if !description.extradata.is_empty() {
let padding = usize::try_from(ffi::AV_INPUT_BUFFER_PADDING_SIZE).unwrap_or(64);
let buffer = unsafe { ffi::av_mallocz(description.extradata.len() + padding) }.cast::<u8>();
if buffer.is_null() {
return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
}
unsafe {
ptr::copy_nonoverlapping(
description.extradata.as_ptr(),
buffer,
description.extradata.len(),
);
(*raw).extradata = buffer;
(*raw).extradata_size = extradata_size;
}
}
Ok(parameters)
}
pub fn bitstream_filter_list(
spec: &CStr,
parameters: &AVCodecParameters,
time_base: rsmpeg::avutil::AVRational,
) -> Result<rsmpeg::avcodec::AVBSFContext, BridgeError> {
let mut raw = ptr::null_mut();
let status = unsafe { ffi::av_bsf_list_parse_str(spec.as_ptr(), &raw mut raw) };
if status < 0 {
return Err(BridgeError::FfmpegCode(status));
}
let raw = ptr::NonNull::new(raw).ok_or(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)))?;
let mut context = unsafe { rsmpeg::avcodec::AVBSFContextUninit::from_raw(raw) };
let status = unsafe { ffi::avcodec_parameters_copy(context.par_in, parameters.as_ptr()) };
if status < 0 {
return Err(BridgeError::FfmpegCode(status));
}
context.set_time_base_in(time_base);
context.init().map_err(BridgeError::Ffmpeg)
}
pub fn set_stream_codec_tag(stream: &mut rsmpeg::avformat::AVStreamMut<'_>, tag: u32) {
let mut parameters = stream.codecpar_mut();
unsafe { (*parameters.as_mut_ptr()).codec_tag = tag };
}
pub fn set_stream_display_matrix(
stream: &mut rsmpeg::avformat::AVStreamMut<'_>,
display: DisplayRotation,
) -> Result<(), BridgeError> {
let mut parameters = stream.codecpar_mut();
let parameters = parameters.as_mut_ptr();
let entry = unsafe {
ffi::av_packet_side_data_new(
&raw mut (*parameters).coded_side_data,
&raw mut (*parameters).nb_coded_side_data,
ffi::AV_PKT_DATA_DISPLAYMATRIX,
size_of::<[i32; 9]>(),
0,
)
};
if entry.is_null() {
return Err(BridgeError::FfmpegCode(ffi::AVERROR(ffi::ENOMEM)));
}
let mut matrix = [0_i32; 9];
unsafe {
ffi::av_display_rotation_set(matrix.as_mut_ptr(), -display.counter_clockwise_degrees);
if display.flipped {
ffi::av_display_matrix_flip(matrix.as_mut_ptr(), 1, 0);
}
}
unsafe {
ptr::copy_nonoverlapping(
matrix.as_ptr().cast::<u8>(),
(*entry).data,
size_of::<[i32; 9]>(),
);
}
Ok(())
}
#[must_use]
pub fn hardware_device_type_name(device_type: ffi::AVHWDeviceType) -> Option<String> {
let name = unsafe { ffi::av_hwdevice_get_type_name(device_type) };
if name.is_null() {
None
} else {
Some(
unsafe { CStr::from_ptr(name) }
.to_string_lossy()
.into_owned(),
)
}
}
#[must_use]
pub fn yuv_layout(format: ffi::AVPixelFormat) -> Option<YuvPlaneLayout> {
if format == ffi::AV_PIX_FMT_NONE {
return None;
}
let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }?;
let foreign_endian = if cfg!(target_endian = "little") {
ffi::AV_PIX_FMT_FLAG_BE
} else {
0
};
let rejected = foreign_endian
| ffi::AV_PIX_FMT_FLAG_PAL
| ffi::AV_PIX_FMT_FLAG_BITSTREAM
| ffi::AV_PIX_FMT_FLAG_HWACCEL
| ffi::AV_PIX_FMT_FLAG_RGB
| ffi::AV_PIX_FMT_FLAG_ALPHA
| ffi::AV_PIX_FMT_FLAG_BAYER
| ffi::AV_PIX_FMT_FLAG_FLOAT
| ffi::AV_PIX_FMT_FLAG_XYZ;
if descriptor.flags & u64::from(rejected) != 0 || descriptor.nb_components != 3 {
return None;
}
let [luma, u, v, _] = descriptor.comp;
let depth = luma.depth;
if !(8..=16).contains(&depth) || u.depth != depth || v.depth != depth {
return None;
}
if descriptor.log2_chroma_w > 2 || descriptor.log2_chroma_h > 2 {
return None;
}
let bytes = if depth > 8 { 2 } else { 1 };
let shift = luma.shift;
let msb_aligned = match shift {
0 => false,
_ if depth > 8 && shift == 16 - depth => true,
_ => return None,
};
let planar = u.plane == 1
&& v.plane == 2
&& u.step == bytes
&& v.step == bytes
&& u.offset == 0
&& v.offset == 0;
let interleaved = u.plane == 1
&& v.plane == 1
&& u.step == 2 * bytes
&& v.step == 2 * bytes
&& u.offset == 0
&& v.offset == bytes;
if luma.plane != 0
|| luma.step != bytes
|| luma.offset != 0
|| u.shift != shift
|| v.shift != shift
|| !(planar || interleaved)
{
return None;
}
Some(YuvPlaneLayout {
chroma_shift_x: descriptor.log2_chroma_w,
chroma_shift_y: descriptor.log2_chroma_h,
bit_depth: u8::try_from(depth).ok()?,
interleaved_chroma: interleaved,
msb_aligned,
})
}
pub fn yuv_planes(frame: &AVFrame) -> Result<Option<YuvPlanes>, BridgeError> {
frame_yuv_planes(frame, false)
}
pub struct DecodedFrame {
frame: AVFrame,
written: bool,
}
impl DecodedFrame {
pub fn receive(decoder: &mut AVCodecContext) -> Result<Self, RsmpegError> {
let written = decoder_frames_are_initialized(decoder);
let frame = decoder.receive_frame()?;
Ok(Self { frame, written })
}
pub fn yuv_planes(&self) -> Result<Option<YuvPlanes>, BridgeError> {
frame_yuv_planes(&self.frame, self.written)
}
pub fn set_pts(&mut self, pts: i64) {
self.frame.set_pts(pts);
}
#[must_use]
pub fn into_frame(self) -> AVFrame {
self.frame
}
}
impl std::ops::Deref for DecodedFrame {
type Target = AVFrame;
fn deref(&self) -> &AVFrame {
&self.frame
}
}
impl fmt::Debug for DecodedFrame {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("DecodedFrame")
.field("format", &self.frame.format)
.field("width", &self.frame.width)
.field("height", &self.frame.height)
.field("pts", &self.frame.pts)
.field("written", &self.written)
.finish_non_exhaustive()
}
}
fn decoder_frames_are_initialized(decoder: &AVCodecContext) -> bool {
static DEFAULT_GET_BUFFER2: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
let default = *DEFAULT_GET_BUFFER2.get_or_init(|| {
unsafe {
let mut context = ffi::avcodec_alloc_context3(ptr::null());
let get_buffer2 = context
.as_ref()
.and_then(|context| context.get_buffer2)
.map_or(0, |function| function as usize);
ffi::avcodec_free_context(&raw mut context);
get_buffer2
}
});
let wrapped =
unsafe { decoder.codec.as_ref() }.is_none_or(|codec| !codec.wrapper_name.is_null());
let get_buffer2 = decoder.get_buffer2.map_or(0, |function| function as usize);
!wrapped && default != 0 && get_buffer2 == default
}
fn frame_yuv_planes(frame: &AVFrame, share: bool) -> Result<Option<YuvPlanes>, BridgeError> {
let Some(layout) = yuv_layout(frame.format) else {
return Ok(None);
};
let width =
u32::try_from(frame.width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
let height = u32::try_from(frame.height)
.map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
if width == 0 || height == 0 {
return Err(BridgeError::InvalidInput("empty YUV frame"));
}
let bytes = layout.bytes_per_sample();
let chroma_width = width.div_ceil(1 << layout.chroma_shift_x);
let chroma_height = height.div_ceil(1 << layout.chroma_shift_y);
let shared =
(share && !frame.buf[0].is_null()).then(|| std::sync::Arc::new(SharedFrame(frame.clone())));
let copy = |index: usize,
samples: u32,
pairs: u32,
rows: u32,
context: &'static str|
-> Result<FramePlane, BridgeError> {
let row_bytes = samples
.checked_mul(bytes)
.and_then(|row| row.checked_mul(pairs))
.ok_or(BridgeError::InvalidInput(context))?;
if let Some(view) = shared
.as_ref()
.and_then(|frame| PlaneView::new(frame, index, row_bytes, rows))
{
let stride =
u32::try_from(view.stride).map_err(|_| BridgeError::InvalidInput(context))?;
return Ok(FramePlane {
bytes: bytes::Bytes::from_owner(view),
stride,
width: samples,
height: rows,
});
}
Ok(FramePlane {
bytes: copy_video_plane(
frame.data[index],
frame.linesize[index],
row_bytes,
rows,
context,
)?
.into(),
stride: row_bytes,
width: samples,
height: rows,
})
};
let mut planes = vec![copy(0, width, 1, height, "invalid Y plane")?];
if layout.interleaved_chroma {
planes.push(copy(1, chroma_width, 2, chroma_height, "invalid UV plane")?);
} else {
planes.push(copy(1, chroma_width, 1, chroma_height, "invalid U plane")?);
planes.push(copy(2, chroma_width, 1, chroma_height, "invalid V plane")?);
}
Ok(Some(YuvPlanes {
layout,
width,
height,
planes,
}))
}
struct SharedFrame(AVFrame);
unsafe impl Sync for SharedFrame {}
struct PlaneView {
frame: std::sync::Arc<SharedFrame>,
data: *const u8,
len: usize,
stride: usize,
}
unsafe impl Send for PlaneView {}
impl PlaneView {
fn new(
frame: &std::sync::Arc<SharedFrame>,
index: usize,
row_bytes: u32,
rows: u32,
) -> Option<Self> {
let inner = &frame.0;
let data = inner.data[index].cast_const();
let stride = usize::try_from(inner.linesize[index]).ok()?;
let row_bytes = usize::try_from(row_bytes).ok()?;
if data.is_null() || stride < row_bytes || rows == 0 {
return None;
}
let len = stride.checked_mul(usize::try_from(rows).ok()?)?;
let start = data as usize;
let end = start.checked_add(len)?;
let inside = inner.buf.iter().any(|buffer| {
let Some(buffer) = (unsafe { buffer.as_ref() }) else {
return false;
};
let buffer_start = buffer.data as usize;
buffer_start
.checked_add(buffer.size)
.is_some_and(|buffer_end| buffer_start <= start && end <= buffer_end)
});
inside.then(|| Self {
frame: std::sync::Arc::clone(frame),
data,
len,
stride,
})
}
}
impl AsRef<[u8]> for PlaneView {
fn as_ref(&self) -> &[u8] {
let _ = &self.frame;
unsafe { slice::from_raw_parts(self.data, self.len) }
}
}
fn copy_video_plane(
data: *const u8,
linesize: i32,
width: u32,
height: u32,
context: &'static str,
) -> Result<Vec<u8>, BridgeError> {
let row_bytes = usize::try_from(width).map_err(|_| BridgeError::InvalidInput(context))?;
let rows = usize::try_from(height).map_err(|_| BridgeError::InvalidInput(context))?;
let stride =
usize::try_from(linesize.unsigned_abs()).map_err(|_| BridgeError::InvalidInput(context))?;
if data.is_null() || linesize == 0 || stride < row_bytes {
return Err(BridgeError::InvalidInput(context));
}
let byte_len = row_bytes
.checked_mul(rows)
.ok_or(BridgeError::InvalidInput(context))?;
let mut output = Vec::with_capacity(byte_len);
let signed_stride =
isize::try_from(linesize).map_err(|_| BridgeError::InvalidInput(context))?;
for row in 0..rows {
let row = isize::try_from(row).map_err(|_| BridgeError::InvalidInput(context))?;
let offset = row
.checked_mul(signed_stride)
.ok_or(BridgeError::InvalidInput(context))?;
let source = unsafe { slice::from_raw_parts(data.offset(offset), row_bytes) };
output.extend_from_slice(source);
}
debug_assert_eq!(output.len(), byte_len);
Ok(output)
}
pub fn rgba_frame(bytes: &[u8], width: i32, height: i32) -> Result<AVFrame, BridgeError> {
let row_bytes = usize::try_from(width)
.ok()
.and_then(|value| value.checked_mul(4))
.ok_or(BridgeError::InvalidInput("RGBA row width overflow"))?;
let rows =
usize::try_from(height).map_err(|_| BridgeError::InvalidInput("negative RGBA height"))?;
let expected = row_bytes
.checked_mul(rows)
.ok_or(BridgeError::InvalidInput("RGBA frame size overflow"))?;
if bytes.len() != expected {
return Err(BridgeError::InvalidInput("RGBA byte length mismatch"));
}
let mut frame = AVFrame::new();
frame.set_format(ffi::AV_PIX_FMT_RGBA);
frame.set_width(width);
frame.set_height(height);
frame.get_buffer(1)?;
frame.make_writable()?;
let linesize = usize::try_from(frame.linesize[0])
.map_err(|_| BridgeError::InvalidInput("negative RGBA linesize"))?;
if frame.data[0].is_null() || linesize < row_bytes {
return Err(BridgeError::InvalidInput(
"RGBA destination plane is too small",
));
}
unsafe {
for row in 0..rows {
let destination_offset = row.checked_mul(linesize).ok_or(BridgeError::InvalidInput(
"RGBA destination offset overflow",
))?;
let source_offset = row
.checked_mul(row_bytes)
.ok_or(BridgeError::InvalidInput("RGBA source offset overflow"))?;
let destination =
slice::from_raw_parts_mut(frame.data[0].add(destination_offset), row_bytes);
destination.copy_from_slice(&bytes[source_offset..source_offset + row_bytes]);
}
}
Ok(frame)
}
#[allow(clippy::too_many_arguments)]
pub fn yuv420p_frame(
y: &[u8],
y_stride: u32,
u: &[u8],
u_stride: u32,
v: &[u8],
v_stride: u32,
width: i32,
height: i32,
) -> Result<AVFrame, BridgeError> {
let width =
u32::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
let height =
u32::try_from(height).map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
if width == 0 || height == 0 {
return Err(BridgeError::InvalidInput("empty YUV frame"));
}
let chroma_width = width.div_ceil(2);
let chroma_height = height.div_ceil(2);
validate_source_plane(y, y_stride, width, height, "invalid Y source plane")?;
validate_source_plane(
u,
u_stride,
chroma_width,
chroma_height,
"invalid U source plane",
)?;
validate_source_plane(
v,
v_stride,
chroma_width,
chroma_height,
"invalid V source plane",
)?;
let mut frame = AVFrame::new();
frame.set_format(ffi::AV_PIX_FMT_YUV420P);
frame.set_width(i32::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV width"))?);
frame.set_height(i32::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV height"))?);
frame.get_buffer(1)?;
frame.make_writable()?;
copy_plane_to_frame(y, y_stride, width, height, frame.data[0], frame.linesize[0])?;
copy_plane_to_frame(
u,
u_stride,
chroma_width,
chroma_height,
frame.data[1],
frame.linesize[1],
)?;
copy_plane_to_frame(
v,
v_stride,
chroma_width,
chroma_height,
frame.data[2],
frame.linesize[2],
)?;
Ok(frame)
}
#[must_use]
pub fn yuv_pixel_format(layout: YuvPlaneLayout) -> Option<ffi::AVPixelFormat> {
let mut descriptor = ptr::null();
loop {
descriptor = unsafe { ffi::av_pix_fmt_desc_next(descriptor) };
if descriptor.is_null() {
return None;
}
let format = unsafe { ffi::av_pix_fmt_desc_get_id(descriptor) };
if yuv_layout(format) == Some(layout) {
return Some(format);
}
}
}
pub fn planar_frame(
format: ffi::AVPixelFormat,
planes: &[(&[u8], u32)],
width: u32,
height: u32,
) -> Result<AVFrame, BridgeError> {
let descriptor = unsafe { ffi::av_pix_fmt_desc_get(format).as_ref() }
.ok_or(BridgeError::InvalidInput("pixel format has no descriptor"))?;
let count = unsafe { ffi::av_pix_fmt_count_planes(format) };
if usize::try_from(count).ok() != Some(planes.len()) || width == 0 || height == 0 {
return Err(BridgeError::InvalidInput(
"planes do not match the pixel format",
));
}
let signed = |value: u32| {
i32::try_from(value).map_err(|_| BridgeError::InvalidInput("frame size overflow"))
};
let mut frame = AVFrame::new();
frame.set_format(format);
frame.set_width(signed(width)?);
frame.set_height(signed(height)?);
frame.get_buffer(1)?;
frame.make_writable()?;
for (index, (bytes, stride)) in planes.iter().enumerate() {
let plane = i32::try_from(index).unwrap_or(i32::MAX);
let row_bytes = unsafe { ffi::av_image_get_linesize(format, signed(width)?, plane) };
let row_bytes = u32::try_from(row_bytes).map_err(|_| BridgeError::FfmpegCode(row_bytes))?;
let rows = if index == 0 || index == 3 {
height
} else {
height.div_ceil(1 << descriptor.log2_chroma_h)
};
validate_source_plane(bytes, *stride, row_bytes, rows, "invalid source plane")?;
copy_plane_to_frame(
bytes,
*stride,
row_bytes,
rows,
frame.data[index],
frame.linesize[index],
)?;
}
Ok(frame)
}
fn validate_source_plane(
bytes: &[u8],
stride: u32,
width: u32,
height: u32,
context: &'static str,
) -> Result<(), BridgeError> {
let required = usize::try_from(stride).ok().and_then(|stride| {
usize::try_from(height)
.ok()
.and_then(|height| stride.checked_mul(height))
});
if stride < width || required != Some(bytes.len()) {
Err(BridgeError::InvalidInput(context))
} else {
Ok(())
}
}
fn copy_plane_to_frame(
source: &[u8],
source_stride: u32,
width: u32,
height: u32,
destination: *mut u8,
destination_stride: i32,
) -> Result<(), BridgeError> {
let row_bytes =
usize::try_from(width).map_err(|_| BridgeError::InvalidInput("YUV row width overflow"))?;
let rows =
usize::try_from(height).map_err(|_| BridgeError::InvalidInput("YUV row count overflow"))?;
let source_stride = usize::try_from(source_stride)
.map_err(|_| BridgeError::InvalidInput("YUV source stride overflow"))?;
let destination_stride = usize::try_from(destination_stride)
.map_err(|_| BridgeError::InvalidInput("negative YUV destination stride"))?;
if destination.is_null() || destination_stride < row_bytes {
return Err(BridgeError::InvalidInput(
"YUV destination plane is too small",
));
}
for row in 0..rows {
let source_offset = row
.checked_mul(source_stride)
.ok_or(BridgeError::InvalidInput("YUV source offset overflow"))?;
let destination_offset = row
.checked_mul(destination_stride)
.ok_or(BridgeError::InvalidInput("YUV destination offset overflow"))?;
unsafe {
ptr::copy_nonoverlapping(
source.as_ptr().add(source_offset),
destination.add(destination_offset),
row_bytes,
);
}
}
Ok(())
}
pub fn audio_frame(
channels: &[Vec<f32>],
format: ffi::AVSampleFormat,
sample_rate: i32,
pts: i64,
) -> Result<AVFrame, BridgeError> {
let Some(samples) = channels.first().map(Vec::len) else {
return Err(BridgeError::InvalidInput("invalid planar audio shape"));
};
if sample_rate <= 0
|| samples == 0
|| channels.len() > ffi::AV_NUM_DATA_POINTERS as usize
|| channels.iter().any(|channel| channel.len() != samples)
{
return Err(BridgeError::InvalidInput(
"planar audio channel lengths differ",
));
}
let sample_bytes = match format {
ffi::AV_SAMPLE_FMT_FLTP | ffi::AV_SAMPLE_FMT_FLT => size_of::<f32>(),
ffi::AV_SAMPLE_FMT_S16 => size_of::<i16>(),
_ => return Err(BridgeError::InvalidInput("unsupported audio sample format")),
};
let planar = format == ffi::AV_SAMPLE_FMT_FLTP;
let channel_count = i32::try_from(channels.len())
.map_err(|_| BridgeError::InvalidInput("audio channel count overflow"))?;
let sample_count = i32::try_from(samples)
.map_err(|_| BridgeError::InvalidInput("audio sample count overflow"))?;
let mut frame = AVFrame::new();
frame.set_format(format);
frame.set_ch_layout(
rsmpeg::avutil::AVChannelLayout::from_nb_channels(channel_count).into_inner(),
);
frame.set_sample_rate(sample_rate);
frame.set_nb_samples(sample_count);
frame.set_pts(pts);
frame.set_time_base(ffi::AVRational {
num: 1,
den: sample_rate,
});
frame.get_buffer(0)?;
frame.make_writable()?;
let plane_bytes = samples
.checked_mul(sample_bytes)
.and_then(|bytes| bytes.checked_mul(if planar { 1 } else { channels.len() }))
.ok_or(BridgeError::InvalidInput("audio plane byte count overflow"))?;
let plane_capacity = usize::try_from(frame.linesize[0]).unwrap_or(0);
if plane_capacity < plane_bytes {
return Err(BridgeError::InvalidInput(
"audio plane capacity is too small",
));
}
let planes = if planar { channels.len() } else { 1 };
if frame.data[..planes].iter().any(|plane| plane.is_null()) {
return Err(BridgeError::InvalidInput("audio plane pointer is null"));
}
if planar {
for (index, source) in channels.iter().enumerate() {
unsafe {
ptr::copy_nonoverlapping(
source.as_ptr().cast::<u8>(),
frame.data[index],
plane_bytes,
);
}
}
return Ok(frame);
}
let plane = unsafe { std::slice::from_raw_parts_mut(frame.data[0], plane_bytes) };
let interleaved = (0..samples).flat_map(|sample| channels.iter().map(move |c| c[sample]));
if format == ffi::AV_SAMPLE_FMT_FLT {
for (bytes, value) in plane.as_chunks_mut::<4>().0.iter_mut().zip(interleaved) {
*bytes = value.to_ne_bytes();
}
} else {
for (bytes, value) in plane.as_chunks_mut::<2>().0.iter_mut().zip(interleaved) {
#[allow(clippy::cast_possible_truncation)]
let value = (value.clamp(-1.0, 1.0) * 32_767.0).round() as i16;
*bytes = value.to_ne_bytes();
}
}
Ok(frame)
}
#[must_use]
pub fn channel_layout_channels(layout: &rsmpeg::avutil::AVChannelLayout) -> Vec<ffi::AVChannel> {
if layout.order == ffi::AV_CHANNEL_ORDER_UNSPEC {
return Vec::new();
}
let channels = u32::try_from(layout.nb_channels).unwrap_or(0);
(0..channels)
.map(|index| {
layout
.channel_from_index(index)
.unwrap_or(ffi::AV_CHAN_NONE)
})
.collect()
}
pub fn channel_layout_of(
channels: &[ffi::AVChannel],
) -> Result<rsmpeg::avutil::AVChannelLayout, BridgeError> {
let count = i32::try_from(channels.len())
.ok()
.filter(|count| *count > 0)
.ok_or(BridgeError::InvalidInput("a channel layout needs channels"))?;
let mut raw = std::mem::MaybeUninit::<ffi::AVChannelLayout>::zeroed();
let initialized = unsafe { ffi::av_channel_layout_custom_init(raw.as_mut_ptr(), count) };
if initialized < 0 {
return Err(BridgeError::FfmpegCode(initialized));
}
let mut layout = unsafe { rsmpeg::avutil::AVChannelLayout::new(raw.assume_init()) };
for (index, channel) in channels.iter().enumerate() {
unsafe { (*layout.u.map.add(index)).id = *channel };
}
let flags = i32::try_from(ffi::AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL).unwrap_or(0);
let retyped = unsafe {
ffi::av_channel_layout_retype(layout.as_mut_ptr(), ffi::AV_CHANNEL_ORDER_UNSPEC, flags)
};
if retyped < 0 {
return Err(BridgeError::FfmpegCode(retyped));
}
Ok(layout)
}
pub fn audio_f32_interleaved(frame: &AVFrame) -> Result<Vec<f32>, BridgeError> {
if frame.format != ffi::AV_SAMPLE_FMT_FLT || frame.nb_samples < 0 {
return Err(BridgeError::InvalidInput(
"packed audio format or sample count is invalid",
));
}
let channels = usize::try_from(frame.ch_layout().nb_channels)
.map_err(|_| BridgeError::InvalidInput("packed audio channel count is negative"))?;
let frames = usize::try_from(frame.nb_samples)
.map_err(|_| BridgeError::InvalidInput("packed audio sample count is negative"))?;
let samples = frames
.checked_mul(channels)
.ok_or(BridgeError::InvalidInput(
"packed audio sample count overflow",
))?;
let bytes = samples
.checked_mul(size_of::<f32>())
.ok_or(BridgeError::InvalidInput(
"packed audio byte count overflow",
))?;
if channels == 0
|| frame.data[0].is_null()
|| usize::try_from(frame.linesize[0]).unwrap_or(0) < bytes
{
return Err(BridgeError::InvalidInput(
"packed audio plane is missing or too small",
));
}
let mut output = vec![0.0_f32; samples];
unsafe {
ptr::copy_nonoverlapping(frame.data[0], output.as_mut_ptr().cast::<u8>(), bytes);
}
Ok(output)
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct DisplayRotation {
pub counter_clockwise_degrees: f64,
pub flipped: bool,
}
#[must_use]
pub fn display_rotation(parameters: &AVCodecParameters) -> Option<DisplayRotation> {
let count = usize::try_from(parameters.nb_coded_side_data).ok()?;
if count == 0 || count > 64 || parameters.coded_side_data.is_null() {
return None;
}
let entries = unsafe { slice::from_raw_parts(parameters.coded_side_data, count) };
let matrix = entries.iter().find(|entry| {
entry.type_ == ffi::AV_PKT_DATA_DISPLAYMATRIX
&& entry.size >= 9 * size_of::<i32>()
&& !entry.data.is_null()
})?;
unsafe { matrix_rotation(matrix.data) }
}
#[must_use]
pub fn frame_display_rotation(frame: &AVFrame) -> Option<DisplayRotation> {
let entry =
unsafe { ffi::av_frame_get_side_data(frame.as_ptr(), ffi::AV_FRAME_DATA_DISPLAYMATRIX) };
if entry.is_null() {
return None;
}
let entry = unsafe { &*entry };
if entry.size < 9 * size_of::<i32>() || entry.data.is_null() {
return None;
}
unsafe { matrix_rotation(entry.data) }
}
unsafe fn matrix_rotation(data: *const u8) -> Option<DisplayRotation> {
let mut aligned = [0_i32; 9];
unsafe {
ptr::copy_nonoverlapping(
data,
aligned.as_mut_ptr().cast::<u8>(),
size_of::<[i32; 9]>(),
);
}
let determinant = i64::from(aligned[0]) * i64::from(aligned[4])
- i64::from(aligned[1]) * i64::from(aligned[3]);
let flipped = determinant < 0;
if flipped {
unsafe { ffi::av_display_matrix_flip(aligned.as_mut_ptr(), 1, 0) };
}
let angle = unsafe { ffi::av_display_rotation_get(aligned.as_ptr()) };
angle.is_finite().then_some(DisplayRotation {
counter_clockwise_degrees: angle,
flipped,
})
}
const fn size_of<T>() -> usize {
std::mem::size_of::<T>()
}
pub struct Yuv420pFrameBuffer {
frame: AVFrame,
width: usize,
height: usize,
}
pub struct Yuv420pFramePlanes<'a> {
pub y: &'a mut [u8],
pub y_stride: usize,
pub u: &'a mut [u8],
pub v: &'a mut [u8],
pub chroma_stride: usize,
}
impl Yuv420pFrameBuffer {
pub fn new(width: i32, height: i32) -> Result<Self, BridgeError> {
let coded_width =
usize::try_from(width).map_err(|_| BridgeError::InvalidInput("negative YUV width"))?;
let coded_height = usize::try_from(height)
.map_err(|_| BridgeError::InvalidInput("negative YUV height"))?;
if coded_width == 0 || coded_height == 0 {
return Err(BridgeError::InvalidInput("empty YUV frame"));
}
Ok(Self {
frame: zeroed_yuv420p_frame(width, height)?,
width: coded_width,
height: coded_height,
})
}
#[must_use]
pub const fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
pub fn begin_write(&mut self) -> Result<Yuv420pFramePlanes<'_>, BridgeError> {
if !self.frame.is_writable().map_err(BridgeError::Ffmpeg)? {
let mut copy = zeroed_yuv420p_frame(self.frame.width, self.frame.height)?;
let status = unsafe {
let copied = ffi::av_frame_copy(copy.as_mut_ptr(), self.frame.as_ptr());
if copied < 0 {
copied
} else {
ffi::av_frame_copy_props(copy.as_mut_ptr(), self.frame.as_ptr())
}
};
if status < 0 {
return Err(BridgeError::FfmpegCode(status));
}
self.frame = copy;
}
let chroma_width = self.width.div_ceil(2);
let chroma_height = self.height.div_ceil(2);
let y_stride = plane_stride(self.frame.linesize[0], self.width)?;
let u_stride = plane_stride(self.frame.linesize[1], chroma_width)?;
let v_stride = plane_stride(self.frame.linesize[2], chroma_width)?;
if u_stride != v_stride {
return Err(BridgeError::InvalidInput("mismatched chroma strides"));
}
let y = unsafe { writable_plane(self.frame.data[0], y_stride, self.width, self.height)? };
let u =
unsafe { writable_plane(self.frame.data[1], u_stride, chroma_width, chroma_height)? };
let v =
unsafe { writable_plane(self.frame.data[2], v_stride, chroma_width, chroma_height)? };
Ok(Yuv420pFramePlanes {
y,
y_stride,
u,
v,
chroma_stride: u_stride,
})
}
pub fn set_presentation(&mut self, pts: i64, time_base: ffi::AVRational) {
self.frame.set_pts(pts);
self.frame.set_time_base(time_base);
}
#[must_use]
pub const fn frame(&self) -> &AVFrame {
&self.frame
}
#[must_use]
pub const fn size(&self) -> (usize, usize) {
(self.width, self.height)
}
}
fn zeroed_yuv420p_frame(width: i32, height: i32) -> Result<AVFrame, BridgeError> {
let mut frame = AVFrame::new();
frame.set_format(ffi::AV_PIX_FMT_YUV420P);
frame.set_width(width);
frame.set_height(height);
frame.get_buffer(1)?;
for buffer in frame.buf {
if let Some(buffer) = unsafe { buffer.as_ref() } {
unsafe { ptr::write_bytes(buffer.data, 0, buffer.size) };
}
}
Ok(frame)
}
impl fmt::Debug for Yuv420pFrameBuffer {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Yuv420pFrameBuffer")
.field("width", &self.width)
.field("height", &self.height)
.finish_non_exhaustive()
}
}
fn plane_stride(linesize: i32, width: usize) -> Result<usize, BridgeError> {
let stride = usize::try_from(linesize)
.map_err(|_| BridgeError::InvalidInput("negative frame linesize"))?;
if stride < width {
return Err(BridgeError::InvalidInput(
"frame linesize is below its width",
));
}
Ok(stride)
}
unsafe fn writable_plane<'a>(
data: *mut u8,
stride: usize,
width: usize,
height: usize,
) -> Result<&'a mut [u8], BridgeError> {
if data.is_null() {
return Err(BridgeError::InvalidInput("frame plane is null"));
}
let len = stride
.checked_mul(height.saturating_sub(1))
.and_then(|rows| rows.checked_add(width))
.ok_or(BridgeError::InvalidInput("frame plane size overflow"))?;
Ok(unsafe { slice::from_raw_parts_mut(data, len) })
}
pub fn exit_without_handlers(code: u8) -> ! {
#[cfg(unix)]
{
unsafe extern "C" {
fn _exit(status: std::ffi::c_int) -> !;
}
unsafe { _exit(std::ffi::c_int::from(code)) }
}
#[cfg(windows)]
{
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetCurrentProcess() -> *mut std::ffi::c_void;
fn TerminateProcess(process: *mut std::ffi::c_void, exit_code: u32) -> i32;
}
if unsafe { TerminateProcess(GetCurrentProcess(), u32::from(code)) } != 0 {
loop {
std::thread::park();
}
}
std::process::exit(i32::from(code))
}
#[cfg(not(any(unix, windows)))]
std::process::exit(i32::from(code))
}
#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
mod vulkan_shim_tests;
#[cfg(all(test, target_os = "linux", feature = "hwdevice"))]
mod vulkan_access_tests;
#[cfg(test)]
mod tests;