Skip to main content

piaf/capabilities/cea861/
audio.rs

1use crate::model::prelude::Vec;
2
3bitflags::bitflags! {
4    /// Sample-rate support mask from byte 2 of a Short Audio Descriptor.
5    ///
6    /// Each set bit indicates the display/sink supports that sample rate.
7    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
9    pub struct AudioSampleRates: u8 {
10        /// 32 kHz sample rate.
11        const HZ_32000  = 0x01;
12        /// 44.1 kHz sample rate.
13        const HZ_44100  = 0x02;
14        /// 48 kHz sample rate.
15        const HZ_48000  = 0x04;
16        /// 88.2 kHz sample rate.
17        const HZ_88200  = 0x08;
18        /// 96 kHz sample rate.
19        const HZ_96000  = 0x10;
20        /// 176.4 kHz sample rate.
21        const HZ_176400 = 0x20;
22        /// 192 kHz sample rate.
23        const HZ_192000 = 0x40;
24    }
25}
26
27/// Audio format code from bits 6–3 of the first SAD byte.
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AudioFormat {
31    /// Linear PCM (uncompressed).
32    Lpcm,
33    /// Dolby AC-3.
34    Ac3,
35    /// MPEG-1 (Layers 1 & 2).
36    Mpeg1,
37    /// MPEG-1 Layer 3 (MP3).
38    Mp3,
39    /// MPEG-2 multi-channel.
40    Mpeg2Multichannel,
41    /// AAC LC.
42    AacLc,
43    /// DTS.
44    Dts,
45    /// ATRAC.
46    Atrac,
47    /// One Bit Audio (SACD).
48    OneBitAudio,
49    /// Enhanced AC-3 (Dolby Digital Plus / E-AC-3).
50    EnhancedAc3,
51    /// DTS-HD.
52    DtsHd,
53    /// MLP / Dolby TrueHD.
54    MlpTrueHd,
55    /// DST.
56    Dst,
57    /// WMA Pro.
58    WmaPro,
59    /// Extended audio format (AFC = 15); the inner value is bits 7–3 of byte 3.
60    Extended(u8),
61    /// Reserved or unknown format code.
62    Reserved(u8),
63}
64
65/// Format-specific information from byte 3 of a Short Audio Descriptor.
66#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum AudioFormatInfo {
69    /// LPCM (AFC = 1): supported bit depths.
70    Lpcm {
71        /// 16-bit depth is supported.
72        depth_16: bool,
73        /// 20-bit depth is supported.
74        depth_20: bool,
75        /// 24-bit depth is supported.
76        depth_24: bool,
77    },
78    /// Compressed formats (AFC 2–8): maximum bitrate in kbps (raw byte × 8).
79    MaxBitrateKbps(u16),
80    /// All other formats: raw byte 3 value.
81    Raw(u8),
82}
83
84/// A decoded Short Audio Descriptor (3 bytes) from a CEA Audio Data Block (tag `0x01`).
85#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct ShortAudioDescriptor {
88    /// Audio coding format.
89    pub format: AudioFormat,
90    /// Maximum number of audio channels (1–8).
91    pub max_channels: u8,
92    /// Supported sample rates.
93    pub sample_rates: AudioSampleRates,
94    /// Format-specific byte 3 interpretation.
95    pub format_info: AudioFormatInfo,
96}
97
98/// Parses an Audio Data Block payload into a list of [`ShortAudioDescriptor`]s.
99///
100/// `block_data` is the payload slice (excluding the header byte); any trailing bytes
101/// that do not form a complete 3-byte group are silently ignored.
102pub(super) fn parse_audio_data_block(block_data: &[u8]) -> Vec<ShortAudioDescriptor> {
103    let mut out = Vec::new();
104    let mut j = 0;
105    while j + 3 <= block_data.len() {
106        let b0 = block_data[j];
107        let b1 = block_data[j + 1];
108        let b2 = block_data[j + 2];
109
110        let afc = (b0 >> 3) & 0x0F;
111        let max_channels = (b0 & 0x07) + 1;
112        let sample_rates = AudioSampleRates::from_bits_truncate(b1 & 0x7F);
113
114        let format = match afc {
115            1 => AudioFormat::Lpcm,
116            2 => AudioFormat::Ac3,
117            3 => AudioFormat::Mpeg1,
118            4 => AudioFormat::Mp3,
119            5 => AudioFormat::Mpeg2Multichannel,
120            6 => AudioFormat::AacLc,
121            7 => AudioFormat::Dts,
122            8 => AudioFormat::Atrac,
123            9 => AudioFormat::OneBitAudio,
124            10 => AudioFormat::EnhancedAc3,
125            11 => AudioFormat::DtsHd,
126            12 => AudioFormat::MlpTrueHd,
127            13 => AudioFormat::Dst,
128            14 => AudioFormat::WmaPro,
129            15 => AudioFormat::Extended((b2 >> 3) & 0x1F),
130            _ => AudioFormat::Reserved(afc),
131        };
132
133        let format_info = match afc {
134            1 => AudioFormatInfo::Lpcm {
135                depth_16: b2 & 0x01 != 0,
136                depth_20: b2 & 0x02 != 0,
137                depth_24: b2 & 0x04 != 0,
138            },
139            2..=8 => AudioFormatInfo::MaxBitrateKbps((b2 as u16) * 8),
140            _ => AudioFormatInfo::Raw(b2),
141        };
142
143        out.push(ShortAudioDescriptor {
144            format,
145            max_channels,
146            sample_rates,
147            format_info,
148        });
149        j += 3;
150    }
151    out
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157
158    #[test]
159    fn test_lpcm_2ch_stereo() {
160        // LPCM, 2 ch, 32/44.1/48 kHz, 16/20/24-bit
161        // b0 = (1 << 3) | 1 = 0x09  (AFC=1, ch=2-1=1)
162        // b1 = 0x07  (32+44.1+48 kHz)
163        // b2 = 0x07  (16+20+24-bit)
164        let sads = parse_audio_data_block(&[0x09, 0x07, 0x07]);
165        assert_eq!(sads.len(), 1);
166        let sad = &sads[0];
167        assert_eq!(sad.format, AudioFormat::Lpcm);
168        assert_eq!(sad.max_channels, 2);
169        assert!(sad.sample_rates.contains(AudioSampleRates::HZ_32000));
170        assert!(sad.sample_rates.contains(AudioSampleRates::HZ_44100));
171        assert!(sad.sample_rates.contains(AudioSampleRates::HZ_48000));
172        assert!(!sad.sample_rates.contains(AudioSampleRates::HZ_96000));
173        assert_eq!(
174            sad.format_info,
175            AudioFormatInfo::Lpcm {
176                depth_16: true,
177                depth_20: true,
178                depth_24: true
179            }
180        );
181    }
182
183    #[test]
184    fn test_ac3_bitrate() {
185        // AC-3, 6 ch, 48 kHz, max bitrate = 640 kbps → b2 = 640/8 = 80
186        // b0 = (2 << 3) | 5 = 0x15  (AFC=2, ch=6-1=5)
187        // b1 = 0x04  (48 kHz only)
188        // b2 = 80
189        let sads = parse_audio_data_block(&[0x15, 0x04, 80]);
190        assert_eq!(sads.len(), 1);
191        let sad = &sads[0];
192        assert_eq!(sad.format, AudioFormat::Ac3);
193        assert_eq!(sad.max_channels, 6);
194        assert_eq!(sad.format_info, AudioFormatInfo::MaxBitrateKbps(640));
195    }
196
197    #[test]
198    fn test_truehd_raw_byte3() {
199        // MLP/TrueHD (AFC=12), ch=8, sample rate 192 kHz, byte3 raw
200        // b0 = (12 << 3) | 7 = 0x67
201        // b1 = 0x40  (192 kHz)
202        // b2 = 0x00
203        let sads = parse_audio_data_block(&[0x67, 0x40, 0x00]);
204        assert_eq!(sads.len(), 1);
205        let sad = &sads[0];
206        assert_eq!(sad.format, AudioFormat::MlpTrueHd);
207        assert_eq!(sad.max_channels, 8);
208        assert!(sad.sample_rates.contains(AudioSampleRates::HZ_192000));
209        assert_eq!(sad.format_info, AudioFormatInfo::Raw(0x00));
210    }
211
212    #[test]
213    fn test_partial_trailing_bytes_ignored() {
214        // 4 bytes: one complete SAD (3 bytes) + 1 trailing byte → only 1 SAD
215        let sads = parse_audio_data_block(&[0x09, 0x07, 0x07, 0xFF]);
216        assert_eq!(sads.len(), 1);
217    }
218
219    #[test]
220    fn test_multiple_sads() {
221        // Two SADs back-to-back
222        let sads = parse_audio_data_block(&[
223            0x09, 0x07, 0x07, // LPCM 2ch
224            0x15, 0x04, 80, // AC-3 6ch
225        ]);
226        assert_eq!(sads.len(), 2);
227        assert_eq!(sads[0].format, AudioFormat::Lpcm);
228        assert_eq!(sads[1].format, AudioFormat::Ac3);
229    }
230}