use super::avs3_video::Avs3FrameRateCode;
use super::{descriptor_body, write_descriptor_header};
use crate::error::{Error, Result};
use alloc::vec::Vec;
use broadcast_common::len::fit_bits;
use broadcast_common::{Parse, Serialize};
use mpeg_ts::pid::Pid;
pub const TAG: u8 = 62;
const HEADER_LEN: usize = 2;
const FIXED_BODY_LEN: usize = 7;
const REF_LEN: usize = 2;
const MAX_REFS: usize = 127;
const RESERVED3_ONES: u8 = 0b111;
const RESERVED5_ONES: u8 = 0x1F;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum TuwaLibraryRef {
Peid {
pid: Pid,
reserved: u8,
},
StreamId {
stream_id: u8,
reserved_5: u8,
reserved_3: u8,
},
}
impl TuwaLibraryRef {
#[must_use]
pub const fn peid(pid: Pid) -> Self {
Self::Peid {
pid,
reserved: RESERVED3_ONES,
}
}
#[must_use]
pub const fn stream_id(stream_id: u8) -> Self {
Self::StreamId {
stream_id,
reserved_5: RESERVED5_ONES,
reserved_3: RESERVED3_ONES,
}
}
}
impl TuwaLibraryRef {
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Peid { .. } => "PEID",
Self::StreamId { .. } => "stream_id",
}
}
}
broadcast_common::impl_spec_display!(TuwaLibraryRef);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct TuwaLibraryRefs {
pub refs: Vec<TuwaLibraryRef>,
pub id_type_flag_if_empty: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct TuwaAvs3VideoDescriptor {
pub profile_id: u8,
pub level_id: u8,
pub multiple_frame_rate_flag: bool,
pub frame_rate_code: Avs3FrameRateCode,
pub sample_precision: u8,
pub chroma_format: u8,
pub temporal_id_flag: bool,
pub td_mode_flag: bool,
pub library_stream_flag: bool,
pub reserved: u8,
pub colour_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coefficients: u8,
pub library_refs: Option<TuwaLibraryRefs>,
}
fn bit(b: u8, shift: u32) -> bool {
(b >> shift) & 1 != 0
}
impl<'a> Parse<'a> for TuwaAvs3VideoDescriptor {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"TuwaAvs3VideoDescriptor",
"unexpected tag for T/UWA 012.2 AVS3 video descriptor",
)?;
if body.len() < FIXED_BODY_LEN {
return Err(Error::BufferTooShort {
need: FIXED_BODY_LEN,
have: body.len(),
what: "TuwaAvs3VideoDescriptor",
});
}
let (b2, b3) = (body[2], body[3]);
let library_stream_flag = bit(b3, 3);
let library_refs = if library_stream_flag {
if body.len() != FIXED_BODY_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "library stream descriptor has trailing bytes",
});
}
None
} else {
let head = *body.get(FIXED_BODY_LEN).ok_or(Error::BufferTooShort {
need: FIXED_BODY_LEN + 1,
have: body.len(),
what: "TuwaAvs3VideoDescriptor",
})?;
let n = usize::from(head >> 1);
let id_type = head & 1 != 0;
let rest = &body[FIXED_BODY_LEN + 1..];
if rest.len() != n * REF_LEN {
return Err(Error::InvalidDescriptor {
tag: TAG,
reason: "num_ref_library_stream does not match descriptor_length",
});
}
let refs = rest
.chunks_exact(REF_LEN)
.map(|c| {
if id_type {
TuwaLibraryRef::Peid {
pid: Pid::new(u16::from_be_bytes([c[0], c[1]]) >> 3),
reserved: c[1] & 0x07,
}
} else {
TuwaLibraryRef::StreamId {
stream_id: c[0],
reserved_5: c[1] >> 3,
reserved_3: c[1] & 0x07,
}
}
})
.collect();
Some(TuwaLibraryRefs {
refs,
id_type_flag_if_empty: id_type,
})
};
Ok(Self {
profile_id: body[0],
level_id: body[1],
multiple_frame_rate_flag: bit(b2, 7),
frame_rate_code: Avs3FrameRateCode::from_u8((b2 >> 3) & 0x0F),
sample_precision: b2 & 0x07,
chroma_format: b3 >> 6,
temporal_id_flag: bit(b3, 5),
td_mode_flag: bit(b3, 4),
library_stream_flag,
reserved: b3 & 0x07,
colour_primaries: body[4],
transfer_characteristics: body[5],
matrix_coefficients: body[6],
library_refs,
})
}
}
impl TuwaAvs3VideoDescriptor {
fn body_len(&self) -> usize {
match &self.library_refs {
Some(r) => FIXED_BODY_LEN + 1 + r.refs.len() * REF_LEN,
None => FIXED_BODY_LEN,
}
}
}
impl Serialize for TuwaAvs3VideoDescriptor {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
HEADER_LEN + self.body_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let len = self.serialized_len();
if buf.len() < len {
return Err(Error::OutputBufferTooSmall {
need: len,
have: buf.len(),
});
}
if self.library_stream_flag != self.library_refs.is_none() {
return Err(Error::ValueOutOfRange {
field: "library_refs",
reason: "present iff library_stream_flag is false",
});
}
let frc = fit_bits(
u64::from(self.frame_rate_code.to_u8()),
4,
"frame_rate_code",
)? as u8;
let sp = fit_bits(u64::from(self.sample_precision), 3, "sample_precision")? as u8;
let cf = fit_bits(u64::from(self.chroma_format), 2, "chroma_format")? as u8;
let rsv = fit_bits(u64::from(self.reserved), 3, "reserved")? as u8;
write_descriptor_header(buf, TAG, self.body_len())?;
buf[2] = self.profile_id;
buf[3] = self.level_id;
buf[4] = (u8::from(self.multiple_frame_rate_flag) << 7) | (frc << 3) | sp;
buf[5] = (cf << 6)
| (u8::from(self.temporal_id_flag) << 5)
| (u8::from(self.td_mode_flag) << 4)
| (u8::from(self.library_stream_flag) << 3)
| rsv;
buf[6] = self.colour_primaries;
buf[7] = self.transfer_characteristics;
buf[8] = self.matrix_coefficients;
let mut i = HEADER_LEN + FIXED_BODY_LEN;
if let Some(r) = &self.library_refs {
if r.refs.len() > MAX_REFS {
return Err(Error::ValueOutOfRange {
field: "num_ref_library_stream",
reason: "exceeds 7-bit field",
});
}
let id_type = match r.refs.first() {
Some(TuwaLibraryRef::Peid { .. }) => true,
Some(TuwaLibraryRef::StreamId { .. }) => false,
None => r.id_type_flag_if_empty,
};
let n = fit_bits(r.refs.len() as u64, 7, "num_ref_library_stream")? as u8;
buf[i] = (n << 1) | u8::from(id_type);
i += 1;
for entry in &r.refs {
match (*entry, id_type) {
(TuwaLibraryRef::Peid { pid, reserved }, true) => {
let rs = fit_bits(u64::from(reserved), 3, "reserved")? as u16;
let v = (pid.value() << 3) | rs;
buf[i..i + 2].copy_from_slice(&v.to_be_bytes());
}
(
TuwaLibraryRef::StreamId {
stream_id,
reserved_5,
reserved_3,
},
false,
) => {
let r5 = fit_bits(u64::from(reserved_5), 5, "reserved")? as u8;
let r3 = fit_bits(u64::from(reserved_3), 3, "reserved")? as u8;
buf[i] = stream_id;
buf[i + 1] = (r5 << 3) | r3;
}
_ => {
return Err(Error::ValueOutOfRange {
field: "library_refs",
reason: "all references must use the same id_type_flag form",
});
}
}
i += REF_LEN;
}
}
Ok(i)
}
}
impl crate::traits::DescriptorDef<'_> for TuwaAvs3VideoDescriptor {
const TAG: u8 = TAG;
const NAME: &'static str = "TUWA_AVS3_VIDEO";
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn round_trip(bytes: &[u8]) -> TuwaAvs3VideoDescriptor {
let d = TuwaAvs3VideoDescriptor::parse(bytes).unwrap();
let mut out = vec![0u8; d.serialized_len()];
let n = d.serialize_into(&mut out).unwrap();
assert_eq!(&out[..n], bytes, "byte-exact round trip");
d
}
#[test]
fn library_stream_has_no_references() {
let bytes = [TAG, 7, 0x22, 0x42, 0xAA, 0x4F, 9, 14, 9];
let d = round_trip(&bytes);
assert!(d.library_stream_flag && d.library_refs.is_none());
assert_eq!(d.frame_rate_code, Avs3FrameRateCode::Fps30);
assert_eq!(
(
d.colour_primaries,
d.transfer_characteristics,
d.matrix_coefficients
),
(9, 14, 9)
);
assert_eq!(d.reserved, 7);
}
#[test]
fn main_stream_with_peid_references() {
let bytes = [
TAG, 12, 0x22, 0x42, 0xAA, 0x47, 9, 14, 9, 0x05, 0x08, 0x0F, 0xFF, 0xFF,
];
let d = round_trip(&bytes);
let refs = d.library_refs.as_ref().unwrap();
assert_eq!(refs.refs.len(), 2);
assert_eq!(
refs.refs[0],
TuwaLibraryRef::Peid {
pid: Pid::new(0x0101),
reserved: 7
}
);
assert_eq!(refs.refs[1], TuwaLibraryRef::peid(Pid::new(0x1FFF)));
}
#[test]
fn main_stream_with_stream_id_reference() {
let bytes = [TAG, 10, 0x22, 0x42, 0xAA, 0x47, 9, 14, 9, 0x02, 0xE1, 0xFF];
let d = round_trip(&bytes);
let refs = d.library_refs.as_ref().unwrap();
assert_eq!(refs.refs, vec![TuwaLibraryRef::stream_id(0xE1)]);
assert!(!refs.id_type_flag_if_empty);
}
#[test]
fn rejects_bad_tag_length_and_count_mismatch() {
assert!(TuwaAvs3VideoDescriptor::parse(&[0x3F, 7, 0, 0, 0, 0, 0, 0, 0]).is_err());
assert!(TuwaAvs3VideoDescriptor::parse(&[TAG, 6, 0, 0, 0, 0, 0, 0]).is_err());
assert!(TuwaAvs3VideoDescriptor::parse(&[TAG, 7, 0, 0, 0, 0x40, 0, 0, 0]).is_err());
assert!(
TuwaAvs3VideoDescriptor::parse(&[TAG, 10, 0, 0, 0, 0x40, 0, 0, 0, 0x05, 0, 0]).is_err()
);
assert!(TuwaAvs3VideoDescriptor::parse(&[TAG, 8, 0, 0, 0, 0x08, 0, 0, 0, 0]).is_err());
}
#[test]
fn serialize_rejects_inconsistent_descriptors() {
let bytes = [
TAG, 12, 0x22, 0x42, 0xAA, 0x47, 9, 14, 9, 0x05, 0x08, 0x0F, 0xFF, 0xFF,
];
let mut d = TuwaAvs3VideoDescriptor::parse(&bytes).unwrap();
d.library_refs.as_mut().unwrap().refs[1] = TuwaLibraryRef::stream_id(1);
assert!(d.serialize_into(&mut [0u8; 32]).is_err());
let mut d = TuwaAvs3VideoDescriptor::parse(&bytes).unwrap();
d.library_stream_flag = true;
assert!(d.serialize_into(&mut [0u8; 32]).is_err());
let mut d = TuwaAvs3VideoDescriptor::parse(&bytes).unwrap();
d.reserved = 8;
assert!(d.serialize_into(&mut [0u8; 32]).is_err());
let mut d = TuwaAvs3VideoDescriptor::parse(&bytes).unwrap();
d.library_refs.as_mut().unwrap().refs = vec![TuwaLibraryRef::peid(Pid::new(1)); 128];
assert!(d.serialize_into(&mut [0u8; 300]).is_err());
}
}