1use 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 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}