fuchsia_audio/
dai.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::format::SampleSize;
6use fidl_fuchsia_hardware_audio as fhaudio;
7use std::fmt::Display;
8use std::num::NonZeroU32;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct DaiFormat {
13    pub number_of_channels: u32,
14    pub channels_to_use_bitmask: u64,
15    pub sample_format: DaiSampleFormat,
16    pub frame_format: DaiFrameFormat,
17    pub frame_rate: u32,
18    // Stores `bits_per_slot` as `total_bits` and `bits_per_sample` as `valid_bits`.
19    pub sample_size: SampleSize,
20}
21
22impl Display for DaiFormat {
23    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24        write!(
25            f,
26            "{},{}ch,0x{:x},{},{},{}",
27            self.frame_rate,
28            self.number_of_channels,
29            self.channels_to_use_bitmask,
30            self.sample_format,
31            self.sample_size,
32            self.frame_format
33        )
34    }
35}
36
37impl FromStr for DaiFormat {
38    type Err = String;
39
40    fn from_str(s: &str) -> Result<Self, Self::Err> {
41        if s.len() == 0 {
42            return Err("No DAI format specified.".to_string());
43        };
44
45        let splits: Vec<&str> = s.split(",").collect();
46
47        if splits.len() != 6 {
48            return Err("Expected 6 semicolon-separated values: \
49                <FrameRate>,<NumChannels>,<ChannelsToUseBitmask>,\
50                <DaiSampleFormat>,<SampleSize>,<DaiFrameFormat>"
51                .to_string());
52        }
53
54        let frame_rate =
55            splits[0].parse::<u32>().map_err(|_| format!("Invalid frame rate: {}", splits[0]))?;
56        let number_of_channels = splits[1]
57            .strip_suffix("ch")
58            .ok_or_else(|| "Number of channels must be followed by 'ch'".to_string())?
59            .parse::<u32>()
60            .map_err(|_| format!("Invalid number of channels: {}", splits[1]))?;
61        let channels_to_use_bitmask = {
62            let hex_str = splits[2].strip_prefix("0x").ok_or_else(|| {
63                "Channels to use bitmask must be a hexadecimal number starting with '0x'"
64                    .to_string()
65            })?;
66            u64::from_str_radix(hex_str, 16)
67                .map_err(|_| format!("Invalid channels to use bitmask: {}", splits[2]))?
68        };
69        let sample_format = splits[3].parse::<DaiSampleFormat>()?;
70        let sample_size = splits[4].parse::<SampleSize>()?;
71        let frame_format = splits[5].parse::<DaiFrameFormat>()?;
72
73        Ok(Self {
74            number_of_channels,
75            channels_to_use_bitmask,
76            sample_format,
77            frame_format,
78            frame_rate,
79            sample_size,
80        })
81    }
82}
83
84impl TryFrom<fhaudio::DaiFormat> for DaiFormat {
85    type Error = String;
86
87    fn try_from(value: fhaudio::DaiFormat) -> Result<Self, Self::Error> {
88        let bits_per_slot = NonZeroU32::new(value.bits_per_slot as u32)
89            .ok_or_else(|| "'bits_per_slot' cannot be zero".to_string())?;
90        let bits_per_sample = NonZeroU32::new(value.bits_per_sample as u32)
91            .ok_or_else(|| "'bits_per_sample' cannot be zero".to_string())?;
92        let sample_size = SampleSize::from_partial_bits(bits_per_sample, bits_per_slot)
93            .ok_or_else(|| {
94                "'bits_per_sample' must be less than or equal to 'bits_per_slot'".to_string()
95            })?;
96        Ok(Self {
97            number_of_channels: value.number_of_channels,
98            channels_to_use_bitmask: value.channels_to_use_bitmask,
99            sample_format: value.sample_format.into(),
100            frame_format: value.frame_format.into(),
101            frame_rate: value.frame_rate,
102            sample_size,
103        })
104    }
105}
106
107impl From<DaiFormat> for fhaudio::DaiFormat {
108    fn from(value: DaiFormat) -> Self {
109        Self {
110            number_of_channels: value.number_of_channels,
111            channels_to_use_bitmask: value.channels_to_use_bitmask,
112            sample_format: value.sample_format.into(),
113            frame_format: value.frame_format.into(),
114            frame_rate: value.frame_rate,
115            bits_per_slot: value.sample_size.total_bits().get() as u8,
116            bits_per_sample: value.sample_size.valid_bits().get() as u8,
117        }
118    }
119}
120
121#[derive(Debug, Default, Clone, PartialEq)]
122pub struct DaiFormatSet {
123    pub number_of_channels: Vec<u32>,
124    pub sample_formats: Vec<DaiSampleFormat>,
125    pub frame_formats: Vec<DaiFrameFormat>,
126    pub frame_rates: Vec<u32>,
127    pub bits_per_slot: Vec<u8>,
128    pub bits_per_sample: Vec<u8>,
129}
130
131impl From<fhaudio::DaiSupportedFormats> for DaiFormatSet {
132    fn from(value: fhaudio::DaiSupportedFormats) -> Self {
133        Self {
134            number_of_channels: value.number_of_channels,
135            sample_formats: value.sample_formats.into_iter().map(From::from).collect(),
136            frame_formats: value.frame_formats.into_iter().map(From::from).collect(),
137            frame_rates: value.frame_rates,
138            bits_per_slot: value.bits_per_slot,
139            bits_per_sample: value.bits_per_sample,
140        }
141    }
142}
143
144impl From<DaiFormatSet> for fhaudio::DaiSupportedFormats {
145    fn from(value: DaiFormatSet) -> Self {
146        Self {
147            number_of_channels: value.number_of_channels,
148            sample_formats: value.sample_formats.into_iter().map(From::from).collect(),
149            frame_formats: value.frame_formats.into_iter().map(From::from).collect(),
150            frame_rates: value.frame_rates,
151            bits_per_slot: value.bits_per_slot,
152            bits_per_sample: value.bits_per_sample,
153        }
154    }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum DaiSampleFormat {
159    Pdm,
160    PcmSigned,
161    PcmUnsigned,
162    PcmFloat,
163}
164
165impl Display for DaiSampleFormat {
166    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167        let s = match self {
168            DaiSampleFormat::Pdm => "pdm",
169            DaiSampleFormat::PcmSigned => "pcm_signed",
170            DaiSampleFormat::PcmUnsigned => "pcm_unsigned",
171            DaiSampleFormat::PcmFloat => "pcm_float",
172        };
173        f.write_str(s)
174    }
175}
176
177impl FromStr for DaiSampleFormat {
178    type Err = String;
179
180    fn from_str(s: &str) -> Result<Self, Self::Err> {
181        match s {
182            "pdm" => Ok(Self::Pdm),
183            "pcm_signed" => Ok(Self::PcmSigned),
184            "pcm_unsigned" => Ok(Self::PcmUnsigned),
185            "pcm_float" => Ok(Self::PcmFloat),
186            _ => Err(format!("Invalid DAI sample format: {}", s)),
187        }
188    }
189}
190
191impl From<fhaudio::DaiSampleFormat> for DaiSampleFormat {
192    fn from(value: fhaudio::DaiSampleFormat) -> Self {
193        match value {
194            fhaudio::DaiSampleFormat::Pdm => Self::Pdm,
195            fhaudio::DaiSampleFormat::PcmSigned => Self::PcmSigned,
196            fhaudio::DaiSampleFormat::PcmUnsigned => Self::PcmUnsigned,
197            fhaudio::DaiSampleFormat::PcmFloat => Self::PcmFloat,
198        }
199    }
200}
201
202impl From<DaiSampleFormat> for fhaudio::DaiSampleFormat {
203    fn from(value: DaiSampleFormat) -> Self {
204        match value {
205            DaiSampleFormat::Pdm => Self::Pdm,
206            DaiSampleFormat::PcmSigned => Self::PcmSigned,
207            DaiSampleFormat::PcmUnsigned => Self::PcmUnsigned,
208            DaiSampleFormat::PcmFloat => Self::PcmFloat,
209        }
210    }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub enum DaiFrameFormat {
215    Standard(DaiFrameFormatStandard),
216    Custom(DaiFrameFormatCustom),
217}
218
219impl Display for DaiFrameFormat {
220    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221        match self {
222            DaiFrameFormat::Standard(standard) => standard.fmt(f),
223            DaiFrameFormat::Custom(custom) => {
224                f.write_str("custom:")?;
225                custom.fmt(f)
226            }
227        }
228    }
229}
230
231impl FromStr for DaiFrameFormat {
232    type Err = String;
233
234    fn from_str(s: &str) -> Result<Self, Self::Err> {
235        if let Some(custom_str) = s.strip_prefix("custom:") {
236            let custom_fmt = custom_str.parse::<DaiFrameFormatCustom>()?;
237            return Ok(Self::Custom(custom_fmt));
238        }
239        let standard_fmt = s.parse::<DaiFrameFormatStandard>()?;
240        Ok(Self::Standard(standard_fmt))
241    }
242}
243
244impl From<fhaudio::DaiFrameFormat> for DaiFrameFormat {
245    fn from(value: fhaudio::DaiFrameFormat) -> Self {
246        match value {
247            fhaudio::DaiFrameFormat::FrameFormatStandard(standard) => {
248                Self::Standard(standard.into())
249            }
250            fhaudio::DaiFrameFormat::FrameFormatCustom(custom) => Self::Custom(custom.into()),
251        }
252    }
253}
254
255impl From<DaiFrameFormat> for fhaudio::DaiFrameFormat {
256    fn from(value: DaiFrameFormat) -> Self {
257        match value {
258            DaiFrameFormat::Standard(standard) => Self::FrameFormatStandard(standard.into()),
259            DaiFrameFormat::Custom(custom) => Self::FrameFormatCustom(custom.into()),
260        }
261    }
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub enum DaiFrameFormatStandard {
266    None,
267    I2S,
268    StereoLeft,
269    StereoRight,
270    Tdm1,
271    Tdm2,
272    Tdm3,
273}
274
275impl Display for DaiFrameFormatStandard {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        let s = match self {
278            DaiFrameFormatStandard::None => "none",
279            DaiFrameFormatStandard::I2S => "i2s",
280            DaiFrameFormatStandard::StereoLeft => "stereo_left",
281            DaiFrameFormatStandard::StereoRight => "stereo_right",
282            DaiFrameFormatStandard::Tdm1 => "tdm1",
283            DaiFrameFormatStandard::Tdm2 => "tdm2",
284            DaiFrameFormatStandard::Tdm3 => "tdm3",
285        };
286        f.write_str(s)
287    }
288}
289
290impl FromStr for DaiFrameFormatStandard {
291    type Err = String;
292
293    fn from_str(s: &str) -> Result<Self, Self::Err> {
294        match s {
295            "none" => Ok(DaiFrameFormatStandard::None),
296            "i2s" => Ok(DaiFrameFormatStandard::I2S),
297            "stereo_left" => Ok(DaiFrameFormatStandard::StereoLeft),
298            "stereo_right" => Ok(DaiFrameFormatStandard::StereoRight),
299            "tdm1" => Ok(DaiFrameFormatStandard::Tdm1),
300            "tdm2" => Ok(DaiFrameFormatStandard::Tdm2),
301            "tdm3" => Ok(DaiFrameFormatStandard::Tdm3),
302            _ => Err(format!("Invalid DAI frame format: {}", s)),
303        }
304    }
305}
306
307impl From<fhaudio::DaiFrameFormatStandard> for DaiFrameFormatStandard {
308    fn from(value: fhaudio::DaiFrameFormatStandard) -> Self {
309        match value {
310            fhaudio::DaiFrameFormatStandard::None => Self::None,
311            fhaudio::DaiFrameFormatStandard::I2S => Self::I2S,
312            fhaudio::DaiFrameFormatStandard::StereoLeft => Self::StereoLeft,
313            fhaudio::DaiFrameFormatStandard::StereoRight => Self::StereoRight,
314            fhaudio::DaiFrameFormatStandard::Tdm1 => Self::Tdm1,
315            fhaudio::DaiFrameFormatStandard::Tdm2 => Self::Tdm2,
316            fhaudio::DaiFrameFormatStandard::Tdm3 => Self::Tdm3,
317        }
318    }
319}
320
321impl From<DaiFrameFormatStandard> for fhaudio::DaiFrameFormatStandard {
322    fn from(value: DaiFrameFormatStandard) -> Self {
323        match value {
324            DaiFrameFormatStandard::None => Self::None,
325            DaiFrameFormatStandard::I2S => Self::I2S,
326            DaiFrameFormatStandard::StereoLeft => Self::StereoLeft,
327            DaiFrameFormatStandard::StereoRight => Self::StereoRight,
328            DaiFrameFormatStandard::Tdm1 => Self::Tdm1,
329            DaiFrameFormatStandard::Tdm2 => Self::Tdm2,
330            DaiFrameFormatStandard::Tdm3 => Self::Tdm3,
331        }
332    }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336pub struct DaiFrameFormatCustom {
337    pub justification: DaiFrameFormatJustification,
338    pub clocking: DaiFrameFormatClocking,
339    pub frame_sync_sclks_offset: i8,
340    pub frame_sync_size: u8,
341}
342
343impl Display for DaiFrameFormatCustom {
344    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
345        write!(
346            f,
347            "{};{};{};{}",
348            self.justification, self.clocking, self.frame_sync_sclks_offset, self.frame_sync_size
349        )
350    }
351}
352
353impl FromStr for DaiFrameFormatCustom {
354    type Err = String;
355
356    fn from_str(s: &str) -> Result<Self, Self::Err> {
357        if s.len() == 0 {
358            return Err("No DAI frame format specified.".to_string());
359        };
360
361        let splits: Vec<&str> = s.split(";").collect();
362
363        if splits.len() != 4 {
364            return Err("Expected 4 semicolon-separated values: \
365                <Justification>;<Clocking>;<FrameSyncOffset>;<FrameSyncSize>"
366                .to_string());
367        }
368
369        let justification = splits[0].parse::<DaiFrameFormatJustification>()?;
370        let clocking = splits[1].parse::<DaiFrameFormatClocking>()?;
371        let frame_sync_sclks_offset = splits[2]
372            .parse::<i8>()
373            .map_err(|_| format!("Invalid frame sync offset: {}", splits[2]))?;
374        let frame_sync_size = splits[3]
375            .parse::<u8>()
376            .map_err(|_| format!("Invalid frame sync size: {}", splits[3]))?;
377
378        Ok(Self { justification, clocking, frame_sync_sclks_offset, frame_sync_size })
379    }
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub enum DaiFrameFormatJustification {
384    Left,
385    Right,
386}
387
388impl Display for DaiFrameFormatJustification {
389    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390        let s = match self {
391            DaiFrameFormatJustification::Left => "left_justified",
392            DaiFrameFormatJustification::Right => "right_justified",
393        };
394        f.write_str(s)
395    }
396}
397
398impl FromStr for DaiFrameFormatJustification {
399    type Err = String;
400
401    fn from_str(s: &str) -> Result<Self, Self::Err> {
402        match s {
403            "left_justified" => Ok(DaiFrameFormatJustification::Left),
404            "right_justified" => Ok(DaiFrameFormatJustification::Right),
405            _ => Err(format!("Invalid DAI frame justification: {}", s)),
406        }
407    }
408}
409
410#[derive(Debug, Clone, Copy, PartialEq, Eq)]
411pub enum DaiFrameFormatClocking {
412    RaisingSclk,
413    FallingSclk,
414}
415
416impl Display for DaiFrameFormatClocking {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        let s = match self {
419            DaiFrameFormatClocking::RaisingSclk => "raising_sclk",
420            DaiFrameFormatClocking::FallingSclk => "falling_sclk",
421        };
422        f.write_str(s)
423    }
424}
425
426impl FromStr for DaiFrameFormatClocking {
427    type Err = String;
428
429    fn from_str(s: &str) -> Result<Self, Self::Err> {
430        match s {
431            "raising_sclk" => Ok(DaiFrameFormatClocking::RaisingSclk),
432            "falling_sclk" => Ok(DaiFrameFormatClocking::FallingSclk),
433            _ => Err(format!("Invalid DAI frame clocking: {}", s)),
434        }
435    }
436}
437
438impl From<DaiFrameFormatCustom> for fhaudio::DaiFrameFormatCustom {
439    fn from(value: DaiFrameFormatCustom) -> Self {
440        Self {
441            left_justified: match value.justification {
442                DaiFrameFormatJustification::Left => true,
443                DaiFrameFormatJustification::Right => false,
444            },
445            sclk_on_raising: match value.clocking {
446                DaiFrameFormatClocking::RaisingSclk => true,
447                DaiFrameFormatClocking::FallingSclk => false,
448            },
449            frame_sync_sclks_offset: value.frame_sync_sclks_offset,
450            frame_sync_size: value.frame_sync_size,
451        }
452    }
453}
454
455impl From<fhaudio::DaiFrameFormatCustom> for DaiFrameFormatCustom {
456    fn from(value: fhaudio::DaiFrameFormatCustom) -> Self {
457        Self {
458            justification: if value.left_justified {
459                DaiFrameFormatJustification::Left
460            } else {
461                DaiFrameFormatJustification::Right
462            },
463            clocking: if value.sclk_on_raising {
464                DaiFrameFormatClocking::RaisingSclk
465            } else {
466                DaiFrameFormatClocking::FallingSclk
467            },
468            frame_sync_sclks_offset: value.frame_sync_sclks_offset,
469            frame_sync_size: value.frame_sync_size,
470        }
471    }
472}
473
474#[cfg(test)]
475pub mod test {
476    use super::*;
477    use crate::format::{BITS_16, BITS_32};
478    use test_case::test_case;
479
480    #[test_case("none", DaiFrameFormat::Standard(DaiFrameFormatStandard::None); "standard: none")]
481    #[test_case("i2s", DaiFrameFormat::Standard(DaiFrameFormatStandard::I2S); "standard: i2s")]
482    #[test_case(
483        "stereo_left",
484        DaiFrameFormat::Standard(DaiFrameFormatStandard::StereoLeft);
485        "standard: stereo_left"
486    )]
487    #[test_case(
488        "stereo_right",
489        DaiFrameFormat::Standard(DaiFrameFormatStandard::StereoRight);
490        "standard: stereo_right"
491    )]
492    #[test_case("tdm1", DaiFrameFormat::Standard(DaiFrameFormatStandard::Tdm1); "standard: tdm1")]
493    #[test_case("tdm2", DaiFrameFormat::Standard(DaiFrameFormatStandard::Tdm2); "standard: tdm2")]
494    #[test_case("tdm3", DaiFrameFormat::Standard(DaiFrameFormatStandard::Tdm3); "standard: tdm3")]
495    #[test_case(
496        "custom:left_justified;raising_sclk;0;1",
497        DaiFrameFormat::Custom(DaiFrameFormatCustom {
498            justification: DaiFrameFormatJustification::Left,
499            clocking: DaiFrameFormatClocking::RaisingSclk,
500            frame_sync_sclks_offset: 0,
501            frame_sync_size: 1,
502        });
503        "custom 1"
504    )]
505    #[test_case(
506        "custom:right_justified;falling_sclk;-1;0",
507        DaiFrameFormat::Custom(DaiFrameFormatCustom {
508            justification: DaiFrameFormatJustification::Right,
509            clocking: DaiFrameFormatClocking::FallingSclk,
510            frame_sync_sclks_offset: -1,
511            frame_sync_size: 0,
512        });
513        "custom 2"
514    )]
515    fn test_dai_frame_format_display_parse(s: &str, dai_frame_format: DaiFrameFormat) {
516        assert_eq!(s.parse::<DaiFrameFormat>().unwrap(), dai_frame_format);
517        assert_eq!(dai_frame_format.to_string(), s);
518    }
519
520    #[test]
521    fn test_dai_format_from_to_hw() {
522        let hw_dai_format = fhaudio::DaiFormat {
523            number_of_channels: 2,
524            channels_to_use_bitmask: 0x3,
525            sample_format: fhaudio::DaiSampleFormat::PcmSigned,
526            frame_format: fhaudio::DaiFrameFormat::FrameFormatStandard(
527                fhaudio::DaiFrameFormatStandard::I2S,
528            ),
529            frame_rate: 48000,
530            bits_per_slot: 32,
531            bits_per_sample: 16,
532        };
533
534        let dai_format = DaiFormat {
535            number_of_channels: 2,
536            channels_to_use_bitmask: 0x3,
537            sample_format: DaiSampleFormat::PcmSigned,
538            frame_format: DaiFrameFormat::Standard(DaiFrameFormatStandard::I2S),
539            frame_rate: 48000,
540            sample_size: SampleSize::from_partial_bits(BITS_16, BITS_32).unwrap(),
541        };
542
543        assert_eq!(DaiFormat::try_from(hw_dai_format).unwrap(), dai_format);
544        assert_eq!(fhaudio::DaiFormat::from(dai_format), hw_dai_format);
545    }
546
547    #[test_case(
548        "48000,2ch,0x3,pcm_signed,16in32,i2s",
549        DaiFormat {
550            number_of_channels: 2,
551            channels_to_use_bitmask: 0x3,
552            sample_format: DaiSampleFormat::PcmSigned,
553            frame_format: DaiFrameFormat::Standard(DaiFrameFormatStandard::I2S),
554            frame_rate: 48000,
555            sample_size: SampleSize::from_partial_bits(BITS_16, BITS_32).unwrap(),
556        };
557        "standard frame format"
558    )]
559    #[test_case(
560        "96000,8ch,0xff,pcm_float,32in32,custom:right_justified;falling_sclk;-1;0",
561        DaiFormat {
562            number_of_channels: 8,
563            channels_to_use_bitmask: 0xff,
564            sample_format: DaiSampleFormat::PcmFloat,
565            frame_format: DaiFrameFormat::Custom(DaiFrameFormatCustom {
566                justification: DaiFrameFormatJustification::Right,
567                clocking: DaiFrameFormatClocking::FallingSclk,
568                frame_sync_sclks_offset: -1,
569                frame_sync_size: 0,
570            }),
571            frame_rate: 96000,
572            sample_size: SampleSize::from_full_bits(BITS_32),
573        };
574        "custom frame format"
575    )]
576    fn test_dai_format_display_parse(s: &str, dai_format: DaiFormat) {
577        assert_eq!(s.parse::<DaiFormat>().unwrap(), dai_format);
578        assert_eq!(dai_format.to_string(), s);
579    }
580
581    #[test_case("48000,2,0x3,pcm_signed,16in32,i2s"; "missing num channels suffix")]
582    #[test_case("48000,2ch,3,pcm_signed,16in32,i2s"; "missing channels to use prefix")]
583    #[test_case("48000,2ch,0xINVALID,pcm_signed,16in32,i2s"; "invalid channels to use")]
584    #[test_case("48000,2ch,0x3,pcm_signed,32in16,i2s"; "invalid sample size")]
585    #[test_case("48000,2ch,0x3,pcm_signed,16in32,custom:INVALID"; "invalid custom frame format")]
586    fn test_dai_format_parse_invalid(s: &str) {
587        assert!(s.parse::<DaiFormat>().is_err());
588    }
589}