Skip to main content

dvb_si/descriptors/extension/
audio_preselection.rs

1//! Audio Preselection Descriptor — ETSI EN 300 468 §6.4.1 (tag_extension 0x19).
2use super::*;
3use alloc::vec::Vec;
4
5impl<'a> ExtensionBodyDef<'a> for AudioPreselection<'a> {
6    const TAG_EXTENSION: u8 = 0x19;
7    const NAME: &'static str = "AUDIO_PRESELECTION";
8}
9/// audio_preselection body (Table 110, §6.4.1). The preselection loop is unfolded.
10#[derive(Debug, Clone, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize))]
12#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
13pub struct AudioPreselection<'a> {
14    /// Preselection entries (num_preselections is their count).
15    pub preselections: Vec<Preselection<'a>>,
16}
17
18/// A single preselection entry in the audio_preselection_descriptor loop
19/// (Table 110, §6.4.1).
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
23pub struct Preselection<'a> {
24    /// preselection_id(5).
25    pub preselection_id: u8,
26    /// audio_rendering_indication(3) — Table 111.
27    pub audio_rendering_indication: u8,
28    /// audio_description(1).
29    pub audio_description: bool,
30    /// spoken_subtitles(1).
31    pub spoken_subtitles: bool,
32    /// dialogue_enhancement(1).
33    pub dialogue_enhancement: bool,
34    /// interactivity_enabled(1).
35    pub interactivity_enabled: bool,
36    /// ISO_639_language_code(24), present iff language_code_present.
37    pub language_code: Option<LangCode>,
38    /// message_id(8), present iff text_label_present.
39    pub message_id: Option<u8>,
40    /// component_tag bytes (num_aux_components of them), present iff multi_stream_info_present.
41    pub aux_component_tags: Option<&'a [u8]>,
42    /// future_extension_byte run, present iff future_extension.
43    pub future_extension: Option<&'a [u8]>,
44}
45
46impl<'a> Parse<'a> for AudioPreselection<'a> {
47    type Error = crate::error::Error;
48    fn parse(sel: &'a [u8]) -> Result<Self> {
49        if sel.is_empty() {
50            return Err(Error::BufferTooShort {
51                need: 1,
52                have: sel.len(),
53                what: "audio_preselection body",
54            });
55        }
56        let num_preselections = sel[0] >> 3;
57        let mut pos = 1;
58        let mut preselections = Vec::with_capacity(num_preselections as usize);
59        for _ in 0..num_preselections {
60            if pos + 2 > sel.len() {
61                return Err(Error::BufferTooShort {
62                    need: pos + 2,
63                    have: sel.len(),
64                    what: "audio_preselection body",
65                });
66            }
67            let byte_a = sel[pos];
68            let byte_b = sel[pos + 1];
69            pos += 2;
70            let preselection_id = byte_a >> 3;
71            let audio_rendering_indication = byte_a & 0x07;
72            let audio_description = (byte_b >> 7) & 1 != 0;
73            let spoken_subtitles = (byte_b >> 6) & 1 != 0;
74            let dialogue_enhancement = (byte_b >> 5) & 1 != 0;
75            let interactivity_enabled = (byte_b >> 4) & 1 != 0;
76            let language_code_present = (byte_b >> 3) & 1 != 0;
77            let text_label_present = (byte_b >> 2) & 1 != 0;
78            let multi_stream_info_present = (byte_b >> 1) & 1 != 0;
79            let future_extension = byte_b & 1 != 0;
80
81            let language_code = if language_code_present {
82                if pos + ISO_639_LEN > sel.len() {
83                    return Err(Error::BufferTooShort {
84                        need: pos + ISO_639_LEN,
85                        have: sel.len(),
86                        what: "audio_preselection body",
87                    });
88                }
89                let lc = LangCode([sel[pos], sel[pos + 1], sel[pos + 2]]);
90                pos += ISO_639_LEN;
91                Some(lc)
92            } else {
93                None
94            };
95
96            let message_id = if text_label_present {
97                if pos >= sel.len() {
98                    return Err(Error::BufferTooShort {
99                        need: pos + 1,
100                        have: sel.len(),
101                        what: "audio_preselection body",
102                    });
103                }
104                let id = sel[pos];
105                pos += 1;
106                Some(id)
107            } else {
108                None
109            };
110
111            let aux_component_tags = if multi_stream_info_present {
112                if pos >= sel.len() {
113                    return Err(Error::BufferTooShort {
114                        need: pos + 1,
115                        have: sel.len(),
116                        what: "audio_preselection body",
117                    });
118                }
119                let num_aux = sel[pos] >> 5;
120                pos += 1;
121                if pos + num_aux as usize > sel.len() {
122                    return Err(Error::BufferTooShort {
123                        need: pos + num_aux as usize,
124                        have: sel.len(),
125                        what: "audio_preselection body",
126                    });
127                }
128                let tags = &sel[pos..pos + num_aux as usize];
129                pos += num_aux as usize;
130                Some(tags)
131            } else {
132                None
133            };
134
135            let future_ext = if future_extension {
136                if pos >= sel.len() {
137                    return Err(Error::BufferTooShort {
138                        need: pos + 1,
139                        have: sel.len(),
140                        what: "audio_preselection body",
141                    });
142                }
143                let ext_len = (sel[pos] & 0x1F) as usize;
144                pos += 1;
145                if pos + ext_len > sel.len() {
146                    return Err(Error::BufferTooShort {
147                        need: pos + ext_len,
148                        have: sel.len(),
149                        what: "audio_preselection body",
150                    });
151                }
152                let ext = &sel[pos..pos + ext_len];
153                pos += ext_len;
154                Some(ext)
155            } else {
156                None
157            };
158
159            preselections.push(Preselection {
160                preselection_id,
161                audio_rendering_indication,
162                audio_description,
163                spoken_subtitles,
164                dialogue_enhancement,
165                interactivity_enabled,
166                language_code,
167                message_id,
168                aux_component_tags,
169                future_extension: future_ext,
170            });
171        }
172        Ok(AudioPreselection { preselections })
173    }
174}
175
176impl Serialize for AudioPreselection<'_> {
177    type Error = crate::error::Error;
178    fn serialized_len(&self) -> usize {
179        let body: usize = self
180            .preselections
181            .iter()
182            .map(|p| {
183                2 + p.language_code.map_or(0, |_| ISO_639_LEN)
184                    + p.message_id.map_or(0, |_| 1)
185                    + p.aux_component_tags.map_or(0, |t| 1 + t.len())
186                    + p.future_extension.map_or(0, |e| 1 + e.len())
187            })
188            .sum();
189        1 + body
190    }
191    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
192        let len = self.serialized_len();
193        if buf.len() < len {
194            return Err(Error::OutputBufferTooSmall {
195                need: len,
196                have: buf.len(),
197            });
198        }
199        buf[0] = ((self.preselections.len() as u8) & 0x1F) << 3;
200        let mut p = 1;
201        for s in &self.preselections {
202            buf[p] = ((s.preselection_id & 0x1F) << 3) | (s.audio_rendering_indication & 0x07);
203            buf[p + 1] = (u8::from(s.audio_description) << 7)
204                | (u8::from(s.spoken_subtitles) << 6)
205                | (u8::from(s.dialogue_enhancement) << 5)
206                | (u8::from(s.interactivity_enabled) << 4)
207                | (u8::from(s.language_code.is_some()) << 3)
208                | (u8::from(s.message_id.is_some()) << 2)
209                | (u8::from(s.aux_component_tags.is_some()) << 1)
210                | u8::from(s.future_extension.is_some());
211            p += 2;
212            if let Some(ref lc) = s.language_code {
213                buf[p..p + ISO_639_LEN].copy_from_slice(&lc.0);
214                p += ISO_639_LEN;
215            }
216            if let Some(id) = s.message_id {
217                buf[p] = id;
218                p += 1;
219            }
220            if let Some(tags) = s.aux_component_tags {
221                buf[p] = ((tags.len() as u8) & 0x07) << 5;
222                p += 1;
223                buf[p..p + tags.len()].copy_from_slice(tags);
224                p += tags.len();
225            }
226            if let Some(ext) = s.future_extension {
227                buf[p] = ext.len() as u8 & 0x1F;
228                p += 1;
229                buf[p..p + ext.len()].copy_from_slice(ext);
230                p += ext.len();
231            }
232        }
233        Ok(len)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::descriptors::extension::test_support::*;
241    use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
242    use crate::text::LangCode;
243
244    #[test]
245    fn parse_structured_round_trip_two_preselections() {
246        let num_pre = 0x02 << 3;
247        let pre0_a = (1 << 3) | 2; // id=1, rendering=2
248        let pre0_b = (1 << 6) | (1 << 4); // spoken_subtitles=1, interactivity_enabled=1
249        let pre1_a = (2 << 3) | 3; // id=2, rendering=3
250                                   // all flags: ad=1, ss=0, de=1, ie=0, lang=1, text=1, multi=1, futext=1
251        let pre1_b = (1 << 7) | (1 << 5) | (1 << 3) | (1 << 2) | (1 << 1) | 1;
252        let lang = b"foo";
253        let msg_id = 0xABu8;
254        let aux_tags: &[u8] = &[0x01, 0x02];
255        let aux_byte = ((aux_tags.len() as u8) & 0x07) << 5;
256        let fut_ext: &[u8] = &[0x25, 0x89, 0x63, 0x21, 0x47];
257        let fut_byte = fut_ext.len() as u8 & 0x1F;
258
259        let mut sel = vec![num_pre];
260        sel.push(pre0_a);
261        sel.push(pre0_b);
262        sel.push(pre1_a);
263        sel.push(pre1_b);
264        sel.extend_from_slice(lang);
265        sel.push(msg_id);
266        sel.push(aux_byte);
267        sel.extend_from_slice(aux_tags);
268        sel.push(fut_byte);
269        sel.extend_from_slice(fut_ext);
270
271        let bytes = wrap(0x19, &sel);
272        let d = ExtensionDescriptor::parse(&bytes).unwrap();
273        match &d.body {
274            ExtensionBody::AudioPreselection(b) => {
275                assert_eq!(b.preselections.len(), 2);
276
277                let p0 = &b.preselections[0];
278                assert_eq!(p0.preselection_id, 1);
279                assert_eq!(p0.audio_rendering_indication, 2);
280                assert!(!p0.audio_description);
281                assert!(p0.spoken_subtitles);
282                assert!(!p0.dialogue_enhancement);
283                assert!(p0.interactivity_enabled);
284                assert_eq!(p0.language_code, None);
285                assert_eq!(p0.message_id, None);
286                assert_eq!(p0.aux_component_tags, None);
287                assert_eq!(p0.future_extension, None);
288
289                let p1 = &b.preselections[1];
290                assert_eq!(p1.preselection_id, 2);
291                assert_eq!(p1.audio_rendering_indication, 3);
292                assert!(p1.audio_description);
293                assert!(!p1.spoken_subtitles);
294                assert!(p1.dialogue_enhancement);
295                assert!(!p1.interactivity_enabled);
296                assert_eq!(p1.language_code, Some(LangCode(*b"foo")));
297                assert_eq!(p1.message_id, Some(0xAB));
298                assert_eq!(p1.aux_component_tags, Some(aux_tags as &[u8]));
299                assert_eq!(p1.future_extension, Some(fut_ext as &[u8]));
300            }
301            other => panic!("expected AudioPreselection, got {other:?}"),
302        }
303        round_trip(&d);
304    }
305
306    #[test]
307    fn tsduck_byte_exact_test_015() {
308        // TSDuck test-015 reference: 2 preselections — one minimal, one with all
309        // optional fields present (lang "foo", message_id 0xAB, 2 aux tags, 5
310        // future_extension bytes).
311        let hex = "7f1319100a5013af666f6fab400102052589632147";
312        let bytes = from_hex(hex);
313        let d = ExtensionDescriptor::parse(&bytes).unwrap_or_else(|e| panic!("parse {hex}: {e:?}"));
314        assert_eq!(
315            d.kind(),
316            Some(super::super::ExtensionTag::AudioPreselection)
317        );
318        match &d.body {
319            ExtensionBody::AudioPreselection(b) => {
320                assert_eq!(b.preselections.len(), 2);
321
322                let p0 = &b.preselections[0];
323                assert_eq!(p0.preselection_id, 1);
324                assert_eq!(p0.audio_rendering_indication, 2);
325                assert!(!p0.audio_description);
326                assert!(p0.spoken_subtitles);
327                assert!(!p0.dialogue_enhancement);
328                assert!(p0.interactivity_enabled);
329                assert_eq!(p0.language_code, None);
330                assert_eq!(p0.message_id, None);
331                assert_eq!(p0.aux_component_tags, None);
332                assert_eq!(p0.future_extension, None);
333
334                let p1 = &b.preselections[1];
335                assert_eq!(p1.preselection_id, 2);
336                assert_eq!(p1.audio_rendering_indication, 3);
337                assert!(p1.audio_description);
338                assert!(!p1.spoken_subtitles);
339                assert!(p1.dialogue_enhancement);
340                assert!(!p1.interactivity_enabled);
341                assert_eq!(p1.language_code, Some(LangCode(*b"foo")));
342                assert_eq!(p1.message_id, Some(0xAB));
343                assert_eq!(p1.aux_component_tags, Some(&[0x01u8, 0x02u8][..]));
344                assert_eq!(
345                    p1.future_extension,
346                    Some(&[0x25u8, 0x89, 0x63, 0x21, 0x47][..])
347                );
348            }
349            other => panic!("expected AudioPreselection, got {other:?}"),
350        }
351        round_trip(&d);
352    }
353}