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, Copy, Debug, PartialEq, Eq)]
pub struct PictureRect {
pub left: u32,
pub top: u32,
pub width: u32,
pub height: u32,
}
impl PictureRect {
pub fn new(left: u32, top: u32, width: u32, height: u32) -> Self {
Self {
left,
top,
width,
height,
}
}
}
pub const INFERRED_VIDEO_FORMAT: u8 = 5;
pub const INFERRED_FULL_RANGE: bool = false;
pub const INFERRED_COLOUR_CODE: u8 = 2;
pub const MPEG2_INFERRED_COLOUR_CODE: u8 = 1;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VideoFormat {
Component,
Pal,
Ntsc,
Secam,
Mac,
Unspecified,
Reserved(u8),
}
impl VideoFormat {
pub fn from_wire(code: u8) -> Option<Self> {
match code {
0 => Some(Self::Component),
1 => Some(Self::Pal),
2 => Some(Self::Ntsc),
3 => Some(Self::Secam),
4 => Some(Self::Mac),
5 => Some(Self::Unspecified),
6 | 7 => Some(Self::Reserved(code)),
_ => None,
}
}
pub fn code(&self) -> u8 {
match self {
Self::Component => 0,
Self::Pal => 1,
Self::Ntsc => 2,
Self::Secam => 3,
Self::Mac => 4,
Self::Unspecified => 5,
Self::Reserved(code) => *code,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Component => "Component",
Self::Pal => "PAL",
Self::Ntsc => "NTSC",
Self::Secam => "SECAM",
Self::Mac => "MAC",
Self::Unspecified => "Unspecified video format",
Self::Reserved(_) => "reserved",
}
}
pub fn inferred() -> Self {
Self::from_wire(INFERRED_VIDEO_FORMAT).unwrap_or(Self::Unspecified)
}
}
broadcast_common::impl_spec_display!(VideoFormat, Reserved);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColourPrimaries {
Reserved(u8),
Bt709,
Unspecified,
Bt470SystemM,
Bt470SystemBg,
Bt601_525,
Smpte240M,
GenericFilm,
Bt2020,
Xyz,
SmpteRp431_2,
SmpteEg432_1,
EbuTech3213E,
}
impl ColourPrimaries {
pub fn from_wire(code: u8) -> Self {
match code {
1 => Self::Bt709,
2 => Self::Unspecified,
4 => Self::Bt470SystemM,
5 => Self::Bt470SystemBg,
6 => Self::Bt601_525,
7 => Self::Smpte240M,
8 => Self::GenericFilm,
9 => Self::Bt2020,
10 => Self::Xyz,
11 => Self::SmpteRp431_2,
12 => Self::SmpteEg432_1,
22 => Self::EbuTech3213E,
other => Self::Reserved(other),
}
}
pub fn code(&self) -> u8 {
match self {
Self::Reserved(code) => *code,
Self::Bt709 => 1,
Self::Unspecified => 2,
Self::Bt470SystemM => 4,
Self::Bt470SystemBg => 5,
Self::Bt601_525 => 6,
Self::Smpte240M => 7,
Self::GenericFilm => 8,
Self::Bt2020 => 9,
Self::Xyz => 10,
Self::SmpteRp431_2 => 11,
Self::SmpteEg432_1 => 12,
Self::EbuTech3213E => 22,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Reserved(_) => "reserved",
Self::Bt709 => "Rec. ITU-R BT.709-6",
Self::Unspecified => "Unspecified",
Self::Bt470SystemM => "Rec. ITU-R BT.470-7 System M (historical)",
Self::Bt470SystemBg => "Rec. ITU-R BT.470-7 System B, G (historical)",
Self::Bt601_525 => "Rec. ITU-R BT.601-7 525",
Self::Smpte240M => "SMPTE 240M (1999, historical)",
Self::GenericFilm => "Generic film (colour filters using Illuminant C)",
Self::Bt2020 => "Rec. ITU-R BT.2020-2",
Self::Xyz => "SMPTE ST 428-1 (2006)",
Self::SmpteRp431_2 => "SMPTE RP 431-2 (2011)",
Self::SmpteEg432_1 => "SMPTE EG 432-1 (2010)",
Self::EbuTech3213E => "EBU Tech. 3213-E (1975)",
}
}
pub fn inferred() -> Self {
Self::Unspecified
}
}
broadcast_common::impl_spec_display!(ColourPrimaries, Reserved);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TransferCharacteristics {
Reserved(u8),
Bt709,
Unspecified,
Gamma22,
Gamma28,
Bt601,
Smpte240M,
Linear,
Log100,
Log316,
Iec61966_2_4,
Bt1361Extended,
Iec61966_2_1,
Bt2020_10Bit,
Bt2020_12Bit,
SmpteSt2084,
SmpteSt428_1,
AribStdB67,
}
impl TransferCharacteristics {
pub fn from_wire(code: u8) -> Self {
match code {
1 => Self::Bt709,
2 => Self::Unspecified,
4 => Self::Gamma22,
5 => Self::Gamma28,
6 => Self::Bt601,
7 => Self::Smpte240M,
8 => Self::Linear,
9 => Self::Log100,
10 => Self::Log316,
11 => Self::Iec61966_2_4,
12 => Self::Bt1361Extended,
13 => Self::Iec61966_2_1,
14 => Self::Bt2020_10Bit,
15 => Self::Bt2020_12Bit,
16 => Self::SmpteSt2084,
17 => Self::SmpteSt428_1,
18 => Self::AribStdB67,
other => Self::Reserved(other),
}
}
pub fn code(&self) -> u8 {
match self {
Self::Reserved(code) => *code,
Self::Bt709 => 1,
Self::Unspecified => 2,
Self::Gamma22 => 4,
Self::Gamma28 => 5,
Self::Bt601 => 6,
Self::Smpte240M => 7,
Self::Linear => 8,
Self::Log100 => 9,
Self::Log316 => 10,
Self::Iec61966_2_4 => 11,
Self::Bt1361Extended => 12,
Self::Iec61966_2_1 => 13,
Self::Bt2020_10Bit => 14,
Self::Bt2020_12Bit => 15,
Self::SmpteSt2084 => 16,
Self::SmpteSt428_1 => 17,
Self::AribStdB67 => 18,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Reserved(_) => "reserved",
Self::Bt709 => "Rec. ITU-R BT.709-6",
Self::Unspecified => "Unspecified",
Self::Gamma22 => "Assumed display gamma 2.2",
Self::Gamma28 => "Assumed display gamma 2.8",
Self::Bt601 => "Rec. ITU-R BT.601-7 525 or 625",
Self::Smpte240M => "SMPTE 240M (1999, historical)",
Self::Linear => "Linear transfer characteristics",
Self::Log100 => "Logarithmic transfer characteristic (100:1 range)",
Self::Log316 => "Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)",
Self::Iec61966_2_4 => "IEC 61966-2-4",
Self::Bt1361Extended => {
"Rec. ITU-R BT.1361-0 extended colour gamut system (historical)"
}
Self::Iec61966_2_1 => {
"IEC 61966-2-1 sRGB (with matrix_coefficients equal to 0) \
/ IEC 61966-2-1 sYCC (with matrix_coefficients equal to 5)"
}
Self::Bt2020_10Bit => "Rec. ITU-R BT.2020-2 (10 bit system)",
Self::Bt2020_12Bit => "Rec. ITU-R BT.2020-2 (12 bit system)",
Self::SmpteSt2084 => "SMPTE ST 2084 (2014) / Rec. ITU-R BT.2100-2 PQ system",
Self::SmpteSt428_1 => "SMPTE ST 428-1 (2006)",
Self::AribStdB67 => "ARIB STD-B67 / Rec. ITU-R BT.2100-2 HLG system",
}
}
pub fn inferred() -> Self {
Self::Unspecified
}
}
broadcast_common::impl_spec_display!(TransferCharacteristics, Reserved);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MatrixCoefficients {
Gbr,
Bt709,
Unspecified,
Reserved(u8),
Fcc,
Bt470SystemB,
Bt470SystemM,
Smpte240M,
YCgCo,
Bt2020NonConstant,
Bt2020Constant,
SmpteSt2085,
ChromaticityDerivedNonConstant,
ChromaticityDerivedConstant,
ICtCp,
IptC2,
YCgCoRe,
YCgCoRo,
}
impl MatrixCoefficients {
pub fn from_wire(code: u8) -> Self {
match code {
0 => Self::Gbr,
1 => Self::Bt709,
2 => Self::Unspecified,
4 => Self::Fcc,
5 => Self::Bt470SystemB,
6 => Self::Bt470SystemM,
7 => Self::Smpte240M,
8 => Self::YCgCo,
9 => Self::Bt2020NonConstant,
10 => Self::Bt2020Constant,
11 => Self::SmpteSt2085,
12 => Self::ChromaticityDerivedNonConstant,
13 => Self::ChromaticityDerivedConstant,
14 => Self::ICtCp,
15 => Self::IptC2,
16 => Self::YCgCoRe,
17 => Self::YCgCoRo,
other => Self::Reserved(other),
}
}
pub fn code(&self) -> u8 {
match self {
Self::Gbr => 0,
Self::Bt709 => 1,
Self::Unspecified => 2,
Self::Reserved(code) => *code,
Self::Fcc => 4,
Self::Bt470SystemB => 5,
Self::Bt470SystemM => 6,
Self::Smpte240M => 7,
Self::YCgCo => 8,
Self::Bt2020NonConstant => 9,
Self::Bt2020Constant => 10,
Self::SmpteSt2085 => 11,
Self::ChromaticityDerivedNonConstant => 12,
Self::ChromaticityDerivedConstant => 13,
Self::ICtCp => 14,
Self::IptC2 => 15,
Self::YCgCoRe => 16,
Self::YCgCoRo => 17,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Gbr => "GBR",
Self::Bt709 => "Rec. ITU-R BT.709-6",
Self::Unspecified => "Unspecified",
Self::Reserved(_) => "reserved",
Self::Fcc => "FCC Title 47 CFR 73.682 (a) (20)",
Self::Bt470SystemB => {
"Rec. ITU-R BT.470-7 System B, G (historical) / Rec. ITU-R BT.601-7 625"
}
Self::Bt470SystemM => "Rec. ITU-R BT.601-7 525 / SMPTE 170M (2004)",
Self::Smpte240M => "SMPTE 240M (1999, historical)",
Self::YCgCo => "YCgCo or YCgCo-R",
Self::Bt2020NonConstant => {
"Rec. ITU-R BT.2020-2 non-constant luminance system / Rec. ITU-R BT.2100-2 Y'CbCr"
}
Self::Bt2020Constant => "Rec. ITU-R BT.2020-2 constant luminance system",
Self::SmpteSt2085 => "Y'D'ZD'X (SMPTE ST 2085 (2015))",
Self::ChromaticityDerivedNonConstant => {
"Chromaticity-derived non-constant luminance system"
}
Self::ChromaticityDerivedConstant => "Chromaticity-derived constant luminance system",
Self::ICtCp => "ICtCp (Rec. ITU-R BT.2100-2)",
Self::IptC2 => "IPT-C2 (IPT-PQ-C2)",
Self::YCgCoRe => "YCgCo-Re",
Self::YCgCoRo => "YCgCo-Ro",
}
}
pub fn inferred() -> Self {
Self::Unspecified
}
}
broadcast_common::impl_spec_display!(MatrixCoefficients, Reserved);
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ColourDescription {
pub primaries: ColourPrimaries,
pub transfer: TransferCharacteristics,
pub matrix: MatrixCoefficients,
}
impl ColourDescription {
pub fn from_wire(primaries: u8, transfer: u8, matrix: u8) -> Self {
Self {
primaries: ColourPrimaries::from_wire(primaries),
transfer: TransferCharacteristics::from_wire(transfer),
matrix: MatrixCoefficients::from_wire(matrix),
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VideoSignalType {
pub video_format: VideoFormat,
pub full_range: bool,
pub colour: Option<ColourDescription>,
}
impl VideoSignalType {
pub fn new(
video_format: VideoFormat,
full_range: bool,
colour: Option<ColourDescription>,
) -> Self {
Self {
video_format,
full_range,
colour,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DisplayGeometry {
pub coded_width: u32,
pub coded_height: u32,
pub output_rect: PictureRect,
pub default_display_rect: Option<PictureRect>,
pub aspect_ratio: Option<AspectRatioIdc>,
pub overscan_appropriate: Option<bool>,
pub video_signal: Option<VideoSignalType>,
pub mpeg2: bool,
}
impl DisplayGeometry {
pub fn new(coded_width: u32, coded_height: u32, aspect_ratio: Option<AspectRatioIdc>) -> Self {
Self {
coded_width,
coded_height,
output_rect: PictureRect::new(0, 0, coded_width, coded_height),
default_display_rect: None,
aspect_ratio,
overscan_appropriate: None,
video_signal: None,
mpeg2: false,
}
}
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()
}
CodecConfig::Mpeg2Video { esds, .. } => {
let es = esds
.es_descriptor
.decoder_config
.as_ref()?
.decoder_specific_info
.as_ref()?;
Self::from_mpeg2_es(&es.data).ok()
}
_ => None,
}
}
fn from_facts(f: crate::sps::DisplayFacts) -> Self {
Self {
coded_width: f.coded_width,
coded_height: f.coded_height,
output_rect: f.output_rect,
default_display_rect: f.default_display_rect,
aspect_ratio: f.aspect_ratio,
overscan_appropriate: f.overscan_appropriate,
video_signal: f.video_signal,
mpeg2: false,
}
}
pub fn is_mpeg2(&self) -> bool {
self.mpeg2
}
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()
}
pub fn full_range(&self) -> bool {
self.video_signal.is_some_and(|vst| vst.full_range)
}
pub fn video_format(&self) -> VideoFormat {
self.video_signal
.map_or_else(VideoFormat::inferred, |vst| vst.video_format)
}
pub fn colour_description(&self) -> ColourDescription {
self.video_signal
.and_then(|vst| vst.colour)
.unwrap_or(if self.mpeg2 {
ColourDescription::from_wire(
MPEG2_INFERRED_COLOUR_CODE,
MPEG2_INFERRED_COLOUR_CODE,
MPEG2_INFERRED_COLOUR_CODE,
)
} else {
ColourDescription {
primaries: ColourPrimaries::inferred(),
transfer: TransferCharacteristics::inferred(),
matrix: MatrixCoefficients::inferred(),
}
})
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ActiveFormatDescription {
Unknown,
Box16x9Top,
Box14x9Top,
BoxWiderThan16x9Centre,
SameAsCodedFrame,
Centre4x3,
Centre16x9,
Centre14x9,
Centre4x3ShootProtect14x9,
Centre16x9ShootProtect14x9,
Centre16x9ShootProtect4x3,
Reserved(u8),
}
impl ActiveFormatDescription {
pub fn code(&self) -> u8 {
match self {
Self::Unknown => 0b0000,
Self::Box16x9Top => 0b0010,
Self::Box14x9Top => 0b0011,
Self::BoxWiderThan16x9Centre => 0b0100,
Self::SameAsCodedFrame => 0b1000,
Self::Centre4x3 => 0b1001,
Self::Centre16x9 => 0b1010,
Self::Centre14x9 => 0b1011,
Self::Centre4x3ShootProtect14x9 => 0b1101,
Self::Centre16x9ShootProtect14x9 => 0b1110,
Self::Centre16x9ShootProtect4x3 => 0b1111,
Self::Reserved(v) => *v,
}
}
pub fn from_code(code: u8) -> Self {
match code & 0x0F {
0b0000 => Self::Unknown,
0b0010 => Self::Box16x9Top,
0b0011 => Self::Box14x9Top,
0b0100 => Self::BoxWiderThan16x9Centre,
0b1000 => Self::SameAsCodedFrame,
0b1001 => Self::Centre4x3,
0b1010 => Self::Centre16x9,
0b1011 => Self::Centre14x9,
0b1101 => Self::Centre4x3ShootProtect14x9,
0b1110 => Self::Centre16x9ShootProtect14x9,
0b1111 => Self::Centre16x9ShootProtect4x3,
other => Self::Reserved(other),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Unknown => "AFD unknown",
Self::Box16x9Top => "box 16:9 (top)",
Self::Box14x9Top => "box 14:9 (top)",
Self::BoxWiderThan16x9Centre => "box > 16:9 (centre)",
Self::SameAsCodedFrame => "same as coded frame",
Self::Centre4x3 => "4:3 (centre)",
Self::Centre16x9 => "16:9 (centre)",
Self::Centre14x9 => "14:9 (centre)",
Self::Centre4x3ShootProtect14x9 => "4:3 (with shoot and protect 14:9 centre)",
Self::Centre16x9ShootProtect14x9 => "16:9 (with shoot and protect 14:9 centre)",
Self::Centre16x9ShootProtect4x3 => "16:9 (with shoot and protect 4:3 centre)",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(ActiveFormatDescription, Reserved);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BarData {
pub top_bar_end_line: Option<u16>,
pub bottom_bar_start_line: Option<u16>,
pub left_bar_end_pixel: Option<u16>,
pub right_bar_start_pixel: Option<u16>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UserDataCarriage {
AvcSei,
HevcPrefixSei,
Mpeg2PictureUserData,
}
impl UserDataCarriage {
pub fn name(&self) -> &'static str {
match self {
Self::AvcSei => "AVC SEI",
Self::HevcPrefixSei => "HEVC prefix SEI",
Self::Mpeg2PictureUserData => "MPEG-2 picture user data",
}
}
}
broadcast_common::impl_spec_display!(UserDataCarriage);
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ActiveFormat {
pub afd: Option<ActiveFormatDescription>,
pub bar_data: Option<BarData>,
pub carriage: UserDataCarriage,
}
impl ActiveFormat {
pub fn new(
afd: Option<ActiveFormatDescription>,
bar_data: Option<BarData>,
carriage: UserDataCarriage,
) -> Self {
Self {
afd,
bar_data,
carriage,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum AfdPersistence {
#[default]
UntilReplaced,
ResetAtRandomAccess,
}
impl AfdPersistence {
pub fn name(&self) -> &'static str {
match self {
Self::UntilReplaced => "until replaced",
Self::ResetAtRandomAccess => "reset at random access",
}
}
}
broadcast_common::impl_spec_display!(AfdPersistence);
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DisplayInfo {
pub geometry: Option<DisplayGeometry>,
pub active_format: Option<ActiveFormat>,
}
impl DisplayInfo {
pub fn new(geometry: Option<DisplayGeometry>) -> Self {
Self {
geometry,
active_format: None,
}
}
pub fn with_active_format(mut self, active_format: Option<ActiveFormat>) -> Self {
self.active_format = active_format;
self
}
pub fn from_geometry(geometry: Option<DisplayGeometry>) -> Option<Self> {
geometry.map(|g| Self::new(Some(g)))
}
pub fn sample_aspect_ratio(&self) -> Option<SampleAspectRatio> {
self.geometry.as_ref()?.typed_sample_aspect_ratio()
}
pub fn changes_from(&self, previous: Option<&Self>) -> DisplayChangeSet {
let Some(previous) = previous else {
return DisplayChangeSet {
initial: true,
..DisplayChangeSet::default()
};
};
if previous == self {
return DisplayChangeSet::default();
}
let previous_display = previous;
let (current, previous) = (self.geometry.as_ref(), previous_display.geometry.as_ref());
DisplayChangeSet {
initial: false,
aspect_ratio: aspect_ratio_of(current) != aspect_ratio_of(previous),
output_rect: output_rect_of(current) != output_rect_of(previous),
video_signal: current.and_then(|g| g.video_signal)
!= previous.and_then(|g| g.video_signal),
overscan: current.and_then(|g| g.overscan_appropriate)
!= previous.and_then(|g| g.overscan_appropriate),
default_display_rect: default_display_rect_of(current)
!= default_display_rect_of(previous),
afd: self.active_format.as_ref().map(|a| a.afd)
!= previous_display.active_format.as_ref().map(|a| a.afd),
bar_data: self.active_format.as_ref().and_then(|a| a.bar_data.clone())
!= previous_display
.active_format
.as_ref()
.and_then(|a| a.bar_data.clone()),
}
}
}
fn aspect_ratio_of(g: Option<&DisplayGeometry>) -> Option<AspectRatioIdc> {
g.and_then(|g| g.aspect_ratio)
}
fn output_rect_of(g: Option<&DisplayGeometry>) -> Option<PictureRect> {
g.map(|g| g.output_rect)
}
fn default_display_rect_of(g: Option<&DisplayGeometry>) -> Option<Option<PictureRect>> {
g.map(|g| g.default_display_rect)
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DisplayChangeSet {
pub initial: bool,
pub aspect_ratio: bool,
pub output_rect: bool,
pub default_display_rect: bool,
pub video_signal: bool,
pub overscan: bool,
pub afd: bool,
pub bar_data: bool,
}
impl DisplayChangeSet {
pub fn initial() -> Self {
Self {
initial: true,
..Self::default()
}
}
pub fn any(&self) -> bool {
*self != Self::default()
}
pub fn names(&self) -> alloc::string::String {
let mut out = alloc::string::String::new();
let mut push = |set: bool, name: &str| {
if set {
if !out.is_empty() {
out.push(' ');
}
out.push_str(name);
}
};
push(self.initial, "initial");
push(self.aspect_ratio, "aspect_ratio");
push(self.output_rect, "output_rect");
push(self.default_display_rect, "default_display_rect");
push(self.video_signal, "video_signal");
push(self.overscan, "overscan");
push(self.afd, "afd");
push(self.bar_data, "bar_data");
out
}
}
#[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);
}
}