fuchsia_audio/
format.rs

1// Copyright 2023 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 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
19// Common sample sizes.
20pub 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        // A valid Wav File Header must have the data format and data length fields.
43        // We need all values corresponding to wav header fields set on the cursor_writer
44        // before writing to stdout.
45        let mut cursor_writer = Cursor::new(Vec::<u8>::new());
46
47        {
48            // Creation of WavWriter writes the Wav File Header to cursor_writer.
49            // This written header has the file size field and data chunk size field both set
50            // to 0, since the number of samples (and resulting file and chunk sizes) are
51            // unknown to the WavWriter at this point.
52            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        // The file and chunk size fields are set to 0 as placeholder values by the
57        // construction of the WavWriter above. We can compute the actual values based on the
58        // command arguments for format and duration, and set the file size and chunk size
59        // fields to the computed values in the cursor_writer before writing to stdout.
60
61        let bytes_to_capture: u32 =
62            self.frames_in_duration(duration) as u32 * self.bytes_per_frame();
63        let total_header_bytes = 44;
64        // The File Size field of a WAV header. 32-bit int starting at position 4, represents
65        // the size of the overall file minus 8 bytes (exclude RIFF description and
66        // file size description)
67        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        // Data size field of a WAV header. For PCM, this is a 32-bit int starting at
73        // position 40 and represents the size of the data section.
74        cursor_writer.seek(SeekFrom::Start(40))?;
75        cursor_writer.write_all(&bytes_to_capture.to_le_bytes()[..])?;
76
77        // Write the completed WAV header to stdout. We then write the raw sample
78        // values from the packets received directly to stdout.
79        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                    // The following are just a guess.
132                    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            // Assuming Int32 is really "24 in 32".
243            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 /* bits per sample */)> 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/// The size of a single sample as a total number of bits, and the number of
388/// "valid" bits.
389///
390/// The number of valid bits is always less than or equal to the total.
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392pub struct SampleSize {
393    valid_bits: NonZeroU32,
394    total_bits: NonZeroU32,
395}
396
397impl SampleSize {
398    /// Returns a [SampleSize] where all given bits are valid.
399    pub const fn from_full_bits(total_bits: NonZeroU32) -> Self {
400        Self { valid_bits: total_bits, total_bits }
401    }
402
403    /// Returns a [SampleSize] where some bits are valid, if the number
404    /// of valid bits is less than or equal to the total bits.
405    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        // It's safe to unwrap because `div_ceil` will always return at least 1 since `total_bits` is non-zero.
418        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
455/// Parses a Duration from string.
456pub 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}