• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::btif::{BluetoothInterface, BtStatus, RawAddress, ToggleableProfile};
2 use crate::topstack::get_dispatchers;
3 
4 use bitflags::bitflags;
5 use num_derive::{FromPrimitive, ToPrimitive};
6 use num_traits::cast::FromPrimitive;
7 use std::convert::{TryFrom, TryInto};
8 use std::fmt::{Debug, Formatter};
9 use std::sync::{Arc, Mutex};
10 use topshim_macros::{cb_variant, log_args, profile_enabled_or, profile_enabled_or_default};
11 
12 use log::warn;
13 
14 #[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd, Clone)]
15 #[repr(u32)]
16 pub enum BtavConnectionState {
17     Disconnected = 0,
18     Connecting,
19     Connected,
20     Disconnecting,
21 }
22 
23 impl From<u32> for BtavConnectionState {
from(item: u32) -> Self24     fn from(item: u32) -> Self {
25         BtavConnectionState::from_u32(item).unwrap()
26     }
27 }
28 
29 #[derive(Debug, FromPrimitive, PartialEq, PartialOrd)]
30 #[repr(u32)]
31 pub enum BtavAudioState {
32     RemoteSuspend = 0,
33     Stopped,
34     Started,
35 }
36 
37 impl From<u32> for BtavAudioState {
from(item: u32) -> Self38     fn from(item: u32) -> Self {
39         BtavAudioState::from_u32(item).unwrap()
40     }
41 }
42 
43 #[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd)]
44 #[repr(i32)]
45 pub enum A2dpCodecIndex {
46     SrcSbc = 0,
47     SrcAac,
48     SrcAptx,
49     SrcAptxHD,
50     SrcLdac,
51     SinkSbc,
52     SinkAac,
53     SinkLdac,
54     Max,
55 }
56 
57 impl A2dpCodecIndex {
58     pub const SRC_MIN: A2dpCodecIndex = A2dpCodecIndex::SrcSbc;
59     pub const SRC_MAX: A2dpCodecIndex = A2dpCodecIndex::SinkSbc;
60     pub const SINK_MIN: A2dpCodecIndex = A2dpCodecIndex::SinkSbc;
61     pub const SINK_MAX: A2dpCodecIndex = A2dpCodecIndex::Max;
62     pub const MAX: A2dpCodecIndex = A2dpCodecIndex::Max;
63     pub const MIN: A2dpCodecIndex = A2dpCodecIndex::SrcSbc;
64 }
65 
66 impl From<i32> for A2dpCodecIndex {
from(item: i32) -> Self67     fn from(item: i32) -> Self {
68         A2dpCodecIndex::from_i32(item).unwrap_or_else(|| A2dpCodecIndex::MIN)
69     }
70 }
71 
72 #[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd)]
73 #[repr(i32)]
74 pub enum A2dpCodecPriority {
75     Disabled = -1,
76     Default = 0,
77     Highest = 1_000_000,
78 }
79 
80 impl From<i32> for A2dpCodecPriority {
from(item: i32) -> Self81     fn from(item: i32) -> Self {
82         A2dpCodecPriority::from_i32(item).unwrap_or_else(|| A2dpCodecPriority::Default)
83     }
84 }
85 
86 #[derive(Debug)]
87 pub struct A2dpError {
88     /// Standard BT status come from a function return or the cloest approximation to the real
89     /// error.
90     pub status: BtStatus,
91     /// An additional value to help explain the error. In the A2DP context, this is often referring
92     /// to the BTA_AV_XXX status.
93     pub error: i32,
94     /// An optional error message that the lower layer wants to deliver.
95     pub error_message: Option<String>,
96 }
97 
98 bitflags! {
99     #[derive(Clone, Copy, Debug, PartialEq)]
100     pub struct A2dpCodecSampleRate: i32 {
101         const RATE_NONE = 0x0;
102         const RATE_44100 = 0x01;
103         const RATE_48000 = 0x02;
104         const RATE_88200 = 0x04;
105         const RATE_96000 = 0x08;
106         const RATE_176400 = 0x10;
107         const RATE_192000 = 0x20;
108         const RATE_16000 = 0x40;
109         const RATE_24000 = 0x80;
110     }
111 }
112 
113 impl A2dpCodecSampleRate {
validate_bits(val: i32) -> bool114     pub fn validate_bits(val: i32) -> bool {
115         val <= A2dpCodecSampleRate::all().bits()
116     }
117 }
118 
119 impl TryInto<i32> for A2dpCodecSampleRate {
120     type Error = ();
try_into(self) -> Result<i32, Self::Error>121     fn try_into(self) -> Result<i32, Self::Error> {
122         Ok(self.bits())
123     }
124 }
125 
126 impl TryFrom<i32> for A2dpCodecSampleRate {
127     type Error = ();
try_from(val: i32) -> Result<Self, Self::Error>128     fn try_from(val: i32) -> Result<Self, Self::Error> {
129         Self::from_bits(val).ok_or(())
130     }
131 }
132 
133 bitflags! {
134     #[derive(Clone, Copy, Debug, PartialEq)]
135     pub struct A2dpCodecBitsPerSample: i32 {
136         const SAMPLE_NONE = 0x0;
137         const SAMPLE_16 = 0x01;
138         const SAMPLE_24 = 0x02;
139         const SAMPLE_32 = 0x04;
140     }
141 }
142 
143 impl A2dpCodecBitsPerSample {
validate_bits(val: i32) -> bool144     pub fn validate_bits(val: i32) -> bool {
145         val <= A2dpCodecBitsPerSample::all().bits()
146     }
147 }
148 
149 impl TryInto<i32> for A2dpCodecBitsPerSample {
150     type Error = ();
try_into(self) -> Result<i32, Self::Error>151     fn try_into(self) -> Result<i32, Self::Error> {
152         Ok(self.bits())
153     }
154 }
155 
156 impl TryFrom<i32> for A2dpCodecBitsPerSample {
157     type Error = ();
try_from(val: i32) -> Result<Self, Self::Error>158     fn try_from(val: i32) -> Result<Self, Self::Error> {
159         Self::from_bits(val).ok_or(())
160     }
161 }
162 
163 bitflags! {
164     #[derive(Clone, Copy, Debug, PartialEq)]
165     pub struct A2dpCodecChannelMode: i32 {
166         const MODE_NONE = 0x0;
167         const MODE_MONO = 0x01;
168         const MODE_STEREO = 0x02;
169     }
170 }
171 
172 impl A2dpCodecChannelMode {
validate_bits(val: i32) -> bool173     pub fn validate_bits(val: i32) -> bool {
174         val <= A2dpCodecChannelMode::all().bits()
175     }
176 }
177 
178 impl TryInto<i32> for A2dpCodecChannelMode {
179     type Error = ();
try_into(self) -> Result<i32, Self::Error>180     fn try_into(self) -> Result<i32, Self::Error> {
181         Ok(self.bits())
182     }
183 }
184 
185 impl TryFrom<i32> for A2dpCodecChannelMode {
186     type Error = ();
try_from(val: i32) -> Result<Self, Self::Error>187     fn try_from(val: i32) -> Result<Self, Self::Error> {
188         Self::from_bits(val).ok_or(())
189     }
190 }
191 
192 #[cxx::bridge(namespace = bluetooth::topshim::rust)]
193 pub mod ffi {
194     unsafe extern "C++" {
195         include!("types/raw_address.h");
196         #[namespace = ""]
197         type RawAddress = crate::btif::RawAddress;
198     }
199 
200     #[derive(Debug, Copy, Clone)]
201     pub struct A2dpCodecConfig {
202         pub codec_type: i32,
203         pub codec_priority: i32,
204         pub sample_rate: i32,
205         pub bits_per_sample: i32,
206         pub channel_mode: i32,
207         pub codec_specific_1: i64,
208         pub codec_specific_2: i64,
209         pub codec_specific_3: i64,
210         pub codec_specific_4: i64,
211     }
212 
213     #[derive(Debug, Default)]
214     pub struct RustPresentationPosition {
215         remote_delay_report_ns: u64,
216         total_bytes_read: u64,
217         data_position_sec: i64,
218         data_position_nsec: i32,
219     }
220 
221     #[derive(Debug)]
222     pub struct A2dpError<'a> {
223         status: u32,
224         error_code: u8,
225         error_msg: &'a CxxString,
226     }
227 
228     unsafe extern "C++" {
229         include!("btav/btav_shim.h");
230         include!("btav_sink/btav_sink_shim.h");
231 
232         type A2dpIntf;
233         type A2dpSinkIntf;
234 
GetA2dpProfile(btif: *const u8) -> UniquePtr<A2dpIntf>235         unsafe fn GetA2dpProfile(btif: *const u8) -> UniquePtr<A2dpIntf>;
236 
init(self: &A2dpIntf) -> i32237         fn init(self: &A2dpIntf) -> i32;
connect(self: &A2dpIntf, bt_addr: RawAddress) -> u32238         fn connect(self: &A2dpIntf, bt_addr: RawAddress) -> u32;
disconnect(self: &A2dpIntf, bt_addr: RawAddress) -> u32239         fn disconnect(self: &A2dpIntf, bt_addr: RawAddress) -> u32;
set_silence_device(self: &A2dpIntf, bt_addr: RawAddress, silent: bool) -> i32240         fn set_silence_device(self: &A2dpIntf, bt_addr: RawAddress, silent: bool) -> i32;
set_active_device(self: &A2dpIntf, bt_addr: RawAddress) -> i32241         fn set_active_device(self: &A2dpIntf, bt_addr: RawAddress) -> i32;
config_codec( self: &A2dpIntf, bt_addr: RawAddress, codec_preferences: Vec<A2dpCodecConfig>, ) -> i32242         fn config_codec(
243             self: &A2dpIntf,
244             bt_addr: RawAddress,
245             codec_preferences: Vec<A2dpCodecConfig>,
246         ) -> i32;
set_audio_config(self: &A2dpIntf, config: A2dpCodecConfig) -> bool247         fn set_audio_config(self: &A2dpIntf, config: A2dpCodecConfig) -> bool;
start_audio_request(self: &A2dpIntf) -> bool248         fn start_audio_request(self: &A2dpIntf) -> bool;
stop_audio_request(self: &A2dpIntf) -> bool249         fn stop_audio_request(self: &A2dpIntf) -> bool;
suspend_audio_request(self: &A2dpIntf) -> bool250         fn suspend_audio_request(self: &A2dpIntf) -> bool;
cleanup(self: &A2dpIntf)251         fn cleanup(self: &A2dpIntf);
get_presentation_position(self: &A2dpIntf) -> RustPresentationPosition252         fn get_presentation_position(self: &A2dpIntf) -> RustPresentationPosition;
253         // A2dp sink functions
254 
GetA2dpSinkProfile(btif: *const u8) -> UniquePtr<A2dpSinkIntf>255         unsafe fn GetA2dpSinkProfile(btif: *const u8) -> UniquePtr<A2dpSinkIntf>;
256 
init(self: &A2dpSinkIntf) -> i32257         fn init(self: &A2dpSinkIntf) -> i32;
connect(self: &A2dpSinkIntf, bt_addr: RawAddress) -> i32258         fn connect(self: &A2dpSinkIntf, bt_addr: RawAddress) -> i32;
disconnect(self: &A2dpSinkIntf, bt_addr: RawAddress) -> i32259         fn disconnect(self: &A2dpSinkIntf, bt_addr: RawAddress) -> i32;
set_active_device(self: &A2dpSinkIntf, bt_addr: RawAddress) -> i32260         fn set_active_device(self: &A2dpSinkIntf, bt_addr: RawAddress) -> i32;
cleanup(self: &A2dpSinkIntf)261         fn cleanup(self: &A2dpSinkIntf);
262     }
263     extern "Rust" {
connection_state_callback(addr: RawAddress, state: u32, error: A2dpError)264         fn connection_state_callback(addr: RawAddress, state: u32, error: A2dpError);
audio_state_callback(addr: RawAddress, state: u32)265         fn audio_state_callback(addr: RawAddress, state: u32);
audio_config_callback( addr: RawAddress, codec_config: A2dpCodecConfig, codecs_local_capabilities: &Vec<A2dpCodecConfig>, codecs_selectable_capabilities: &Vec<A2dpCodecConfig>, )266         fn audio_config_callback(
267             addr: RawAddress,
268             codec_config: A2dpCodecConfig,
269             codecs_local_capabilities: &Vec<A2dpCodecConfig>,
270             codecs_selectable_capabilities: &Vec<A2dpCodecConfig>,
271         );
mandatory_codec_preferred_callback(addr: RawAddress)272         fn mandatory_codec_preferred_callback(addr: RawAddress);
273 
274         // Currently only by qualification tests.
sink_audio_config_callback(addr: RawAddress, sample_rate: u32, channel_count: u8)275         fn sink_audio_config_callback(addr: RawAddress, sample_rate: u32, channel_count: u8);
sink_connection_state_callback(addr: RawAddress, state: u32, error: A2dpError)276         fn sink_connection_state_callback(addr: RawAddress, state: u32, error: A2dpError);
sink_audio_state_callback(addr: RawAddress, state: u32)277         fn sink_audio_state_callback(addr: RawAddress, state: u32);
278     }
279 }
280 
281 pub type A2dpCodecConfig = ffi::A2dpCodecConfig;
282 pub type PresentationPosition = ffi::RustPresentationPosition;
283 pub type FfiA2dpError<'a> = ffi::A2dpError<'a>;
284 
285 impl Default for A2dpCodecConfig {
default() -> A2dpCodecConfig286     fn default() -> A2dpCodecConfig {
287         A2dpCodecConfig {
288             codec_type: 0,
289             codec_priority: 0,
290             sample_rate: 0,
291             bits_per_sample: 0,
292             channel_mode: 0,
293             codec_specific_1: 0,
294             codec_specific_2: 0,
295             codec_specific_3: 0,
296             codec_specific_4: 0,
297         }
298     }
299 }
300 
301 impl<'a> Into<A2dpError> for FfiA2dpError<'a> {
into(self) -> A2dpError302     fn into(self) -> A2dpError {
303         A2dpError {
304             status: self.status.into(),
305             error: self.error_code as i32,
306             error_message: if self.error_msg == "" {
307                 None
308             } else {
309                 Some(self.error_msg.to_string())
310             },
311         }
312     }
313 }
314 
315 #[derive(Debug)]
316 pub enum A2dpCallbacks {
317     ConnectionState(RawAddress, BtavConnectionState, A2dpError),
318     AudioState(RawAddress, BtavAudioState),
319     AudioConfig(RawAddress, A2dpCodecConfig, Vec<A2dpCodecConfig>, Vec<A2dpCodecConfig>),
320     MandatoryCodecPreferred(RawAddress),
321 }
322 
323 pub struct A2dpCallbacksDispatcher {
324     pub dispatch: Box<dyn Fn(A2dpCallbacks) + Send>,
325 }
326 
327 impl Debug for A2dpCallbacksDispatcher {
fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error>328     fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
329         write!(f, "A2dpCallbacksDispatcher {{}}")
330     }
331 }
332 
333 type A2dpCb = Arc<Mutex<A2dpCallbacksDispatcher>>;
334 
335 cb_variant!(A2dpCb, connection_state_callback -> A2dpCallbacks::ConnectionState,
336 RawAddress, u32 -> BtavConnectionState, FfiA2dpError -> A2dpError,{
337     let _2 = _2.into();
338 });
339 
340 cb_variant!(A2dpCb, audio_state_callback -> A2dpCallbacks::AudioState, RawAddress, u32 -> BtavAudioState);
341 
342 cb_variant!(A2dpCb, mandatory_codec_preferred_callback -> A2dpCallbacks::MandatoryCodecPreferred, RawAddress);
343 
344 cb_variant!(A2dpCb, audio_config_callback -> A2dpCallbacks::AudioConfig,
345 RawAddress, A2dpCodecConfig, &Vec<A2dpCodecConfig>, &Vec<A2dpCodecConfig>, {
346     let _2: Vec<A2dpCodecConfig> = _2.to_vec();
347     let _3: Vec<A2dpCodecConfig> = _3.to_vec();
348 });
349 
350 pub struct A2dp {
351     internal: cxx::UniquePtr<ffi::A2dpIntf>,
352     _is_init: bool,
353     _is_enabled: bool,
354 }
355 
356 // For *const u8 opaque btif
357 unsafe impl Send for A2dp {}
358 
359 impl ToggleableProfile for A2dp {
is_enabled(&self) -> bool360     fn is_enabled(&self) -> bool {
361         self._is_enabled
362     }
363 
enable(&mut self) -> bool364     fn enable(&mut self) -> bool {
365         self.internal.init();
366         self._is_enabled = true;
367         true
368     }
369 
370     #[profile_enabled_or(false)]
disable(&mut self) -> bool371     fn disable(&mut self) -> bool {
372         self.internal.cleanup();
373         self._is_enabled = false;
374         true
375     }
376 }
377 
378 impl A2dp {
379     #[log_args]
new(intf: &BluetoothInterface) -> A2dp380     pub fn new(intf: &BluetoothInterface) -> A2dp {
381         let a2dpif: cxx::UniquePtr<ffi::A2dpIntf>;
382         unsafe {
383             a2dpif = ffi::GetA2dpProfile(intf.as_raw_ptr());
384         }
385 
386         A2dp { internal: a2dpif, _is_init: false, _is_enabled: false }
387     }
388 
389     #[log_args]
is_initialized(&self) -> bool390     pub fn is_initialized(&self) -> bool {
391         self._is_init
392     }
393 
394     #[log_args]
initialize(&mut self, callbacks: A2dpCallbacksDispatcher) -> bool395     pub fn initialize(&mut self, callbacks: A2dpCallbacksDispatcher) -> bool {
396         if get_dispatchers().lock().unwrap().set::<A2dpCb>(Arc::new(Mutex::new(callbacks))) {
397             panic!("Tried to set dispatcher for A2dp callbacks while it already exists");
398         }
399 
400         if self._is_init {
401             warn!("A2dp has already been initialized");
402             return false;
403         }
404 
405         true
406     }
407 
408     #[log_args]
409     #[profile_enabled_or(BtStatus::NotReady)]
connect(&mut self, addr: RawAddress) -> BtStatus410     pub fn connect(&mut self, addr: RawAddress) -> BtStatus {
411         BtStatus::from(self.internal.connect(addr))
412     }
413 
414     #[log_args]
415     #[profile_enabled_or]
set_active_device(&mut self, addr: RawAddress)416     pub fn set_active_device(&mut self, addr: RawAddress) {
417         self.internal.set_active_device(addr);
418     }
419 
420     #[log_args]
421     #[profile_enabled_or(BtStatus::NotReady)]
disconnect(&mut self, addr: RawAddress) -> BtStatus422     pub fn disconnect(&mut self, addr: RawAddress) -> BtStatus {
423         BtStatus::from(self.internal.disconnect(addr))
424     }
425 
426     #[log_args]
427     #[profile_enabled_or]
config_codec(&self, addr: RawAddress, config: Vec<A2dpCodecConfig>)428     pub fn config_codec(&self, addr: RawAddress, config: Vec<A2dpCodecConfig>) {
429         self.internal.config_codec(addr, config);
430     }
431 
432     #[log_args]
433     #[profile_enabled_or(false)]
start_audio_request(&self) -> bool434     pub fn start_audio_request(&self) -> bool {
435         self.internal.start_audio_request()
436     }
437 
438     #[log_args]
439     #[profile_enabled_or]
stop_audio_request(&self)440     pub fn stop_audio_request(&self) {
441         self.internal.stop_audio_request();
442     }
443 
444     #[log_args]
445     #[profile_enabled_or]
suspend_audio_request(&self)446     pub fn suspend_audio_request(&self) {
447         self.internal.suspend_audio_request();
448     }
449 
450     #[log_args]
451     #[profile_enabled_or_default]
get_presentation_position(&self) -> PresentationPosition452     pub fn get_presentation_position(&self) -> PresentationPosition {
453         self.internal.get_presentation_position()
454     }
455 }
456 
457 #[derive(Debug)]
458 pub enum A2dpSinkCallbacks {
459     ConnectionState(RawAddress, BtavConnectionState, A2dpError),
460     AudioState(RawAddress, BtavAudioState),
461     AudioConfig(RawAddress, u32, u8),
462 }
463 
464 pub struct A2dpSinkCallbacksDispatcher {
465     pub dispatch: Box<dyn Fn(A2dpSinkCallbacks) + Send>,
466 }
467 
468 impl Debug for A2dpSinkCallbacksDispatcher {
fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error>469     fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
470         write!(f, "A2dpSinkCallbacksDispatcher {{}}")
471     }
472 }
473 
474 type A2dpSinkCb = Arc<Mutex<A2dpSinkCallbacksDispatcher>>;
475 
476 cb_variant!(A2dpSinkCb, sink_connection_state_callback -> A2dpSinkCallbacks::ConnectionState,
477     RawAddress, u32 -> BtavConnectionState, FfiA2dpError -> A2dpError,{
478         let _2 = _2.into();
479 });
480 
481 cb_variant!(A2dpSinkCb, sink_audio_state_callback -> A2dpSinkCallbacks::AudioState, RawAddress, u32 -> BtavAudioState);
482 
483 cb_variant!(A2dpSinkCb, sink_audio_config_callback -> A2dpSinkCallbacks::AudioConfig, RawAddress, u32, u8);
484 
485 pub struct A2dpSink {
486     internal: cxx::UniquePtr<ffi::A2dpSinkIntf>,
487     _is_init: bool,
488     _is_enabled: bool,
489 }
490 
491 // For *const u8 opaque btif
492 unsafe impl Send for A2dpSink {}
493 
494 impl ToggleableProfile for A2dpSink {
is_enabled(&self) -> bool495     fn is_enabled(&self) -> bool {
496         self._is_enabled
497     }
498 
enable(&mut self) -> bool499     fn enable(&mut self) -> bool {
500         self.internal.init();
501         self._is_enabled = true;
502         true
503     }
504 
505     #[profile_enabled_or(false)]
disable(&mut self) -> bool506     fn disable(&mut self) -> bool {
507         self.internal.cleanup();
508         self._is_enabled = false;
509         true
510     }
511 }
512 
513 impl A2dpSink {
514     #[log_args]
new(intf: &BluetoothInterface) -> A2dpSink515     pub fn new(intf: &BluetoothInterface) -> A2dpSink {
516         let a2dp_sink: cxx::UniquePtr<ffi::A2dpSinkIntf>;
517         unsafe {
518             a2dp_sink = ffi::GetA2dpSinkProfile(intf.as_raw_ptr());
519         }
520 
521         A2dpSink { internal: a2dp_sink, _is_init: false, _is_enabled: false }
522     }
523 
524     #[log_args]
is_initialized(&self) -> bool525     pub fn is_initialized(&self) -> bool {
526         self._is_init
527     }
528 
529     #[log_args]
initialize(&mut self, callbacks: A2dpSinkCallbacksDispatcher) -> bool530     pub fn initialize(&mut self, callbacks: A2dpSinkCallbacksDispatcher) -> bool {
531         if get_dispatchers().lock().unwrap().set::<A2dpSinkCb>(Arc::new(Mutex::new(callbacks))) {
532             panic!("Tried to set dispatcher for A2dp Sink Callbacks while it already exists");
533         }
534         self._is_init = true;
535         true
536     }
537 
538     #[log_args]
539     #[profile_enabled_or]
connect(&mut self, bt_addr: RawAddress)540     pub fn connect(&mut self, bt_addr: RawAddress) {
541         self.internal.connect(bt_addr);
542     }
543 
544     #[log_args]
545     #[profile_enabled_or]
disconnect(&mut self, bt_addr: RawAddress)546     pub fn disconnect(&mut self, bt_addr: RawAddress) {
547         self.internal.disconnect(bt_addr);
548     }
549 
550     #[log_args]
551     #[profile_enabled_or]
set_active_device(&mut self, bt_addr: RawAddress)552     pub fn set_active_device(&mut self, bt_addr: RawAddress) {
553         self.internal.set_active_device(bt_addr);
554     }
555 
556     #[log_args]
557     #[profile_enabled_or]
cleanup(&mut self)558     pub fn cleanup(&mut self) {}
559 }
560 
561 #[cfg(test)]
562 mod tests {
563     use super::*;
564 
565     #[test]
validate_sample_rate()566     fn validate_sample_rate() {
567         assert!(!A2dpCodecSampleRate::validate_bits(256));
568         assert!(A2dpCodecSampleRate::validate_bits(2 + 32 + 128));
569     }
570 
571     #[test]
validate_bits_per_sample()572     fn validate_bits_per_sample() {
573         assert!(!A2dpCodecBitsPerSample::validate_bits(8));
574         assert!(A2dpCodecBitsPerSample::validate_bits(1 + 4));
575     }
576 
577     #[test]
validate_channel_mode()578     fn validate_channel_mode() {
579         assert!(!A2dpCodecChannelMode::validate_bits(4));
580         assert!(A2dpCodecChannelMode::validate_bits(1 + 2));
581     }
582 }
583