bt_hfp/
codec_id.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::format_err;
6use fidl_fuchsia_media as media;
7
8use crate::audio;
9
10/// Codec IDs. See HFP 1.8, Section 10 / Appendix B.
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12pub struct CodecId(u8);
13
14impl CodecId {
15    pub const CVSD: CodecId = CodecId(0x01);
16    pub const MSBC: CodecId = CodecId(0x02);
17}
18
19impl From<u8> for CodecId {
20    fn from(x: u8) -> Self {
21        Self(x)
22    }
23}
24
25impl Into<u8> for CodecId {
26    fn into(self) -> u8 {
27        self.0
28    }
29}
30
31// Convenience conversions for interacting with AT library.
32// TODO(https://fxbug.dev/71403): Remove this once AT library supports specifying correct widths.
33impl Into<i64> for CodecId {
34    fn into(self) -> i64 {
35        self.0 as i64
36    }
37}
38
39fn unsupported_codec_id(codec: CodecId) -> audio::Error {
40    audio::Error::UnsupportedParameters { source: format_err!("Unknown CodecId: {codec:?}") }
41}
42
43impl TryFrom<CodecId> for media::EncoderSettings {
44    type Error = audio::Error;
45
46    fn try_from(value: CodecId) -> Result<Self, Self::Error> {
47        match value {
48            CodecId::MSBC => Ok(media::EncoderSettings::Msbc(Default::default())),
49            CodecId::CVSD => Ok(media::EncoderSettings::Cvsd(Default::default())),
50            _ => Err(unsupported_codec_id(value)),
51        }
52    }
53}
54
55impl TryFrom<CodecId> for media::PcmFormat {
56    type Error = audio::Error;
57
58    fn try_from(value: CodecId) -> Result<Self, Self::Error> {
59        let frames_per_second = match value {
60            CodecId::CVSD => 64000,
61            CodecId::MSBC => 16000,
62            _ => return Err(unsupported_codec_id(value)),
63        };
64        Ok(media::PcmFormat {
65            pcm_mode: media::AudioPcmMode::Linear,
66            bits_per_sample: 16,
67            frames_per_second,
68            channel_map: vec![media::AudioChannelId::Lf],
69        })
70    }
71}
72
73impl TryFrom<CodecId> for media::DomainFormat {
74    type Error = audio::Error;
75
76    fn try_from(value: CodecId) -> Result<Self, Self::Error> {
77        Ok(media::DomainFormat::Audio(media::AudioFormat::Uncompressed(
78            media::AudioUncompressedFormat::Pcm(media::PcmFormat::try_from(value)?),
79        )))
80    }
81}
82
83impl TryFrom<CodecId> for fidl_fuchsia_hardware_audio::DaiSupportedFormats {
84    type Error = audio::Error;
85
86    fn try_from(value: CodecId) -> Result<Self, Self::Error> {
87        let frames_per_second = match value {
88            CodecId::CVSD => 64000,
89            CodecId::MSBC => 16000,
90            _ => return Err(unsupported_codec_id(value)),
91        };
92        use fidl_fuchsia_hardware_audio::*;
93        Ok(DaiSupportedFormats {
94            number_of_channels: vec![1],
95            sample_formats: vec![fidl_fuchsia_hardware_audio::DaiSampleFormat::PcmSigned],
96            frame_formats: vec![DaiFrameFormat::FrameFormatStandard(DaiFrameFormatStandard::I2S)],
97            frame_rates: vec![frames_per_second],
98            bits_per_slot: vec![16],
99            bits_per_sample: vec![16],
100        })
101    }
102}
103
104#[cfg(test)]
105impl TryFrom<CodecId> for fidl_fuchsia_hardware_audio::Format {
106    type Error = audio::Error;
107    fn try_from(value: CodecId) -> Result<Self, Self::Error> {
108        let frame_rate = match value {
109            CodecId::CVSD => 64000,
110            CodecId::MSBC => 16000,
111            _ => {
112                return Err(audio::Error::UnsupportedParameters {
113                    source: format_err!("Unsupported CodecID {value}"),
114                })
115            }
116        };
117        Ok(Self {
118            pcm_format: Some(fidl_fuchsia_hardware_audio::PcmFormat {
119                number_of_channels: 1u8,
120                sample_format: fidl_fuchsia_hardware_audio::SampleFormat::PcmSigned,
121                bytes_per_sample: 2u8,
122                valid_bits_per_sample: 16u8,
123                frame_rate,
124            }),
125            ..Default::default()
126        })
127    }
128}
129
130impl PartialEq<i64> for CodecId {
131    fn eq(&self, other: &i64) -> bool {
132        self.0 as i64 == *other
133    }
134}
135
136impl std::fmt::Display for CodecId {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        match self.0 {
139            0x01 => write!(f, "{}", "CVSD"),
140            0x02 => write!(f, "{}", "MSBC"),
141            unknown => write!(f, "Unknown({:#x})", unknown),
142        }
143    }
144}
145
146impl CodecId {
147    pub fn is_supported(&self) -> bool {
148        match self {
149            &CodecId::MSBC | &CodecId::CVSD => true,
150            _ => false,
151        }
152    }
153
154    pub fn oob_bytes(&self) -> Vec<u8> {
155        use bt_a2dp::media_types::{
156            SbcAllocation, SbcBlockCount, SbcChannelMode, SbcCodecInfo, SbcSamplingFrequency,
157            SbcSubBands,
158        };
159        match self {
160            &CodecId::MSBC => SbcCodecInfo::new(
161                SbcSamplingFrequency::FREQ16000HZ,
162                SbcChannelMode::MONO,
163                SbcBlockCount::SIXTEEN,
164                SbcSubBands::EIGHT,
165                SbcAllocation::LOUDNESS,
166                26,
167                26,
168            )
169            .unwrap()
170            .to_bytes()
171            .to_vec(),
172            // CVSD has no oob_bytes
173            _ => vec![],
174        }
175    }
176
177    pub fn mime_type(&self) -> Result<&str, audio::Error> {
178        match self {
179            &CodecId::MSBC => Ok("audio/msbc"),
180            &CodecId::CVSD => Ok("audio/cvsd"),
181            _ => Err(audio::Error::UnsupportedParameters { source: format_err!("codec {self}") }),
182        }
183    }
184}
185
186pub fn codecs_to_string(codecs: &Vec<CodecId>) -> String {
187    let codecs_string: Vec<String> = codecs.iter().map(ToString::to_string).collect();
188    let codecs_string: Vec<&str> = codecs_string.iter().map(AsRef::as_ref).collect();
189    let joined = codecs_string.join(", ");
190    joined
191}
192
193#[cfg(test)]
194mod test {
195    use super::*;
196
197    #[fuchsia::test]
198    fn codecs_format() {
199        let cvsd = CodecId(0x1);
200        let mbsc = CodecId(0x2);
201        let unknown = CodecId(0xf);
202
203        let cvsd_string = format!("{:}", cvsd);
204        assert_eq!(String::from("CVSD"), cvsd_string);
205
206        let mbsc_string = format!("{:}", mbsc);
207        assert_eq!(String::from("MSBC"), mbsc_string);
208
209        let unknown_string = format!("{:}", unknown);
210        assert_eq!(String::from("Unknown(0xf)"), unknown_string);
211
212        let joined_string = codecs_to_string(&vec![cvsd, mbsc, unknown]);
213        assert_eq!(String::from("CVSD, MSBC, Unknown(0xf)"), joined_string);
214    }
215}