| /* |
| * Copyright 2021 HIMSA II K/S - www.himsa.com. |
| * Represented by EHIMA - www.ehima.com |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <base/functional/bind.h> |
| #include <bluetooth/log.h> |
| #include <lc3.h> |
| |
| #include <mutex> |
| |
| #include "bta/include/bta_le_audio_broadcaster_api.h" |
| #include "bta/le_audio/broadcaster/state_machine.h" |
| #include "bta/le_audio/codec_interface.h" |
| #include "bta/le_audio/content_control_id_keeper.h" |
| #include "bta/le_audio/le_audio_types.h" |
| #include "bta/le_audio/le_audio_utils.h" |
| #include "bta/le_audio/metrics_collector.h" |
| #include "bta_le_audio_api.h" |
| #include "common/strings.h" |
| #include "hci/controller_interface.h" |
| #include "internal_include/stack_config.h" |
| #include "main/shim/entry.h" |
| #include "os/log.h" |
| #include "osi/include/properties.h" |
| #include "stack/include/bt_types.h" |
| #include "stack/include/btm_api_types.h" |
| #include "stack/include/btm_iso_api.h" |
| |
| using bluetooth::common::ToString; |
| using bluetooth::hci::IsoManager; |
| using bluetooth::hci::iso_manager::big_create_cmpl_evt; |
| using bluetooth::hci::iso_manager::big_terminate_cmpl_evt; |
| using bluetooth::hci::iso_manager::BigCallbacks; |
| using bluetooth::le_audio::BasicAudioAnnouncementData; |
| using bluetooth::le_audio::BasicAudioAnnouncementSubgroup; |
| using bluetooth::le_audio::BroadcastId; |
| using bluetooth::le_audio::CodecManager; |
| using bluetooth::le_audio::ContentControlIdKeeper; |
| using bluetooth::le_audio::DsaMode; |
| using bluetooth::le_audio::LeAudioCodecConfiguration; |
| using bluetooth::le_audio::LeAudioSourceAudioHalClient; |
| using bluetooth::le_audio::PublicBroadcastAnnouncementData; |
| using bluetooth::le_audio::broadcaster::BigConfig; |
| using bluetooth::le_audio::broadcaster::BroadcastConfiguration; |
| using bluetooth::le_audio::broadcaster::BroadcastQosConfig; |
| using bluetooth::le_audio::broadcaster::BroadcastStateMachine; |
| using bluetooth::le_audio::broadcaster::BroadcastStateMachineConfig; |
| using bluetooth::le_audio::broadcaster::BroadcastSubgroupCodecConfig; |
| using bluetooth::le_audio::broadcaster::IBroadcastStateMachineCallbacks; |
| using bluetooth::le_audio::types::AudioContexts; |
| using bluetooth::le_audio::types::CodecLocation; |
| using bluetooth::le_audio::types::kLeAudioCodingFormatLC3; |
| using bluetooth::le_audio::types::LeAudioContextType; |
| using bluetooth::le_audio::types::LeAudioLtvMap; |
| using bluetooth::le_audio::utils::GetAudioContextsFromSourceMetadata; |
| |
| using namespace bluetooth; |
| |
| namespace { |
| class LeAudioBroadcasterImpl; |
| LeAudioBroadcasterImpl* instance; |
| std::mutex instance_mutex; |
| |
| /* Class definitions */ |
| |
| /* LeAudioBroadcasterImpl class represents main implementation class for le |
| * audio broadcaster feature in the stack. |
| * |
| * This class may be bonded with Test socket which allows to drive an instance |
| * for test purposes. |
| */ |
| class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks { |
| enum class AudioDataPathState { |
| INACTIVE, |
| ACTIVE, |
| SUSPENDED, |
| }; |
| |
| public: |
| LeAudioBroadcasterImpl( |
| bluetooth::le_audio::LeAudioBroadcasterCallbacks* callbacks_) |
| : callbacks_(callbacks_), |
| current_phy_(PHY_LE_2M), |
| audio_data_path_state_(AudioDataPathState::INACTIVE), |
| le_audio_source_hal_client_(nullptr) { |
| log::info(""); |
| |
| /* Register State machine callbacks */ |
| BroadcastStateMachine::Initialize(&state_machine_callbacks_, |
| &state_machine_adv_callbacks_); |
| |
| GenerateBroadcastIds(); |
| } |
| |
| ~LeAudioBroadcasterImpl() override = default; |
| |
| void GenerateBroadcastIds(void) { |
| btsnd_hcic_ble_rand(base::Bind([](BT_OCTET8 rand) { |
| if (!instance) return; |
| |
| /* LE Rand returns 8 octets. Lets' make 2 outstanding Broadcast Ids out |
| * of it */ |
| for (int i = 0; i < 8; i += 4) { |
| BroadcastId broadcast_id = 0; |
| /* Broadcast ID should be 3 octets long (BAP v1.0 spec.) */ |
| STREAM_TO_UINT24(broadcast_id, rand); |
| if (broadcast_id == bluetooth::le_audio::kBroadcastIdInvalid) continue; |
| instance->available_broadcast_ids_.emplace_back(broadcast_id); |
| } |
| |
| if (instance->available_broadcast_ids_.empty()) { |
| log::fatal("Unable to generate proper broadcast identifiers."); |
| } |
| })); |
| } |
| |
| void CleanUp() { |
| log::info("Broadcaster"); |
| broadcasts_.clear(); |
| callbacks_ = nullptr; |
| is_iso_running_ = false; |
| |
| if (!LeAudioClient::IsLeAudioClientRunning()) |
| IsoManager::GetInstance()->Stop(); |
| |
| queued_start_broadcast_request_ = std::nullopt; |
| queued_create_broadcast_request_ = std::nullopt; |
| |
| if (le_audio_source_hal_client_) { |
| le_audio_source_hal_client_->Stop(); |
| auto result = |
| CodecManager::GetInstance()->UpdateActiveBroadcastAudioHalClient( |
| le_audio_source_hal_client_.get(), false); |
| log::assert_that(result, "Could not update session in codec manager"); |
| le_audio_source_hal_client_.reset(); |
| } |
| } |
| |
| void Stop() { |
| log::info("Broadcaster"); |
| |
| for (auto& sm_pair : broadcasts_) { |
| StopAudioBroadcast(sm_pair.first); |
| } |
| } |
| |
| static PublicBroadcastAnnouncementData preparePublicAnnouncement( |
| uint8_t features, const LeAudioLtvMap& metadata) { |
| PublicBroadcastAnnouncementData announcement; |
| |
| /* Prepare the announcement */ |
| announcement.features = features; |
| announcement.metadata = metadata.Values(); |
| return announcement; |
| } |
| |
| static BasicAudioAnnouncementData prepareBasicAnnouncement( |
| const std::vector<BroadcastSubgroupCodecConfig>& subgroup_configs, |
| const std::vector<LeAudioLtvMap>& metadata_group) { |
| BasicAudioAnnouncementData announcement; |
| |
| /* Prepare the announcement */ |
| announcement.presentation_delay_us = 40000; /* us */ |
| |
| log::assert_that(subgroup_configs.size() == metadata_group.size(), |
| "The number of metadata subgroups {} does not match the " |
| "number of subgroup configurations {}.", |
| metadata_group.size(), subgroup_configs.size()); |
| |
| uint8_t subgroup_idx = 0; |
| uint8_t bis_index = 0; |
| while (subgroup_idx < subgroup_configs.size() && |
| subgroup_idx < metadata_group.size()) { |
| const auto& subgroup_config = subgroup_configs.at(subgroup_idx); |
| const auto& metadata = metadata_group.at(subgroup_idx); |
| |
| auto const& codec_id = subgroup_config.GetLeAudioCodecId(); |
| auto const subgroup_codec_spec = |
| subgroup_config.GetCommonBisCodecSpecData(); |
| auto opt_vendor_spec_data = subgroup_config.GetVendorCodecSpecData(); |
| |
| /* Note: Currently we have a single audio source configured with a one |
| * set of codec/pcm parameters thus we can use a single subgroup |
| * for all the BISes. Configure common BIS codec params at the |
| * subgroup level. |
| */ |
| BasicAudioAnnouncementSubgroup config = { |
| .codec_config = |
| { |
| .codec_id = codec_id.coding_format, |
| .vendor_company_id = codec_id.vendor_company_id, |
| .vendor_codec_id = codec_id.vendor_codec_id, |
| .codec_specific_params = |
| opt_vendor_spec_data.has_value() |
| ? std::map<uint8_t, std::vector<uint8_t>>{} |
| : subgroup_codec_spec.Values(), |
| .vendor_codec_specific_params = |
| std::move(opt_vendor_spec_data), |
| }, |
| .metadata = metadata.Values(), |
| .bis_configs = {}, |
| }; |
| |
| for (uint8_t bis_cfg_idx = 0; |
| bis_cfg_idx < subgroup_config.GetAllBisConfigCount(); |
| ++bis_cfg_idx) { |
| auto bis_cfg_num_of_bises = subgroup_config.GetNumBis(bis_cfg_idx); |
| for (uint8_t bis_num = 0; bis_num < bis_cfg_num_of_bises; ++bis_num) { |
| // Internally BISes are indexed from 0 in each subgroup, but the BT |
| // spec requires the indices to start from 1 in the entire BIG. |
| ++bis_index; |
| |
| // Check for vendor byte array |
| bluetooth::le_audio::BasicAudioAnnouncementBisConfig bis_config; |
| auto vendor_config = |
| subgroup_config.GetBisVendorCodecSpecData(bis_num); |
| if (vendor_config) { |
| bis_config.vendor_codec_specific_params = vendor_config.value(); |
| } |
| |
| // Check for non vendor LTVs |
| auto config_ltv = |
| subgroup_config.GetBisCodecSpecData(bis_num, bis_cfg_idx); |
| if (config_ltv) { |
| // Remove the part which is common with the parent subgroup |
| // parameters |
| config_ltv->RemoveAllTypes(subgroup_codec_spec); |
| bis_config.codec_specific_params = config_ltv->Values(); |
| } |
| |
| bis_config.bis_index = bis_index; |
| config.bis_configs.push_back(std::move(bis_config)); |
| } |
| } |
| |
| announcement.subgroup_configs.push_back(config); |
| ++subgroup_idx; |
| } |
| |
| return announcement; |
| } |
| |
| void UpdateStreamingContextTypeOnAllSubgroups(const AudioContexts& contexts) { |
| log::debug("context_type_map={}", contexts.to_string()); |
| |
| auto ccids = ContentControlIdKeeper::GetInstance()->GetAllCcids(contexts); |
| if (ccids.empty()) { |
| log::warn("No content providers available for context_type_map={}.", |
| contexts.to_string()); |
| } |
| |
| std::vector<uint8_t> stream_context_vec(2); |
| auto pp = stream_context_vec.data(); |
| UINT16_TO_STREAM(pp, contexts.value()); |
| |
| for (auto const& kv_it : broadcasts_) { |
| auto& broadcast = kv_it.second; |
| if (broadcast->GetState() == BroadcastStateMachine::State::STREAMING) { |
| auto announcement = broadcast->GetBroadcastAnnouncement(); |
| bool broadcast_update = false; |
| |
| // Replace context type and CCID list |
| for (auto& subgroup : announcement.subgroup_configs) { |
| auto subgroup_ltv = LeAudioLtvMap(subgroup.metadata); |
| bool subgroup_update = false; |
| |
| auto existing_context = |
| subgroup_ltv.Find(bluetooth::le_audio::types:: |
| kLeAudioMetadataTypeStreamingAudioContext); |
| if (existing_context) { |
| if (memcmp(stream_context_vec.data(), existing_context->data(), |
| existing_context->size()) != 0) { |
| subgroup_ltv.Add(bluetooth::le_audio::types:: |
| kLeAudioMetadataTypeStreamingAudioContext, |
| stream_context_vec); |
| subgroup_update = true; |
| } |
| } else { |
| subgroup_ltv.Add(bluetooth::le_audio::types:: |
| kLeAudioMetadataTypeStreamingAudioContext, |
| stream_context_vec); |
| subgroup_update = true; |
| } |
| |
| auto existing_ccid_list = subgroup_ltv.Find( |
| bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList); |
| if (existing_ccid_list) { |
| if (ccids.empty()) { |
| subgroup_ltv.Remove( |
| bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList); |
| subgroup_update = true; |
| |
| } else if (!std::is_permutation(ccids.begin(), ccids.end(), |
| existing_ccid_list->begin())) { |
| subgroup_ltv.Add( |
| bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList, |
| ccids); |
| subgroup_update = true; |
| } |
| } else if (!ccids.empty()) { |
| subgroup_ltv.Add( |
| bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList, |
| ccids); |
| subgroup_update = true; |
| } |
| |
| if (subgroup_update) { |
| subgroup.metadata = subgroup_ltv.Values(); |
| broadcast_update = true; |
| } |
| } |
| |
| if (broadcast_update) { |
| broadcast->UpdateBroadcastAnnouncement(std::move(announcement)); |
| } |
| } |
| } |
| } |
| |
| void UpdateMetadata( |
| uint32_t broadcast_id, const std::string& broadcast_name, |
| const std::vector<uint8_t>& public_metadata, |
| const std::vector<std::vector<uint8_t>>& subgroup_metadata) override { |
| std::vector<LeAudioLtvMap> subgroup_ltvs; |
| |
| if (broadcasts_.count(broadcast_id) == 0) { |
| log::error("No such broadcast_id={}", broadcast_id); |
| return; |
| } |
| |
| log::info("For broadcast_id={}", broadcast_id); |
| |
| for (const std::vector<uint8_t>& metadata : subgroup_metadata) { |
| /* Prepare the announcement format */ |
| bool is_metadata_valid; |
| auto ltv = LeAudioLtvMap::Parse(metadata.data(), metadata.size(), is_metadata_valid); |
| if (!is_metadata_valid) { |
| log::error("Invalid metadata provided."); |
| return; |
| } |
| |
| auto context_type = AudioContexts(LeAudioContextType::MEDIA); |
| |
| /* Adds multiple contexts and CCIDs regardless of the incoming audio |
| * context. Android has only two CCIDs, one for Media and one for |
| * Conversational context. Even though we are not broadcasting |
| * Conversational streams, some PTS test cases wants multiple CCIDs. |
| */ |
| if (stack_config_get_interface() |
| ->get_pts_force_le_audio_multiple_contexts_metadata()) { |
| context_type = |
| LeAudioContextType::MEDIA | LeAudioContextType::CONVERSATIONAL; |
| auto stream_context_vec = |
| ltv.Find(bluetooth::le_audio::types:: |
| kLeAudioMetadataTypeStreamingAudioContext); |
| if (stream_context_vec) { |
| auto pp = stream_context_vec.value().data(); |
| if (stream_context_vec.value().size() < 2) { |
| log::error("stream_context_vec.value() size < 2"); |
| return; |
| } |
| UINT16_TO_STREAM(pp, context_type.value()); |
| } |
| } |
| |
| auto stream_context_vec = |
| ltv.Find(bluetooth::le_audio::types:: |
| kLeAudioMetadataTypeStreamingAudioContext); |
| if (stream_context_vec) { |
| auto pp = stream_context_vec.value().data(); |
| if (stream_context_vec.value().size() < 2) { |
| log::error("stream_context_vec.value() size < 2"); |
| return; |
| } |
| STREAM_TO_UINT16(context_type.value_ref(), pp); |
| } |
| |
| // Append the CCID list |
| auto ccid_vec = |
| ContentControlIdKeeper::GetInstance()->GetAllCcids(context_type); |
| if (!ccid_vec.empty()) { |
| ltv.Add(bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList, |
| ccid_vec); |
| } |
| |
| // Push to subgroup ltvs |
| subgroup_ltvs.push_back(ltv); |
| } |
| |
| if (broadcasts_[broadcast_id]->IsPublicBroadcast()) { |
| // Only update broadcast name and public metadata if current broadcast is |
| // public Otherwise ignore those fields |
| bool is_public_metadata_valid; |
| LeAudioLtvMap public_ltv = |
| LeAudioLtvMap::Parse(public_metadata.data(), public_metadata.size(), |
| is_public_metadata_valid); |
| if (!is_public_metadata_valid) { |
| log::error("Invalid public metadata provided."); |
| return; |
| } |
| PublicBroadcastAnnouncementData pb_announcement = |
| preparePublicAnnouncement(broadcasts_[broadcast_id] |
| ->GetPublicBroadcastAnnouncement() |
| .features, |
| public_ltv); |
| |
| broadcasts_[broadcast_id]->UpdatePublicBroadcastAnnouncement( |
| broadcast_id, broadcast_name, pb_announcement); |
| } |
| |
| auto& subgroup_configs = broadcasts_[broadcast_id]->GetCodecConfig(); |
| BasicAudioAnnouncementData announcement = |
| prepareBasicAnnouncement(subgroup_configs, subgroup_ltvs); |
| |
| broadcasts_[broadcast_id]->UpdateBroadcastAnnouncement( |
| std::move(announcement)); |
| } |
| |
| /* Choose the dominating audio context when multiple contexts are mixed */ |
| LeAudioContextType ChooseConfigurationContextType( |
| AudioContexts audio_contexts) { |
| log::debug("Got contexts={}", bluetooth::common::ToString(audio_contexts)); |
| |
| /* Prioritize the most common use cases. */ |
| if (audio_contexts.any()) { |
| LeAudioContextType context_priority_list[] = { |
| LeAudioContextType::LIVE, LeAudioContextType::GAME, |
| LeAudioContextType::MEDIA, LeAudioContextType::EMERGENCYALARM, |
| LeAudioContextType::ALERTS, LeAudioContextType::INSTRUCTIONAL, |
| LeAudioContextType::NOTIFICATIONS, LeAudioContextType::SOUNDEFFECTS, |
| }; |
| for (auto ct : context_priority_list) { |
| if (audio_contexts.test(ct)) { |
| log::debug("Selecting configuration context type: {}", ToString(ct)); |
| return ct; |
| } |
| } |
| } |
| |
| auto fallback_config = LeAudioContextType::MEDIA; |
| log::debug("Selecting configuration context type: {}", |
| ToString(fallback_config)); |
| return fallback_config; |
| } |
| |
| void CreateAudioBroadcast( |
| bool is_public, const std::string& broadcast_name, |
| const std::optional<bluetooth::le_audio::BroadcastCode>& broadcast_code, |
| const std::vector<uint8_t>& public_metadata, |
| const std::vector<uint8_t>& subgroup_quality, |
| const std::vector<std::vector<uint8_t>>& subgroup_metadata) override { |
| uint8_t public_features = 0; |
| LeAudioLtvMap public_ltv; |
| std::vector<LeAudioLtvMap> subgroup_ltvs; |
| |
| if (queued_create_broadcast_request_) { |
| log::error("Not processed yet queued broadcast"); |
| callbacks_->OnBroadcastCreated(bluetooth::le_audio::kBroadcastIdInvalid, |
| false); |
| return; |
| } |
| |
| if (is_public) { |
| // Prepare public broadcast announcement format |
| bool is_metadata_valid; |
| public_ltv = LeAudioLtvMap::Parse( |
| public_metadata.data(), public_metadata.size(), is_metadata_valid); |
| if (!is_metadata_valid) { |
| log::error("Invalid metadata provided."); |
| callbacks_->OnBroadcastCreated(bluetooth::le_audio::kBroadcastIdInvalid, |
| false); |
| return; |
| } |
| // Prepare public features byte |
| // bit 0 Encryption broadcast stream encrypted or not |
| // bit 1 Standard quality audio configuration present or not |
| // bit 2 High quality audio configuration present or not |
| // bit 3-7 RFU |
| public_features = static_cast<uint8_t>(broadcast_code ? 1 : 0); |
| } |
| |
| auto broadcast_id = available_broadcast_ids_.back(); |
| available_broadcast_ids_.pop_back(); |
| if (available_broadcast_ids_.size() == 0) GenerateBroadcastIds(); |
| |
| auto context_type = AudioContexts(LeAudioContextType::MEDIA); |
| |
| /* Adds multiple contexts and CCIDs regardless of the incoming audio |
| * context. Android has only two CCIDs, one for Media and one for |
| * Conversational context. Even though we are not broadcasting |
| * Conversational streams, some PTS test cases wants multiple CCIDs. |
| */ |
| if (stack_config_get_interface() |
| ->get_pts_force_le_audio_multiple_contexts_metadata()) { |
| context_type = |
| LeAudioContextType::MEDIA | LeAudioContextType::CONVERSATIONAL; |
| } |
| |
| for (const uint8_t quality : subgroup_quality) { |
| if (quality == bluetooth::le_audio::QUALITY_STANDARD) { |
| public_features |= bluetooth::le_audio::kLeAudioQualityStandard; |
| } else if (quality == bluetooth::le_audio::QUALITY_HIGH) { |
| public_features |= bluetooth::le_audio::kLeAudioQualityHigh; |
| } |
| } |
| |
| for (const std::vector<uint8_t>& metadata : subgroup_metadata) { |
| /* Prepare the announcement format */ |
| bool is_metadata_valid; |
| auto ltv = LeAudioLtvMap::Parse(metadata.data(), metadata.size(), is_metadata_valid); |
| if (!is_metadata_valid) { |
| log::error("Invalid metadata provided."); |
| callbacks_->OnBroadcastCreated(bluetooth::le_audio::kBroadcastIdInvalid, |
| false); |
| return; |
| } |
| |
| if (stack_config_get_interface() |
| ->get_pts_force_le_audio_multiple_contexts_metadata()) { |
| auto stream_context_vec = |
| ltv.Find(bluetooth::le_audio::types:: |
| kLeAudioMetadataTypeStreamingAudioContext); |
| if (stream_context_vec) { |
| if (stream_context_vec.value().size() < 2) { |
| log::error("kLeAudioMetadataTypeStreamingAudioContext size < 2"); |
| callbacks_->OnBroadcastCreated( |
| bluetooth::le_audio::kBroadcastIdInvalid, false); |
| return; |
| } |
| auto pp = stream_context_vec.value().data(); |
| UINT16_TO_STREAM(pp, context_type.value()); |
| } |
| } |
| |
| auto stream_context_vec = |
| ltv.Find(bluetooth::le_audio::types:: |
| kLeAudioMetadataTypeStreamingAudioContext); |
| if (stream_context_vec) { |
| if (stream_context_vec.value().size() < 2) { |
| log::error("kLeAudioMetadataTypeStreamingAudioContext size < 2"); |
| callbacks_->OnBroadcastCreated( |
| bluetooth::le_audio::kBroadcastIdInvalid, false); |
| return; |
| } |
| |
| auto pp = stream_context_vec.value().data(); |
| STREAM_TO_UINT16(context_type.value_ref(), pp); |
| } |
| |
| // Append the CCID list |
| auto ccid_vec = |
| ContentControlIdKeeper::GetInstance()->GetAllCcids(context_type); |
| if (!ccid_vec.empty()) { |
| ltv.Add(bluetooth::le_audio::types::kLeAudioMetadataTypeCcidList, |
| ccid_vec); |
| } |
| |
| // Push to subgroup ltvs |
| subgroup_ltvs.push_back(ltv); |
| } |
| |
| // Prepare the configuration requirements for each subgroup. |
| // Note: For now, each subgroup contains exactly the same content, but |
| // differs in codec configuration. |
| CodecManager::BroadcastConfigurationRequirements requirements; |
| for (auto& idx : subgroup_quality) { |
| requirements.subgroup_quality.push_back( |
| {ChooseConfigurationContextType(context_type), idx}); |
| } |
| |
| if (!le_audio_source_hal_client_) { |
| le_audio_source_hal_client_ = |
| LeAudioSourceAudioHalClient::AcquireBroadcast(); |
| if (!le_audio_source_hal_client_) { |
| log::error("Could not acquire le audio"); |
| return; |
| } |
| auto result = |
| CodecManager::GetInstance()->UpdateActiveBroadcastAudioHalClient( |
| le_audio_source_hal_client_.get(), true); |
| log::assert_that(result, "Could not update session in codec manager"); |
| } |
| |
| auto config = CodecManager::GetInstance()->GetBroadcastConfig(requirements); |
| if (!config) { |
| log::error("No valid broadcast offload config"); |
| callbacks_->OnBroadcastCreated(bluetooth::le_audio::kBroadcastIdInvalid, |
| false); |
| return; |
| } |
| |
| if (public_features & bluetooth::le_audio::kLeAudioQualityHigh && |
| config->GetSamplingFrequencyHzMax() < 48000) { |
| log::warn( |
| "Preferred quality isn't supported. Fallback to standard audio " |
| "quality"); |
| public_features &= (0xFFFF & ~bluetooth::le_audio::kLeAudioQualityHigh); |
| public_features |= bluetooth::le_audio::kLeAudioQualityStandard; |
| } |
| |
| BroadcastStateMachineConfig msg = { |
| .is_public = is_public, |
| .broadcast_id = broadcast_id, |
| .broadcast_name = broadcast_name, |
| .streaming_phy = GetStreamingPhy(), |
| .config = *config, |
| .announcement = |
| prepareBasicAnnouncement(config->subgroups, subgroup_ltvs), |
| .broadcast_code = std::move(broadcast_code)}; |
| if (is_public) { |
| msg.public_announcement = |
| preparePublicAnnouncement(public_features, public_ltv); |
| } |
| |
| // If there is ongoing ISO traffic, it might be a unicast stream |
| if (is_iso_running_) { |
| log::info("Iso is still active. Queueing broadcast creation for later."); |
| if (queued_create_broadcast_request_) { |
| log::warn( |
| "Already queued. Updating queued broadcast creation with the new " |
| "configuration."); |
| } |
| queued_create_broadcast_request_ = std::move(msg); |
| return; |
| } |
| |
| InstantiateBroadcast(std::move(msg)); |
| } |
| |
| void InstantiateBroadcast(BroadcastStateMachineConfig msg) { |
| log::info("CreateAudioBroadcast"); |
| |
| /* Put the new broadcast on the initialization queue, notify the error and |
| * drop the pending broadcast data if init fails. |
| */ |
| pending_broadcasts_.push_back( |
| BroadcastStateMachine::CreateInstance(std::move(msg))); |
| if (!pending_broadcasts_.back()->Initialize()) { |
| pending_broadcasts_.pop_back(); |
| callbacks_->OnBroadcastCreated(bluetooth::le_audio::kBroadcastIdInvalid, |
| false); |
| } |
| } |
| |
| void SuspendAudioBroadcast(uint32_t broadcast_id) override { |
| log::info("broadcast_id={}", broadcast_id); |
| |
| if (broadcasts_.count(broadcast_id) != 0) { |
| log::info("Stopping AudioHalClient"); |
| if (le_audio_source_hal_client_) le_audio_source_hal_client_->Stop(); |
| broadcasts_[broadcast_id]->SetMuted(true); |
| broadcasts_[broadcast_id]->ProcessMessage( |
| BroadcastStateMachine::Message::SUSPEND, nullptr); |
| } else { |
| log::error("No such broadcast_id={}", broadcast_id); |
| } |
| } |
| |
| static bool IsAnyoneStreaming() { |
| if (!instance) return false; |
| |
| auto const& iter = |
| std::find_if(instance->broadcasts_.cbegin(), |
| instance->broadcasts_.cend(), [](auto const& sm) { |
| return sm.second->GetState() == |
| BroadcastStateMachine::State::STREAMING; |
| }); |
| return (iter != instance->broadcasts_.cend()); |
| } |
| |
| void StartAudioBroadcast(uint32_t broadcast_id) override { |
| log::info("Starting broadcast_id={}", broadcast_id); |
| |
| if (queued_start_broadcast_request_) { |
| log::error("Not processed yet start broadcast request"); |
| return; |
| } |
| |
| if (is_iso_running_) { |
| queued_start_broadcast_request_ = broadcast_id; |
| return; |
| } |
| |
| if (IsAnyoneStreaming()) { |
| log::error("Stop the other broadcast first!"); |
| return; |
| } |
| |
| if (broadcasts_.count(broadcast_id) != 0) { |
| if (!le_audio_source_hal_client_) { |
| le_audio_source_hal_client_ = |
| LeAudioSourceAudioHalClient::AcquireBroadcast(); |
| if (!le_audio_source_hal_client_) { |
| log::error("Could not acquire le audio"); |
| return; |
| } |
| |
| auto result = |
| CodecManager::GetInstance()->UpdateActiveBroadcastAudioHalClient( |
| le_audio_source_hal_client_.get(), true); |
| log::assert_that(result, "Could not update session in codec manager"); |
| } |
| |
| broadcasts_[broadcast_id]->ProcessMessage( |
| BroadcastStateMachine::Message::START, nullptr); |
| bluetooth::le_audio::MetricsCollector::Get()->OnBroadcastStateChanged( |
| true); |
| } else { |
| log::error("No such broadcast_id={}", broadcast_id); |
| } |
| } |
| |
| void StopAudioBroadcast(uint32_t broadcast_id) override { |
| if (broadcasts_.count(broadcast_id) == 0) { |
| log::error("no such broadcast_id={}", broadcast_id); |
| return; |
| } |
| |
| log::info("Stopping AudioHalClient, broadcast_id={}", broadcast_id); |
| |
| if (le_audio_source_hal_client_) le_audio_source_hal_client_->Stop(); |
| broadcasts_[broadcast_id]->SetMuted(true); |
| broadcasts_[broadcast_id]->ProcessMessage( |
| BroadcastStateMachine::Message::STOP, nullptr); |
| bluetooth::le_audio::MetricsCollector::Get()->OnBroadcastStateChanged( |
| false); |
| } |
| |
| void DestroyAudioBroadcast(uint32_t broadcast_id) override { |
| log::info("Destroying broadcast_id={}", broadcast_id); |
| broadcasts_.erase(broadcast_id); |
| |
| if (broadcasts_.empty() && le_audio_source_hal_client_) { |
| auto result = |
| CodecManager::GetInstance()->UpdateActiveBroadcastAudioHalClient( |
| le_audio_source_hal_client_.get(), false); |
| log::assert_that(result, "Could not update session in codec manager"); |
| le_audio_source_hal_client_.reset(); |
| } |
| } |
| |
| std::optional<bluetooth::le_audio::BroadcastMetadata> GetBroadcastMetadataOpt( |
| bluetooth::le_audio::BroadcastId broadcast_id) { |
| bluetooth::le_audio::BroadcastMetadata metadata; |
| for (auto const& kv_it : broadcasts_) { |
| if (kv_it.second->GetBroadcastId() == broadcast_id) { |
| metadata.is_public = kv_it.second->IsPublicBroadcast(); |
| metadata.broadcast_id = kv_it.second->GetBroadcastId(); |
| metadata.broadcast_name = kv_it.second->GetBroadcastName(); |
| metadata.adv_sid = kv_it.second->GetAdvertisingSid(); |
| metadata.pa_interval = kv_it.second->GetPaInterval(); |
| metadata.addr = kv_it.second->GetOwnAddress(); |
| metadata.addr_type = kv_it.second->GetOwnAddressType(); |
| metadata.broadcast_code = kv_it.second->GetBroadcastCode(); |
| metadata.basic_audio_announcement = |
| kv_it.second->GetBroadcastAnnouncement(); |
| metadata.public_announcement = |
| kv_it.second->GetPublicBroadcastAnnouncement(); |
| return metadata; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| void GetBroadcastMetadata(uint32_t broadcast_id) override { |
| if (broadcasts_.count(broadcast_id) == 0) { |
| log::error("No such broadcast_id={}", broadcast_id); |
| return; |
| } |
| |
| auto meta = GetBroadcastMetadataOpt(broadcast_id); |
| if (!meta) { |
| log::error("No metadata for broadcast_id={}", broadcast_id); |
| return; |
| } |
| callbacks_->OnBroadcastMetadataChanged(broadcast_id, |
| std::move(meta.value())); |
| } |
| |
| void GetAllBroadcastStates(void) override { |
| for (auto const& kv_it : broadcasts_) { |
| callbacks_->OnBroadcastStateChanged( |
| kv_it.second->GetBroadcastId(), |
| static_cast<bluetooth::le_audio::BroadcastState>( |
| kv_it.second->GetState())); |
| } |
| } |
| |
| void IsValidBroadcast( |
| uint32_t broadcast_id, uint8_t addr_type, RawAddress addr, |
| base::Callback<void(uint8_t /* broadcast_id */, uint8_t /* addr_type */, |
| RawAddress /* addr */, bool /* is_local */)> |
| cb) override { |
| if (broadcasts_.count(broadcast_id) == 0) { |
| log::error("No such broadcast_id={}", broadcast_id); |
| std::move(cb).Run(broadcast_id, addr_type, addr, false); |
| return; |
| } |
| |
| broadcasts_[broadcast_id]->RequestOwnAddress(base::Bind( |
| [](uint32_t broadcast_id, uint8_t req_address_type, |
| RawAddress req_address, |
| base::Callback<void(uint8_t /* broadcast_id */, |
| uint8_t /* addr_type */, RawAddress /* addr */, |
| bool /* is_local */)> |
| cb, |
| uint8_t rcv_address_type, RawAddress rcv_address) { |
| bool is_local = (req_address_type == rcv_address_type) && |
| (req_address == rcv_address); |
| std::move(cb).Run(broadcast_id, req_address_type, req_address, |
| is_local); |
| }, |
| broadcast_id, addr_type, addr, std::move(cb))); |
| } |
| |
| void SetStreamingPhy(uint8_t phy) override { current_phy_ = phy; } |
| |
| uint8_t GetStreamingPhy(void) const override { return current_phy_; } |
| |
| BroadcastId BroadcastIdFromBigHandle(uint8_t big_handle) const { |
| auto pair_it = |
| std::find_if(broadcasts_.begin(), broadcasts_.end(), |
| [big_handle](auto const& entry) { |
| return entry.second->GetAdvertisingSid() == big_handle; |
| }); |
| if (pair_it != broadcasts_.end()) { |
| return pair_it->second->GetBroadcastId(); |
| } |
| return bluetooth::le_audio::kBroadcastIdInvalid; |
| } |
| |
| void OnSetupIsoDataPath(uint8_t status, uint16_t conn_handle, |
| uint8_t big_handle) override { |
| auto broadcast_id = BroadcastIdFromBigHandle(big_handle); |
| log::assert_that(broadcasts_.count(broadcast_id) != 0, |
| "assert failed: broadcasts_.count(broadcast_id) != 0"); |
| broadcasts_[broadcast_id]->OnSetupIsoDataPath(status, conn_handle); |
| } |
| |
| void OnRemoveIsoDataPath(uint8_t status, uint16_t conn_handle, |
| uint8_t big_handle) override { |
| auto broadcast_id = BroadcastIdFromBigHandle(big_handle); |
| log::assert_that(broadcasts_.count(broadcast_id) != 0, |
| "assert failed: broadcasts_.count(broadcast_id) != 0"); |
| broadcasts_[broadcast_id]->OnRemoveIsoDataPath(status, conn_handle); |
| } |
| |
| void OnBigEvent(uint8_t event, void* data) override { |
| switch (event) { |
| case bluetooth::hci::iso_manager::kIsoEventBigOnCreateCmpl: { |
| auto* evt = static_cast<big_create_cmpl_evt*>(data); |
| auto broadcast_id = BroadcastIdFromBigHandle(evt->big_id); |
| log::assert_that(broadcasts_.count(broadcast_id) != 0, |
| "assert failed: broadcasts_.count(broadcast_id) != 0"); |
| broadcasts_[broadcast_id]->HandleHciEvent(HCI_BLE_CREATE_BIG_CPL_EVT, |
| evt); |
| |
| } break; |
| case bluetooth::hci::iso_manager::kIsoEventBigOnTerminateCmpl: { |
| auto* evt = static_cast<big_terminate_cmpl_evt*>(data); |
| auto broadcast_id = BroadcastIdFromBigHandle(evt->big_id); |
| log::assert_that(broadcasts_.count(broadcast_id) != 0, |
| "assert failed: broadcasts_.count(broadcast_id) != 0"); |
| broadcasts_[broadcast_id]->HandleHciEvent(HCI_BLE_TERM_BIG_CPL_EVT, |
| evt); |
| auto result = |
| CodecManager::GetInstance()->UpdateActiveBroadcastAudioHalClient( |
| le_audio_source_hal_client_.get(), false); |
| log::assert_that(result, "Could not update session in codec manager"); |
| le_audio_source_hal_client_.reset(); |
| } break; |
| default: |
| log::error("Invalid event={}", event); |
| } |
| } |
| |
| void IsoTrafficEventCb(bool is_active) { |
| is_iso_running_ = is_active; |
| log::info("is_iso_running: {}", is_iso_running_); |
| if (!is_iso_running_) { |
| if (queued_start_broadcast_request_) { |
| auto broadcast_id = *queued_start_broadcast_request_; |
| queued_start_broadcast_request_ = std::nullopt; |
| |
| log::info("Start queued broadcast."); |
| StartAudioBroadcast(broadcast_id); |
| } |
| if (queued_create_broadcast_request_) { |
| auto broadcast_msg = std::move(*queued_create_broadcast_request_); |
| queued_create_broadcast_request_ = std::nullopt; |
| |
| log::info("Create queued broadcast."); |
| InstantiateBroadcast(std::move(broadcast_msg)); |
| } |
| } |
| } |
| |
| void Dump(int fd) { |
| std::stringstream stream; |
| |
| stream << " Number of broadcasts: " << broadcasts_.size() << "\n"; |
| for (auto& broadcast_pair : broadcasts_) { |
| auto& broadcast = broadcast_pair.second; |
| if (broadcast) stream << *broadcast; |
| } |
| |
| dprintf(fd, "%s", stream.str().c_str()); |
| } |
| |
| private: |
| static class BroadcastStateMachineCallbacks |
| : public IBroadcastStateMachineCallbacks { |
| void OnStateMachineCreateStatus(uint32_t broadcast_id, |
| bool initialized) override { |
| auto pending_broadcast = std::find_if( |
| instance->pending_broadcasts_.begin(), |
| instance->pending_broadcasts_.end(), [broadcast_id](auto& sm) { |
| return (sm->GetBroadcastId() == broadcast_id); |
| }); |
| log::assert_that(pending_broadcast != instance->pending_broadcasts_.end(), |
| "assert failed: pending_broadcast != " |
| "instance->pending_broadcasts_.end()"); |
| log::assert_that( |
| instance->broadcasts_.count(broadcast_id) == 0, |
| "assert failed: instance->broadcasts_.count(broadcast_id) == 0"); |
| |
| if (initialized) { |
| const uint32_t broadcast_id = (*pending_broadcast)->GetBroadcastId(); |
| log::info("broadcast_id={} state={}", broadcast_id, |
| ToString((*pending_broadcast)->GetState())); |
| |
| instance->broadcasts_[broadcast_id] = std::move(*pending_broadcast); |
| } else { |
| log::error("Failed creating broadcast!"); |
| } |
| instance->pending_broadcasts_.erase(pending_broadcast); |
| instance->callbacks_->OnBroadcastCreated(broadcast_id, initialized); |
| } |
| |
| void OnStateMachineDestroyed(uint32_t broadcast_id) override { |
| /* This is a special case when state machine destructor calls this |
| * callback. It may happen during the Cleanup() call when all state |
| * machines are erased and instance can already be set to null to avoid |
| * unnecessary calls. |
| */ |
| if (instance) instance->callbacks_->OnBroadcastDestroyed(broadcast_id); |
| } |
| |
| static int getStreamerCount() { |
| return std::count_if(instance->broadcasts_.begin(), |
| instance->broadcasts_.end(), [](auto const& sm) { |
| log::verbose("broadcast_id={}, state={}", |
| sm.second->GetBroadcastId(), |
| ToString(sm.second->GetState())); |
| return sm.second->GetState() == |
| BroadcastStateMachine::State::STREAMING; |
| }); |
| } |
| |
| void OnStateMachineEvent(uint32_t broadcast_id, |
| BroadcastStateMachine::State state, |
| const void* data) override { |
| log::info("broadcast_id={} state={}", broadcast_id, ToString(state)); |
| |
| switch (state) { |
| case BroadcastStateMachine::State::STOPPED: |
| /* Pass through */ |
| case BroadcastStateMachine::State::CONFIGURING: |
| /* Pass through */ |
| case BroadcastStateMachine::State::CONFIGURED: |
| /* Pass through */ |
| case BroadcastStateMachine::State::STOPPING: |
| /* Nothing to do here? */ |
| break; |
| case BroadcastStateMachine::State::STREAMING: |
| if (getStreamerCount() == 1) { |
| log::info("Starting AudioHalClient"); |
| |
| if (instance->broadcasts_.count(broadcast_id) != 0) { |
| const auto& broadcast = instance->broadcasts_.at(broadcast_id); |
| const auto& broadcast_config = broadcast->GetBroadcastConfig(); |
| |
| // Reconfigure encoder instances for the new stream requirements |
| audio_receiver_.CheckAndReconfigureEncoders(broadcast_config); |
| |
| broadcast->SetMuted(false); |
| auto is_started = instance->le_audio_source_hal_client_->Start( |
| broadcast_config.GetAudioHalClientConfig(), &audio_receiver_); |
| if (!is_started) { |
| /* Audio Source setup failed - stop the broadcast */ |
| instance->StopAudioBroadcast(broadcast_id); |
| return; |
| } |
| |
| instance->audio_data_path_state_ = AudioDataPathState::ACTIVE; |
| } |
| } |
| break; |
| }; |
| |
| instance->callbacks_->OnBroadcastStateChanged( |
| broadcast_id, |
| static_cast<bluetooth::le_audio::BroadcastState>(state)); |
| } |
| |
| void OnOwnAddressResponse(uint32_t broadcast_id, uint8_t addr_type, |
| RawAddress addr) override { |
| /* Not used currently */ |
| } |
| |
| void OnBigCreated(const std::vector<uint16_t>& conn_handle) { |
| CodecManager::GetInstance()->UpdateBroadcastConnHandle( |
| conn_handle, |
| std::bind( |
| &LeAudioSourceAudioHalClient::UpdateBroadcastAudioConfigToHal, |
| instance->le_audio_source_hal_client_.get(), |
| std::placeholders::_1)); |
| } |
| } state_machine_callbacks_; |
| |
| static class BroadcastAdvertisingCallbacks : public AdvertisingCallbacks { |
| void OnAdvertisingSetStarted(int reg_id, uint8_t advertiser_id, |
| int8_t tx_power, uint8_t status) { |
| if (!instance) return; |
| |
| if (reg_id == BroadcastStateMachine::kLeAudioBroadcastRegId && |
| !instance->pending_broadcasts_.empty()) { |
| instance->pending_broadcasts_.back()->OnCreateAnnouncement( |
| advertiser_id, tx_power, status); |
| } else { |
| log::warn( |
| "Ignored OnAdvertisingSetStarted callback reg_id:{} " |
| "advertiser_id:{}", |
| reg_id, advertiser_id); |
| } |
| } |
| |
| void OnAdvertisingEnabled(uint8_t advertiser_id, bool enable, |
| uint8_t status) { |
| if (!instance) return; |
| |
| auto const& iter = std::find_if( |
| instance->broadcasts_.cbegin(), instance->broadcasts_.cend(), |
| [advertiser_id](auto const& sm) { |
| return sm.second->GetAdvertisingSid() == advertiser_id; |
| }); |
| if (iter != instance->broadcasts_.cend()) { |
| iter->second->OnEnableAnnouncement(enable, status); |
| } else { |
| log::warn("Ignored OnAdvertisingEnabled callback advertiser_id:{}", |
| advertiser_id); |
| } |
| } |
| |
| void OnAdvertisingDataSet(uint8_t advertiser_id, uint8_t status) { |
| log::warn( |
| "Not being used, ignored OnAdvertisingDataSet callback " |
| "advertiser_id:{}", |
| advertiser_id); |
| } |
| |
| void OnScanResponseDataSet(uint8_t advertiser_id, uint8_t status) { |
| log::warn( |
| "Not being used, ignored OnScanResponseDataSet callback " |
| "advertiser_id:{}", |
| advertiser_id); |
| } |
| |
| void OnAdvertisingParametersUpdated(uint8_t advertiser_id, int8_t tx_power, |
| uint8_t status) { |
| log::warn( |
| "Not being used, ignored OnAdvertisingParametersUpdated callback " |
| "advertiser_id:{}", |
| advertiser_id); |
| } |
| |
| void OnPeriodicAdvertisingParametersUpdated(uint8_t advertiser_id, |
| uint8_t status) { |
| log::warn( |
| "Not being used, ignored OnPeriodicAdvertisingParametersUpdated " |
| "callback advertiser_id:{}", |
| advertiser_id); |
| } |
| |
| void OnPeriodicAdvertisingDataSet(uint8_t advertiser_id, uint8_t status) { |
| log::warn( |
| "Not being used, ignored OnPeriodicAdvertisingDataSet callback " |
| "advertiser_id:{}", |
| advertiser_id); |
| } |
| |
| void OnPeriodicAdvertisingEnabled(uint8_t advertiser_id, bool enable, |
| uint8_t status) { |
| log::warn( |
| "Not being used, ignored OnPeriodicAdvertisingEnabled callback " |
| "advertiser_id:{}", |
| advertiser_id); |
| } |
| |
| void OnOwnAddressRead(uint8_t advertiser_id, uint8_t address_type, |
| RawAddress address) { |
| log::warn( |
| "Not being used, ignored OnOwnAddressRead callback advertiser_id:{}", |
| advertiser_id); |
| } |
| } state_machine_adv_callbacks_; |
| |
| static class LeAudioSourceCallbacksImpl |
| : public LeAudioSourceAudioHalClient::Callbacks { |
| public: |
| LeAudioSourceCallbacksImpl() = default; |
| void CheckAndReconfigureEncoders( |
| const BroadcastConfiguration& broadcast_config) { |
| /* TODO: Move software codec instance management to the Codec Manager */ |
| if (CodecManager::GetInstance()->GetCodecLocation() == |
| CodecLocation::ADSP) { |
| return; |
| } |
| |
| auto codec_config = broadcast_config.GetAudioHalClientConfig(); |
| |
| /* Note: Currently we support only a single subgroup software encoding. |
| * In future consider mirroring the same data in a different quality |
| * subgroups. |
| */ |
| auto const& subgroup_config = broadcast_config.subgroups.at(0); |
| |
| auto const& codec_id = subgroup_config.GetLeAudioCodecId(); |
| /* TODO: We should act smart and reuse current configurations */ |
| sw_enc_.clear(); |
| while (sw_enc_.size() != subgroup_config.GetNumChannelsTotal()) { |
| auto codec = |
| bluetooth::le_audio::CodecInterface::CreateInstance(codec_id); |
| |
| auto codec_status = codec->InitEncoder(codec_config, codec_config); |
| if (codec_status != |
| bluetooth::le_audio::CodecInterface::Status::STATUS_OK) { |
| log::error("Channel {} codec setup failed with err: {}", |
| (uint32_t)sw_enc_.size(), codec_status); |
| return; |
| } |
| |
| sw_enc_.emplace_back(std::move(codec)); |
| } |
| |
| broadcast_config_ = broadcast_config; |
| } |
| |
| static void sendBroadcastData( |
| const std::unique_ptr<BroadcastStateMachine>& broadcast, |
| std::vector<std::unique_ptr<bluetooth::le_audio::CodecInterface>>& |
| encoders) { |
| auto const& config = broadcast->GetBigConfig(); |
| if (config == std::nullopt) { |
| log::error( |
| "Broadcast broadcast_id={} has no valid BIS configurations in " |
| "state={}", |
| broadcast->GetBroadcastId(), ToString(broadcast->GetState())); |
| return; |
| } |
| |
| if (config->connection_handles.size() < encoders.size()) { |
| log::error("Not enough BIS'es to broadcast all channels!"); |
| return; |
| } |
| |
| for (uint8_t chan = 0; chan < encoders.size(); ++chan) { |
| IsoManager::GetInstance()->SendIsoData( |
| config->connection_handles[chan], |
| (const uint8_t*)encoders[chan]->GetDecodedSamples().data(), |
| encoders[chan]->GetDecodedSamples().size() * 2); |
| } |
| } |
| |
| virtual void OnAudioDataReady(const std::vector<uint8_t>& data) override { |
| if (!instance) return; |
| |
| log::verbose("Received {} bytes.", data.size()); |
| |
| if (!broadcast_config_.has_value() || |
| (broadcast_config_->subgroups.size() == 0)) { |
| log::error("Codec was not configured properly"); |
| return; |
| } |
| |
| /* Note: Currently we support only a single subgroup. |
| * In future consider mirroring the same data in a different quality |
| * subgroups. |
| */ |
| auto const& subgroup_config = broadcast_config_->subgroups.at(0); |
| |
| /* Constants for the channel data configuration */ |
| const auto num_bis = subgroup_config.GetNumBis(); |
| const auto bytes_per_sample = (subgroup_config.GetBitsPerSample() / 8); |
| |
| /* Prepare encoded data for all channels */ |
| for (uint8_t bis_idx = 0; bis_idx < num_bis; ++bis_idx) { |
| auto initial_channel_offset = bis_idx * bytes_per_sample; |
| sw_enc_[bis_idx]->Encode( |
| data.data() + initial_channel_offset, num_bis, |
| subgroup_config.GetBisOctetsPerCodecFrame(bis_idx)); |
| } |
| |
| /* Currently there is no way to broadcast multiple distinct streams. |
| * We just receive all system sounds mixed into a one stream and each |
| * broadcast gets the same data. |
| */ |
| for (auto& broadcast_pair : instance->broadcasts_) { |
| auto& broadcast = broadcast_pair.second; |
| if ((broadcast->GetState() == |
| BroadcastStateMachine::State::STREAMING) && |
| !broadcast->IsMuted()) |
| sendBroadcastData(broadcast, sw_enc_); |
| } |
| log::verbose("All data sent."); |
| } |
| |
| virtual void OnAudioSuspend(void) override { |
| log::info(""); |
| /* TODO: Should we suspend all broadcasts - remove BIGs? */ |
| if (instance) |
| instance->audio_data_path_state_ = AudioDataPathState::SUSPENDED; |
| } |
| |
| virtual void OnAudioResume(void) override { |
| log::info(""); |
| if (!instance) return; |
| |
| /* TODO: Should we resume all broadcasts - recreate BIGs? */ |
| instance->audio_data_path_state_ = AudioDataPathState::ACTIVE; |
| |
| if (!IsAnyoneStreaming()) { |
| instance->le_audio_source_hal_client_->CancelStreamingRequest(); |
| return; |
| } |
| |
| instance->le_audio_source_hal_client_->ConfirmStreamingRequest(); |
| } |
| |
| virtual void OnAudioMetadataUpdate( |
| const std::vector<struct playback_track_metadata_v7> source_metadata, |
| DsaMode dsa_mode) override { |
| log::info(""); |
| if (!instance) return; |
| |
| /* TODO: Should we take supported contexts from ASCS? */ |
| auto contexts = GetAudioContextsFromSourceMetadata(source_metadata); |
| if (contexts.any()) { |
| /* NOTICE: We probably don't want to change the stream configuration |
| * on each metadata change, so just update the context type metadata. |
| * Since we are not able to identify individual track streams and |
| * they are all mixed inside a single data stream, we will update |
| * the metadata of all BIS subgroups with the same combined context. |
| */ |
| instance->UpdateStreamingContextTypeOnAllSubgroups(contexts); |
| } |
| } |
| |
| private: |
| std::optional<BroadcastConfiguration> broadcast_config_; |
| std::vector<std::unique_ptr<bluetooth::le_audio::CodecInterface>> sw_enc_; |
| } audio_receiver_; |
| |
| bluetooth::le_audio::LeAudioBroadcasterCallbacks* callbacks_; |
| std::map<uint32_t, std::unique_ptr<BroadcastStateMachine>> broadcasts_; |
| std::vector<std::unique_ptr<BroadcastStateMachine>> pending_broadcasts_; |
| std::optional<BroadcastStateMachineConfig> queued_create_broadcast_request_; |
| std::optional<uint32_t> queued_start_broadcast_request_; |
| |
| /* Some BIG params are set globally */ |
| uint8_t current_phy_; |
| AudioDataPathState audio_data_path_state_; |
| std::unique_ptr<LeAudioSourceAudioHalClient> le_audio_source_hal_client_; |
| std::vector<BroadcastId> available_broadcast_ids_; |
| |
| // Flag to track iso state |
| bool is_iso_running_ = false; |
| }; |
| |
| /* Static members definitions */ |
| LeAudioBroadcasterImpl::BroadcastStateMachineCallbacks |
| LeAudioBroadcasterImpl::state_machine_callbacks_; |
| LeAudioBroadcasterImpl::LeAudioSourceCallbacksImpl |
| LeAudioBroadcasterImpl::audio_receiver_; |
| LeAudioBroadcasterImpl::BroadcastAdvertisingCallbacks |
| LeAudioBroadcasterImpl::state_machine_adv_callbacks_; |
| } /* namespace */ |
| |
| void LeAudioBroadcaster::Initialize( |
| bluetooth::le_audio::LeAudioBroadcasterCallbacks* callbacks, |
| base::Callback<bool()> audio_hal_verifier) { |
| std::scoped_lock<std::mutex> lock(instance_mutex); |
| log::info(""); |
| if (instance) { |
| log::error("Already initialized"); |
| return; |
| } |
| |
| if (!bluetooth::shim::GetController()->SupportsBleIsochronousBroadcaster() && |
| !osi_property_get_bool("persist.bluetooth.fake_iso_support", false)) { |
| log::warn("Isochronous Broadcast not supported by the controller!"); |
| return; |
| } |
| |
| if (!std::move(audio_hal_verifier).Run()) { |
| log::fatal("HAL requirements not met. Init aborted."); |
| } |
| |
| IsoManager::GetInstance()->Start(); |
| |
| instance = new LeAudioBroadcasterImpl(callbacks); |
| /* Register HCI event handlers */ |
| IsoManager::GetInstance()->RegisterBigCallbacks(instance); |
| /* Register for active traffic */ |
| IsoManager::GetInstance()->RegisterOnIsoTrafficActiveCallback( |
| [](bool is_active) { |
| if (instance) instance->IsoTrafficEventCb(is_active); |
| }); |
| } |
| |
| bool LeAudioBroadcaster::IsLeAudioBroadcasterRunning() { return instance; } |
| |
| LeAudioBroadcaster* LeAudioBroadcaster::Get(void) { |
| log::info(""); |
| log::assert_that(instance != nullptr, "assert failed: instance != nullptr"); |
| return instance; |
| } |
| |
| void LeAudioBroadcaster::Stop(void) { |
| log::info(""); |
| |
| if (instance) { |
| instance->Stop(); |
| } |
| } |
| |
| void LeAudioBroadcaster::Cleanup(void) { |
| std::scoped_lock<std::mutex> lock(instance_mutex); |
| log::info(""); |
| |
| if (instance == nullptr) return; |
| |
| LeAudioBroadcasterImpl* ptr = instance; |
| instance = nullptr; |
| |
| ptr->CleanUp(); |
| delete ptr; |
| } |
| |
| void LeAudioBroadcaster::DebugDump(int fd) { |
| std::scoped_lock<std::mutex> lock(instance_mutex); |
| dprintf(fd, "Le Audio Broadcaster:\n"); |
| if (instance) instance->Dump(fd); |
| dprintf(fd, "\n"); |
| } |