blob: 6b3f178742980257470b2dbd58eaa09187192574 [file] [log] [blame]
//! 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};
use bt_topshim::topstack;
use log::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<dyn IBluetoothMediaCallback + Send>) -> bool;
/// initializes media (both A2dp and AVRCP) stack
fn initialize(&mut self) -> bool;
/// clean up media stack
fn cleanup(&mut self) -> bool;
// TODO (b/204488289): Accept and validate RawAddress instead.
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;
}
pub trait IBluetoothMediaCallback {
///
fn on_bluetooth_audio_device_added(
&self,
addr: String,
sample_rate: i32,
bits_per_sample: i32,
channel_mode: 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<Mutex<BluetoothInterface>>,
initialized: bool,
callbacks: Vec<(u32, Box<dyn IBluetoothMediaCallback + Send>)>,
callback_last_id: u32,
tx: Sender<Message>,
a2dp: Option<A2dp>,
avrcp: Option<Avrcp>,
a2dp_states: HashMap<RawAddress, BtavConnectionState>,
hfp: Option<Hfp>,
hfp_states: HashMap<RawAddress, BthfConnectionState>,
selectable_caps: HashMap<RawAddress, Vec<A2dpCodecConfig>>,
}
impl BluetoothMedia {
pub fn new(tx: Sender<Message>, intf: Arc<Mutex<BluetoothInterface>>) -> 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;
}
self.for_all_callbacks(|callback| {
callback.on_bluetooth_audio_device_added(
addr.to_string(),
cap.sample_rate,
cap.bits_per_sample,
cap.channel_mode,
);
});
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 => {
// TODO: Integrate with A2dp
}
BthfConnectionState::Connecting => {}
BthfConnectionState::Disconnected => {}
BthfConnectionState::Disconnecting => {
// TODO: Integrate with A2dp
}
}
}
}
}
fn for_all_callbacks<F: Fn(&Box<dyn IBluetoothMediaCallback + Send>)>(&self, f: F) {
for callback in &self.callbacks {
f(&callback.1);
}
}
}
fn get_a2dp_dispatcher(tx: Sender<Message>) -> 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<Message>) -> 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<Message>) -> 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<dyn IBluetoothMediaCallback + Send>) -> 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) {
let addr = RawAddress::from_string(device.clone());
if addr.is_none() {
warn!("Invalid device string {}", device);
return;
}
self.a2dp.as_mut().unwrap().connect(device);
self.hfp.as_mut().unwrap().connect(addr.unwrap());
}
fn cleanup(&mut self) -> bool {
true
}
fn set_active_device(&mut self, device: String) {
self.a2dp.as_mut().unwrap().set_active_device(device);
}
fn disconnect(&mut self, device: String) {
let addr = RawAddress::from_string(device.clone());
if addr.is_none() {
warn!("Invalid device string {}", device);
return;
}
self.a2dp.as_mut().unwrap().disconnect(device);
self.hfp.as_mut().unwrap().disconnect(addr.unwrap());
}
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 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,
}
}
}