use super::{descriptor_body, write_descriptor_header};
use crate::error::{Error, Result};
use crate::text::LangCode;
use alloc::vec::Vec;
use broadcast_common::len::{fit_bits, fit_u8, need_out};
use broadcast_common::{Parse, Serialize};
pub const TAG: u8 = 0xD3;
const HEADER_LEN: usize = 2;
const LANG_LEN: usize = 3;
const RESERVED6_ONES: u8 = 0x3F;
const RESERVED3_ONES: u8 = 0b111;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct Avs2AudioCodec0 {
pub bitrate_index: u8,
pub bitstream_type: bool,
pub reserved: u8,
pub raw_frame_length: u16,
}
impl Avs2AudioCodec0 {
#[must_use]
pub const fn new(bitrate_index: u8, bitstream_type: bool, raw_frame_length: u16) -> Self {
Self {
bitrate_index,
bitstream_type,
reserved: RESERVED3_ONES,
raw_frame_length,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct Avs2AudioVersion {
pub audio_codec_id: u8,
pub anc_data_index: bool,
pub coding_profile: u8,
pub codec0: Option<Avs2AudioCodec0>,
pub resolution: u8,
pub reserved: u8,
}
impl Avs2AudioVersion {
#[must_use]
pub const fn new(
audio_codec_id: u8,
anc_data_index: bool,
coding_profile: u8,
codec0: Option<Avs2AudioCodec0>,
resolution: u8,
) -> Self {
Self {
audio_codec_id,
anc_data_index,
coding_profile,
codec0,
resolution,
reserved: RESERVED6_ONES,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct Avs2AudioDescriptor {
pub num_channels: u8,
pub sample_rate_index: u8,
pub reserved: u8,
pub text: Option<Vec<u16>>,
pub language: Option<LangCode>,
pub version: Option<Avs2AudioVersion>,
pub trailing: Vec<u8>,
}
impl Avs2AudioDescriptor {
#[must_use]
pub const fn new(num_channels: u8, sample_rate_index: u8) -> Self {
Self {
num_channels,
sample_rate_index,
reserved: 0,
text: None,
language: None,
version: None,
trailing: Vec::new(),
}
}
}
struct Cursor<'a> {
body: &'a [u8],
pos: usize,
}
impl Cursor<'_> {
fn take(&mut self, n: usize) -> Result<&[u8]> {
let end = self.pos + n;
let s = self.body.get(self.pos..end).ok_or(Error::BufferTooShort(
broadcast_common::error::BufferTooShort {
need: end,
have: self.body.len(),
what: "Avs2AudioDescriptor",
},
))?;
self.pos = end;
Ok(s)
}
fn u8(&mut self) -> Result<u8> {
Ok(self.take(1)?[0])
}
}
impl<'a> Parse<'a> for Avs2AudioDescriptor {
type Error = crate::error::Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = descriptor_body(
bytes,
TAG,
"Avs2AudioDescriptor",
"unexpected tag for AVS2_audio_descriptor",
)?;
let mut c = Cursor { body, pos: 0 };
let num_channels = c.u8()?;
let b1 = c.u8()?;
let avs_version_flag = b1 & 0x08 != 0;
let text_present = b1 & 0x04 != 0;
let language_present = b1 & 0x02 != 0;
let text = if text_present {
let n = usize::from(c.u8()?);
let raw = c.take(n * 2)?;
Some(
raw.chunks_exact(2)
.map(|p| u16::from_be_bytes([p[0], p[1]]))
.collect(),
)
} else {
None
};
let language = if language_present {
let l = c.take(LANG_LEN)?;
Some(LangCode([l[0], l[1], l[2]]))
} else {
None
};
let version = if avs_version_flag {
let v0 = c.u8()?;
let audio_codec_id = v0 >> 4;
let codec0 = if audio_codec_id == 0 {
let x = c.u8()?;
let hi = c.u8()?;
let lo = c.u8()?;
Some(Avs2AudioCodec0 {
bitrate_index: x >> 4,
bitstream_type: x & 0x08 != 0,
reserved: x & 0x07,
raw_frame_length: u16::from_be_bytes([hi, lo]),
})
} else {
None
};
let r = c.u8()?;
Some(Avs2AudioVersion {
audio_codec_id,
anc_data_index: v0 & 0x08 != 0,
coding_profile: v0 & 0x07,
codec0,
resolution: r >> 6,
reserved: r & 0x3F,
})
} else {
None
};
Ok(Self {
num_channels,
sample_rate_index: b1 >> 4,
reserved: b1 & 0x01,
text,
language,
version,
trailing: body[c.pos..].to_vec(),
})
}
}
impl Avs2AudioDescriptor {
fn body_bytes(&self) -> Result<Vec<u8>> {
let mut out = Vec::new();
out.push(self.num_channels);
let sri = fit_bits(u64::from(self.sample_rate_index), 4, "sample_rate_index")? as u8;
let rsv = fit_bits(u64::from(self.reserved), 1, "reserved")? as u8;
out.push(
(sri << 4)
| (u8::from(self.version.is_some()) << 3)
| (u8::from(self.text.is_some()) << 2)
| (u8::from(self.language.is_some()) << 1)
| rsv,
);
if let Some(text) = &self.text {
out.push(fit_u8(text.len(), "text_length")?);
for u in text {
out.extend_from_slice(&u.to_be_bytes());
}
}
if let Some(l) = &self.language {
out.extend_from_slice(&l.0);
}
if let Some(v) = &self.version {
let id = fit_bits(u64::from(v.audio_codec_id), 4, "audio_codec_id")? as u8;
let prof = fit_bits(u64::from(v.coding_profile), 3, "coding_profile")? as u8;
out.push((id << 4) | (u8::from(v.anc_data_index) << 3) | prof);
match (id, &v.codec0) {
(0, Some(c)) => {
let br = fit_bits(u64::from(c.bitrate_index), 4, "bitrate_index")? as u8;
let rs = fit_bits(u64::from(c.reserved), 3, "reserved")? as u8;
out.push((br << 4) | (u8::from(c.bitstream_type) << 3) | rs);
out.extend_from_slice(&c.raw_frame_length.to_be_bytes());
}
(0, None) | (1..=15, Some(_)) => {
return Err(Error::ValueOutOfRange {
field: "codec0",
reason: "present iff audio_codec_id == 0",
});
}
_ => {}
}
let res = fit_bits(u64::from(v.resolution), 2, "resolution")? as u8;
let rs6 = fit_bits(u64::from(v.reserved), 6, "reserved")? as u8;
out.push((res << 6) | rs6);
}
out.extend_from_slice(&self.trailing);
Ok(out)
}
}
impl Serialize for Avs2AudioDescriptor {
type Error = crate::error::Error;
fn serialized_len(&self) -> usize {
self.body_bytes()
.map_or(HEADER_LEN, |b| HEADER_LEN + b.len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body = self.body_bytes()?;
let len = HEADER_LEN + body.len();
need_out(buf, len)?;
write_descriptor_header(buf, TAG, body.len())?;
buf[HEADER_LEN..len].copy_from_slice(&body);
Ok(len)
}
}
impl crate::traits::DescriptorDef<'_> for Avs2AudioDescriptor {
const TAG: u8 = TAG;
const NAME: &'static str = "AVS2_AUDIO";
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn round_trip(bytes: &[u8]) -> Avs2AudioDescriptor {
let d = Avs2AudioDescriptor::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 minimal_two_byte_form() {
let bytes = [TAG, 2, 6, 0x20];
let d = round_trip(&bytes);
assert_eq!((d.num_channels, d.sample_rate_index), (6, 2));
assert!(d.text.is_none() && d.language.is_none() && d.version.is_none());
assert!(d.trailing.is_empty());
}
#[test]
fn full_form_with_version_block() {
let b1 = (3 << 4) | 0b1110;
let bytes = [
TAG, 16, 2, b1, 2, 0x00, 0x41, 0x00, 0x42, b'c', b'h', b'i',
0x02, 0x5F, 0x04, 0x00, 0x7F, 0xAB,
];
let v = bytes.to_vec();
let d = round_trip(&v);
assert_eq!(d.text.as_deref(), Some(&[0x41u16, 0x42][..]));
assert_eq!(d.language, Some(LangCode(*b"chi")));
let ver = d.version.unwrap();
assert_eq!((ver.audio_codec_id, ver.coding_profile), (0, 2));
let c0 = ver.codec0.unwrap();
assert_eq!(
(c0.bitrate_index, c0.bitstream_type, c0.raw_frame_length),
(5, true, 0x0400)
);
assert_eq!((ver.resolution, ver.reserved), (1, 0x3F));
assert_eq!(d.trailing, [0xAB]);
}
#[test]
fn nonzero_codec_id_has_no_codec0_block() {
let bytes = [TAG, 5, 2, 0x18, 0x13, 0xBF, 0xEE];
let d = round_trip(&bytes);
let ver = d.version.unwrap();
assert_eq!(ver.audio_codec_id, 1);
assert!(ver.codec0.is_none());
assert_eq!(d.trailing, [0xEE]);
}
#[test]
fn rejects_wrong_tag_and_truncation() {
assert!(matches!(
Avs2AudioDescriptor::parse(&[0xD2, 2, 6, 0x20]),
Err(Error::InvalidDescriptor { tag: 0xD2, .. })
));
assert!(matches!(
Avs2AudioDescriptor::parse(&[TAG, 1, 6]),
Err(Error::BufferTooShort(..))
));
assert!(matches!(
Avs2AudioDescriptor::parse(&[TAG, 4, 2, 0x04, 5, 0]),
Err(Error::BufferTooShort(..))
));
assert!(matches!(
Avs2AudioDescriptor::parse(&[TAG, 2, 2, 0x02]),
Err(Error::BufferTooShort(..))
));
assert!(matches!(
Avs2AudioDescriptor::parse(&[TAG, 2, 2, 0x08]),
Err(Error::BufferTooShort(..))
));
}
#[test]
fn serialize_rejects_inconsistent_and_overwide_fields() {
let mut d = Avs2AudioDescriptor::new(2, 16);
assert!(matches!(
&(d.serialize_into(&mut [0u8; 8])),
Err(crate::error::Error::FieldOverflow(_))
));
d.sample_rate_index = 1;
d.version = Some(Avs2AudioVersion::new(0, false, 1, None, 1));
assert!(
matches!(
&(d.serialize_into(&mut [0u8; 16])),
Err(crate::error::Error::ValueOutOfRange { .. })
),
"codec id 0 needs codec0"
);
d.version = Some(Avs2AudioVersion::new(
1,
false,
1,
Some(Avs2AudioCodec0::new(1, false, 1)),
1,
));
assert!(
matches!(
&(d.serialize_into(&mut [0u8; 16])),
Err(crate::error::Error::ValueOutOfRange { .. })
),
"codec0 only for id 0"
);
d.version = Some(Avs2AudioVersion::new(1, false, 8, None, 1));
assert!(matches!(
&(d.serialize_into(&mut [0u8; 16])),
Err(crate::error::Error::FieldOverflow(_))
));
}
}