Skip to main content

dvb_si/descriptors/extension/
ac4.rs

1//! AC-4 Descriptor — ETSI EN 300 468 Annex D, §D.5 (tag_extension 0x15).
2use super::*;
3
4impl<'a> ExtensionBodyDef<'a> for Ac4<'a> {
5    const TAG_EXTENSION: u8 = 0x15;
6    const NAME: &'static str = "AC4";
7}
8
9/// AC-4 channel mode — ETSI EN 300 468 Table D.12.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12#[non_exhaustive]
13pub enum Ac4ChannelMode {
14    /// Mono content.
15    Mono,
16    /// Stereo content.
17    Stereo,
18    /// Multichannel content.
19    Multichannel,
20    /// Reserved for future use.
21    Reserved(u8),
22}
23
24impl Ac4ChannelMode {
25    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
26    #[must_use]
27    pub fn from_u8(v: u8) -> Self {
28        match v {
29            0 => Ac4ChannelMode::Mono,
30            1 => Ac4ChannelMode::Stereo,
31            2 => Ac4ChannelMode::Multichannel,
32            other => Ac4ChannelMode::Reserved(other),
33        }
34    }
35
36    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
37    #[must_use]
38    pub const fn to_u8(self) -> u8 {
39        match self {
40            Ac4ChannelMode::Mono => 0,
41            Ac4ChannelMode::Stereo => 1,
42            Ac4ChannelMode::Multichannel => 2,
43            Ac4ChannelMode::Reserved(v) => v,
44        }
45    }
46
47    /// Human-readable spec name per Table D.12.
48    #[must_use]
49    pub fn name(self) -> &'static str {
50        match self {
51            Ac4ChannelMode::Mono => "Mono content",
52            Ac4ChannelMode::Stereo => "Stereo content",
53            Ac4ChannelMode::Multichannel => "Multichannel content",
54            Ac4ChannelMode::Reserved(_) => "reserved for future use",
55        }
56    }
57}
58broadcast_common::impl_spec_display!(Ac4ChannelMode, Reserved);
59
60/// AC-4 body (annex D). `toc` + `additional_info` are raw.
61#[derive(Debug, Clone, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
64pub struct Ac4<'a> {
65    /// ac4_config_flag(1).
66    pub ac4_config_flag: bool,
67    /// ac4_toc_flag(1).
68    pub ac4_toc_flag: bool,
69    /// ac4_dialog_enhancement_enabled(1), present iff `ac4_config_flag`.
70    pub ac4_dialog_enhancement_enabled: Option<bool>,
71    /// ac4_channel_mode(2) — Table D.12, present iff `ac4_config_flag`.
72    pub ac4_channel_mode: Option<Ac4ChannelMode>,
73    /// Length-delimited `ac4_toc` bytes, present iff `ac4_toc_flag`.
74    ///
75    /// Kept opaque by design: EN 300 468 carries the AC-4 TOC as an
76    /// `ac4_toc_byte` run whose structure (`ac4_toc()`) is defined in the AC-4
77    /// codec spec — ETSI TS 103 190-2 §6.2.1 (vendored at
78    /// `specs/etsi_ts_103_190_2_v01.03.01_ac4.pdf`), not in DVB SI. Typing it is
79    /// a separate, nested codec-bitstream effort tracked in issue #102.
80    pub toc: Option<&'a [u8]>,
81    /// Trailing additional_info_byte run.
82    pub additional_info: &'a [u8],
83}
84
85impl<'a> Parse<'a> for Ac4<'a> {
86    type Error = crate::error::Error;
87    fn parse(sel: &'a [u8]) -> Result<Self> {
88        if sel.is_empty() {
89            return Err(Error::BufferTooShort {
90                need: 1,
91                have: sel.len(),
92                what: "AC-4 body",
93            });
94        }
95        let flags = sel[0];
96        let ac4_config_flag = (flags & 0x80) != 0;
97        let ac4_toc_flag = (flags & 0x40) != 0;
98        let mut pos = 1;
99        let (ac4_dialog_enhancement_enabled, ac4_channel_mode) = if ac4_config_flag {
100            if sel.len() < pos + 1 {
101                return Err(Error::BufferTooShort {
102                    need: pos + 1,
103                    have: sel.len(),
104                    what: "AC-4 body",
105                });
106            }
107            let c = sel[pos];
108            pos += 1;
109            (
110                Some((c & 0x80) != 0),
111                Some(Ac4ChannelMode::from_u8((c >> 5) & 0x03)),
112            )
113        } else {
114            (None, None)
115        };
116        let toc = if ac4_toc_flag {
117            if sel.len() < pos + 1 {
118                return Err(Error::BufferTooShort {
119                    need: pos + 1,
120                    have: sel.len(),
121                    what: "AC-4 body",
122                });
123            }
124            let toc_len = sel[pos] as usize;
125            pos += 1;
126            if sel.len() < pos + toc_len {
127                return Err(Error::BufferTooShort {
128                    need: pos + toc_len,
129                    have: sel.len(),
130                    what: "AC-4 body",
131                });
132            }
133            let t = &sel[pos..pos + toc_len];
134            pos += toc_len;
135            Some(t)
136        } else {
137            None
138        };
139        Ok(Ac4 {
140            ac4_config_flag,
141            ac4_toc_flag,
142            ac4_dialog_enhancement_enabled,
143            ac4_channel_mode,
144            toc,
145            additional_info: &sel[pos..],
146        })
147    }
148}
149
150impl Serialize for Ac4<'_> {
151    type Error = crate::error::Error;
152    fn serialized_len(&self) -> usize {
153        1 + usize::from(self.ac4_config_flag)
154            + self.toc.map_or(0, |t| 1 + t.len())
155            + self.additional_info.len()
156    }
157    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
158        let len = self.serialized_len();
159        if buf.len() < len {
160            return Err(Error::OutputBufferTooSmall {
161                need: len,
162                have: buf.len(),
163            });
164        }
165        buf[0] = (u8::from(self.ac4_config_flag) << 7) | (u8::from(self.ac4_toc_flag) << 6);
166        let mut p = 1;
167        if self.ac4_config_flag {
168            let de = self.ac4_dialog_enhancement_enabled.unwrap_or(false);
169            let cm = self
170                .ac4_channel_mode
171                .unwrap_or(Ac4ChannelMode::Mono)
172                .to_u8()
173                & 0x03;
174            buf[p] = (u8::from(de) << 7) | (cm << 5);
175            p += 1;
176        }
177        if let Some(t) = self.toc {
178            buf[p] = t.len() as u8;
179            p += 1;
180            buf[p..p + t.len()].copy_from_slice(t);
181            p += t.len();
182        }
183        buf[p..p + self.additional_info.len()].copy_from_slice(self.additional_info);
184        Ok(len)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::descriptors::extension::test_support::*;
192    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
193
194    #[test]
195    fn parse_ac4_full() {
196        // config_flag=1, toc_flag=1; config byte de=1 cm=2; toc len 2 = [0x11,0x22]; extra 0x33
197        let sel = [0xC0, 0x80 | (0x02 << 5), 0x02, 0x11, 0x22, 0x33];
198        let bytes = wrap(0x15, &sel);
199        let d = ExtensionDescriptor::parse(&bytes).unwrap();
200        match &d.body {
201            ExtensionBody::Ac4(b) => {
202                assert!(b.ac4_config_flag);
203                assert!(b.ac4_toc_flag);
204                assert_eq!(b.ac4_dialog_enhancement_enabled, Some(true));
205                assert_eq!(b.ac4_channel_mode, Some(Ac4ChannelMode::Multichannel));
206                assert_eq!(b.toc, Some([0x11u8, 0x22].as_slice()));
207                assert_eq!(b.additional_info, &[0x33]);
208            }
209            other => panic!("expected Ac4, got {other:?}"),
210        }
211        round_trip(&d);
212    }
213
214    #[test]
215    fn parse_ac4_minimal() {
216        let sel = [0x00]; // no config, no toc, no extra
217        let bytes = wrap(0x15, &sel);
218        let d = ExtensionDescriptor::parse(&bytes).unwrap();
219        match &d.body {
220            ExtensionBody::Ac4(b) => {
221                assert!(!b.ac4_config_flag);
222                assert!(!b.ac4_toc_flag);
223                assert_eq!(b.toc, None);
224                assert!(b.additional_info.is_empty());
225            }
226            other => panic!("expected Ac4, got {other:?}"),
227        }
228        round_trip(&d);
229    }
230
231    #[test]
232    fn ac4_channel_mode_roundtrip() {
233        for b in 0u8..=3 {
234            assert_eq!(Ac4ChannelMode::from_u8(b).to_u8(), b);
235        }
236        assert_eq!(Ac4ChannelMode::Reserved(4).to_u8(), 4);
237    }
238
239    #[test]
240    fn ac4_channel_mode_name() {
241        assert_eq!(Ac4ChannelMode::Mono.name(), "Mono content");
242        assert_eq!(Ac4ChannelMode::Stereo.name(), "Stereo content");
243        assert_eq!(Ac4ChannelMode::Multichannel.name(), "Multichannel content");
244        assert_eq!(
245            Ac4ChannelMode::Reserved(3).name(),
246            "reserved for future use"
247        );
248    }
249}