Skip to main content

bt_broadcast_assistant/
types.rs

1// Copyright 2023 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 bt_bap::types::*;
6use bt_bass::types::{BigSubgroup, BisSync};
7use bt_common::core::{Address, AddressType};
8use bt_common::core::{AdvertisingSetId, PeriodicAdvertisingInterval};
9use bt_common::packet_encoding::Error as PacketError;
10use std::collections::HashMap;
11
12/// Broadcast source data as advertised through Basic Audio Announcement
13/// PA and Broadcast Audio Announcement.
14/// See BAP spec v1.0.1 Section 3.7.2.1 and Section 3.7.2.2 for details.
15// TODO(b/308481381): fill out endpoint from basic audio announcement from PA trains.
16#[derive(Clone, Default, Debug, PartialEq)]
17pub struct BroadcastSource {
18    pub(crate) address: Option<Address>,
19    pub(crate) address_type: Option<AddressType>,
20    pub(crate) advertising_sid: Option<AdvertisingSetId>,
21    pub(crate) broadcast_id: Option<BroadcastId>,
22    pub(crate) periodic_advertising_interval: Option<PeriodicAdvertisingInterval>,
23    pub(crate) endpoint: Option<BroadcastAudioSourceEndpoint>,
24}
25
26impl BroadcastSource {
27    /// Returns whether or not this BroadcastSource has enough information
28    /// to be added by the Broadcast Assistant.
29    pub(crate) fn into_add_source(&self) -> bool {
30        // Address and PA interval information are not necessary since
31        // default value can be used for PA interval and Address is looked up
32        // when the add source operation is triggered.
33        self.advertising_sid.is_some() && self.broadcast_id.is_some() && self.endpoint.is_some()
34    }
35
36    pub fn with_address(&mut self, address: [u8; 6]) -> &mut Self {
37        self.address = Some(address);
38        self
39    }
40
41    pub fn with_address_type(&mut self, type_: AddressType) -> &mut Self {
42        self.address_type = Some(type_);
43        self
44    }
45
46    pub fn with_broadcast_id(&mut self, bid: BroadcastId) -> &mut Self {
47        self.broadcast_id = Some(bid);
48        self
49    }
50
51    pub fn with_advertising_sid(&mut self, sid: AdvertisingSetId) -> &mut Self {
52        self.advertising_sid = Some(sid);
53        self
54    }
55
56    pub fn with_periodic_advertising_interval(
57        &mut self,
58        interval: PeriodicAdvertisingInterval,
59    ) -> &mut Self {
60        self.periodic_advertising_interval = Some(interval);
61        self
62    }
63
64    pub fn with_endpoint(&mut self, endpoint: BroadcastAudioSourceEndpoint) -> &mut Self {
65        self.endpoint = Some(endpoint);
66        self
67    }
68
69    /// Merge fields from other broadcast source into this broadcast source.
70    /// Set fields in other source take priority over this source.
71    /// If a field in the other broadcast source is none, it's ignored and
72    /// the existing values are kept.
73    pub(crate) fn merge(&mut self, other: &BroadcastSource) {
74        if let Some(address) = other.address {
75            self.address = Some(address);
76        }
77        if let Some(address_type) = other.address_type {
78            self.address_type = Some(address_type);
79        }
80        if let Some(advertising_sid) = other.advertising_sid {
81            self.advertising_sid = Some(advertising_sid);
82        }
83        if let Some(broadcast_id) = other.broadcast_id {
84            self.broadcast_id = Some(broadcast_id);
85        }
86        if let Some(pa_interval) = other.periodic_advertising_interval {
87            self.periodic_advertising_interval = Some(pa_interval);
88        }
89        if let Some(endpoint) = &other.endpoint {
90            self.endpoint = Some(endpoint.clone());
91        }
92    }
93
94    /// Returns the representation of this object's endpoint field to
95    /// broadcast isochronous groups presetation that's usable with
96    /// Broadcast Audio Scan Service operations.
97    ///
98    /// # Arguments
99    ///
100    /// * `bis_sync` - BIG to BIS sync information. If the set is empty, no
101    ///   preference value is used for all the BIGs
102    pub(crate) fn endpoint_to_big_subgroups(
103        &self,
104        bis_sync: HashMap<u8, BisSync>,
105    ) -> Result<Vec<BigSubgroup>, PacketError> {
106        if self.endpoint.is_none() {
107            return Err(PacketError::InvalidParameter(
108                "cannot convert empty Broadcast Audio Source Endpoint data to BIG subgroups data"
109                    .to_string(),
110            ));
111        }
112        let mut subgroups = Vec::new();
113
114        for (big_index, group) in self.endpoint.as_ref().unwrap().big.iter().enumerate() {
115            let bis_sync = bis_sync.get(&(big_index as u8)).cloned().unwrap_or_default();
116            subgroups.push(BigSubgroup::new(Some(bis_sync)).with_metadata(group.metadata.clone()));
117        }
118        Ok(subgroups)
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    use std::collections::HashMap;
127
128    use bt_common::core::CodecId;
129    use bt_common::generic_audio::metadata_ltv::Metadata;
130
131    #[test]
132    fn broadcast_source() {
133        let mut b = BroadcastSource::default();
134        assert!(!b.into_add_source());
135
136        b.with_advertising_sid(AdvertisingSetId(0x1));
137        assert!(!b.into_add_source());
138
139        b.with_broadcast_id(BroadcastId::try_from(0x010203).unwrap());
140        assert!(!b.into_add_source());
141
142        b.endpoint_to_big_subgroups(HashMap::from([(0, BisSync::sync(vec![1]).unwrap())]))
143            .expect_err("should fail no endpoint data");
144
145        b.with_endpoint(BroadcastAudioSourceEndpoint {
146            presentation_delay_ms: 0x010203,
147            big: vec![BroadcastIsochronousGroup {
148                codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd),
149                codec_specific_configs: vec![],
150                metadata: vec![Metadata::BroadcastAudioImmediateRenderingFlag],
151                bis: vec![BroadcastIsochronousStream {
152                    bis_index: 1,
153                    codec_specific_config: vec![],
154                }],
155            }],
156        });
157
158        assert!(b.into_add_source());
159        let subgroups = b
160            .endpoint_to_big_subgroups(HashMap::from([
161                (0, BisSync::sync(vec![1]).unwrap()),
162                (1, BisSync::sync(vec![1]).unwrap()),
163            ]))
164            .expect("should succeed");
165        assert_eq!(subgroups.len(), 1);
166        assert_eq!(
167            subgroups[0],
168            BigSubgroup::new(Some(BisSync::sync(vec![1]).unwrap()))
169                .with_metadata(vec![Metadata::BroadcastAudioImmediateRenderingFlag])
170        );
171    }
172}