| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use bt_bass::client::BigToBisSync; |
| use bt_bass::types::{BigSubgroup, BisSync, BroadcastId}; |
| use bt_common::core::ltv::LtValue; |
| use bt_common::core::{Address, AddressType, CodecId}; |
| use bt_common::core::{AdvertisingSetId, PaInterval}; |
| use bt_common::generic_audio::codec_configuration::CodecConfiguration; |
| use bt_common::generic_audio::metadata_ltv::Metadata; |
| use bt_common::packet_encoding::{Decodable, Error as PacketError}; |
| |
| /// Broadcast source data as advertised through Basic Audio Announcement |
| /// PA and Broadcast Audio Announcement. |
| /// See BAP spec v1.0.1 Section 3.7.2.1 and Section 3.7.2.2 for details. |
| // TODO(b/308481381): fill out endpoint from basic audio announcement from PA trains. |
| #[derive(Clone, Default, Debug, PartialEq)] |
| pub struct BroadcastSource { |
| pub(crate) address: Option<Address>, |
| pub(crate) address_type: Option<AddressType>, |
| pub(crate) advertising_sid: Option<AdvertisingSetId>, |
| pub(crate) broadcast_id: Option<BroadcastId>, |
| pub(crate) pa_interval: Option<PaInterval>, |
| pub(crate) endpoint: Option<BroadcastAudioSourceEndpoint>, |
| } |
| |
| impl BroadcastSource { |
| /// Returns whether or not this BroadcastSource has enough information |
| /// to be added by the Broadcast Assistant. |
| pub(crate) fn into_add_source(&self) -> bool { |
| // PA interval is not necessary since default value can be used. |
| self.address.is_some() |
| && self.address_type.is_some() |
| && self.advertising_sid.is_some() |
| && self.broadcast_id.is_some() |
| && self.endpoint.is_some() |
| } |
| |
| pub fn with_address(&mut self, address: [u8; 6]) -> &mut Self { |
| self.address = Some(address); |
| self |
| } |
| |
| pub fn with_address_type(&mut self, type_: AddressType) -> &mut Self { |
| self.address_type = Some(type_); |
| self |
| } |
| |
| pub fn with_broadcast_id(&mut self, bid: BroadcastId) -> &mut Self { |
| self.broadcast_id = Some(bid); |
| self |
| } |
| |
| pub fn with_advertising_sid(&mut self, sid: AdvertisingSetId) -> &mut Self { |
| self.advertising_sid = Some(sid); |
| self |
| } |
| |
| pub fn with_endpoint(&mut self, endpoint: BroadcastAudioSourceEndpoint) -> &mut Self { |
| self.endpoint = Some(endpoint); |
| self |
| } |
| |
| /// Merge fields from other broadcast source into this broadcast source. |
| /// Set fields in other source take priority over this source. |
| /// If a field in the other broadcast source is none, it's ignored and |
| /// the existing values are kept. |
| pub(crate) fn merge(&mut self, other: &BroadcastSource) { |
| if let Some(address) = other.address { |
| self.address = Some(address); |
| } |
| if let Some(address_type) = other.address_type { |
| self.address_type = Some(address_type); |
| } |
| if let Some(advertising_sid) = other.advertising_sid { |
| self.advertising_sid = Some(advertising_sid); |
| } |
| if let Some(broadcast_id) = other.broadcast_id { |
| self.broadcast_id = Some(broadcast_id); |
| } |
| if let Some(pa_interval) = other.pa_interval { |
| self.pa_interval = Some(pa_interval); |
| } |
| if let Some(endpoint) = &other.endpoint { |
| self.endpoint = Some(endpoint.clone()); |
| } |
| } |
| } |
| |
| /// Parameters exposed as part of Basic Audio Announcement from Broadcast |
| /// Sources. See BAP spec v1.0.1 Section 3.7.2.2 for more details. |
| // TODO(b/308481381): Fill out the struct. |
| #[derive(Clone, Debug, PartialEq)] |
| pub struct BroadcastAudioSourceEndpoint { |
| // Delay is 3 bytes. |
| pub(crate) presentation_delay_ms: u32, |
| pub(crate) big: Vec<BroadcastIsochronousGroup>, |
| } |
| |
| impl BroadcastAudioSourceEndpoint { |
| // Should contain presentation delay, num BIG, and at least one BIG praram. |
| const MIN_PACKET_SIZE: usize = 3 + 1 + BroadcastIsochronousGroup::MIN_PACKET_SIZE; |
| |
| /// Returns the representation of this object's broadcast isochronous groups |
| /// that's usable with Broadcast Audio Scan Service operations. |
| /// |
| /// # Arguments |
| /// |
| /// * `bis_sync` - BIG to BIS sync information. If the set is empty, no |
| /// preference value is used for all the BIGs |
| pub(crate) fn get_bass_subgroups( |
| &self, |
| bis_sync: BigToBisSync, |
| ) -> Result<Vec<BigSubgroup>, PacketError> { |
| let mut subgroups = Vec::new(); |
| let sync_map = bt_bass::client::big_to_bis_sync_indices(&bis_sync); |
| |
| for (big_index, group) in self.big.iter().enumerate() { |
| let bis_sync = match sync_map.get(&(big_index as u8)) { |
| Some(bis_indices) => { |
| let mut bis_sync: BisSync = BisSync(0); |
| bis_sync.set_sync(bis_indices)?; |
| bis_sync |
| } |
| _ => BisSync::default(), |
| }; |
| subgroups.push(BigSubgroup::new(Some(bis_sync)).with_metadata(group.metadata.clone())); |
| } |
| Ok(subgroups) |
| } |
| } |
| |
| impl Decodable for BroadcastAudioSourceEndpoint { |
| type Error = PacketError; |
| |
| fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> { |
| if buf.len() < Self::MIN_PACKET_SIZE { |
| return Err(PacketError::UnexpectedDataLength); |
| } |
| |
| let mut idx = 0 as usize; |
| let presentation_delay = u32::from_le_bytes([buf[idx], buf[idx + 1], buf[idx + 2], 0x00]); |
| idx += 3; |
| |
| let num_big: usize = buf[idx] as usize; |
| idx += 1; |
| if num_big < 1 { |
| return Err(PacketError::InvalidParameter(format!( |
| "num of subgroups shall be at least 1 got {num_big}" |
| ))); |
| } |
| |
| let mut big = Vec::new(); |
| while big.len() < num_big { |
| let (group, len) = BroadcastIsochronousGroup::decode(&buf[idx..]) |
| .map_err(|e| PacketError::InvalidParameter(format!("{e}")))?; |
| big.push(group); |
| idx += len; |
| } |
| |
| Ok((Self { presentation_delay_ms: presentation_delay, big }, idx)) |
| } |
| } |
| |
| #[derive(Clone, Debug, PartialEq)] |
| pub struct BroadcastIsochronousGroup { |
| pub(crate) codec_id: CodecId, |
| pub(crate) codec_specific_config: Vec<CodecConfiguration>, |
| pub(crate) metadata: Vec<Metadata>, |
| pub(crate) bis: Vec<BroadcastIsochronousStream>, |
| } |
| |
| impl BroadcastIsochronousGroup { |
| // Should contain num BIS, codec id, codec specific config len, metadata len, |
| // and at least one BIS praram. |
| const MIN_PACKET_SIZE: usize = |
| 1 + CodecId::BYTE_SIZE + 1 + 1 + BroadcastIsochronousStream::MIN_PACKET_SIZE; |
| } |
| |
| impl Decodable for BroadcastIsochronousGroup { |
| type Error = PacketError; |
| |
| fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> { |
| if buf.len() < BroadcastIsochronousGroup::MIN_PACKET_SIZE { |
| return Err(PacketError::UnexpectedDataLength); |
| } |
| |
| let mut idx = 0; |
| let num_bis = buf[idx] as usize; |
| idx += 1; |
| if num_bis < 1 { |
| return Err(PacketError::InvalidParameter(format!( |
| "num of BIS shall be at least 1 got {num_bis}" |
| ))); |
| } |
| |
| let (codec_id, read_bytes) = CodecId::decode(&buf[idx..])?; |
| idx += read_bytes; |
| |
| let codec_config_len = buf[idx] as usize; |
| idx += 1; |
| |
| let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]); |
| if consumed != codec_config_len { |
| return Err(bt_common::packet_encoding::Error::UnexpectedDataLength); |
| } |
| |
| let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect(); |
| idx += codec_config_len; |
| |
| let metadata_len = buf[idx] as usize; |
| idx += 1; |
| |
| let (results_metadata, consumed_len) = Metadata::decode_all(&buf[idx..idx + metadata_len]); |
| if consumed_len != metadata_len { |
| return Err(PacketError::UnexpectedDataLength); |
| } |
| // Ignore any undecodable metadata types |
| let metadata = results_metadata.into_iter().filter_map(Result::ok).collect(); |
| idx += consumed_len; |
| |
| let mut bis = Vec::new(); |
| while bis.len() < num_bis { |
| let (stream, len) = BroadcastIsochronousStream::decode(&buf[idx..]) |
| .map_err(|e| PacketError::InvalidParameter(e.to_string()))?; |
| bis.push(stream); |
| idx += len; |
| } |
| |
| Ok(( |
| BroadcastIsochronousGroup { |
| codec_id, |
| codec_specific_config: codec_specific_configs, |
| metadata, |
| bis, |
| }, |
| idx, |
| )) |
| } |
| } |
| |
| #[derive(Clone, Debug, PartialEq)] |
| pub struct BroadcastIsochronousStream { |
| pub(crate) bis_index: u8, |
| pub(crate) codec_specific_config: Vec<CodecConfiguration>, |
| } |
| |
| impl BroadcastIsochronousStream { |
| const MIN_PACKET_SIZE: usize = 1 + 1; |
| } |
| |
| impl Decodable for BroadcastIsochronousStream { |
| type Error = PacketError; |
| |
| fn decode(buf: &[u8]) -> core::result::Result<(Self, usize), Self::Error> { |
| if buf.len() < BroadcastIsochronousStream::MIN_PACKET_SIZE { |
| return Err(PacketError::UnexpectedDataLength); |
| } |
| |
| let mut idx = 0; |
| |
| let bis_index = buf[idx]; |
| idx += 1; |
| |
| let codec_config_len = buf[idx] as usize; |
| idx += 1; |
| |
| let (results, consumed) = CodecConfiguration::decode_all(&buf[idx..idx + codec_config_len]); |
| if consumed != codec_config_len { |
| return Err(bt_common::packet_encoding::Error::UnexpectedDataLength); |
| } |
| let codec_specific_configs = results.into_iter().filter_map(Result::ok).collect(); |
| idx += codec_config_len; |
| |
| Ok(( |
| BroadcastIsochronousStream { bis_index, codec_specific_config: codec_specific_configs }, |
| idx, |
| )) |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::collections::HashSet; |
| |
| use super::*; |
| |
| use bt_common::generic_audio::codec_configuration::{FrameDuration, SamplingFrequency}; |
| use bt_common::generic_audio::AudioLocation; |
| |
| #[test] |
| fn decode_bis() { |
| #[rustfmt::skip] |
| let buf = [ |
| 0x01, 0x09, // bis index and codec specific config len |
| 0x02, 0x01, 0x06, // sampling frequency LTV |
| 0x05, 0x03, 0x03, 0x00, 0x00, 0x0C, // audio location LTV |
| ]; |
| |
| let (bis, _read_bytes) = |
| BroadcastIsochronousStream::decode(&buf[..]).expect("should not fail"); |
| assert_eq!( |
| bis, |
| BroadcastIsochronousStream { |
| bis_index: 0x01, |
| codec_specific_config: vec![ |
| CodecConfiguration::SamplingFrequency(SamplingFrequency::F32000Hz), |
| CodecConfiguration::AudioChannelAllocation(HashSet::from([ |
| AudioLocation::FrontLeft, |
| AudioLocation::FrontRight, |
| AudioLocation::LeftSurround, |
| AudioLocation::RightSurround |
| ])), |
| ], |
| } |
| ); |
| } |
| |
| #[test] |
| fn decode_big() { |
| #[rustfmt::skip] |
| let buf = [ |
| 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id |
| 0x04, 0x03, 0x04, 0x04, 0x10, // codec specific config len, octets per codec frame LTV |
| 0x03, 0x02, 0x08, 0x01, // metadata len, audio active state LTV |
| 0x01, 0x00, // bis index, codec specific config len (bis #1) |
| 0x02, 0x03, 0x02, 0x02, 0x01, // bis index, codec specific config len, frame duration LTV (bis #2) |
| ]; |
| |
| let (big, _read_bytes) = |
| BroadcastIsochronousGroup::decode(&buf[..]).expect("should not fail"); |
| assert_eq!( |
| big, |
| BroadcastIsochronousGroup { |
| codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent), |
| codec_specific_config: vec![CodecConfiguration::OctetsPerCodecFrame(0x1004),], |
| metadata: vec![Metadata::AudioActiveState(true)], |
| bis: vec![ |
| BroadcastIsochronousStream { bis_index: 0x01, codec_specific_config: vec![] }, |
| BroadcastIsochronousStream { |
| bis_index: 0x02, |
| codec_specific_config: vec![CodecConfiguration::FrameDuration( |
| FrameDuration::TenMs |
| )], |
| }, |
| ], |
| } |
| ); |
| } |
| |
| #[test] |
| fn decode_base() { |
| #[rustfmt::skip] |
| let buf = [ |
| 0x10, 0x20, 0x30, 0x02, // presentation delay, num of subgroups |
| 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id (big #1) |
| 0x00, // codec specific config len |
| 0x00, // metadata len, |
| 0x01, 0x00, // bis index, codec specific config len (big #1 / bis #1) |
| 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, // num of bis, codec id (big #2) |
| 0x00, // codec specific config len |
| 0x00, // metadata len, |
| 0x01, 0x03, 0x02, 0x05, 0x08, // bis index, codec specific config len, codec frame blocks LTV (big #2 / bis #2) |
| ]; |
| |
| let (base, _read_bytes) = |
| BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail"); |
| assert_eq!(base.presentation_delay_ms, 0x00302010); |
| assert_eq!(base.big.len(), 2); |
| assert_eq!( |
| base.big[0], |
| BroadcastIsochronousGroup { |
| codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Transparent), |
| codec_specific_config: vec![], |
| metadata: vec![], |
| bis: vec![BroadcastIsochronousStream { |
| bis_index: 0x01, |
| codec_specific_config: vec![], |
| },], |
| } |
| ); |
| assert_eq!( |
| base.big[1], |
| BroadcastIsochronousGroup { |
| codec_id: CodecId::Assigned(bt_common::core::CodingFormat::Cvsd), |
| codec_specific_config: vec![], |
| metadata: vec![], |
| bis: vec![BroadcastIsochronousStream { |
| bis_index: 0x01, |
| codec_specific_config: vec![CodecConfiguration::CodecFramesPerSdu(0x08)], |
| },], |
| } |
| ); |
| } |
| |
| #[test] |
| fn decode_base_complex() { |
| // BroadcastAudioSourceEndpoint { presentation_delay_ms: 20000, big: |
| // [BroadcastIsochronousGroup { codec_id: Assigned(Lc3), codec_specific_config: |
| // [SamplingFrequency(F24000Hz), FrameDuration(TenMs), |
| // AudioChannelAllocation({FrontLeft, FrontRight}), OctetsPerCodecFrame(60)], |
| // metadata: [StreamingAudioContexts([Media])], bis: |
| // [BroadcastIsochronousStream { bis_index: 1, codec_specific_config: |
| // [AudioChannelAllocation({FrontLeft})] }, BroadcastIsochronousStream { |
| // bis_index: 2, codec_specific_con$ |
| // ig: [AudioChannelAllocation({FrontRight})] }] }] } |
| #[rustfmt::skip] |
| let buf = [ |
| 0x20, 0x4e, 0x00, 0x01, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x05, 0x02, 0x02, 0x01, 0x05, |
| 0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x04, 0x3c, 0x00, 0x04, 0x03, 0x02, 0x04, 0x00, 0x01, 0x06, 0x05, 0x03, 0x01, |
| 0x00, 0x00, 0x00, 0x02, 0x06, 0x05, 0x03, 0x02, 0x00, 0x00, 0x00 |
| ]; |
| let (base, _read_bytes) = |
| BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail"); |
| println!("{base:?}"); |
| |
| // BroadcastAudioSourceEndpoint { presentation_delay_ms: 20000, big: |
| // [BroadcastIsochronousGroup { codec_id: Assigned(Lc3), codec_specific_config: |
| // [SamplingFrequency(F24000Hz), FrameDuration(TenMs), |
| // AudioChannelAllocation({FrontLeft}), OctetsPerCodecFrame(60)], |
| // metadata: [BroadcastAudioImmediateRenderingFlag], bis: |
| // [BroadcastIsochronousStream { bis_index: 1, codec_specific_config: |
| // [SamplingFrequency(F24000Hz)] }] }] } |
| #[rustfmt::skip] |
| let buf = [ |
| 0x20, 0x4e, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10, 0x02, 0x01, 0x05, |
| 0x02, 0x02, 0x01, 0x05, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03, 0x04, 0x3c, 0x00, 0x02, |
| 0x01, 0x09, 0x01, 0x03, 0x02, 0x01, 0x05, |
| ]; |
| let (base, _read_bytes) = |
| BroadcastAudioSourceEndpoint::decode(&buf[..]).expect("should not fail"); |
| println!("{base:?}"); |
| } |
| } |