Skip to main content

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 anyhow::{Context, format_err};
6use fuchsia_bluetooth::types::{PeerId, Uuid};
7use futures::stream::BoxStream;
8use std::collections::HashSet;
9use std::str::FromStr;
10use thiserror::Error;
11use {
12    fidl_fuchsia_audio_device as audio_device, fidl_fuchsia_bluetooth_bredr as bredr,
13    fidl_fuchsia_media as media,
14};
15
16use crate::codec_id::CodecId;
17use crate::sco;
18
19#[derive(Error, Debug)]
20pub enum Error {
21    #[error("Parameters aren't supported {:?}", .source)]
22    UnsupportedParameters { source: anyhow::Error },
23    #[error("Audio is already started")]
24    AlreadyStarted,
25    #[error("AudioCore Error: {:?}", .source)]
26    AudioCore { source: anyhow::Error },
27    #[error("FIDL Error: {:?}", .0)]
28    Fidl(#[from] fidl::Error),
29    #[error("Audio is not started")]
30    NotStarted,
31    #[error("Could not find suitable devices")]
32    DiscoveryFailed,
33    #[error("Operation is in progress: {}", .description)]
34    InProgress { description: String },
35}
36
37impl Error {
38    fn audio_core(e: anyhow::Error) -> Self {
39        Self::AudioCore { source: e }
40    }
41}
42
43mod codec;
44pub use codec::CodecControl;
45
46mod dai;
47pub use dai::DaiControl;
48
49mod inband;
50pub use inband::InbandControl;
51
52mod partial_offload;
53pub use partial_offload::PartialOffloadControl;
54
55mod test;
56pub use test::TestControl;
57
58const DEVICE_NAME: &'static str = "Bluetooth HFP";
59
60// Used to build audio device IDs for peers
61const HF_INPUT_UUID: Uuid =
62    Uuid::new16(bredr::ServiceClassProfileIdentifier::Handsfree.into_primitive());
63const HF_OUTPUT_UUID: Uuid =
64    Uuid::new16(bredr::ServiceClassProfileIdentifier::HandsfreeAudioGateway.into_primitive());
65
66#[derive(Debug)]
67pub enum ControlEvent {
68    /// Request from Control to start the audio for a connected Peer.
69    /// Control::start() or Control::failed_request() should be called after this event
70    /// has been received; incompatible calls may return `Error::InProgress`.
71    RequestStart { id: PeerId },
72    /// Request from the Control to stop the audio to a connected Peer.
73    /// Any calls for which audio is currently routed to the HF for this peer will be transferred
74    /// to the AG.
75    /// Control::stop() should be called after this event has been received; incompatible
76    /// calls made in this state may return `Error::InProgress`
77    /// Note that Control::failed_request() may not be called for this event (audio can
78    /// always be stopped)
79    RequestStop { id: PeerId },
80    /// Event produced when an audio path has been started and audio is flowing to/from the peer.
81    Started { id: PeerId },
82    /// Event produced when the audio path has been stopped and audio is not flowing to/from the
83    /// peer.
84    /// This event can be spontaeously produced by the Control implementation to indicate an
85    /// error in the audio path (either during or after a requested start).
86    Stopped { id: PeerId, error: Option<Error> },
87}
88
89impl ControlEvent {
90    pub fn id(&self) -> PeerId {
91        match self {
92            ControlEvent::RequestStart { id } => *id,
93            ControlEvent::RequestStop { id } => *id,
94            ControlEvent::Started { id } => *id,
95            ControlEvent::Stopped { id, error: _ } => *id,
96        }
97    }
98}
99
100pub trait Control: Send {
101    /// Send to indicate when connected to a peer. `supported_codecs` indicates the set of codecs which are
102    /// communicated from the peer.  Depending on the audio control implementation,
103    /// this may add a (stopped) media device.  Audio control implementations can request audio be started
104    /// for peers that are connected.
105    fn connect(&mut self, id: PeerId, supported_codecs: &[CodecId]);
106
107    /// Send to indicate that a peer has been disconnected.  This shall tear down any audio path
108    /// set up for the peer and send a `ControlEvent::Stopped` for each.  This shall be idempotent
109    /// (calling disconnect on a disconnected PeerId does nothing)
110    fn disconnect(&mut self, id: PeerId);
111
112    /// Request to start sending audio to the peer.  If the request succeeds `Ok(())` will be
113    /// returned, but audio may not be started until a `ControlEvent::Started` event is
114    /// produced in the events.
115    fn start(
116        &mut self,
117        id: PeerId,
118        connection: sco::Connection,
119        codec: CodecId,
120    ) -> Result<(), Error>;
121
122    /// Request to stop the audio to a peer.
123    /// If the Audio is not started, an Err(Error::NotStarted) will be returned.
124    /// If the requests succeeds `Ok(())` will be returned but audio may not be stopped until a
125    /// `ControlEvent::Stopped` is produced in the events.
126    fn stop(&mut self, id: PeerId) -> Result<(), Error>;
127
128    /// Get a stream of the events produced by this audio control.
129    /// May panic if the event stream has already been taken.
130    fn take_events(&self) -> BoxStream<'static, ControlEvent>;
131
132    /// Respond with failure to a request from the event stream.
133    /// `request` should be the request that failed.  If a request was not made by this audio
134    /// control the failure shall be ignored.
135    fn failed_request(&self, request: ControlEvent, error: Error);
136}
137
138#[derive(Debug, Clone, Copy, PartialEq)]
139pub enum OffloadType {
140    Dai,
141    Codec,
142}
143
144impl FromStr for OffloadType {
145    type Err = anyhow::Error;
146
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        match s {
149            "dai" => Ok(OffloadType::Dai),
150            "codec" => Ok(OffloadType::Codec),
151            _ => Err(format_err!("Invalid offload type: {}", s)),
152        }
153    }
154}
155
156pub async fn setup_audio(
157    offload_type: OffloadType,
158    controller_codecs: HashSet<CodecId>,
159) -> Result<Box<dyn Control>, anyhow::Error> {
160    match offload_type {
161        OffloadType::Dai => {
162            let audio_proxy = fuchsia_component::client::connect_to_protocol::<
163                media::AudioDeviceEnumeratorMarker,
164            >()
165            .with_context(|| format!("Error connecting to audio_core"))?;
166            Ok(Box::new(
167                PartialOffloadControl::setup_audio_core(audio_proxy, controller_codecs).await?,
168            ))
169        }
170        OffloadType::Codec => {
171            let provider =
172                fuchsia_component::client::connect_to_protocol::<audio_device::ProviderMarker>()
173                    .with_context(|| format!("Error connecting to audio_device_registry"))?;
174            let codec = CodecControl::new(provider);
175            Ok(Box::new(codec))
176        }
177    }
178}