bt_hfp/
audio.rs

1// Copyright 2021 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 fuchsia_bluetooth::types::{PeerId, Uuid};
6use fuchsia_sync::Mutex;
7use futures::stream::BoxStream;
8use futures::StreamExt;
9use std::collections::{HashMap, HashSet};
10use std::sync::Arc;
11use thiserror::Error;
12use {fidl_fuchsia_bluetooth_bredr as bredr, fidl_fuchsia_media as media};
13
14use crate::codec_id::CodecId;
15use crate::sco;
16
17#[derive(Error, Debug)]
18pub enum Error {
19    #[error("Parameters aren't supported {:?}", .source)]
20    UnsupportedParameters { source: anyhow::Error },
21    #[error("Audio is already started")]
22    AlreadyStarted,
23    #[error("AudioCore Error: {:?}", .source)]
24    AudioCore { source: anyhow::Error },
25    #[error("FIDL Error: {:?}", .0)]
26    Fidl(#[from] fidl::Error),
27    #[error("Audio is not started")]
28    NotStarted,
29    #[error("Could not find suitable devices")]
30    DiscoveryFailed,
31    #[error("Operation is in progress: {}", .description)]
32    InProgress { description: String },
33}
34
35impl Error {
36    fn audio_core(e: anyhow::Error) -> Self {
37        Self::AudioCore { source: e }
38    }
39}
40
41mod dai;
42use dai::DaiControl;
43
44mod inband;
45use inband::InbandControl;
46
47mod codec;
48pub use codec::CodecControl;
49
50const DEVICE_NAME: &'static str = "Bluetooth HFP";
51
52// Used to build audio device IDs for peers
53const HF_INPUT_UUID: Uuid =
54    Uuid::new16(bredr::ServiceClassProfileIdentifier::Handsfree.into_primitive());
55const HF_OUTPUT_UUID: Uuid =
56    Uuid::new16(bredr::ServiceClassProfileIdentifier::HandsfreeAudioGateway.into_primitive());
57
58#[derive(Debug)]
59pub enum ControlEvent {
60    /// Request from Control to start the audio for a connected Peer.
61    /// Control::start() or Control::failed_request() should be called after this event
62    /// has been received; incompatible calls may return `Error::InProgress`.
63    RequestStart { id: PeerId },
64    /// Request from the Control to stop the audio to a connected Peer.
65    /// Any calls for which audio is currently routed to the HF for this peer will be transferred
66    /// to the AG.
67    /// Control::stop() should be called after this event has been received; incompatible
68    /// calls made in this state may return `Error::InProgress`
69    /// Note that Control::failed_request() may not be called for this event (audio can
70    /// always be stopped)
71    RequestStop { id: PeerId },
72    /// Event produced when an audio path has been started and audio is flowing to/from the peer.
73    Started { id: PeerId },
74    /// Event produced when the audio path has been stopped and audio is not flowing to/from the
75    /// peer.
76    /// This event can be spontaeously produced by the Control implementation to indicate an
77    /// error in the audio path (either during or after a requested start).
78    Stopped { id: PeerId, error: Option<Error> },
79}
80
81impl ControlEvent {
82    pub fn id(&self) -> PeerId {
83        match self {
84            ControlEvent::RequestStart { id } => *id,
85            ControlEvent::RequestStop { id } => *id,
86            ControlEvent::Started { id } => *id,
87            ControlEvent::Stopped { id, error: _ } => *id,
88        }
89    }
90}
91
92pub trait Control: Send {
93    /// Send to indicate when connected to a peer. `supported_codecs` indicates the set of codecs which are
94    /// communicated from the peer.  Depending on the audio control implementation,
95    /// this may add a (stopped) media device.  Audio control implementations can request audio be started
96    /// for peers that are connected.
97    fn connect(&mut self, id: PeerId, supported_codecs: &[CodecId]);
98
99    /// Send to indicate that a peer has been disconnected.  This shall tear down any audio path
100    /// set up for the peer and send a `ControlEvent::Stopped` for each.  This shall be idempotent
101    /// (calling disconnect on a disconnected PeerId does nothing)
102    fn disconnect(&mut self, id: PeerId);
103
104    /// Request to start sending audio to the peer.  If the request succeeds `Ok(())` will be
105    /// returned, but audio may not be started until a `ControlEvent::Started` event is
106    /// produced in the events.
107    fn start(
108        &mut self,
109        id: PeerId,
110        connection: sco::Connection,
111        codec: CodecId,
112    ) -> Result<(), Error>;
113
114    /// Request to stop the audio to a peer.
115    /// If the Audio is not started, an Err(Error::NotStarted) will be returned.
116    /// If the requests succeeds `Ok(())` will be returned but audio may not be stopped until a
117    /// `ControlEvent::Stopped` is produced in the events.
118    fn stop(&mut self, id: PeerId) -> Result<(), Error>;
119
120    /// Get a stream of the events produced by this audio control.
121    /// May panic if the event stream has already been taken.
122    fn take_events(&self) -> BoxStream<'static, ControlEvent>;
123
124    /// Respond with failure to a request from the event stream.
125    /// `request` should be the request that failed.  If a request was not made by this audio
126    /// control the failure shall be ignored.
127    fn failed_request(&self, request: ControlEvent, error: Error);
128}
129
130/// A Control that either sends the audio directly to the controller (using an offload
131/// Control) or encodes audio locally and sends it in the SCO channel, depending on
132/// whether the codec is in the list of offload-supported codecs.
133pub struct PartialOffloadControl {
134    offload_codecids: HashSet<CodecId>,
135    /// Used to control when the audio can be sent offloaded
136    offload: Box<dyn Control>,
137    /// Used to encode audio locally and send inband
138    inband: InbandControl,
139    /// The set of started peers. Value is true if the audio encoding is handled by the controller.
140    started: HashMap<PeerId, bool>,
141}
142
143impl PartialOffloadControl {
144    pub async fn setup_audio_core(
145        audio_proxy: media::AudioDeviceEnumeratorProxy,
146        offload_supported: HashSet<CodecId>,
147    ) -> Result<Self, Error> {
148        let dai = DaiControl::discover(audio_proxy.clone()).await?;
149        let inband = InbandControl::create(audio_proxy)?;
150        Ok(Self {
151            offload_codecids: offload_supported,
152            offload: Box::new(dai),
153            inband,
154            started: Default::default(),
155        })
156    }
157}
158
159impl Control for PartialOffloadControl {
160    fn start(
161        &mut self,
162        id: PeerId,
163        connection: sco::Connection,
164        codec: CodecId,
165    ) -> Result<(), Error> {
166        if self.started.contains_key(&id) {
167            return Err(Error::AlreadyStarted);
168        }
169        let result = if self.offload_codecids.contains(&codec) {
170            self.offload.start(id, connection, codec)
171        } else {
172            self.inband.start(id, connection, codec)
173        };
174        if result.is_ok() {
175            let _ = self.started.insert(id, self.offload_codecids.contains(&codec));
176        }
177        result
178    }
179
180    fn stop(&mut self, id: PeerId) -> Result<(), Error> {
181        let stop_result = match self.started.get(&id) {
182            None => return Err(Error::NotStarted),
183            Some(true) => self.offload.stop(id),
184            Some(false) => self.inband.stop(id),
185        };
186        if stop_result.is_ok() {
187            let _ = self.started.remove(&id);
188        }
189        stop_result
190    }
191
192    fn connect(&mut self, id: PeerId, supported_codecs: &[CodecId]) {
193        // TODO(b/341114499): Consider not connecting this here, since it could create a device we
194        // don't want to use.
195        self.inband.connect(id, supported_codecs);
196        if supported_codecs.iter().any(|i| self.offload_codecids.contains(i)) {
197            self.offload.connect(id, supported_codecs);
198        }
199    }
200
201    fn disconnect(&mut self, id: PeerId) {
202        self.inband.disconnect(id);
203        self.offload.disconnect(id);
204    }
205
206    fn take_events(&self) -> BoxStream<'static, ControlEvent> {
207        let inband_events = self.inband.take_events();
208        let controller_events = self.offload.take_events();
209        futures::stream::select_all([inband_events, controller_events]).boxed()
210    }
211
212    fn failed_request(&self, request: ControlEvent, error: Error) {
213        // We only support requests from the controller Control (inband does not make
214        // requests).
215        self.offload.failed_request(request, error);
216    }
217}
218
219struct TestControlInner {
220    started: HashSet<PeerId>,
221    connected: HashMap<PeerId, HashSet<CodecId>>,
222    connections: HashMap<PeerId, sco::Connection>,
223    event_sender: futures::channel::mpsc::Sender<ControlEvent>,
224}
225
226#[derive(Clone)]
227pub struct TestControl {
228    inner: Arc<Mutex<TestControlInner>>,
229}
230
231impl TestControl {
232    pub fn unexpected_stop(&self, id: PeerId, error: Error) {
233        let mut lock = self.inner.lock();
234        let _ = lock.started.remove(&id);
235        let _ = lock.connections.remove(&id);
236        let _ = lock.event_sender.try_send(ControlEvent::Stopped { id, error: Some(error) });
237    }
238
239    pub fn is_started(&self, id: PeerId) -> bool {
240        let lock = self.inner.lock();
241        lock.started.contains(&id) && lock.connections.contains_key(&id)
242    }
243
244    pub fn is_connected(&self, id: PeerId) -> bool {
245        let lock = self.inner.lock();
246        lock.connected.contains_key(&id)
247    }
248}
249
250impl Default for TestControl {
251    fn default() -> Self {
252        // Make a disconnected sender, we do not care about whether it succeeds.
253        let (event_sender, _) = futures::channel::mpsc::channel(0);
254        Self {
255            inner: Arc::new(Mutex::new(TestControlInner {
256                started: Default::default(),
257                connected: Default::default(),
258                connections: Default::default(),
259                event_sender,
260            })),
261        }
262    }
263}
264
265impl Control for TestControl {
266    fn start(
267        &mut self,
268        id: PeerId,
269        connection: sco::Connection,
270        _codec: CodecId,
271    ) -> Result<(), Error> {
272        let mut lock = self.inner.lock();
273        if !lock.started.insert(id) {
274            return Err(Error::AlreadyStarted);
275        }
276        let _ = lock.connections.insert(id, connection);
277        let _ = lock.event_sender.try_send(ControlEvent::Started { id });
278        Ok(())
279    }
280
281    fn stop(&mut self, id: PeerId) -> Result<(), Error> {
282        let mut lock = self.inner.lock();
283        if !lock.started.remove(&id) {
284            return Err(Error::NotStarted);
285        }
286        let _ = lock.connections.remove(&id);
287        let _ = lock.event_sender.try_send(ControlEvent::Stopped { id, error: None });
288        Ok(())
289    }
290
291    fn connect(&mut self, id: PeerId, supported_codecs: &[CodecId]) {
292        let mut lock = self.inner.lock();
293        let _ = lock.connected.insert(id, supported_codecs.iter().cloned().collect());
294    }
295
296    fn disconnect(&mut self, id: PeerId) {
297        let _ = self.stop(id);
298        let mut lock = self.inner.lock();
299        let _ = lock.connected.remove(&id);
300    }
301
302    fn take_events(&self) -> BoxStream<'static, ControlEvent> {
303        let mut lock = self.inner.lock();
304        // Replace the sender.
305        let (sender, receiver) = futures::channel::mpsc::channel(1);
306        lock.event_sender = sender;
307        receiver.boxed()
308    }
309
310    fn failed_request(&self, _request: ControlEvent, _error: Error) {
311        // Nothing to do here for the moment
312    }
313}