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