#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MediaType {
Audio,
Video,
Subtitle,
Data,
Unknown,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ChannelPosition {
FrontLeft,
FrontRight,
FrontCenter,
LowFrequency,
BackLeft,
BackRight,
FrontLeftOfCenter,
FrontRightOfCenter,
BackCenter,
SideLeft,
SideRight,
TopFrontLeft,
TopFrontRight,
TopBackLeft,
TopBackRight,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ChannelLayout {
Mono,
Stereo,
Stereo21,
Surround30,
Quad,
Surround40,
Surround41,
Surround50,
Surround51,
Surround60,
Surround61,
Surround70,
Surround71,
LoRo,
LtRt,
DiscreteN(u16),
}
impl ChannelLayout {
pub fn channel_count(&self) -> u16 {
match self {
Self::Mono => 1,
Self::Stereo | Self::LoRo | Self::LtRt => 2,
Self::Stereo21 | Self::Surround30 => 3,
Self::Quad | Self::Surround40 => 4,
Self::Surround41 | Self::Surround50 => 5,
Self::Surround51 | Self::Surround60 => 6,
Self::Surround61 | Self::Surround70 => 7,
Self::Surround71 => 8,
Self::DiscreteN(n) => *n,
}
}
pub fn positions(&self) -> &'static [ChannelPosition] {
use ChannelPosition::*;
match self {
Self::Mono => &[FrontCenter],
Self::Stereo | Self::LoRo | Self::LtRt => &[FrontLeft, FrontRight],
Self::Stereo21 => &[FrontLeft, FrontRight, LowFrequency],
Self::Surround30 => &[FrontLeft, FrontRight, FrontCenter],
Self::Quad => &[FrontLeft, FrontRight, SideLeft, SideRight],
Self::Surround40 => &[FrontLeft, FrontRight, FrontCenter, BackCenter],
Self::Surround41 => &[FrontLeft, FrontRight, FrontCenter, BackCenter, LowFrequency],
Self::Surround50 => &[FrontLeft, FrontRight, FrontCenter, SideLeft, SideRight],
Self::Surround51 => &[
FrontLeft,
FrontRight,
FrontCenter,
LowFrequency,
SideLeft,
SideRight,
],
Self::Surround60 => &[
FrontLeft,
FrontRight,
FrontCenter,
BackCenter,
SideLeft,
SideRight,
],
Self::Surround61 => &[
FrontLeft,
FrontRight,
FrontCenter,
LowFrequency,
BackCenter,
SideLeft,
SideRight,
],
Self::Surround70 => &[
FrontLeft,
FrontRight,
FrontCenter,
SideLeft,
SideRight,
BackLeft,
BackRight,
],
Self::Surround71 => &[
FrontLeft,
FrontRight,
FrontCenter,
LowFrequency,
SideLeft,
SideRight,
BackLeft,
BackRight,
],
Self::DiscreteN(_) => &[],
}
}
pub fn positions_owned(&self) -> Vec<ChannelPosition> {
self.positions().to_vec()
}
pub fn position(&self, idx: usize) -> Option<ChannelPosition> {
self.positions().get(idx).copied()
}
pub fn has_lfe(&self) -> bool {
self.positions()
.iter()
.any(|p| matches!(p, ChannelPosition::LowFrequency))
}
pub fn is_surround(&self) -> bool {
self.channel_count() > 2 || self.has_lfe()
}
pub fn from_count(n: u16) -> ChannelLayout {
match n {
1 => Self::Mono,
2 => Self::Stereo,
3 => Self::Surround30,
4 => Self::Quad,
5 => Self::Surround50,
6 => Self::Surround51,
7 => Self::Surround61,
8 => Self::Surround71,
other => Self::DiscreteN(other),
}
}
}
impl std::fmt::Display for ChannelLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Mono => "mono",
Self::Stereo => "stereo",
Self::Stereo21 => "2.1",
Self::Surround30 => "3.0",
Self::Quad => "quad",
Self::Surround40 => "4.0",
Self::Surround41 => "4.1",
Self::Surround50 => "5.0",
Self::Surround51 => "5.1",
Self::Surround60 => "6.0",
Self::Surround61 => "6.1",
Self::Surround70 => "7.0",
Self::Surround71 => "7.1",
Self::LoRo => "loro",
Self::LtRt => "ltrt",
Self::DiscreteN(n) => return write!(f, "discrete{n}"),
};
f.write_str(s)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseChannelLayoutError(pub String);
impl std::fmt::Display for ParseChannelLayoutError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unrecognised channel layout: {:?}", self.0)
}
}
impl std::error::Error for ParseChannelLayoutError {}
impl std::str::FromStr for ChannelLayout {
type Err = ParseChannelLayoutError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lower = s.trim().to_ascii_lowercase();
let layout = match lower.as_str() {
"mono" | "1.0" => Self::Mono,
"stereo" | "2.0" => Self::Stereo,
"2.1" => Self::Stereo21,
"3.0" | "surround3" | "surround30" => Self::Surround30,
"quad" => Self::Quad,
"4.0" | "surround4" | "surround40" => Self::Surround40,
"4.1" | "surround41" => Self::Surround41,
"5.0" | "surround5" | "surround50" => Self::Surround50,
"5.1" | "surround51" => Self::Surround51,
"6.0" | "surround6" | "surround60" => Self::Surround60,
"6.1" | "surround61" => Self::Surround61,
"7.0" | "surround7" | "surround70" => Self::Surround70,
"7.1" | "surround71" => Self::Surround71,
"loro" | "lo/ro" => Self::LoRo,
"ltrt" | "lt/rt" => Self::LtRt,
other => {
if let Some(rest) = other.strip_prefix("discrete") {
if let Ok(n) = rest.parse::<u16>() {
return Ok(Self::DiscreteN(n));
}
}
return Err(ParseChannelLayoutError(s.to_owned()));
}
};
Ok(layout)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[repr(u8)]
pub enum SampleFormat {
U8 = 0,
S8 = 1,
S16 = 2,
S24 = 3,
S32 = 4,
F32 = 5,
F64 = 6,
U8P = 7,
S16P = 8,
S32P = 9,
F32P = 10,
F64P = 11,
}
impl SampleFormat {
pub fn is_planar(&self) -> bool {
matches!(
self,
Self::U8P | Self::S16P | Self::S32P | Self::F32P | Self::F64P
)
}
pub fn bytes_per_sample(&self) -> usize {
match self {
Self::U8 | Self::U8P | Self::S8 => 1,
Self::S16 | Self::S16P => 2,
Self::S24 => 3,
Self::S32 | Self::S32P | Self::F32 | Self::F32P => 4,
Self::F64 | Self::F64P => 8,
}
}
pub fn is_float(&self) -> bool {
matches!(self, Self::F32 | Self::F64 | Self::F32P | Self::F64P)
}
pub fn plane_count(&self, channels: u16) -> usize {
if self.is_planar() {
channels as usize
} else {
1
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
#[repr(u16)]
pub enum PixelFormat {
Yuv420P = 0,
Yuv422P = 1,
Yuv444P = 2,
Rgb24 = 3,
Rgba = 4,
Gray8 = 5,
Pal8 = 6,
Bgr24 = 7,
Bgra = 8,
Argb = 9,
Abgr = 10,
Rgb48Le = 11,
Rgba64Le = 12,
Gray16Le = 13,
Gray10Le = 14,
Gray12Le = 15,
Yuv420P10Le = 16,
Yuv422P10Le = 17,
Yuv444P10Le = 18,
Yuv420P12Le = 19,
Yuv422P12Le = 20,
Yuv444P12Le = 21,
YuvJ420P = 22,
YuvJ422P = 23,
YuvJ444P = 24,
Nv12 = 25,
Nv21 = 26,
Ya8 = 27,
Yuva420P = 28,
MonoBlack = 29,
MonoWhite = 30,
Yuyv422 = 31,
Uyvy422 = 32,
Cmyk = 33,
}
impl PixelFormat {
pub fn is_planar(&self) -> bool {
matches!(
self,
Self::Yuv420P
| Self::Yuv422P
| Self::Yuv444P
| Self::Yuv420P10Le
| Self::Yuv422P10Le
| Self::Yuv444P10Le
| Self::Yuv420P12Le
| Self::Yuv422P12Le
| Self::Yuv444P12Le
| Self::YuvJ420P
| Self::YuvJ422P
| Self::YuvJ444P
| Self::Nv12
| Self::Nv21
| Self::Yuva420P
)
}
pub fn is_palette(&self) -> bool {
matches!(self, Self::Pal8)
}
pub fn has_alpha(&self) -> bool {
matches!(
self,
Self::Rgba
| Self::Bgra
| Self::Argb
| Self::Abgr
| Self::Rgba64Le
| Self::Ya8
| Self::Yuva420P
)
}
pub fn plane_count(&self) -> usize {
match self {
Self::Nv12 | Self::Nv21 => 2,
Self::Yuv420P
| Self::Yuv422P
| Self::Yuv444P
| Self::Yuv420P10Le
| Self::Yuv422P10Le
| Self::Yuv444P10Le
| Self::Yuv420P12Le
| Self::Yuv422P12Le
| Self::Yuv444P12Le
| Self::YuvJ420P
| Self::YuvJ422P
| Self::YuvJ444P => 3,
Self::Yuva420P => 4,
_ => 1,
}
}
pub fn bits_per_pixel_approx(&self) -> u32 {
match self {
Self::MonoBlack | Self::MonoWhite => 1,
Self::Gray8 | Self::Pal8 => 8,
Self::Ya8 => 16,
Self::Gray16Le | Self::Gray10Le | Self::Gray12Le => 16,
Self::Rgb24 | Self::Bgr24 => 24,
Self::Rgba | Self::Bgra | Self::Argb | Self::Abgr => 32,
Self::Rgb48Le => 48,
Self::Rgba64Le => 64,
Self::Yuyv422 | Self::Uyvy422 => 16,
Self::Cmyk => 32,
Self::Yuv420P | Self::YuvJ420P | Self::Nv12 | Self::Nv21 => 12,
Self::Yuv422P | Self::YuvJ422P => 16,
Self::Yuv444P | Self::YuvJ444P => 24,
Self::Yuv420P10Le | Self::Yuv420P12Le => 24,
Self::Yuv422P10Le | Self::Yuv422P12Le => 32,
Self::Yuv444P10Le | Self::Yuv444P12Le => 48,
Self::Yuva420P => 20,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pixel_format_discriminants_pinned() {
assert_eq!(PixelFormat::Yuv420P as u16, 0);
assert_eq!(PixelFormat::Yuv422P as u16, 1);
assert_eq!(PixelFormat::Yuv444P as u16, 2);
assert_eq!(PixelFormat::Rgb24 as u16, 3);
assert_eq!(PixelFormat::Rgba as u16, 4);
assert_eq!(PixelFormat::Gray8 as u16, 5);
assert_eq!(PixelFormat::Pal8 as u16, 6);
assert_eq!(PixelFormat::Bgr24 as u16, 7);
assert_eq!(PixelFormat::Bgra as u16, 8);
assert_eq!(PixelFormat::Argb as u16, 9);
assert_eq!(PixelFormat::Abgr as u16, 10);
assert_eq!(PixelFormat::Rgb48Le as u16, 11);
assert_eq!(PixelFormat::Rgba64Le as u16, 12);
assert_eq!(PixelFormat::Gray16Le as u16, 13);
assert_eq!(PixelFormat::Gray10Le as u16, 14);
assert_eq!(PixelFormat::Gray12Le as u16, 15);
assert_eq!(PixelFormat::Yuv420P10Le as u16, 16);
assert_eq!(PixelFormat::Yuv422P10Le as u16, 17);
assert_eq!(PixelFormat::Yuv444P10Le as u16, 18);
assert_eq!(PixelFormat::Yuv420P12Le as u16, 19);
assert_eq!(PixelFormat::Yuv422P12Le as u16, 20);
assert_eq!(PixelFormat::Yuv444P12Le as u16, 21);
assert_eq!(PixelFormat::YuvJ420P as u16, 22);
assert_eq!(PixelFormat::YuvJ422P as u16, 23);
assert_eq!(PixelFormat::YuvJ444P as u16, 24);
assert_eq!(PixelFormat::Nv12 as u16, 25);
assert_eq!(PixelFormat::Nv21 as u16, 26);
assert_eq!(PixelFormat::Ya8 as u16, 27);
assert_eq!(PixelFormat::Yuva420P as u16, 28);
assert_eq!(PixelFormat::MonoBlack as u16, 29);
assert_eq!(PixelFormat::MonoWhite as u16, 30);
assert_eq!(PixelFormat::Yuyv422 as u16, 31);
assert_eq!(PixelFormat::Uyvy422 as u16, 32);
assert_eq!(PixelFormat::Cmyk as u16, 33);
}
#[test]
fn sample_format_discriminants_pinned() {
assert_eq!(SampleFormat::U8 as u8, 0);
assert_eq!(SampleFormat::S8 as u8, 1);
assert_eq!(SampleFormat::S16 as u8, 2);
assert_eq!(SampleFormat::S24 as u8, 3);
assert_eq!(SampleFormat::S32 as u8, 4);
assert_eq!(SampleFormat::F32 as u8, 5);
assert_eq!(SampleFormat::F64 as u8, 6);
assert_eq!(SampleFormat::U8P as u8, 7);
assert_eq!(SampleFormat::S16P as u8, 8);
assert_eq!(SampleFormat::S32P as u8, 9);
assert_eq!(SampleFormat::F32P as u8, 10);
assert_eq!(SampleFormat::F64P as u8, 11);
}
#[test]
fn high_bit_yuv_planar_metadata() {
assert!(PixelFormat::Yuv420P10Le.is_planar());
assert!(PixelFormat::Yuv422P10Le.is_planar());
assert!(PixelFormat::Yuv444P10Le.is_planar());
assert!(PixelFormat::Yuv420P12Le.is_planar());
assert!(PixelFormat::Yuv422P12Le.is_planar());
assert!(PixelFormat::Yuv444P12Le.is_planar());
assert_eq!(PixelFormat::Yuv420P12Le.plane_count(), 3);
assert_eq!(PixelFormat::Yuv422P12Le.plane_count(), 3);
assert_eq!(PixelFormat::Yuv444P12Le.plane_count(), 3);
assert!(!PixelFormat::Yuv422P12Le.has_alpha());
assert!(!PixelFormat::Yuv444P12Le.has_alpha());
assert!(!PixelFormat::Yuv422P12Le.is_palette());
assert!(!PixelFormat::Yuv444P12Le.is_palette());
}
#[test]
fn channel_layout_round_trip_count_for_known_layouts() {
for n in 1..=8u16 {
let layout = ChannelLayout::from_count(n);
assert_eq!(layout.channel_count(), n, "round-trip failed for n={n}");
assert!(
!matches!(layout, ChannelLayout::DiscreteN(_)),
"from_count({n}) unexpectedly produced DiscreteN"
);
}
}
#[test]
fn channel_layout_from_count_default_table() {
assert_eq!(ChannelLayout::from_count(1), ChannelLayout::Mono);
assert_eq!(ChannelLayout::from_count(2), ChannelLayout::Stereo);
assert_eq!(ChannelLayout::from_count(3), ChannelLayout::Surround30);
assert_eq!(ChannelLayout::from_count(4), ChannelLayout::Quad);
assert_eq!(ChannelLayout::from_count(5), ChannelLayout::Surround50);
assert_eq!(ChannelLayout::from_count(6), ChannelLayout::Surround51);
assert_eq!(ChannelLayout::from_count(7), ChannelLayout::Surround61);
assert_eq!(ChannelLayout::from_count(8), ChannelLayout::Surround71);
}
#[test]
fn channel_layout_unknown_count_falls_through_to_discrete() {
assert_eq!(ChannelLayout::from_count(0), ChannelLayout::DiscreteN(0));
assert_eq!(ChannelLayout::from_count(13), ChannelLayout::DiscreteN(13));
assert_eq!(
ChannelLayout::from_count(64).channel_count(),
64,
"DiscreteN must report the count it was constructed with"
);
}
#[test]
fn channel_layout_position_lookup() {
assert_eq!(
ChannelLayout::Stereo.position(0),
Some(ChannelPosition::FrontLeft)
);
assert_eq!(
ChannelLayout::Stereo.position(1),
Some(ChannelPosition::FrontRight)
);
assert_eq!(ChannelLayout::Stereo.position(2), None);
let s51 = ChannelLayout::Surround51;
assert_eq!(s51.position(0), Some(ChannelPosition::FrontLeft));
assert_eq!(s51.position(1), Some(ChannelPosition::FrontRight));
assert_eq!(s51.position(2), Some(ChannelPosition::FrontCenter));
assert_eq!(s51.position(3), Some(ChannelPosition::LowFrequency));
assert_eq!(s51.position(4), Some(ChannelPosition::SideLeft));
assert_eq!(s51.position(5), Some(ChannelPosition::SideRight));
assert_eq!(s51.position(6), None);
assert_eq!(ChannelLayout::DiscreteN(13).position(0), None);
}
#[test]
fn channel_layout_lfe_and_surround_predicates() {
assert!(ChannelLayout::Surround51.has_lfe());
assert!(ChannelLayout::Surround71.has_lfe());
assert!(ChannelLayout::Stereo21.has_lfe());
assert!(!ChannelLayout::Quad.has_lfe());
assert!(!ChannelLayout::Surround50.has_lfe());
assert!(!ChannelLayout::Stereo.has_lfe());
assert!(!ChannelLayout::Mono.is_surround());
assert!(!ChannelLayout::Stereo.is_surround());
assert!(!ChannelLayout::LoRo.is_surround());
assert!(!ChannelLayout::LtRt.is_surround());
assert!(ChannelLayout::Stereo21.is_surround());
assert!(ChannelLayout::Surround51.is_surround());
assert!(ChannelLayout::Surround71.is_surround());
}
#[test]
fn channel_layout_display_and_fromstr_round_trip() {
use std::str::FromStr;
let cases = [
ChannelLayout::Mono,
ChannelLayout::Stereo,
ChannelLayout::Stereo21,
ChannelLayout::Surround30,
ChannelLayout::Quad,
ChannelLayout::Surround40,
ChannelLayout::Surround41,
ChannelLayout::Surround50,
ChannelLayout::Surround51,
ChannelLayout::Surround60,
ChannelLayout::Surround61,
ChannelLayout::Surround70,
ChannelLayout::Surround71,
ChannelLayout::LoRo,
ChannelLayout::LtRt,
ChannelLayout::DiscreteN(13),
];
for layout in cases {
let s = layout.to_string();
let parsed = ChannelLayout::from_str(&s).expect("display output must parse back");
assert_eq!(parsed, layout, "round-trip failed via {s:?}");
}
}
#[test]
fn channel_layout_fromstr_accepts_aliases_and_case() {
use std::str::FromStr;
assert_eq!(
ChannelLayout::from_str("STEREO").unwrap(),
ChannelLayout::Stereo
);
assert_eq!(
ChannelLayout::from_str("2.0").unwrap(),
ChannelLayout::Stereo
);
assert_eq!(
ChannelLayout::from_str("5.1").unwrap(),
ChannelLayout::Surround51
);
assert_eq!(
ChannelLayout::from_str("Lo/Ro").unwrap(),
ChannelLayout::LoRo
);
assert_eq!(
ChannelLayout::from_str("lt/rt").unwrap(),
ChannelLayout::LtRt
);
assert!(ChannelLayout::from_str("absurd_layout").is_err());
}
#[test]
fn channel_layout_positions_owned_matches_static_slice() {
for layout in [
ChannelLayout::Mono,
ChannelLayout::Surround51,
ChannelLayout::Surround71,
] {
assert_eq!(layout.positions_owned(), layout.positions());
}
assert!(ChannelLayout::DiscreteN(7).positions_owned().is_empty());
}
#[test]
fn sample_format_plane_count_interleaved_is_one() {
for ch in [1u16, 2, 6, 8, 64, 0] {
assert_eq!(SampleFormat::S16.plane_count(ch), 1);
assert_eq!(SampleFormat::F32.plane_count(ch), 1);
assert_eq!(SampleFormat::U8.plane_count(ch), 1);
assert_eq!(SampleFormat::S24.plane_count(ch), 1);
}
}
#[test]
fn sample_format_plane_count_planar_matches_channels() {
assert_eq!(SampleFormat::S16P.plane_count(1), 1);
assert_eq!(SampleFormat::S16P.plane_count(2), 2);
assert_eq!(SampleFormat::F32P.plane_count(6), 6);
assert_eq!(SampleFormat::F64P.plane_count(8), 8);
assert_eq!(SampleFormat::S32P.plane_count(0), 0);
}
#[test]
fn high_bit_yuv_bits_per_pixel_approx() {
assert_eq!(PixelFormat::Yuv422P10Le.bits_per_pixel_approx(), 32);
assert_eq!(PixelFormat::Yuv422P12Le.bits_per_pixel_approx(), 32);
assert_eq!(PixelFormat::Yuv444P10Le.bits_per_pixel_approx(), 48);
assert_eq!(PixelFormat::Yuv444P12Le.bits_per_pixel_approx(), 48);
assert_eq!(PixelFormat::Yuv420P12Le.bits_per_pixel_approx(), 24);
}
}