Skip to main content

bt_hfp/audio/
partial_offload.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 fidl_fuchsia_media as media;
6use fuchsia_bluetooth::types::PeerId;
7use futures::StreamExt;
8use futures::stream::BoxStream;
9use std::collections::{HashMap, HashSet};
10
11use crate::codec_id::CodecId;
12use crate::sco;
13
14use super::dai::DaiControl;
15use super::inband::InbandControl;
16use super::{Control, ControlEvent, Error};
17
18/// A Control that either sends the audio directly to the controller (using an offload
19/// Control) or encodes audio locally and sends it in the SCO channel, depending on
20/// whether the codec is in the list of offload-supported codecs.
21pub struct PartialOffloadControl {
22    offload_codecids: HashSet<CodecId>,
23    /// Used to control when the audio can be sent offloaded
24    offload: Box<dyn Control>,
25    /// Used to encode audio locally and send inband
26    inband: InbandControl,
27    /// The set of started peers. Value is true if the audio encoding is handled by the controller.
28    started: HashMap<PeerId, bool>,
29}
30
31impl PartialOffloadControl {
32    pub async fn setup_audio_core(
33        audio_proxy: media::AudioDeviceEnumeratorProxy,
34        offload_supported: HashSet<CodecId>,
35    ) -> Result<Self, Error> {
36        let dai = DaiControl::discover(audio_proxy.clone()).await?;
37        let inband = InbandControl::create(audio_proxy)?;
38        Ok(Self {
39            offload_codecids: offload_supported,
40            offload: Box::new(dai),
41            inband,
42            started: Default::default(),
43        })
44    }
45}
46
47impl Control for PartialOffloadControl {
48    fn start(
49        &mut self,
50        id: PeerId,
51        connection: sco::Connection,
52        codec: CodecId,
53    ) -> Result<(), Error> {
54        if self.started.contains_key(&id) {
55            return Err(Error::AlreadyStarted);
56        }
57        let result = if self.offload_codecids.contains(&codec) {
58            self.offload.start(id, connection, codec)
59        } else {
60            self.inband.start(id, connection, codec)
61        };
62        if result.is_ok() {
63            let _ = self.started.insert(id, self.offload_codecids.contains(&codec));
64        }
65        result
66    }
67
68    fn stop(&mut self, id: PeerId) -> Result<(), Error> {
69        let stop_result = match self.started.get(&id) {
70            None => return Err(Error::NotStarted),
71            Some(true) => self.offload.stop(id),
72            Some(false) => self.inband.stop(id),
73        };
74        if stop_result.is_ok() {
75            let _ = self.started.remove(&id);
76        }
77        stop_result
78    }
79
80    fn connect(&mut self, id: PeerId, supported_codecs: &[CodecId]) {
81        // TODO(b/341114499): Consider not connecting this here, since it could create a device we
82        // don't want to use.
83        self.inband.connect(id, supported_codecs);
84        if supported_codecs.iter().any(|i| self.offload_codecids.contains(i)) {
85            self.offload.connect(id, supported_codecs);
86        }
87    }
88
89    fn disconnect(&mut self, id: PeerId) {
90        self.inband.disconnect(id);
91        self.offload.disconnect(id);
92    }
93
94    fn take_events(&self) -> BoxStream<'static, ControlEvent> {
95        let inband_events = self.inband.take_events();
96        let controller_events = self.offload.take_events();
97        futures::stream::select_all([inband_events, controller_events]).boxed()
98    }
99
100    fn failed_request(&self, request: ControlEvent, error: Error) {
101        // We only support requests from the controller Control (inband does not make
102        // requests).
103        self.offload.failed_request(request, error);
104    }
105}