diff --git a/examples/transcode-audio.rs b/examples/transcode-audio.rs index c5321ee..5b4337a 100644 --- a/examples/transcode-audio.rs +++ b/examples/transcode-audio.rs @@ -91,7 +91,7 @@ fn transcoder>( let channel_layout = codec .channel_layouts() .map(|cls| cls.best(decoder.channel_layout().channels())) - .unwrap_or(ffmpeg::channel_layout::ChannelLayout::STEREO); + .unwrap_or(ffmpeg::channel_layout::ChannelLayoutMask::STEREO); if global { encoder.set_flags(ffmpeg::codec::flag::Flags::GLOBAL_HEADER); diff --git a/src/codec/audio.rs b/src/codec/audio.rs index 3b484a7..87c8edb 100644 --- a/src/codec/audio.rs +++ b/src/codec/audio.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use super::codec::Codec; use crate::ffi::*; -use crate::{format, ChannelLayout}; +use crate::{format, ChannelLayoutMask}; #[derive(PartialEq, Eq, Copy, Clone)] pub struct Audio { @@ -36,17 +36,22 @@ impl Audio { } } - pub fn channel_layouts(&self) -> Option { + pub fn channel_layouts(&self) -> Option { unsafe { if (*self.codec.as_ptr()).channel_layouts.is_null() { None } else { - Some(ChannelLayoutIter::new( + Some(ChannelLayoutMaskIter::new( (*self.codec.as_ptr()).channel_layouts, )) } } } + + #[cfg(feature = "ffmpeg_5_1")] + pub fn ch_layouts(&self) -> Option { + unsafe { ChannelLayoutIter::from_raw((*self.codec.as_ptr()).ch_layouts) } + } } impl Deref for Audio { @@ -111,17 +116,17 @@ impl Iterator for FormatIter { } } -pub struct ChannelLayoutIter { +pub struct ChannelLayoutMaskIter { ptr: *const u64, } -impl ChannelLayoutIter { +impl ChannelLayoutMaskIter { pub fn new(ptr: *const u64) -> Self { - ChannelLayoutIter { ptr } + ChannelLayoutMaskIter { ptr } } - pub fn best(self, max: i32) -> ChannelLayout { - self.fold(ChannelLayout::MONO, |acc, cur| { + pub fn best(self, max: i32) -> ChannelLayoutMask { + self.fold(ChannelLayoutMask::MONO, |acc, cur| { if cur.channels() > acc.channels() && cur.channels() <= max { cur } else { @@ -131,8 +136,8 @@ impl ChannelLayoutIter { } } -impl Iterator for ChannelLayoutIter { - type Item = ChannelLayout; +impl Iterator for ChannelLayoutMaskIter { + type Item = ChannelLayoutMask; fn next(&mut self) -> Option<::Item> { unsafe { @@ -140,10 +145,52 @@ impl Iterator for ChannelLayoutIter { return None; } - let layout = ChannelLayout::from_bits_truncate(*self.ptr); + let layout = ChannelLayoutMask::from_bits_truncate(*self.ptr); self.ptr = self.ptr.offset(1); Some(layout) } } } + +#[cfg(feature = "ffmpeg_5_1")] +pub use ch_layout::ChannelLayoutIter; + +#[cfg(feature = "ffmpeg_5_1")] +mod ch_layout { + use super::*; + use crate::ChannelLayout; + + pub struct ChannelLayoutIter<'a> { + next: &'a AVChannelLayout, + } + + impl<'a> ChannelLayoutIter<'a> { + pub unsafe fn from_raw(ptr: *const AVChannelLayout) -> Option { + ptr.as_ref().map(|next| Self { next }) + } + } + + impl<'a> Iterator for ChannelLayoutIter<'a> { + type Item = ChannelLayout<'a>; + + fn next(&mut self) -> Option { + unsafe { + let curr = self.next; + if *curr == zeroed_layout() { + return None; + } + + // SAFETY: We trust that there is always an initialized layout up until + // the zeroed-out AVChannelLayout, which signals the end of iteration. + self.next = (curr as *const AVChannelLayout).add(1).as_ref().unwrap(); + Some(ChannelLayout::from(curr)) + } + } + } + + // TODO: Remove this with a const variable when zeroed() is const (1.75.0) + unsafe fn zeroed_layout() -> AVChannelLayout { + std::mem::zeroed() + } +} diff --git a/src/codec/decoder/audio.rs b/src/codec/decoder/audio.rs index cf0db61..3e65e4a 100644 --- a/src/codec/decoder/audio.rs +++ b/src/codec/decoder/audio.rs @@ -12,7 +12,10 @@ use crate::frame; use crate::util::format; #[cfg(not(feature = "ffmpeg_5_0"))] use crate::{packet, Error}; -use crate::{AudioService, ChannelLayout}; +use crate::{AudioService, ChannelLayoutMask}; + +#[cfg(feature = "ffmpeg_5_1")] +use crate::ChannelLayout; pub struct Audio(pub Opened); @@ -69,22 +72,34 @@ impl Audio { unsafe { (*self.as_ptr()).block_align as usize } } - pub fn channel_layout(&self) -> ChannelLayout { - unsafe { ChannelLayout::from_bits_truncate((*self.as_ptr()).channel_layout) } + pub fn channel_layout(&self) -> ChannelLayoutMask { + unsafe { ChannelLayoutMask::from_bits_truncate((*self.as_ptr()).channel_layout) } } - pub fn set_channel_layout(&mut self, value: ChannelLayout) { + pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) { unsafe { (*self.as_mut_ptr()).channel_layout = value.bits(); } } - pub fn request_channel_layout(&mut self, value: ChannelLayout) { + pub fn request_channel_layout(&mut self, value: ChannelLayoutMask) { unsafe { (*self.as_mut_ptr()).request_channel_layout = value.bits(); } } + #[cfg(feature = "ffmpeg_5_1")] + pub fn ch_layout(&self) -> ChannelLayout { + unsafe { ChannelLayout::from(&self.as_ptr().as_ref().unwrap().ch_layout) } + } + + #[cfg(feature = "ffmpeg_5_1")] + pub fn set_ch_layout(&mut self, value: ChannelLayout) { + unsafe { + self.as_mut_ptr().as_mut().unwrap().ch_layout = value.into_owned(); + } + } + pub fn audio_service(&mut self) -> AudioService { unsafe { AudioService::from((*self.as_mut_ptr()).audio_service_type) } } diff --git a/src/codec/encoder/audio.rs b/src/codec/encoder/audio.rs index eb5320b..9f521c3 100644 --- a/src/codec/encoder/audio.rs +++ b/src/codec/encoder/audio.rs @@ -10,7 +10,10 @@ use crate::codec::{traits, Context}; use crate::util::format; #[cfg(not(feature = "ffmpeg_5_0"))] use crate::{frame, packet}; -use crate::{ChannelLayout, Dictionary, Error}; +use crate::{ChannelLayoutMask, Dictionary, Error}; + +#[cfg(feature = "ffmpeg_5_1")] +use crate::ChannelLayout; pub struct Audio(pub Super); @@ -93,14 +96,14 @@ impl Audio { unsafe { format::Sample::from((*self.as_ptr()).sample_fmt) } } - pub fn set_channel_layout(&mut self, value: ChannelLayout) { + pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) { unsafe { (*self.as_mut_ptr()).channel_layout = value.bits(); } } - pub fn channel_layout(&self) -> ChannelLayout { - unsafe { ChannelLayout::from_bits_truncate((*self.as_ptr()).channel_layout) } + pub fn channel_layout(&self) -> ChannelLayoutMask { + unsafe { ChannelLayoutMask::from_bits_truncate((*self.as_ptr()).channel_layout) } } pub fn set_channels(&mut self, value: i32) { @@ -112,6 +115,18 @@ impl Audio { pub fn channels(&self) -> u16 { unsafe { (*self.as_ptr()).channels as u16 } } + + #[cfg(feature = "ffmpeg_5_1")] + pub fn ch_layout(&self) -> ChannelLayout { + unsafe { ChannelLayout::from(&self.as_ptr().as_ref().unwrap().ch_layout) } + } + + #[cfg(feature = "ffmpeg_5_1")] + pub fn set_ch_layout(&mut self, value: ChannelLayout) { + unsafe { + self.as_mut_ptr().as_mut().unwrap().ch_layout = value.into_owned(); + } + } } impl Deref for Audio { diff --git a/src/filter/context/context.rs b/src/filter/context/context.rs index 7b2a2a3..94ae437 100644 --- a/src/filter/context/context.rs +++ b/src/filter/context/context.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; use super::{Sink, Source}; use crate::ffi::*; -use crate::{format, option, ChannelLayout}; +use crate::{format, option, ChannelLayoutMask}; use libc::c_void; pub struct Context<'a> { @@ -49,7 +49,7 @@ impl<'a> Context<'a> { let _ = option::Settable::set(self, "sample_rates", &i64::from(value)); } - pub fn set_channel_layout(&mut self, value: ChannelLayout) { + pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) { let _ = option::Settable::set(self, "channel_layouts", &value.bits()); } } diff --git a/src/lib.rs b/src/lib.rs index c1c6078..ad6774b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,11 @@ pub use crate::sys as ffi; #[macro_use] pub mod util; -pub use crate::util::channel_layout::{self, ChannelLayout}; +pub use crate::util::channel_layout::{self, ChannelLayoutMask}; +#[cfg(feature = "ffmpeg_5_1")] +pub use crate::util::channel_layout::{ + Channel, ChannelCustom, ChannelLayout, ChannelLayoutIter, ChannelOrder, +}; pub use crate::util::chroma; pub use crate::util::color; pub use crate::util::dictionary; diff --git a/src/software/mod.rs b/src/software/mod.rs index 6957805..821e72e 100644 --- a/src/software/mod.rs +++ b/src/software/mod.rs @@ -38,8 +38,8 @@ pub mod resampling; #[cfg(feature = "software-resampling")] #[inline] pub fn resampler( - (in_format, in_layout, in_rate): (crate::format::Sample, crate::ChannelLayout, u32), - (out_format, out_layout, out_rate): (crate::format::Sample, crate::ChannelLayout, u32), + (in_format, in_layout, in_rate): (crate::format::Sample, crate::ChannelLayoutMask, u32), + (out_format, out_layout, out_rate): (crate::format::Sample, crate::ChannelLayoutMask, u32), ) -> Result { resampling::Context::get( in_format, in_layout, in_rate, out_format, out_layout, out_rate, diff --git a/src/software/resampling/context.rs b/src/software/resampling/context.rs index 6949fc4..01901cb 100644 --- a/src/software/resampling/context.rs +++ b/src/software/resampling/context.rs @@ -4,14 +4,14 @@ use super::Delay; use crate::ffi::*; use crate::util::format; use crate::Dictionary; -use crate::{frame, ChannelLayout, Error}; +use crate::{frame, ChannelLayoutMask, Error}; use libc::c_int; use std::ffi::c_void; #[derive(Eq, PartialEq, Copy, Clone)] pub struct Definition { pub format: format::Sample, - pub channel_layout: ChannelLayout, + pub channel_layout: ChannelLayoutMask, pub rate: u32, } @@ -40,10 +40,10 @@ impl Context { /// Create a resampler with the given definitions. pub fn get( src_format: format::Sample, - src_channel_layout: ChannelLayout, + src_channel_layout: ChannelLayoutMask, src_rate: u32, dst_format: format::Sample, - dst_channel_layout: ChannelLayout, + dst_channel_layout: ChannelLayoutMask, dst_rate: u32, ) -> Result { Self::get_with( @@ -60,10 +60,10 @@ impl Context { /// Create a resampler with the given definitions and custom options dictionary. pub fn get_with( src_format: format::Sample, - src_channel_layout: ChannelLayout, + src_channel_layout: ChannelLayoutMask, src_rate: u32, dst_format: format::Sample, - dst_channel_layout: ChannelLayout, + dst_channel_layout: ChannelLayoutMask, dst_rate: u32, options: Dictionary, ) -> Result { diff --git a/src/software/resampling/extensions.rs b/src/software/resampling/extensions.rs index ebbec2d..8e209c6 100644 --- a/src/software/resampling/extensions.rs +++ b/src/software/resampling/extensions.rs @@ -1,13 +1,13 @@ use super::Context; use crate::util::format; -use crate::{decoder, frame, ChannelLayout, Error}; +use crate::{decoder, frame, ChannelLayoutMask, Error}; impl frame::Audio { #[inline] pub fn resampler( &self, format: format::Sample, - channel_layout: ChannelLayout, + channel_layout: ChannelLayoutMask, rate: u32, ) -> Result { Context::get( @@ -26,7 +26,7 @@ impl decoder::Audio { pub fn resampler( &self, format: format::Sample, - channel_layout: ChannelLayout, + channel_layout: ChannelLayoutMask, rate: u32, ) -> Result { Context::get( diff --git a/src/util/channel_layout/channel.rs b/src/util/channel_layout/channel.rs new file mode 100644 index 0000000..881f808 --- /dev/null +++ b/src/util/channel_layout/channel.rs @@ -0,0 +1,270 @@ +use crate::ffi::*; + +use std::ffi::CString; + +#[cfg(feature = "serialize")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +pub enum Channel { + None, + FrontLeft, + FrontRight, + FrontCenter, + LowFrequency, + BackLeft, + BackRight, + FrontLeftOfCenter, + FrontRightOfCenter, + BackCenter, + SideLeft, + SideRight, + TopCenter, + TopFrontLeft, + TopFrontCenter, + TopFrontRight, + TopBackLeft, + TopBackCenter, + TopBackRight, + StereoLeft, + StereoRight, + WideLeft, + WideRight, + SurroundDirectLeft, + SurroundDirectRight, + LowFrequency2, + TopSideLeft, + TopSideRight, + BottomFrontCenter, + BottomFrontLeft, + BottomFrontRight, + + /// Channel is empty and can be safely skipped. + Unused, + + /// Channel contains data, but its position is unknown. + Unknown, + + /// Defines the start of channel IDs when using Ambisonic. + AmbisonicBase, + /// Defines the end of channel IDs when using Ambisonic. + AmbisonicEnd, +} + +impl Channel { + /// Get an abbreviated, human-readable string describing this channel. + pub fn name(self) -> String { + let mut buf = vec![0u8; 32]; + + unsafe { + let ret_val = av_channel_name(buf.as_mut_ptr() as _, buf.len(), AVChannel::from(self)); + + match usize::try_from(ret_val) { + Ok(out_len) if out_len > 0 => { + #[cfg(feature = "ffmpeg_6_1")] + // 6.1 changed out_len to include the NUL byte, which we don't want + let out_len = out_len - 1; + + buf.truncate(out_len); + String::from_utf8_unchecked(buf) + } + // `av_channel_name` returned an error, or 0 bytes written. + _ => String::new(), + } + } + } + + /// Get a human-readable string describing this channel. + pub fn description(self) -> String { + let mut buf = vec![0u8; 256]; + + unsafe { + let ret_val = + av_channel_description(buf.as_mut_ptr() as _, buf.len(), AVChannel::from(self)); + + match usize::try_from(ret_val) { + Ok(out_len) if out_len > 0 => { + #[cfg(feature = "ffmpeg_6_1")] + // 6.1 changed out_len to include the NUL byte, which we don't want + let out_len = out_len - 1; + + buf.truncate(out_len); + String::from_utf8_unchecked(buf) + } + // `av_channel_description` returned an error, or 0 bytes written. + _ => String::new(), + } + } + } + + /// This is the inverse function of [`name`][Channel::name]. + pub fn from_string>(name: S) -> Self { + let cstr = CString::new(name.as_ref()).expect("no nul byte in name"); + Self::from(unsafe { av_channel_from_string(cstr.as_ptr()) }) + } +} + +impl From for Channel { + fn from(value: AVChannel) -> Self { + use crate::ffi::AVChannel::*; + use Channel::*; + + match value { + AV_CHAN_NONE => None, + AV_CHAN_FRONT_LEFT => FrontLeft, + AV_CHAN_FRONT_RIGHT => FrontRight, + AV_CHAN_FRONT_CENTER => FrontCenter, + AV_CHAN_LOW_FREQUENCY => LowFrequency, + AV_CHAN_BACK_LEFT => BackLeft, + AV_CHAN_BACK_RIGHT => BackRight, + AV_CHAN_FRONT_LEFT_OF_CENTER => FrontLeftOfCenter, + AV_CHAN_FRONT_RIGHT_OF_CENTER => FrontRightOfCenter, + AV_CHAN_BACK_CENTER => BackCenter, + AV_CHAN_SIDE_LEFT => SideLeft, + AV_CHAN_SIDE_RIGHT => SideRight, + AV_CHAN_TOP_CENTER => TopCenter, + AV_CHAN_TOP_FRONT_LEFT => TopFrontLeft, + AV_CHAN_TOP_FRONT_CENTER => TopFrontCenter, + AV_CHAN_TOP_FRONT_RIGHT => TopFrontRight, + AV_CHAN_TOP_BACK_LEFT => TopBackLeft, + AV_CHAN_TOP_BACK_CENTER => TopBackCenter, + AV_CHAN_TOP_BACK_RIGHT => TopBackRight, + AV_CHAN_STEREO_LEFT => StereoLeft, + AV_CHAN_STEREO_RIGHT => StereoRight, + AV_CHAN_WIDE_LEFT => WideLeft, + AV_CHAN_WIDE_RIGHT => WideRight, + AV_CHAN_SURROUND_DIRECT_LEFT => SurroundDirectLeft, + AV_CHAN_SURROUND_DIRECT_RIGHT => SurroundDirectRight, + AV_CHAN_LOW_FREQUENCY_2 => LowFrequency2, + AV_CHAN_TOP_SIDE_LEFT => TopSideLeft, + AV_CHAN_TOP_SIDE_RIGHT => TopSideRight, + AV_CHAN_BOTTOM_FRONT_CENTER => BottomFrontCenter, + AV_CHAN_BOTTOM_FRONT_LEFT => BottomFrontLeft, + AV_CHAN_BOTTOM_FRONT_RIGHT => BottomFrontRight, + AV_CHAN_UNUSED => Unused, + AV_CHAN_UNKNOWN => Unknown, + AV_CHAN_AMBISONIC_BASE => AmbisonicBase, + AV_CHAN_AMBISONIC_END => AmbisonicEnd, + + #[cfg(feature = "non-exhaustive-enums")] + _ => unimplemented!(), + } + } +} + +impl From for AVChannel { + fn from(value: Channel) -> Self { + use crate::ffi::AVChannel::*; + use Channel::*; + + match value { + None => AV_CHAN_NONE, + FrontLeft => AV_CHAN_FRONT_LEFT, + FrontRight => AV_CHAN_FRONT_RIGHT, + FrontCenter => AV_CHAN_FRONT_CENTER, + LowFrequency => AV_CHAN_LOW_FREQUENCY, + BackLeft => AV_CHAN_BACK_LEFT, + BackRight => AV_CHAN_BACK_RIGHT, + FrontLeftOfCenter => AV_CHAN_FRONT_LEFT_OF_CENTER, + FrontRightOfCenter => AV_CHAN_FRONT_RIGHT_OF_CENTER, + BackCenter => AV_CHAN_BACK_CENTER, + SideLeft => AV_CHAN_SIDE_LEFT, + SideRight => AV_CHAN_SIDE_RIGHT, + TopCenter => AV_CHAN_TOP_CENTER, + TopFrontLeft => AV_CHAN_TOP_FRONT_LEFT, + TopFrontCenter => AV_CHAN_TOP_FRONT_CENTER, + TopFrontRight => AV_CHAN_TOP_FRONT_RIGHT, + TopBackLeft => AV_CHAN_TOP_BACK_LEFT, + TopBackCenter => AV_CHAN_TOP_BACK_CENTER, + TopBackRight => AV_CHAN_TOP_BACK_RIGHT, + StereoLeft => AV_CHAN_STEREO_LEFT, + StereoRight => AV_CHAN_STEREO_RIGHT, + WideLeft => AV_CHAN_WIDE_LEFT, + WideRight => AV_CHAN_WIDE_RIGHT, + SurroundDirectLeft => AV_CHAN_SURROUND_DIRECT_LEFT, + SurroundDirectRight => AV_CHAN_SURROUND_DIRECT_RIGHT, + LowFrequency2 => AV_CHAN_LOW_FREQUENCY_2, + TopSideLeft => AV_CHAN_TOP_SIDE_LEFT, + TopSideRight => AV_CHAN_TOP_SIDE_RIGHT, + BottomFrontCenter => AV_CHAN_BOTTOM_FRONT_CENTER, + BottomFrontLeft => AV_CHAN_BOTTOM_FRONT_LEFT, + BottomFrontRight => AV_CHAN_BOTTOM_FRONT_RIGHT, + Unused => AV_CHAN_UNUSED, + Unknown => AV_CHAN_UNKNOWN, + AmbisonicBase => AV_CHAN_AMBISONIC_BASE, + AmbisonicEnd => AV_CHAN_AMBISONIC_END, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + // just test everything + const TEST_VALUES: &[Channel] = &[ + Channel::None, + Channel::FrontLeft, + Channel::FrontRight, + Channel::FrontCenter, + Channel::LowFrequency, + Channel::BackLeft, + Channel::BackRight, + Channel::FrontLeftOfCenter, + Channel::FrontRightOfCenter, + Channel::BackCenter, + Channel::SideLeft, + Channel::SideRight, + Channel::TopCenter, + Channel::TopFrontLeft, + Channel::TopFrontCenter, + Channel::TopFrontRight, + Channel::TopBackLeft, + Channel::TopBackCenter, + Channel::TopBackRight, + Channel::StereoLeft, + Channel::StereoRight, + Channel::WideLeft, + Channel::WideRight, + Channel::SurroundDirectLeft, + Channel::SurroundDirectRight, + Channel::LowFrequency2, + Channel::TopSideLeft, + Channel::TopSideRight, + Channel::BottomFrontCenter, + Channel::BottomFrontLeft, + Channel::BottomFrontRight, + Channel::Unused, + Channel::Unknown, + Channel::AmbisonicBase, + Channel::AmbisonicEnd, + ]; + + #[test] + fn name() { + for ch in TEST_VALUES { + let name = ch.name(); + assert!(!name.is_empty()); + println!("{name}"); + } + } + + #[test] + fn description() { + for ch in TEST_VALUES { + let desc = ch.description(); + assert!(!desc.is_empty()); + println!("{desc}"); + } + } + + #[test] + fn from_string() { + for ch in TEST_VALUES { + let name = ch.name(); + let found = Channel::from_string(name); + assert_eq!(found, *ch); + } + } +} diff --git a/src/util/channel_layout/channel_custom.rs b/src/util/channel_layout/channel_custom.rs new file mode 100644 index 0000000..f790727 --- /dev/null +++ b/src/util/channel_layout/channel_custom.rs @@ -0,0 +1,90 @@ +use crate::ffi::{AVChannel, AVChannelCustom}; + +use super::Channel; + +/// Wrapper around [`AVChannelCustom`][crate::ffi::AVChannelCustom]. +/// +/// This struct does not support reading or writing user data via the opaque pointer. +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct ChannelCustom(AVChannelCustom); + +impl ChannelCustom { + pub fn new(id: Channel) -> Self { + Self(AVChannelCustom { + id: AVChannel::from(id), + name: [0; 16], + opaque: std::ptr::null_mut(), + }) + } + + pub fn named>(id: Channel, name: S) -> Self { + let name = name.as_ref(); + let name = to_char_array(name.as_bytes()); + + Self(AVChannelCustom { + id: AVChannel::from(id), + name, + opaque: std::ptr::null_mut(), + }) + } +} + +fn to_char_array(bytes: &[u8]) -> [i8; 16] { + let mut result = [0i8; 16]; + + // Only take the first 15 bytes, leaving at least one NUL byte + for (b, r) in bytes.iter().take(15).zip(&mut result) { + *r = *b as i8; + } + + result +} + +impl From for ChannelCustom { + fn from(value: AVChannelCustom) -> Self { + Self(value) + } +} + +impl From for AVChannelCustom { + fn from(value: ChannelCustom) -> Self { + value.0 + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::alloc::Layout; + + #[test] + fn is_repr_transparent() { + // ChannelLayout::map relies on this being true. + assert_eq!( + Layout::new::(), + Layout::new::() + ); + } + + #[test] + fn new() { + let custom = ChannelCustom::new(Channel::FrontRight); + assert_eq!(custom.0.id, AVChannel::AV_CHAN_FRONT_RIGHT); + assert_eq!(custom.0.name, [0; 16]); + assert!(custom.0.opaque.is_null()); + } + + #[test] + fn named() { + // "Bottom front ri\0" + let c_str_name = [ + 66, 111, 116, 116, 111, 109, 32, 102, 114, 111, 110, 116, 32, 114, 105, 0, + ]; + + let custom = ChannelCustom::named(Channel::BottomFrontRight, "Bottom front right"); + assert_eq!(custom.0.id, AVChannel::AV_CHAN_BOTTOM_FRONT_RIGHT); + assert_eq!(custom.0.name, c_str_name); + assert!(custom.0.opaque.is_null()); + } +} diff --git a/src/util/channel_layout/iter.rs b/src/util/channel_layout/iter.rs new file mode 100644 index 0000000..ac4acf2 --- /dev/null +++ b/src/util/channel_layout/iter.rs @@ -0,0 +1,44 @@ +use ffmpeg_sys_the_third::av_channel_layout_standard; +use libc::c_void; + +use super::ChannelLayout; + +pub struct ChannelLayoutIter { + opaque: *mut c_void, +} + +impl ChannelLayoutIter { + pub const fn new() -> Self { + Self { + opaque: std::ptr::null_mut(), + } + } +} + +impl Iterator for ChannelLayoutIter { + type Item = ChannelLayout<'static>; + + fn next(&mut self) -> Option { + unsafe { + av_channel_layout_standard(&mut self.opaque as _) + .as_ref() + .map(ChannelLayout::from) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn iter() { + let iter = ChannelLayoutIter::new(); + let count = iter.count(); + println!("{count}"); + assert!(count > 0); + + let mut iter = ChannelLayoutIter::new(); + assert!(iter.all(|ch| ch.is_valid())); + } +} diff --git a/src/util/channel_layout/layout.rs b/src/util/channel_layout/layout.rs new file mode 100644 index 0000000..05b0b64 --- /dev/null +++ b/src/util/channel_layout/layout.rs @@ -0,0 +1,473 @@ +use std::borrow::Borrow; +use std::borrow::Cow; +use std::ffi::CString; +use std::mem::{align_of, size_of}; + +use crate::ffi::*; +use libc::{c_int, c_uint}; + +use super::Channel; +use super::ChannelCustom; +use super::ChannelLayoutIter; +use super::ChannelLayoutMask; +use super::ChannelOrder; + +#[derive(Debug, Clone, PartialEq)] +pub struct ChannelLayout<'a>(Cow<'a, AVChannelLayout>); + +impl<'a> ChannelLayout<'a> { + /// Get a new channel layout with an unspecified channel ordering. + pub fn unspecified(channels: u32) -> Self { + let mut layout = AVChannelLayout::empty(); + layout.order = AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC; + layout.nb_channels = channels as c_int; + + Self(Cow::Owned(layout)) + } + + pub fn custom(channels: Vec) -> Self { + #[cold] + fn alloc_failed(channels: usize) -> ! { + use std::alloc::{handle_alloc_error, Layout}; + + let alloc_size = channels * size_of::(); + let layout = + Layout::from_size_align(alloc_size, align_of::()).unwrap(); + handle_alloc_error(layout) + } + + let mut layout = AVChannelLayout::empty(); + layout.order = AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM; + layout.nb_channels = channels.len() as c_int; + unsafe { + layout.u.map = av_malloc_array(channels.len(), size_of::()) as _; + if layout.u.map.is_null() { + alloc_failed(channels.len()); + } + + for (i, ch) in channels.into_iter().enumerate() { + std::ptr::write(layout.u.map.add(i), AVChannelCustom::from(ch)); + } + } + + Self(Cow::Owned(layout)) + } + + /// Get the default channel layout for a given number of channels. + /// + /// If no default layout exists for the given number of channels, + /// an unspecified layout will be returned. + pub fn default_for_channels(channels: u32) -> Self { + let mut layout = AVChannelLayout::empty(); + + unsafe { + av_channel_layout_default(&mut layout as _, channels as c_int); + } + + Self(Cow::Owned(layout)) + } + + /// Get an iterator over all standard channel layouts. + pub fn standard_layouts() -> ChannelLayoutIter { + ChannelLayoutIter::new() + } + + /// Initialize a native channel layout from a bitmask indicating which + /// channels are present. + /// + /// This will return [None] for invalid bitmask values. + pub fn from_mask(layout_mask: ChannelLayoutMask) -> Option { + let mut layout = AVChannelLayout::empty(); + let ret = unsafe { av_channel_layout_from_mask(&mut layout as _, layout_mask.bits()) }; + + match ret { + 0 => Some(Self(Cow::Owned(layout))), + // This should only ever return 0 or AVERROR(EINVAL) + _ => None, + } + } + + /// Initialize a channel layout from a given string description. + /// + /// This can be + /// - the formal channel layout name (as returned by [`description`][ChannelLayout::description]), + /// - one or more channel names concatenated with "+", each optionally containing a + /// custom name after an "@", e.g. "FL@Left+FR@Right+LFE", + /// - a decimal or hexadecimal value of a native channel layout (e.g. "4" or "0x4"), + /// - the number of channels with the default layout (e.g. "4c"), + /// - the number of unordered channels (e.g. "4C" or "4 channels") or + /// - the ambisonic order followed by optional non-diegetic channels (e.g. "ambisonic 2+stereo") + pub fn from_string>(description: S) -> Option { + let mut layout = AVChannelLayout::empty(); + let cstr = CString::new(description.as_ref()).expect("no nul byte in description"); + let ret = unsafe { av_channel_layout_from_string(&mut layout as _, cstr.as_ptr()) }; + + match ret { + 0 => Some(Self(Cow::Owned(layout))), + // This should only ever return 0 or AVERROR_INVALIDDATA + _ => None, + } + } + + /// The [`ChannelOrder`][super::ChannelOrder] used in this layout. + pub fn order(&self) -> ChannelOrder { + ChannelOrder::from(self.0.order) + } + + /// The number of channels in this layout. + pub fn channels(&self) -> u32 { + self.0.nb_channels as u32 + } + + /// If [`order`][ChannelLayout::order] is [`Native`][ChannelOrder::Native]: + /// A [`ChannelLayoutMask`] containing the channels of this layout. + /// + /// If [`order`][ChannelLayout::order] is [`Ambisonic`][ChannelOrder::Ambisonic]: + /// A [`ChannelLayoutMask`] containing the non-diegetic channels of this layout. + /// + /// Otherwise: [`None`]. + pub fn mask(&self) -> Option { + match self.order() { + ChannelOrder::Unspecified | ChannelOrder::Custom => None, + ChannelOrder::Native | ChannelOrder::Ambisonic => unsafe { + Some(ChannelLayoutMask::from_bits_truncate(self.0.u.mask)) + }, + } + } + + /// Returns the custom channel map for this layout. + /// + /// None if [`order`][ChannelLayout::order] is not [`Custom`][ChannelOrder::Custom]. + pub fn map(&self) -> Option<&[ChannelCustom]> { + if self.order() != ChannelOrder::Custom { + return None; + } + + unsafe { + // SAFETY: ChannelCustom is repr(transparent) around AVChannelCustom + Some(std::slice::from_raw_parts( + self.0.u.map as _, + self.0.nb_channels as usize, + )) + } + } + + /// Extracts the owned `AVChannelLayout`. + /// + /// Clones it if not already owned. + pub fn into_owned(self) -> AVChannelLayout { + self.0.into_owned() + } + + /// Exposes a pointer to the contained `AVChannelLayout` for FFI purposes. + /// + /// This is guaranteed to be a non-null pointer. + pub fn as_ptr(&self) -> *const AVChannelLayout { + self.0.as_ref() as _ + } + + /// Get a human-readable [`String`] describing the channel layout properties. + /// + /// The returned string will be in the same format that is accepted by [`from_string`][ChannelLayout::from_string], + /// allowing to rebuild the same channel layout (excluding opaque pointers). + pub fn description(&self) -> String { + let mut buf = vec![0u8; 256]; + + unsafe { + let ret_val = + av_channel_layout_describe(self.as_ptr(), buf.as_mut_ptr() as _, buf.len()); + + match usize::try_from(ret_val) { + Ok(out_len) if out_len > 0 => { + #[cfg(feature = "ffmpeg_6_1")] + // 6.1 changed out_len to include the NUL byte, which we don't want + let out_len = out_len - 1; + + buf.truncate(out_len); + String::from_utf8_unchecked(buf) + } + // `av_channel_layout_describe` returned an error, or 0 bytes written. + _ => String::new(), + } + } + } + + /// Get the channel with the given index in a channel layout. + /// + /// Returns [`Channel::None`] when the index is invalid or the channel order is unspecified. + pub fn channel_from_index(&self, idx: u32) -> Channel { + Channel::from(unsafe { av_channel_layout_channel_from_index(self.as_ptr(), idx as c_uint) }) + } + + /// Get the index of a given channel in a channel layout. + pub fn index_from_channel(&self, channel: Channel) -> Option { + unsafe { + u32::try_from(av_channel_layout_index_from_channel( + self.as_ptr(), + AVChannel::from(channel), + )) + .ok() + } + } + + /// Get the index in a channel layout of a channel described by the given string. + /// + /// Returns the first match. Accepts channel names in the same format as [`from_string`][ChannelLayout::from_string]. + pub fn index_from_string>(&self, name: S) -> Option { + let cstr = CString::new(name.as_ref()).expect("no nul byte in name"); + let ret = unsafe { av_channel_layout_index_from_string(self.as_ptr(), cstr.as_ptr()) }; + + u32::try_from(ret).ok() + } + + /// Get a channel described by the given string. + /// + /// Accepts channel names in the same format as [`from_string`][ChannelLayout::from_string]. + /// + /// Returns [`Channel::None`] when the string is invalid or the channel order is unspecified. + pub fn channel_from_string>(&self, name: S) -> Channel { + let cstr = CString::new(name.as_ref()).expect("no nul byte in name"); + + Channel::from(unsafe { + av_channel_layout_channel_from_string(self.as_ptr(), cstr.as_ptr()) + }) + } + + /// Find out what channels from a given set are present in this layout, without regard for their positions. + pub fn subset(&self, mask: ChannelLayoutMask) -> ChannelLayoutMask { + ChannelLayoutMask::from_bits_truncate(unsafe { + av_channel_layout_subset(self.as_ptr(), mask.bits()) + }) + } + + /// Check whether this layout is valid (i.e. can describe audio data). + #[doc(alias = "check")] + pub fn is_valid(&self) -> bool { + unsafe { av_channel_layout_check(self.as_ptr()) != 0 } + } +} + +impl<'a> From for ChannelLayout<'a> { + fn from(value: AVChannelLayout) -> Self { + Self(Cow::Owned(value)) + } +} + +impl<'a> From<&'a AVChannelLayout> for ChannelLayout<'a> { + fn from(value: &'a AVChannelLayout) -> Self { + Self(Cow::Borrowed(value)) + } +} + +impl<'a> Borrow for ChannelLayout<'a> { + fn borrow(&self) -> &AVChannelLayout { + &self.0 + } +} + +// Type alias to reduce line length below +type Scl = ChannelLayout<'static>; + +// Constants +impl<'a> ChannelLayout<'a> { + pub const MONO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_MONO)); + pub const STEREO: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO)); + pub const _2POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2POINT1)); + pub const _2_1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_1)); + pub const SURROUND: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_SURROUND)); + pub const _3POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1)); + pub const _4POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT0)); + pub const _4POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_4POINT1)); + pub const _2_2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_2_2)); + pub const QUAD: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_QUAD)); + pub const _5POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0)); + pub const _5POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1)); + pub const _5POINT0_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT0_BACK)); + pub const _5POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1_BACK)); + pub const _6POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0)); + pub const _6POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT0_FRONT)); + pub const _3POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_3POINT1POINT2)); + pub const HEXAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXAGONAL)); + pub const _6POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1)); + pub const _6POINT1_BACK: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_BACK)); + pub const _6POINT1_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_6POINT1_FRONT)); + pub const _7POINT0: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0)); + pub const _7POINT0_FRONT: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT0_FRONT)); + pub const _7POINT1: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1)); + pub const _7POINT1_WIDE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE)); + pub const _7POINT1_WIDE_BACK: Scl = + ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1_WIDE_BACK)); + pub const _5POINT1POINT2_BACK: Scl = + ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT2_BACK)); + pub const OCTAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_OCTAGONAL)); + pub const CUBE: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_CUBE)); + pub const _5POINT1POINT4_BACK: Scl = + ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_5POINT1POINT4_BACK)); + pub const _7POINT1POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT2)); + pub const _7POINT1POINT4_BACK: Scl = + ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_7POINT1POINT4_BACK)); + pub const HEXADECAGONAL: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_HEXADECAGONAL)); + pub const STEREO_DOWNMIX: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_STEREO_DOWNMIX)); + pub const _22POINT2: Scl = ChannelLayout(Cow::Owned(AV_CHANNEL_LAYOUT_22POINT2)); +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn unspecified() { + let empty = ChannelLayout::unspecified(0); + assert_eq!(empty.order(), ChannelOrder::Unspecified); + assert_eq!(empty.channels(), 0); + assert!(!empty.is_valid()); + + let unspec = ChannelLayout::unspecified(42); + assert_eq!(unspec.order(), ChannelOrder::Unspecified); + assert_eq!(unspec.channels(), 42); + assert!(unspec.is_valid()); + } + + #[test] + fn custom() { + let channels = vec![ + ChannelCustom::new(Channel::FrontLeft), + ChannelCustom::new(Channel::FrontRight), + ChannelCustom::named(Channel::LowFrequency, "bass"), + ChannelCustom::named(Channel::BackLeft, "back left"), + // name too long on purpose -> should truncate to 15 chars + NUL + ChannelCustom::named(Channel::BottomFrontCenter, "bottom front center"), + ]; + + let custom = ChannelLayout::custom(channels.clone()); + assert!(custom.is_valid()); + assert_eq!(custom.channels(), 5); + assert_eq!(custom.order(), ChannelOrder::Custom); + assert_eq!(custom.map().unwrap(), &channels); + } + + #[test] + fn defaults() { + let unspec = ChannelLayout::default_for_channels(0); + assert!(unspec.order() == ChannelOrder::Unspecified); + assert!(!unspec.is_valid()); + + for i in 1..12 { + let layout = ChannelLayout::default_for_channels(i); + assert_eq!(layout.channels(), i); + assert!(layout.is_valid(), "default layout invalid for {i} channels"); + assert!(!layout.description().is_empty()); + } + } + + #[test] + fn from_mask() { + use ChannelLayout as Layout; + use ChannelLayoutMask as Mask; + + assert_eq!(Layout::from_mask(Mask::empty()), None); + + let tests = [ + (Mask::MONO, Layout::MONO), + (Mask::STEREO, Layout::STEREO), + (Mask::_2POINT1, Layout::_2POINT1), + (Mask::_2_1, Layout::_2_1), + (Mask::SURROUND, Layout::SURROUND), + (Mask::_3POINT1, Layout::_3POINT1), + (Mask::_4POINT0, Layout::_4POINT0), + (Mask::_4POINT1, Layout::_4POINT1), + (Mask::_2_2, Layout::_2_2), + (Mask::QUAD, Layout::QUAD), + (Mask::_5POINT0, Layout::_5POINT0), + (Mask::_5POINT1, Layout::_5POINT1), + (Mask::_5POINT0_BACK, Layout::_5POINT0_BACK), + (Mask::_5POINT1_BACK, Layout::_5POINT1_BACK), + (Mask::_6POINT0, Layout::_6POINT0), + (Mask::_6POINT0_FRONT, Layout::_6POINT0_FRONT), + (Mask::HEXAGONAL, Layout::HEXAGONAL), + (Mask::_3POINT1POINT2, Layout::_3POINT1POINT2), + (Mask::_6POINT1, Layout::_6POINT1), + (Mask::_6POINT1_BACK, Layout::_6POINT1_BACK), + (Mask::_6POINT1_FRONT, Layout::_6POINT1_FRONT), + (Mask::_7POINT0, Layout::_7POINT0), + (Mask::_7POINT0_FRONT, Layout::_7POINT0_FRONT), + (Mask::_7POINT1, Layout::_7POINT1), + (Mask::_7POINT1_WIDE, Layout::_7POINT1_WIDE), + (Mask::_7POINT1_WIDE_BACK, Layout::_7POINT1_WIDE_BACK), + (Mask::_5POINT1POINT2_BACK, Layout::_5POINT1POINT2_BACK), + (Mask::OCTAGONAL, Layout::OCTAGONAL), + (Mask::CUBE, Layout::CUBE), + (Mask::_5POINT1POINT4_BACK, Layout::_5POINT1POINT4_BACK), + (Mask::_7POINT1POINT2, Layout::_7POINT1POINT2), + (Mask::_7POINT1POINT4_BACK, Layout::_7POINT1POINT4_BACK), + (Mask::HEXADECAGONAL, Layout::HEXADECAGONAL), + (Mask::STEREO_DOWNMIX, Layout::STEREO_DOWNMIX), + (Mask::_22POINT2, Layout::_22POINT2), + ]; + + for (mask, expected) in tests { + let result = Layout::from_mask(mask).expect("can find layout for bitmask"); + assert_eq!( + result.order(), + ChannelOrder::Native, + "layout from mask must use native order" + ); + assert_eq!(result.mask(), Some(mask)); + assert_eq!(result, expected); + } + } + + #[test] + fn from_string() { + let test_strings = [ + ("1 channels (FRC)", ChannelOrder::Native, 1), + ("FL@Left+FR@Right+LFE", ChannelOrder::Custom, 3), + ("0x4", ChannelOrder::Native, 1), + ("4c", ChannelOrder::Native, 4), + ("7 channels", ChannelOrder::Unspecified, 7), + ("ambisonic 2+stereo", ChannelOrder::Ambisonic, 11), + ]; + + for (s, order, channels) in test_strings { + let result = ChannelLayout::from_string(s).expect("can find layout for description"); + assert!(result.is_valid()); + assert_eq!(result.order(), order); + assert_eq!(result.channels(), channels); + } + } + + #[test] + fn describe() { + use ChannelLayout as Layout; + use ChannelLayoutMask as Mask; + + let tests = [ + (Layout::MONO, "mono"), + (Layout::STEREO, "stereo"), + (Layout::_5POINT1, "5.1(side)"), + ( + Layout::from_string("FL@Left+FR@Right+LFE").unwrap(), + "3 channels (FL@Left+FR@Right+LFE)", + ), + ( + Layout::from_mask(Mask::FRONT_RIGHT_OF_CENTER).unwrap(), + "1 channels (FRC)", + ), + #[cfg(feature = "ffmpeg_6_1")] + (Layout::_7POINT1POINT4_BACK, "7.1.4"), + #[cfg(not(feature = "ffmpeg_6_1"))] + ( + Layout::_7POINT1POINT4_BACK, + "12 channels (FL+FR+FC+LFE+BL+BR+SL+SR+TFL+TFR+TBL+TBR)", + ), + ]; + + for (layout, expected) in tests { + assert!(layout.is_valid()); + + let desc = layout.description(); + assert!(!desc.is_empty()); + assert_eq!(desc, expected); + } + } +} diff --git a/src/util/channel_layout.rs b/src/util/channel_layout/mask.rs similarity index 94% rename from src/util/channel_layout.rs rename to src/util/channel_layout/mask.rs index f6b9d6c..e5f8e70 100644 --- a/src/util/channel_layout.rs +++ b/src/util/channel_layout/mask.rs @@ -2,7 +2,7 @@ use crate::ffi::*; use libc::c_ulonglong; bitflags! { - pub struct ChannelLayout: c_ulonglong { + pub struct ChannelLayoutMask: c_ulonglong { const FRONT_LEFT = AV_CH_FRONT_LEFT; const FRONT_RIGHT = AV_CH_FRONT_RIGHT; const FRONT_CENTER = AV_CH_FRONT_CENTER; @@ -75,15 +75,17 @@ bitflags! { } } -impl ChannelLayout { +impl ChannelLayoutMask { #[inline] pub fn channels(&self) -> i32 { unsafe { av_get_channel_layout_nb_channels(self.bits()) } } - pub fn default(number: i32) -> ChannelLayout { + pub fn default(number: i32) -> ChannelLayoutMask { unsafe { - ChannelLayout::from_bits_truncate(av_get_default_channel_layout(number) as c_ulonglong) + ChannelLayoutMask::from_bits_truncate( + av_get_default_channel_layout(number) as c_ulonglong + ) } } } diff --git a/src/util/channel_layout/mod.rs b/src/util/channel_layout/mod.rs new file mode 100644 index 0000000..9a21da8 --- /dev/null +++ b/src/util/channel_layout/mod.rs @@ -0,0 +1,27 @@ +mod mask; +pub use mask::*; + +#[cfg(feature = "ffmpeg_5_1")] +mod channel_custom; +#[cfg(feature = "ffmpeg_5_1")] +pub use channel_custom::*; + +#[cfg(feature = "ffmpeg_5_1")] +mod channel; +#[cfg(feature = "ffmpeg_5_1")] +pub use channel::*; + +#[cfg(feature = "ffmpeg_5_1")] +mod iter; +#[cfg(feature = "ffmpeg_5_1")] +pub use iter::*; + +#[cfg(feature = "ffmpeg_5_1")] +mod layout; +#[cfg(feature = "ffmpeg_5_1")] +pub use layout::*; + +#[cfg(feature = "ffmpeg_5_1")] +mod order; +#[cfg(feature = "ffmpeg_5_1")] +pub use order::*; diff --git a/src/util/channel_layout/order.rs b/src/util/channel_layout/order.rs new file mode 100644 index 0000000..3abf0fe --- /dev/null +++ b/src/util/channel_layout/order.rs @@ -0,0 +1,47 @@ +use crate::ffi::AVChannelOrder; + +use AVChannelOrder::*; +use ChannelOrder::*; + +/// Specifies an order for audio channels. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ChannelOrder { + /// No channel order. Only the channel count is specified. + Unspecified, + + /// Native channel order, i.e. the channels are in the same order in which they + /// are defined in the [`Channel`][super::Channel] enum. This supports up to 63 channels. + Native, + + /// The channel order does not correspond to any predefined order and is stored as an + /// explicit map. This can be used to support layouts with more than 64 channels or with + /// empty channels at arbitrary positions. + Custom, + + /// The audio is represented as the decomposition of the sound field into spherical harmonics. + Ambisonic, +} + +impl From for ChannelOrder { + fn from(value: AVChannelOrder) -> Self { + match value { + AV_CHANNEL_ORDER_UNSPEC => Unspecified, + AV_CHANNEL_ORDER_NATIVE => Native, + AV_CHANNEL_ORDER_CUSTOM => Custom, + AV_CHANNEL_ORDER_AMBISONIC => Ambisonic, + #[cfg(feature = "non-exhaustive-enums")] + _ => unimplemented!(), + } + } +} + +impl From for AVChannelOrder { + fn from(value: ChannelOrder) -> Self { + match value { + Unspecified => AV_CHANNEL_ORDER_UNSPEC, + Native => AV_CHANNEL_ORDER_NATIVE, + Custom => AV_CHANNEL_ORDER_CUSTOM, + Ambisonic => AV_CHANNEL_ORDER_AMBISONIC, + } + } +} diff --git a/src/util/frame/audio.rs b/src/util/frame/audio.rs index 9614252..2352897 100644 --- a/src/util/frame/audio.rs +++ b/src/util/frame/audio.rs @@ -5,9 +5,12 @@ use std::slice; use super::Frame; use crate::ffi::*; use crate::util::format; -use crate::ChannelLayout; +use crate::ChannelLayoutMask; use libc::{c_int, c_ulonglong}; +#[cfg(feature = "ffmpeg_5_1")] +use crate::ChannelLayout; + #[derive(PartialEq, Eq)] pub struct Audio(Frame); @@ -18,7 +21,12 @@ impl Audio { } #[inline] - pub unsafe fn alloc(&mut self, format: format::Sample, samples: usize, layout: ChannelLayout) { + pub unsafe fn alloc( + &mut self, + format: format::Sample, + samples: usize, + layout: ChannelLayoutMask, + ) { self.set_format(format); self.set_samples(samples); self.set_channel_layout(layout); @@ -34,7 +42,7 @@ impl Audio { } #[inline] - pub fn new(format: format::Sample, samples: usize, layout: ChannelLayout) -> Self { + pub fn new(format: format::Sample, samples: usize, layout: ChannelLayoutMask) -> Self { unsafe { let mut frame = Audio::empty(); frame.alloc(format, samples, layout); @@ -62,17 +70,33 @@ impl Audio { } #[inline] - pub fn channel_layout(&self) -> ChannelLayout { - unsafe { ChannelLayout::from_bits_truncate((*self.as_ptr()).channel_layout as c_ulonglong) } + pub fn channel_layout(&self) -> ChannelLayoutMask { + unsafe { + ChannelLayoutMask::from_bits_truncate((*self.as_ptr()).channel_layout as c_ulonglong) + } } #[inline] - pub fn set_channel_layout(&mut self, value: ChannelLayout) { + pub fn set_channel_layout(&mut self, value: ChannelLayoutMask) { unsafe { (*self.as_mut_ptr()).channel_layout = value.bits() as u64; } } + #[cfg(feature = "ffmpeg_5_1")] + #[inline] + pub fn ch_layout(&self) -> ChannelLayout { + unsafe { ChannelLayout::from(&self.as_ref().unwrap().ch_layout) } + } + + #[cfg(feature = "ffmpeg_5_1")] + #[inline] + pub fn set_ch_layout(&mut self, value: ChannelLayout) { + unsafe { + self.as_mut().unwrap().ch_layout = value.into_owned(); + } + } + #[inline] pub fn channels(&self) -> u16 { unsafe { (*self.as_ptr()).channels as u16 } diff --git a/src/util/frame/mod.rs b/src/util/frame/mod.rs index 03066eb..c950ae9 100644 --- a/src/util/frame/mod.rs +++ b/src/util/frame/mod.rs @@ -58,6 +58,16 @@ impl Frame { self.ptr } + #[inline(always)] + pub unsafe fn as_ref(&self) -> Option<&AVFrame> { + self.ptr.as_ref() + } + + #[inline(always)] + pub unsafe fn as_mut(&mut self) -> Option<&mut AVFrame> { + self.ptr.as_mut() + } + #[inline(always)] pub unsafe fn is_empty(&self) -> bool { (*self.as_ptr()).data[0].is_null() diff --git a/src/util/option/traits.rs b/src/util/option/traits.rs index 8e96f0f..223976f 100644 --- a/src/util/option/traits.rs +++ b/src/util/option/traits.rs @@ -5,7 +5,7 @@ use std::mem; use crate::ffi::*; use crate::util::format; -use crate::{ChannelLayout, Error, Rational}; +use crate::{ChannelLayoutMask, Error, Rational}; use libc::{c_int, c_void}; macro_rules! check { @@ -130,7 +130,7 @@ pub trait Settable: Target { } } - fn set_channel_layout(&mut self, name: &str, layout: ChannelLayout) -> Result<(), Error> { + fn set_channel_layout(&mut self, name: &str, layout: ChannelLayoutMask) -> Result<(), Error> { unsafe { let name = CString::new(name).unwrap();