/*
* 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
#include
#include
#include
#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& subgroup_configs,
const std::vector& 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>{}
: 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 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& public_metadata,
const std::vector>& subgroup_metadata) override {
std::vector 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& 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& broadcast_code,
const std::vector& public_metadata,
const std::vector& subgroup_quality,
const std::vector>& subgroup_metadata) override {
uint8_t public_features = 0;
LeAudioLtvMap public_ltv;
std::vector 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(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& 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 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(
kv_it.second->GetState()));
}
}
void IsValidBroadcast(
uint32_t broadcast_id, uint8_t addr_type, RawAddress addr,
base::Callback
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
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(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(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(state));
}
void OnOwnAddressResponse(uint32_t broadcast_id, uint8_t addr_type,
RawAddress addr) override {
/* Not used currently */
}
void OnBigCreated(const std::vector& 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& broadcast,
std::vector>&
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& 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 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 broadcast_config_;
std::vector> sw_enc_;
} audio_receiver_;
bluetooth::le_audio::LeAudioBroadcasterCallbacks* callbacks_;
std::map> broadcasts_;
std::vector> pending_broadcasts_;
std::optional queued_create_broadcast_request_;
std::optional queued_start_broadcast_request_;
/* Some BIG params are set globally */
uint8_t current_phy_;
AudioDataPathState audio_data_path_state_;
std::unique_ptr le_audio_source_hal_client_;
std::vector 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 audio_hal_verifier) {
std::scoped_lock 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 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 lock(instance_mutex);
dprintf(fd, "Le Audio Broadcaster:\n");
if (instance) instance->Dump(fd);
dprintf(fd, "\n");
}