use crate::error::Result;
use crate::ir::CodecConfig;
use core::num::NonZeroU16;
pub const EXTENDED_SAR_IDC: u8 = 255;
const FIRST_RESERVED_IDC: u8 = 17;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AspectRatioIdc {
Unspecified,
Square,
Sar12x11,
Sar10x11,
Sar16x11,
Sar40x33,
Sar24x11,
Sar20x11,
Sar32x11,
Sar80x33,
Sar18x11,
Sar15x11,
Sar64x33,
Sar160x99,
Sar4x3,
Sar3x2,
Sar2x1,
Reserved(u8),
ExtendedSar {
sar_width: u16,
sar_height: u16,
},
}
const TABLE_E1: [(AspectRatioIdc, u16, u16); 16] = [
(AspectRatioIdc::Square, 1, 1),
(AspectRatioIdc::Sar12x11, 12, 11),
(AspectRatioIdc::Sar10x11, 10, 11),
(AspectRatioIdc::Sar16x11, 16, 11),
(AspectRatioIdc::Sar40x33, 40, 33),
(AspectRatioIdc::Sar24x11, 24, 11),
(AspectRatioIdc::Sar20x11, 20, 11),
(AspectRatioIdc::Sar32x11, 32, 11),
(AspectRatioIdc::Sar80x33, 80, 33),
(AspectRatioIdc::Sar18x11, 18, 11),
(AspectRatioIdc::Sar15x11, 15, 11),
(AspectRatioIdc::Sar64x33, 64, 33),
(AspectRatioIdc::Sar160x99, 160, 99),
(AspectRatioIdc::Sar4x3, 4, 3),
(AspectRatioIdc::Sar3x2, 3, 2),
(AspectRatioIdc::Sar2x1, 2, 1),
];
impl AspectRatioIdc {
pub fn from_wire(idc: u8, extended: Option<(u16, u16)>) -> Self {
match idc {
0 => Self::Unspecified,
EXTENDED_SAR_IDC => {
let (sar_width, sar_height) = extended.unwrap_or((0, 0));
Self::ExtendedSar {
sar_width,
sar_height,
}
}
FIRST_RESERVED_IDC..=254 => Self::Reserved(idc),
_ => TABLE_E1[usize::from(idc) - 1].0,
}
}
pub fn idc(&self) -> u8 {
match self {
Self::Unspecified => 0,
Self::Reserved(idc) => *idc,
Self::ExtendedSar { .. } => EXTENDED_SAR_IDC,
table_row => TABLE_E1
.iter()
.zip(1u8..)
.find_map(|((row, _, _), idc)| (row == table_row).then_some(idc))
.unwrap_or(0),
}
}
pub fn sample_aspect_ratio(&self) -> Option<SampleAspectRatio> {
match self {
Self::Unspecified | Self::Reserved(_) => None,
Self::ExtendedSar {
sar_width,
sar_height,
} => SampleAspectRatio::new(*sar_width, *sar_height),
table_row => TABLE_E1
.iter()
.find(|(row, _, _)| row == table_row)
.and_then(|(_, w, h)| SampleAspectRatio::new(*w, *h)),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Unspecified => "unspecified",
Self::Square => "1:1",
Self::Sar12x11 => "12:11",
Self::Sar10x11 => "10:11",
Self::Sar16x11 => "16:11",
Self::Sar40x33 => "40:33",
Self::Sar24x11 => "24:11",
Self::Sar20x11 => "20:11",
Self::Sar32x11 => "32:11",
Self::Sar80x33 => "80:33",
Self::Sar18x11 => "18:11",
Self::Sar15x11 => "15:11",
Self::Sar64x33 => "64:33",
Self::Sar160x99 => "160:99",
Self::Sar4x3 => "4:3",
Self::Sar3x2 => "3:2",
Self::Sar2x1 => "2:1",
Self::Reserved(_) => "reserved",
Self::ExtendedSar { .. } => "Extended_SAR",
}
}
}
broadcast_common::impl_spec_display!(AspectRatioIdc, Reserved);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SampleAspectRatio {
width: NonZeroU16,
height: NonZeroU16,
}
impl SampleAspectRatio {
pub fn new(width: u16, height: u16) -> Option<Self> {
Some(Self {
width: NonZeroU16::new(width)?,
height: NonZeroU16::new(height)?,
})
}
pub fn width(&self) -> u16 {
self.width.get()
}
pub fn height(&self) -> u16 {
self.height.get()
}
pub fn as_pair(&self) -> (u16, u16) {
(self.width(), self.height())
}
pub fn reduced(&self) -> Self {
let (mut a, mut b) = (self.width(), self.height());
while b != 0 {
(a, b) = (b, a % b);
}
let g = a.max(1);
Self {
width: NonZeroU16::new(self.width() / g).unwrap_or(self.width),
height: NonZeroU16::new(self.height() / g).unwrap_or(self.height),
}
}
}
impl From<SampleAspectRatio> for (u16, u16) {
fn from(sar: SampleAspectRatio) -> Self {
sar.as_pair()
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DisplayGeometry {
pub coded_width: u32,
pub coded_height: u32,
pub aspect_ratio: Option<AspectRatioIdc>,
}
impl DisplayGeometry {
pub fn new(coded_width: u32, coded_height: u32, aspect_ratio: Option<AspectRatioIdc>) -> Self {
Self {
coded_width,
coded_height,
aspect_ratio,
}
}
pub fn from_avc_sps(sps_nal: &[u8]) -> Result<Self> {
crate::sps::decode_avc_display_facts(sps_nal).map(Self::from_facts)
}
pub fn from_hevc_sps(sps_nal: &[u8]) -> Result<Self> {
crate::sps::decode_hevc_display_facts(sps_nal).map(Self::from_facts)
}
pub fn from_codec_config(config: &CodecConfig) -> Option<Self> {
match config {
CodecConfig::Avc { config, .. } => {
let sps = config.config.sps.first()?;
Self::from_avc_sps(&sps.0).ok()
}
CodecConfig::Hevc { config, .. } => {
let sps = config
.config
.arrays
.iter()
.filter(|a| a.nal_unit_type == crate::nalu_types::HEVC_SPS_NUT)
.flat_map(|a| a.nalus.iter())
.next()?;
Self::from_hevc_sps(&sps.0).ok()
}
_ => None,
}
}
fn from_facts(f: crate::sps::DisplayFacts) -> Self {
Self {
coded_width: f.coded_width,
coded_height: f.coded_height,
aspect_ratio: f.aspect_ratio,
}
}
pub fn sample_aspect_ratio(&self) -> Option<(u16, u16)> {
self.typed_sample_aspect_ratio().map(Into::into)
}
pub fn typed_sample_aspect_ratio(&self) -> Option<SampleAspectRatio> {
self.aspect_ratio?.sample_aspect_ratio()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn idc_round_trips_for_every_wire_value() {
for idc in 0..=u8::MAX {
let v = AspectRatioIdc::from_wire(idc, Some((3, 4)));
assert_eq!(v.idc(), idc, "idc {idc}");
}
}
#[test]
fn variants_land_on_their_table_e1_rows() {
assert_eq!(
AspectRatioIdc::from_wire(0, None),
AspectRatioIdc::Unspecified
);
assert_eq!(AspectRatioIdc::from_wire(1, None), AspectRatioIdc::Square);
assert_eq!(AspectRatioIdc::from_wire(4, None), AspectRatioIdc::Sar16x11);
assert_eq!(AspectRatioIdc::from_wire(14, None), AspectRatioIdc::Sar4x3);
assert_eq!(AspectRatioIdc::from_wire(16, None), AspectRatioIdc::Sar2x1);
assert_eq!(
AspectRatioIdc::from_wire(17, None),
AspectRatioIdc::Reserved(17)
);
assert_eq!(
AspectRatioIdc::from_wire(255, Some((64, 45))),
AspectRatioIdc::ExtendedSar {
sar_width: 64,
sar_height: 45
}
);
assert_eq!(
AspectRatioIdc::from_wire(4, Some((9, 9))),
AspectRatioIdc::Sar16x11
);
assert_eq!(
AspectRatioIdc::from_wire(255, None).sample_aspect_ratio(),
None
);
}
#[test]
fn labels_follow_the_spec_tokens() {
assert_eq!(AspectRatioIdc::Square.to_string(), "1:1");
assert_eq!(AspectRatioIdc::Sar160x99.to_string(), "160:99");
assert_eq!(AspectRatioIdc::Unspecified.to_string(), "unspecified");
assert_eq!(AspectRatioIdc::Reserved(0x11).to_string(), "reserved(0x11)");
let ext = AspectRatioIdc::from_wire(255, Some((64, 45)));
assert_eq!(ext.to_string(), "Extended_SAR");
}
#[test]
fn sample_aspect_ratio_rejects_a_zero_term() {
assert!(SampleAspectRatio::new(0, 1).is_none());
assert!(SampleAspectRatio::new(1, 0).is_none());
let r = SampleAspectRatio::new(64, 45).unwrap();
assert_eq!((r.width(), r.height()), (64, 45));
assert_eq!(r.reduced(), r);
let r = SampleAspectRatio::new(10, 4).unwrap();
assert_eq!(r.reduced().as_pair(), (5, 2));
}
#[test]
fn codecs_without_an_sps_have_no_geometry() {
let data = CodecConfig::Data {
stream_type: 0x06,
descriptors: alloc::vec::Vec::new(),
carriage: crate::ir::DataCarriage::Pes,
};
assert_eq!(DisplayGeometry::from_codec_config(&data), None);
}
}