//! Anything related to audio and media API. use bt_topshim::btif::{BluetoothInterface, RawAddress}; use bt_topshim::profiles::a2dp::{ A2dp, A2dpCallbacks, A2dpCallbacksDispatcher, A2dpCodecBitsPerSample, A2dpCodecChannelMode, A2dpCodecConfig, A2dpCodecIndex, A2dpCodecSampleRate, BtavConnectionState, PresentationPosition, }; use bt_topshim::profiles::avrcp::{Avrcp, AvrcpCallbacks, AvrcpCallbacksDispatcher}; use bt_topshim::profiles::hfp::{ BthfConnectionState, Hfp, HfpCallbacks, HfpCallbacksDispatcher, HfpCodecCapability, }; use bt_topshim::topstack; use log::{info, warn}; use std::collections::HashMap; use std::convert::TryFrom; use std::sync::Arc; use std::sync::Mutex; use tokio::sync::mpsc::Sender; use crate::Message; pub trait IBluetoothMedia { /// fn register_callback(&mut self, callback: Box) -> bool; /// initializes media (both A2dp and AVRCP) stack fn initialize(&mut self) -> bool; /// clean up media stack fn cleanup(&mut self) -> bool; fn connect(&mut self, device: String); fn set_active_device(&mut self, device: String); fn disconnect(&mut self, device: String); fn set_audio_config( &mut self, sample_rate: i32, bits_per_sample: i32, channel_mode: i32, ) -> bool; fn set_volume(&mut self, volume: i32); fn start_audio_request(&mut self); fn stop_audio_request(&mut self); fn get_presentation_position(&mut self) -> PresentationPosition; fn start_sco_call(&mut self, device: String); fn stop_sco_call(&mut self, device: String); } pub trait IBluetoothMediaCallback { /// Triggered when a Bluetooth audio device is ready to be used. This should /// only be triggered once for a device and send an event to clients. If the /// device supports both HFP and A2DP, both should be ready when this is /// triggered. fn on_bluetooth_audio_device_added( &self, addr: String, sample_rate: i32, bits_per_sample: i32, channel_mode: i32, hfp_cap: i32, ); /// fn on_bluetooth_audio_device_removed(&self, addr: String); /// fn on_absolute_volume_supported_changed(&self, supported: bool); /// fn on_absolute_volume_changed(&self, volume: i32); } /// Actions that `BluetoothMedia` can take on behalf of the stack. pub enum MediaActions { Connect(String), Disconnect(String), } pub struct BluetoothMedia { intf: Arc>, initialized: bool, callbacks: Vec<(u32, Box)>, callback_last_id: u32, tx: Sender, a2dp: Option, avrcp: Option, a2dp_states: HashMap, hfp: Option, hfp_states: HashMap, selectable_caps: HashMap>, } impl BluetoothMedia { pub fn new(tx: Sender, intf: Arc>) -> BluetoothMedia { BluetoothMedia { intf, initialized: false, callbacks: vec![], callback_last_id: 0, tx, a2dp: None, avrcp: None, a2dp_states: HashMap::new(), hfp: None, hfp_states: HashMap::new(), selectable_caps: HashMap::new(), } } pub fn dispatch_a2dp_callbacks(&mut self, cb: A2dpCallbacks) { match cb { A2dpCallbacks::ConnectionState(addr, state) => { if !self.a2dp_states.get(&addr).is_none() && state == *self.a2dp_states.get(&addr).unwrap() { return; } match state { BtavConnectionState::Connected => { if let Some(caps) = self.selectable_caps.get(&addr) { for cap in caps { // TODO: support codecs other than SBC. if A2dpCodecIndex::SrcSbc != A2dpCodecIndex::from(cap.codec_type) { continue; } // TODO: Coordinate with HFP. Should only trigger once. self.for_all_callbacks(|callback| { callback.on_bluetooth_audio_device_added( addr.to_string(), cap.sample_rate, cap.bits_per_sample, cap.channel_mode, HfpCodecCapability::UNSUPPORTED.bits(), ); }); return; } } } BtavConnectionState::Connecting => {} BtavConnectionState::Disconnected => { self.for_all_callbacks(|callback| { callback.on_bluetooth_audio_device_removed(addr.to_string()); }); } BtavConnectionState::Disconnecting => {} }; self.a2dp_states.insert(addr, state); } A2dpCallbacks::AudioState(_addr, _state) => {} A2dpCallbacks::AudioConfig(addr, _config, _local_caps, selectable_caps) => { self.selectable_caps.insert(addr, selectable_caps); } A2dpCallbacks::MandatoryCodecPreferred(_addr) => {} } } pub fn dispatch_avrcp_callbacks(&mut self, cb: AvrcpCallbacks) { match cb { AvrcpCallbacks::AvrcpAbsoluteVolumeEnabled(supported) => { self.for_all_callbacks(|callback| { callback.on_absolute_volume_supported_changed(supported); }); } AvrcpCallbacks::AvrcpAbsoluteVolumeUpdate(volume) => { self.for_all_callbacks(|callback| { callback.on_absolute_volume_changed(i32::from(volume)); }); } } } pub fn dispatch_media_actions(&mut self, action: MediaActions) { match action { MediaActions::Connect(address) => self.connect(address), MediaActions::Disconnect(address) => self.disconnect(address), } } pub fn dispatch_hfp_callbacks(&mut self, cb: HfpCallbacks) { match cb { HfpCallbacks::ConnectionState(state, addr) => { if !self.hfp_states.get(&addr).is_none() && state == *self.hfp_states.get(&addr).unwrap() { return; } match state { BthfConnectionState::Connected => { info!("HFP connected."); } BthfConnectionState::SlcConnected => { info!("HFP SLC connected."); // TODO: Coordinate with A2DP. Should only trigger once. self.for_all_callbacks(|callback| { callback.on_bluetooth_audio_device_added( addr.to_string(), 0, 0, 0, HfpCodecCapability::CVSD.bits(), ); }); } BthfConnectionState::Disconnected => { info!("HFP disconnected."); } BthfConnectionState::Connecting => { info!("HFP connecting."); } BthfConnectionState::Disconnecting => { info!("HFP disconnecting."); } } } } } fn for_all_callbacks)>(&self, f: F) { for callback in &self.callbacks { f(&callback.1); } } } fn get_a2dp_dispatcher(tx: Sender) -> A2dpCallbacksDispatcher { A2dpCallbacksDispatcher { dispatch: Box::new(move |cb| { let txl = tx.clone(); topstack::get_runtime().spawn(async move { let _ = txl.send(Message::A2dp(cb)).await; }); }), } } fn get_avrcp_dispatcher(tx: Sender) -> AvrcpCallbacksDispatcher { AvrcpCallbacksDispatcher { dispatch: Box::new(move |cb| { let txl = tx.clone(); topstack::get_runtime().spawn(async move { let _ = txl.send(Message::Avrcp(cb)).await; }); }), } } fn get_hfp_dispatcher(tx: Sender) -> HfpCallbacksDispatcher { HfpCallbacksDispatcher { dispatch: Box::new(move |cb| { let txl = tx.clone(); topstack::get_runtime().spawn(async move { let _ = txl.send(Message::Hfp(cb)).await; }); }), } } impl IBluetoothMedia for BluetoothMedia { fn register_callback(&mut self, callback: Box) -> bool { self.callback_last_id += 1; self.callbacks.push((self.callback_last_id, callback)); true } fn initialize(&mut self) -> bool { if self.initialized { return false; } self.initialized = true; // TEST A2dp let a2dp_dispatcher = get_a2dp_dispatcher(self.tx.clone()); self.a2dp = Some(A2dp::new(&self.intf.lock().unwrap())); self.a2dp.as_mut().unwrap().initialize(a2dp_dispatcher); // AVRCP let avrcp_dispatcher = get_avrcp_dispatcher(self.tx.clone()); self.avrcp = Some(Avrcp::new(&self.intf.lock().unwrap())); self.avrcp.as_mut().unwrap().initialize(avrcp_dispatcher); // HFP let hfp_dispatcher = get_hfp_dispatcher(self.tx.clone()); self.hfp = Some(Hfp::new(&self.intf.lock().unwrap())); self.hfp.as_mut().unwrap().initialize(hfp_dispatcher); true } fn connect(&mut self, device: String) { if let Some(addr) = RawAddress::from_string(device.clone()) { self.a2dp.as_mut().unwrap().connect(addr); self.hfp.as_mut().unwrap().connect(addr); } else { warn!("Invalid device string {}", device); } } fn cleanup(&mut self) -> bool { true } fn set_active_device(&mut self, device: String) { if let Some(addr) = RawAddress::from_string(device.clone()) { self.a2dp.as_mut().unwrap().set_active_device(addr); } else { warn!("Invalid device string {}", device); } } fn disconnect(&mut self, device: String) { if let Some(addr) = RawAddress::from_string(device.clone()) { self.a2dp.as_mut().unwrap().disconnect(addr); self.hfp.as_mut().unwrap().disconnect(addr); } else { warn!("Invalid device string {}", device); } } fn set_audio_config( &mut self, sample_rate: i32, bits_per_sample: i32, channel_mode: i32, ) -> bool { if !A2dpCodecSampleRate::validate_bits(sample_rate) || !A2dpCodecBitsPerSample::validate_bits(bits_per_sample) || !A2dpCodecChannelMode::validate_bits(channel_mode) { return false; } self.a2dp.as_mut().unwrap().set_audio_config(sample_rate, bits_per_sample, channel_mode); true } fn set_volume(&mut self, volume: i32) { match i8::try_from(volume) { Ok(val) => self.avrcp.as_mut().unwrap().set_volume(val), _ => (), }; } fn start_audio_request(&mut self) { self.a2dp.as_mut().unwrap().start_audio_request(); } fn stop_audio_request(&mut self) { self.a2dp.as_mut().unwrap().stop_audio_request(); } fn start_sco_call(&mut self, device: String) { if let Some(addr) = RawAddress::from_string(device.clone()) { info!("Start sco call for {}", device); match self.hfp.as_mut().unwrap().connect_audio(addr) { 0 => { info!("SCO connect_audio status success."); } x => { warn!("SCO connect_audio status failed: {}", x); } }; } else { warn!("Can't start sco call with: {}", device); } } fn stop_sco_call(&mut self, device: String) { if let Some(addr) = RawAddress::from_string(device.clone()) { info!("Stop sco call for {}", device); self.hfp.as_mut().unwrap().disconnect_audio(addr); } else { warn!("Can't stop sco call with: {}", device); } } fn get_presentation_position(&mut self) -> PresentationPosition { let position = self.a2dp.as_mut().unwrap().get_presentation_position(); PresentationPosition { remote_delay_report_ns: position.remote_delay_report_ns, total_bytes_read: position.total_bytes_read, data_position_sec: position.data_position_sec, data_position_nsec: position.data_position_nsec, } } }