1use anyhow::{anyhow, Error, Result};
6use regex::Regex;
7use std::fmt::Display;
8use std::io::{Cursor, Seek, SeekFrom, Write};
9use std::num::NonZeroU32;
10use std::str::FromStr;
11use std::time::Duration;
12use {
13 fidl_fuchsia_audio as faudio, fidl_fuchsia_audio_controller as fac,
14 fidl_fuchsia_hardware_audio as fhaudio, fidl_fuchsia_media as fmedia,
15};
16
17pub const DURATION_REGEX: &'static str = r"^(\d+)(h|m|s|ms)$";
18
19pub const BITS_8: NonZeroU32 = NonZeroU32::new(8).unwrap();
21pub const BITS_16: NonZeroU32 = NonZeroU32::new(16).unwrap();
22pub const BITS_24: NonZeroU32 = NonZeroU32::new(24).unwrap();
23pub const BITS_32: NonZeroU32 = NonZeroU32::new(32).unwrap();
24
25#[derive(Debug, Eq, PartialEq, Clone, Copy)]
26pub struct Format {
27 pub sample_type: SampleType,
28 pub frames_per_second: u32,
29 pub channels: u32,
30}
31
32impl Format {
33 pub const fn bytes_per_frame(&self) -> u32 {
34 self.sample_type.size().total_bytes().get() * self.channels
35 }
36
37 pub fn frames_in_duration(&self, duration: std::time::Duration) -> u64 {
38 (self.frames_per_second as f64 * duration.as_secs_f64()).ceil() as u64
39 }
40
41 pub fn wav_header_for_duration(&self, duration: Duration) -> Result<Vec<u8>, Error> {
42 let mut cursor_writer = Cursor::new(Vec::<u8>::new());
46
47 {
48 let _writer = hound::WavWriter::new(&mut cursor_writer, (*self).into())
53 .map_err(|e| anyhow!("Failed to create WavWriter from spec: {}", e))?;
54 }
55
56 let bytes_to_capture: u32 =
62 self.frames_in_duration(duration) as u32 * self.bytes_per_frame();
63 let total_header_bytes = 44;
64 let file_size_bytes: u32 = bytes_to_capture as u32 + total_header_bytes - 8;
68
69 cursor_writer.seek(SeekFrom::Start(4))?;
70 cursor_writer.write_all(&file_size_bytes.to_le_bytes()[..])?;
71
72 cursor_writer.seek(SeekFrom::Start(40))?;
75 cursor_writer.write_all(&bytes_to_capture.to_le_bytes()[..])?;
76
77 Ok(cursor_writer.into_inner())
80 }
81}
82
83impl From<hound::WavSpec> for Format {
84 fn from(value: hound::WavSpec) -> Self {
85 Format {
86 sample_type: (value.sample_format, value.bits_per_sample).into(),
87 frames_per_second: value.sample_rate,
88 channels: value.channels as u32,
89 }
90 }
91}
92
93impl From<fmedia::AudioStreamType> for Format {
94 fn from(value: fmedia::AudioStreamType) -> Self {
95 Format {
96 sample_type: value.sample_format.into(),
97 frames_per_second: value.frames_per_second,
98 channels: value.channels,
99 }
100 }
101}
102
103impl From<Format> for fhaudio::PcmFormat {
104 fn from(value: Format) -> Self {
105 Self {
106 number_of_channels: value.channels as u8,
107 sample_format: value.sample_type.into(),
108 bytes_per_sample: value.sample_type.size().total_bytes().get() as u8,
109 valid_bits_per_sample: value.sample_type.size().valid_bits().get() as u8,
110 frame_rate: value.frames_per_second,
111 }
112 }
113}
114
115impl From<Format> for fhaudio::Format {
116 fn from(value: Format) -> Self {
117 fhaudio::Format { pcm_format: Some(value.into()), ..Default::default() }
118 }
119}
120
121impl From<Format> for faudio::Format {
122 fn from(value: Format) -> Self {
123 Self {
124 sample_type: Some(value.sample_type.into()),
125 channel_count: Some(value.channels),
126 frames_per_second: Some(value.frames_per_second),
127 channel_layout: {
128 let channel_config = match value.channels {
129 1 => faudio::ChannelConfig::Mono,
130 2 => faudio::ChannelConfig::Stereo,
131 3 => faudio::ChannelConfig::Surround3,
133 4 => faudio::ChannelConfig::Surround4,
134 6 => faudio::ChannelConfig::Surround51,
135 _ => panic!("channel count not representable as a ChannelConfig"),
136 };
137 Some(faudio::ChannelLayout::Config(channel_config))
138 },
139 ..Default::default()
140 }
141 }
142}
143
144impl TryFrom<faudio::Format> for Format {
145 type Error = String;
146
147 fn try_from(value: faudio::Format) -> Result<Self, Self::Error> {
148 let sample_type = value.sample_type.ok_or_else(|| "missing sample_type".to_string())?;
149 let channel_count =
150 value.channel_count.ok_or_else(|| "missing channel_count".to_string())?;
151 let frames_per_second =
152 value.frames_per_second.ok_or_else(|| "missing frames_per_second".to_string())?;
153 Ok(Self {
154 sample_type: SampleType::try_from(sample_type)?,
155 channels: channel_count,
156 frames_per_second,
157 })
158 }
159}
160
161impl From<Format> for hound::WavSpec {
162 fn from(value: Format) -> Self {
163 Self {
164 channels: value.channels as u16,
165 sample_format: value.sample_type.into(),
166 sample_rate: value.frames_per_second,
167 bits_per_sample: value.sample_type.size().total_bits().get() as u16,
168 }
169 }
170}
171
172impl From<Format> for fmedia::AudioStreamType {
173 fn from(value: Format) -> Self {
174 Self {
175 sample_format: value.sample_type.into(),
176 channels: value.channels,
177 frames_per_second: value.frames_per_second,
178 }
179 }
180}
181
182impl Display for Format {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 write!(f, "{},{},{}ch", self.frames_per_second, self.sample_type, self.channels)
185 }
186}
187
188impl FromStr for Format {
189 type Err = Error;
190
191 fn from_str(s: &str) -> Result<Self, Error> {
192 if s.len() == 0 {
193 return Err(anyhow!("No format specified."));
194 };
195
196 let splits: Vec<&str> = s.split(",").collect();
197
198 if splits.len() != 3 {
199 return Err(anyhow!(
200 "Expected 3 comma-separated values: <SampleRate>,<SampleType>,<Channels> but have {}.",
201 splits.len()
202 ));
203 }
204
205 let frame_rate = match splits[0].parse::<u32>() {
206 Ok(sample_rate) => Ok(sample_rate),
207 Err(_) => Err(anyhow!("First value (sample rate) should be an integer.")),
208 }?;
209
210 let sample_type = match SampleType::from_str(splits[1]) {
211 Ok(sample_type) => Ok(sample_type),
212 Err(_) => Err(anyhow!(
213 "Second value (sample type) should be one of: uint8, int16, int32, float32."
214 )),
215 }?;
216
217 let channels = match splits[2].strip_suffix("ch") {
218 Some(channels) => match channels.parse::<u32>() {
219 Ok(channels) => Ok(channels),
220 Err(_) => Err(anyhow!("Third value (channels) should have form \"<uint>ch\".")),
221 },
222 None => Err(anyhow!("Channel argument should have form \"<uint>ch\".")),
223 }?;
224
225 Ok(Self { frames_per_second: frame_rate, sample_type, channels })
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum SampleType {
231 Uint8,
232 Int16,
233 Int32,
234 Float32,
235}
236
237impl SampleType {
238 pub const fn size(&self) -> SampleSize {
239 match self {
240 Self::Uint8 => SampleSize::from_full_bits(BITS_8),
241 Self::Int16 => SampleSize::from_full_bits(BITS_16),
242 Self::Int32 => SampleSize::from_partial_bits(BITS_24, BITS_32).unwrap(),
244 Self::Float32 => SampleSize::from_full_bits(BITS_32),
245 }
246 }
247
248 pub const fn silence_value(&self) -> u8 {
249 match self {
250 Self::Uint8 => 128,
251 _ => 0,
252 }
253 }
254}
255
256impl Display for SampleType {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 let s = match self {
259 Self::Uint8 => "uint8",
260 Self::Int16 => "int16",
261 Self::Int32 => "int32",
262 Self::Float32 => "float32",
263 };
264 f.write_str(s)
265 }
266}
267
268impl FromStr for SampleType {
269 type Err = anyhow::Error;
270 fn from_str(s: &str) -> Result<Self, Error> {
271 match s {
272 "uint8" => Ok(Self::Uint8),
273 "int16" => Ok(Self::Int16),
274 "int32" => Ok(Self::Int32),
275 "float32" => Ok(Self::Float32),
276 _ => Err(anyhow!("Invalid sampletype: {}.", s)),
277 }
278 }
279}
280
281impl From<SampleType> for fmedia::AudioSampleFormat {
282 fn from(item: SampleType) -> fmedia::AudioSampleFormat {
283 match item {
284 SampleType::Uint8 => Self::Unsigned8,
285 SampleType::Int16 => Self::Signed16,
286 SampleType::Int32 => Self::Signed24In32,
287 SampleType::Float32 => Self::Float,
288 }
289 }
290}
291
292impl From<SampleType> for faudio::SampleType {
293 fn from(value: SampleType) -> Self {
294 match value {
295 SampleType::Uint8 => Self::Uint8,
296 SampleType::Int16 => Self::Int16,
297 SampleType::Int32 => Self::Int32,
298 SampleType::Float32 => Self::Float32,
299 }
300 }
301}
302
303impl From<SampleType> for fhaudio::SampleFormat {
304 fn from(value: SampleType) -> Self {
305 match value {
306 SampleType::Uint8 => Self::PcmUnsigned,
307 SampleType::Int16 | SampleType::Int32 => Self::PcmSigned,
308 SampleType::Float32 => Self::PcmFloat,
309 }
310 }
311}
312
313impl From<SampleType> for hound::SampleFormat {
314 fn from(value: SampleType) -> Self {
315 match value {
316 SampleType::Uint8 | SampleType::Int16 | SampleType::Int32 => Self::Int,
317 SampleType::Float32 => Self::Float,
318 }
319 }
320}
321
322impl From<fmedia::AudioSampleFormat> for SampleType {
323 fn from(item: fmedia::AudioSampleFormat) -> Self {
324 match item {
325 fmedia::AudioSampleFormat::Unsigned8 => Self::Uint8,
326 fmedia::AudioSampleFormat::Signed16 => Self::Int16,
327 fmedia::AudioSampleFormat::Signed24In32 => Self::Int32,
328 fmedia::AudioSampleFormat::Float => Self::Float32,
329 }
330 }
331}
332
333impl TryFrom<faudio::SampleType> for SampleType {
334 type Error = String;
335
336 fn try_from(value: faudio::SampleType) -> Result<Self, Self::Error> {
337 match value {
338 faudio::SampleType::Uint8 => Ok(Self::Uint8),
339 faudio::SampleType::Int16 => Ok(Self::Int16),
340 faudio::SampleType::Int32 => Ok(Self::Int32),
341 faudio::SampleType::Float32 => Ok(Self::Float32),
342 other => Err(format!("unsupported SampleType: {:?}", other)),
343 }
344 }
345}
346
347impl From<(hound::SampleFormat, u16 )> for SampleType {
348 fn from(value: (hound::SampleFormat, u16)) -> Self {
349 let (sample_format, bits_per_sample) = value;
350 match sample_format {
351 hound::SampleFormat::Int => match bits_per_sample {
352 0..=8 => Self::Uint8,
353 9..=16 => Self::Int16,
354 17.. => Self::Int32,
355 },
356 hound::SampleFormat::Float => Self::Float32,
357 }
358 }
359}
360
361impl TryFrom<(fhaudio::SampleFormat, SampleSize)> for SampleType {
362 type Error = String;
363
364 fn try_from(value: (fhaudio::SampleFormat, SampleSize)) -> Result<Self, Self::Error> {
365 let (sample_format, sample_size) = value;
366 let (valid_bits, total_bits) =
367 (sample_size.valid_bits().get(), sample_size.total_bits().get());
368 match sample_format {
369 fhaudio::SampleFormat::PcmUnsigned => match (valid_bits, total_bits) {
370 (8, 8) => Some(Self::Uint8),
371 _ => None,
372 },
373 fhaudio::SampleFormat::PcmSigned => match (valid_bits, total_bits) {
374 (16, 16) => Some(Self::Int16),
375 (24 | 32, 32) => Some(Self::Int32),
376 _ => None,
377 },
378 fhaudio::SampleFormat::PcmFloat => match (valid_bits, total_bits) {
379 (32, 32) => Some(Self::Float32),
380 _ => None,
381 },
382 }
383 .ok_or_else(|| "unsupported sample size".to_string())
384 }
385}
386
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392pub struct SampleSize {
393 valid_bits: NonZeroU32,
394 total_bits: NonZeroU32,
395}
396
397impl SampleSize {
398 pub const fn from_full_bits(total_bits: NonZeroU32) -> Self {
400 Self { valid_bits: total_bits, total_bits }
401 }
402
403 pub const fn from_partial_bits(valid_bits: NonZeroU32, total_bits: NonZeroU32) -> Option<Self> {
406 if valid_bits.get() > total_bits.get() {
407 return None;
408 }
409 Some(Self { valid_bits, total_bits })
410 }
411
412 pub const fn total_bits(&self) -> NonZeroU32 {
413 self.total_bits
414 }
415
416 pub const fn total_bytes(&self) -> NonZeroU32 {
417 NonZeroU32::new(self.total_bits.get().div_ceil(8)).unwrap()
419 }
420
421 pub const fn valid_bits(&self) -> NonZeroU32 {
422 self.valid_bits
423 }
424}
425
426impl Display for SampleSize {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 write!(f, "{}in{}", self.valid_bits, self.total_bits)
429 }
430}
431
432impl FromStr for SampleSize {
433 type Err = String;
434
435 fn from_str(s: &str) -> Result<Self, Self::Err> {
436 let Some((valid_bits_str, total_bits_str)) = s.split_once("in") else {
437 return Err(format!("Invalid sample size: {}. Expected: <ValidBits>in<TotalBits>", s));
438 };
439 let valid_bits = valid_bits_str
440 .parse::<NonZeroU32>()
441 .map_err(|err| format!("Invalid valid bits: {}", err))?;
442 let total_bits = total_bits_str
443 .parse::<NonZeroU32>()
444 .map_err(|err| format!("Invalid total bits: {}", err))?;
445 let sample_size = Self::from_partial_bits(valid_bits, total_bits).ok_or_else(|| {
446 format!(
447 "Invalid sample size: {}. Valid bits must be less than or equal to total bits.",
448 s
449 )
450 })?;
451 Ok(sample_size)
452 }
453}
454
455pub fn parse_duration(value: &str) -> Result<Duration, String> {
457 let re = Regex::new(DURATION_REGEX).map_err(|e| format!("Could not create regex: {}", e))?;
458 let captures = re
459 .captures(&value)
460 .ok_or_else(|| format!("Durations must be specified in the form {}.", DURATION_REGEX))?;
461 let number: u64 = captures[1].parse().map_err(|e| format!("Could not parse number: {}", e))?;
462 let unit = &captures[2];
463
464 match unit {
465 "ms" => Ok(Duration::from_millis(number)),
466 "s" => Ok(Duration::from_secs(number)),
467 "m" => Ok(Duration::from_secs(number * 60)),
468 "h" => Ok(Duration::from_secs(number * 3600)),
469 _ => Err(format!(
470 "Invalid duration string \"{}\"; must be of the form {}.",
471 value, DURATION_REGEX
472 )),
473 }
474}
475
476pub fn str_to_clock(src: &str) -> Result<fac::ClockType, String> {
477 match src.to_lowercase().as_str() {
478 "flexible" => Ok(fac::ClockType::Flexible(fac::Flexible)),
479 "monotonic" => Ok(fac::ClockType::SystemMonotonic(fac::SystemMonotonic)),
480 _ => {
481 let splits: Vec<&str> = src.split(",").collect();
482 if splits[0] == "custom" {
483 let rate_adjust = match splits[1].parse::<i32>() {
484 Ok(rate_adjust) => Some(rate_adjust),
485 Err(_) => None,
486 };
487
488 let offset = match splits[2].parse::<i32>() {
489 Ok(offset) => Some(offset),
490 Err(_) => None,
491 };
492
493 Ok(fac::ClockType::Custom(fac::CustomClockConfig {
494 rate_adjust,
495 offset,
496 ..Default::default()
497 }))
498 } else {
499 Err(format!("Invalid clock argument: {}.", src))
500 }
501 }
502 }
503}
504
505#[cfg(test)]
506pub mod test {
507 use super::*;
508 use test_case::test_case;
509
510 #[test_case(
511 "48000,uint8,2ch",
512 Format { frames_per_second: 48000, sample_type: SampleType::Uint8, channels: 2 };
513 "48000,uint8,2ch"
514 )]
515 #[test_case(
516 "44100,float32,1ch",
517 Format { frames_per_second: 44100, sample_type: SampleType::Float32, channels: 1 };
518 "44100,float32,1ch"
519 )]
520 fn test_format_display_parse(s: &str, format: Format) {
521 assert_eq!(s.parse::<Format>().unwrap(), format);
522 assert_eq!(format.to_string(), s);
523 }
524
525 #[test_case("44100,float,1ch"; "bad sample type")]
526 #[test_case("44100"; "missing sample type and channels")]
527 #[test_case("44100,float32,1"; "invalid channels")]
528 #[test_case("44100,float32"; "missing channels")]
529 #[test_case(",,"; "empty components")]
530 fn test_format_parse_invalid(s: &str) {
531 assert!(s.parse::<Format>().is_err());
532 }
533
534 #[test_case(
535 "16in32",
536 SampleSize::from_partial_bits(BITS_16, BITS_32).unwrap();
537 "16 valid 32 total"
538 )]
539 #[test_case(
540 "32in32",
541 SampleSize::from_full_bits(BITS_32);
542 "32 valid 32 total"
543 )]
544 fn test_samplesize_display_parse(s: &str, sample_size: SampleSize) {
545 assert_eq!(s.parse::<SampleSize>().unwrap(), sample_size);
546 assert_eq!(sample_size.to_string(), s);
547 }
548}