blob: 2dd37b131cd419b117db3cccfbb8075eb0c2e6b4 [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
* Copyright 2020 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 "device_groups.h"
#include <bluetooth/log.h>
#include "bta/include/bta_gatt_api.h"
#include "bta_csis_api.h"
#include "btif/include/btif_profile_storage.h"
#include "btm_iso_api.h"
#include "hci/controller_interface.h"
#include "internal_include/bt_trace.h"
#include "le_audio/le_audio_types.h"
#include "le_audio_set_configuration_provider.h"
#include "main/shim/entry.h"
#include "metrics_collector.h"
#include "os/log.h"
namespace bluetooth::le_audio {
using bluetooth::le_audio::types::ase;
using types::AseState;
using types::AudioContexts;
using types::AudioLocations;
using types::BidirectionalPair;
using types::CisState;
using types::CisType;
using types::DataPathState;
using types::LeAudioContextType;
using types::LeAudioCoreCodecConfig;
/* LeAudioDeviceGroup Class methods implementation */
void LeAudioDeviceGroup::AddNode(
const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
leAudioDevice->group_id_ = group_id_;
leAudioDevices_.push_back(std::weak_ptr<LeAudioDevice>(leAudioDevice));
MetricsCollector::Get()->OnGroupSizeUpdate(group_id_, leAudioDevices_.size());
}
void LeAudioDeviceGroup::RemoveNode(
const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
/* Group information cleaning in the device. */
leAudioDevice->group_id_ = bluetooth::groups::kGroupUnknown;
for (auto ase : leAudioDevice->ases_) {
ase.active = false;
ase.cis_conn_hdl = 0;
}
leAudioDevices_.erase(
std::remove_if(
leAudioDevices_.begin(), leAudioDevices_.end(),
[&leAudioDevice](auto& d) { return d.lock() == leAudioDevice; }),
leAudioDevices_.end());
MetricsCollector::Get()->OnGroupSizeUpdate(group_id_, leAudioDevices_.size());
}
bool LeAudioDeviceGroup::IsEmpty(void) const {
return leAudioDevices_.size() == 0;
}
bool LeAudioDeviceGroup::IsAnyDeviceConnected(void) const {
return (NumOfConnected() != 0);
}
int LeAudioDeviceGroup::Size(void) const { return leAudioDevices_.size(); }
int LeAudioDeviceGroup::NumOfConnected(LeAudioContextType context_type) const {
if (leAudioDevices_.empty()) return 0;
bool check_context_type = (context_type != LeAudioContextType::RFU);
AudioContexts type_set(context_type);
/* return number of connected devices from the set*/
return std::count_if(
leAudioDevices_.begin(), leAudioDevices_.end(),
[type_set, check_context_type](auto& iter) {
auto dev = iter.lock();
if (dev) {
if (dev->conn_id_ == GATT_INVALID_CONN_ID) return false;
if (dev->GetConnectionState() != DeviceConnectState::CONNECTED)
return false;
if (!check_context_type) return true;
return dev->GetSupportedContexts().test_any(type_set);
}
return false;
});
}
void LeAudioDeviceGroup::ClearSinksFromConfiguration(void) {
log::info("Group {}, group_id {}", fmt::ptr(this), group_id_);
auto direction = types::kLeAudioDirectionSink;
stream_conf.stream_params.get(direction).clear();
CodecManager::GetInstance()->ClearCisConfiguration(direction);
}
void LeAudioDeviceGroup::ClearSourcesFromConfiguration(void) {
log::info("Group {}, group_id {}", fmt::ptr(this), group_id_);
auto direction = types::kLeAudioDirectionSource;
stream_conf.stream_params.get(direction).clear();
CodecManager::GetInstance()->ClearCisConfiguration(direction);
}
void LeAudioDeviceGroup::ClearAllCises(void) {
log::info("group_id: {}", group_id_);
cig.cises.clear();
ClearSinksFromConfiguration();
ClearSourcesFromConfiguration();
}
void LeAudioDeviceGroup::UpdateCisConfiguration(uint8_t direction) {
CodecManager::GetInstance()->UpdateCisConfiguration(
cig.cises, stream_conf.stream_params.get(direction), direction);
}
void LeAudioDeviceGroup::Cleanup(void) {
/* Bluetooth is off while streaming - disconnect CISes and remove CIG */
if (GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
auto& sink_stream_locations =
stream_conf.stream_params.sink.stream_locations;
auto& source_stream_locations =
stream_conf.stream_params.source.stream_locations;
if (!sink_stream_locations.empty()) {
for (const auto kv_pair : sink_stream_locations) {
auto cis_handle = kv_pair.first;
bluetooth::hci::IsoManager::GetInstance()->DisconnectCis(
cis_handle, HCI_ERR_PEER_USER);
/* Check the other direction if disconnecting bidirectional CIS */
if (source_stream_locations.empty()) {
continue;
}
source_stream_locations.erase(
std::remove_if(
source_stream_locations.begin(), source_stream_locations.end(),
[&cis_handle](auto& pair) { return pair.first == cis_handle; }),
source_stream_locations.end());
}
}
/* Take care of the non-bidirectional CISes */
if (!source_stream_locations.empty()) {
for (auto [cis_handle, _] : source_stream_locations) {
bluetooth::hci::IsoManager::GetInstance()->DisconnectCis(
cis_handle, HCI_ERR_PEER_USER);
}
}
}
/* Note: CIG will stay in the controller. We cannot remove it here, because
* Cises are not yet disconnected.
* When user start Bluetooth, HCI Reset should remove it
*/
leAudioDevices_.clear();
ClearAllCises();
}
void LeAudioDeviceGroup::Deactivate(void) {
for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice;
leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
for (auto* ase = leAudioDevice->GetFirstActiveAse(); ase;
ase = leAudioDevice->GetNextActiveAse(ase)) {
ase->active = false;
ase->reconfigure = 0;
}
}
}
bool LeAudioDeviceGroup::Activate(
LeAudioContextType context_type,
const BidirectionalPair<AudioContexts>& metadata_context_types,
BidirectionalPair<std::vector<uint8_t>> ccid_lists) {
bool is_activate = false;
for (auto leAudioDevice : leAudioDevices_) {
if (leAudioDevice.expired()) continue;
bool activated = leAudioDevice.lock()->ActivateConfiguredAses(
context_type, metadata_context_types, ccid_lists);
log::info("Device {} is {}",
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice.lock().get()->address_),
activated ? "activated" : " not activated");
if (activated) {
if (!cig.AssignCisIds(leAudioDevice.lock().get())) {
return false;
}
is_activate = true;
}
}
return is_activate;
}
AudioContexts LeAudioDeviceGroup::GetSupportedContexts(int direction) const {
AudioContexts context;
for (auto& device : leAudioDevices_) {
auto shared_dev = device.lock();
if (shared_dev) {
context |= shared_dev->GetSupportedContexts(direction);
}
}
return context;
}
LeAudioDevice* LeAudioDeviceGroup::GetFirstDevice(void) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[](auto& iter) { return !iter.expired(); });
if (iter == leAudioDevices_.end()) return nullptr;
return (iter->lock()).get();
}
LeAudioDevice* LeAudioDeviceGroup::GetFirstDeviceWithAvailableContext(
LeAudioContextType context_type) const {
auto iter = std::find_if(
leAudioDevices_.begin(), leAudioDevices_.end(),
[&context_type](auto& iter) {
if (iter.expired()) return false;
return iter.lock()->GetAvailableContexts().test(context_type);
});
if ((iter == leAudioDevices_.end()) || (iter->expired())) return nullptr;
return (iter->lock()).get();
}
LeAudioDevice* LeAudioDeviceGroup::GetNextDevice(
LeAudioDevice* leAudioDevice) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&leAudioDevice](auto& d) {
if (d.expired())
return false;
else
return (d.lock()).get() == leAudioDevice;
});
/* If reference device not found */
if (iter == leAudioDevices_.end()) return nullptr;
std::advance(iter, 1);
/* If reference device is last in group */
if (iter == leAudioDevices_.end()) return nullptr;
if (iter->expired()) return nullptr;
return (iter->lock()).get();
}
LeAudioDevice* LeAudioDeviceGroup::GetNextDeviceWithAvailableContext(
LeAudioDevice* leAudioDevice, LeAudioContextType context_type) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&leAudioDevice](auto& d) {
if (d.expired())
return false;
else
return (d.lock()).get() == leAudioDevice;
});
/* If reference device not found */
if (iter == leAudioDevices_.end()) return nullptr;
std::advance(iter, 1);
/* If reference device is last in group */
if (iter == leAudioDevices_.end()) return nullptr;
iter = std::find_if(iter, leAudioDevices_.end(), [&context_type](auto& d) {
if (d.expired())
return false;
else
return d.lock()->GetAvailableContexts().test(context_type);
;
});
return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get();
}
bool LeAudioDeviceGroup::IsDeviceInTheGroup(
LeAudioDevice* leAudioDevice) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&leAudioDevice](auto& d) {
if (d.expired())
return false;
else
return (d.lock()).get() == leAudioDevice;
});
if ((iter == leAudioDevices_.end()) || (iter->expired())) return false;
return true;
}
bool LeAudioDeviceGroup::IsGroupReadyToCreateStream(void) const {
auto iter =
std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
if (d.expired())
return false;
else
return !(((d.lock()).get())->IsReadyToCreateStream());
});
return iter == leAudioDevices_.end();
}
bool LeAudioDeviceGroup::IsGroupReadyToSuspendStream(void) const {
auto iter =
std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
if (d.expired())
return false;
else
return !(((d.lock()).get())->IsReadyToSuspendStream());
});
return iter == leAudioDevices_.end();
}
bool LeAudioDeviceGroup::HaveAnyActiveDeviceInUnconfiguredState() const {
auto iter =
std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
if (d.expired())
return false;
else
return (((d.lock()).get())->HaveAnyUnconfiguredAses());
});
return iter != leAudioDevices_.end();
}
bool LeAudioDeviceGroup::HaveAllActiveDevicesAsesTheSameState(
AseState state) const {
auto iter = std::find_if(
leAudioDevices_.begin(), leAudioDevices_.end(), [&state](auto& d) {
if (d.expired())
return false;
else
return !(((d.lock()).get())->HaveAllActiveAsesSameState(state));
});
return iter == leAudioDevices_.end();
}
LeAudioDevice* LeAudioDeviceGroup::GetFirstActiveDevice(void) const {
auto iter =
std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
if (d.expired())
return false;
else
return ((d.lock()).get())->HaveActiveAse();
});
if (iter == leAudioDevices_.end() || iter->expired()) return nullptr;
return (iter->lock()).get();
}
LeAudioDevice* LeAudioDeviceGroup::GetNextActiveDevice(
LeAudioDevice* leAudioDevice) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&leAudioDevice](auto& d) {
if (d.expired())
return false;
else
return (d.lock()).get() == leAudioDevice;
});
if (iter == leAudioDevices_.end() ||
std::distance(iter, leAudioDevices_.end()) < 1)
return nullptr;
iter = std::find_if(std::next(iter, 1), leAudioDevices_.end(), [](auto& d) {
if (d.expired())
return false;
else
return ((d.lock()).get())->HaveActiveAse();
});
return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get();
}
LeAudioDevice* LeAudioDeviceGroup::GetFirstActiveDeviceByCisAndDataPathState(
CisState cis_state, DataPathState data_path_state) const {
auto iter =
std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&data_path_state, &cis_state](auto& d) {
if (d.expired()) {
return false;
}
return (((d.lock()).get())
->GetFirstActiveAseByCisAndDataPathState(
cis_state, data_path_state) != nullptr);
});
if (iter == leAudioDevices_.end()) {
return nullptr;
}
return iter->lock().get();
}
LeAudioDevice* LeAudioDeviceGroup::GetNextActiveDeviceByCisAndDataPathState(
LeAudioDevice* leAudioDevice, CisState cis_state,
DataPathState data_path_state) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&leAudioDevice](auto& d) {
if (d.expired()) {
return false;
}
return d.lock().get() == leAudioDevice;
});
if (std::distance(iter, leAudioDevices_.end()) < 1) {
return nullptr;
}
iter = std::find_if(std::next(iter, 1), leAudioDevices_.end(),
[&cis_state, &data_path_state](auto& d) {
if (d.expired()) {
return false;
}
return (((d.lock()).get())
->GetFirstActiveAseByCisAndDataPathState(
cis_state, data_path_state) != nullptr);
});
if (iter == leAudioDevices_.end()) {
return nullptr;
}
return iter->lock().get();
}
uint32_t LeAudioDeviceGroup::GetSduInterval(uint8_t direction) const {
for (LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
leAudioDevice != nullptr;
leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
if (!ase) continue;
return ase->qos_config.sdu_interval;
}
return 0;
}
uint8_t LeAudioDeviceGroup::GetSCA(void) const {
uint8_t sca = bluetooth::hci::iso_manager::kIsoSca0To20Ppm;
for (const auto& leAudioDevice : leAudioDevices_) {
uint8_t dev_sca =
BTM_GetPeerSCA(leAudioDevice.lock()->address_, BT_TRANSPORT_LE);
/* If we could not read SCA from the peer device or sca is 0,
* then there is no reason to continue.
*/
if ((dev_sca == 0xFF) || (dev_sca == 0)) return 0;
/* The Slaves_Clock_Accuracy parameter shall be the worst-case sleep clock
*accuracy of all the slaves that will participate in the CIG.
*/
if (dev_sca < sca) {
sca = dev_sca;
}
}
return sca;
}
uint8_t LeAudioDeviceGroup::GetPacking(void) const {
if (!stream_conf.conf) {
LOG_ERROR("No stream configuration has been set.");
return bluetooth::hci::kIsoCigPackingSequential;
}
return stream_conf.conf->packing;
}
uint8_t LeAudioDeviceGroup::GetFraming(void) const {
LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
do {
struct ase* ase = leAudioDevice->GetFirstActiveAse();
if (!ase) continue;
do {
if (ase->qos_preferences.supported_framing ==
types::kFramingUnframedPduUnsupported)
return bluetooth::hci::kIsoCigFramingFramed;
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
} while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
return bluetooth::hci::kIsoCigFramingUnframed;
}
/* TODO: Preferred parameter may be other than minimum */
static uint16_t find_max_transport_latency(const LeAudioDeviceGroup* group,
uint8_t direction) {
uint16_t max_transport_latency = 0;
for (LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
leAudioDevice != nullptr;
leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
for (ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
ase != nullptr;
ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)) {
if (!ase) break;
if (max_transport_latency == 0) {
// first assignment
max_transport_latency = ase->qos_config.max_transport_latency;
} else if (ase->qos_config.max_transport_latency <
max_transport_latency) {
if (ase->qos_config.max_transport_latency != 0) {
max_transport_latency = ase->qos_config.max_transport_latency;
} else {
log::warn("Trying to set latency back to 0, ASE ID {}", ase->id);
}
}
}
}
if (max_transport_latency < types::kMaxTransportLatencyMin) {
max_transport_latency = types::kMaxTransportLatencyMin;
} else if (max_transport_latency > types::kMaxTransportLatencyMax) {
max_transport_latency = types::kMaxTransportLatencyMax;
}
return max_transport_latency;
}
uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyStom(void) const {
return find_max_transport_latency(this, types::kLeAudioDirectionSource);
}
uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyMtos(void) const {
return find_max_transport_latency(this, types::kLeAudioDirectionSink);
}
uint32_t LeAudioDeviceGroup::GetTransportLatencyUs(uint8_t direction) const {
if (direction == types::kLeAudioDirectionSink) {
return transport_latency_mtos_us_;
} else if (direction == types::kLeAudioDirectionSource) {
return transport_latency_stom_us_;
} else {
log::error("invalid direction");
return 0;
}
}
void LeAudioDeviceGroup::SetTransportLatency(
uint8_t direction, uint32_t new_transport_latency_us) {
uint32_t* transport_latency_us;
if (direction == types::kLeAudioDirectionSink) {
transport_latency_us = &transport_latency_mtos_us_;
} else if (direction == types::kLeAudioDirectionSource) {
transport_latency_us = &transport_latency_stom_us_;
} else {
log::error("invalid direction");
return;
}
if (*transport_latency_us == new_transport_latency_us) return;
if ((*transport_latency_us != 0) &&
(*transport_latency_us != new_transport_latency_us)) {
log::warn(
"Different transport latency for group: old: {} [us], new: {} [us]",
static_cast<int>(*transport_latency_us),
static_cast<int>(new_transport_latency_us));
return;
}
log::info("updated group {} transport latency: {} [us]",
static_cast<int>(group_id_),
static_cast<int>(new_transport_latency_us));
*transport_latency_us = new_transport_latency_us;
}
uint8_t LeAudioDeviceGroup::GetRtn(uint8_t direction, uint8_t cis_id) const {
LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
do {
auto ases_pair = leAudioDevice->GetAsesByCisId(cis_id);
if (ases_pair.sink && direction == types::kLeAudioDirectionSink) {
return ases_pair.sink->qos_config.retrans_nb;
} else if (ases_pair.source &&
direction == types::kLeAudioDirectionSource) {
return ases_pair.source->qos_config.retrans_nb;
}
} while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
return 0;
}
uint16_t LeAudioDeviceGroup::GetMaxSduSize(uint8_t direction,
uint8_t cis_id) const {
LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
do {
auto ases_pair = leAudioDevice->GetAsesByCisId(cis_id);
if (ases_pair.sink && direction == types::kLeAudioDirectionSink) {
return ases_pair.sink->qos_config.max_sdu_size;
} else if (ases_pair.source &&
direction == types::kLeAudioDirectionSource) {
return ases_pair.source->qos_config.max_sdu_size;
}
} while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
return 0;
}
uint8_t LeAudioDeviceGroup::GetPhyBitmask(uint8_t direction) const {
LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
// local supported PHY's
uint8_t phy_bitfield = bluetooth::hci::kIsoCigPhy1M;
if (bluetooth::shim::GetController()->SupportsBle2mPhy())
phy_bitfield |= bluetooth::hci::kIsoCigPhy2M;
if (!leAudioDevice) {
log::error("No active leaudio device for direction?: {}", direction);
return phy_bitfield;
}
do {
struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
if (!ase) return phy_bitfield;
do {
if (direction == ase->direction) {
phy_bitfield &= leAudioDevice->GetPhyBitmask();
// A value of 0x00 denotes no preference
if (ase->qos_preferences.preferred_phy &&
(phy_bitfield & ase->qos_preferences.preferred_phy)) {
phy_bitfield &= ase->qos_preferences.preferred_phy;
log::debug("Using ASE preferred phy 0x{:02x}",
static_cast<int>(phy_bitfield));
} else {
log::warn(
"ASE preferred 0x{:02x} has nothing common with phy_bitfield "
"0x{:02x}",
static_cast<int>(ase->qos_preferences.preferred_phy),
static_cast<int>(phy_bitfield));
}
}
} while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)));
} while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
return phy_bitfield;
}
uint8_t LeAudioDeviceGroup::GetTargetPhy(uint8_t direction) const {
uint8_t phy_bitfield = GetPhyBitmask(direction);
// prefer to use 2M if supported
if (phy_bitfield & bluetooth::hci::kIsoCigPhy2M)
return types::kTargetPhy2M;
else if (phy_bitfield & bluetooth::hci::kIsoCigPhy1M)
return types::kTargetPhy1M;
else
return 0;
}
bool LeAudioDeviceGroup::GetPresentationDelay(uint32_t* delay,
uint8_t direction) const {
uint32_t delay_min = 0;
uint32_t delay_max = UINT32_MAX;
uint32_t preferred_delay_min = delay_min;
uint32_t preferred_delay_max = delay_max;
LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
LOG_ASSERT(leAudioDevice)
<< __func__ << " Shouldn't be called without an active device.";
do {
struct ase* ase = leAudioDevice->GetFirstActiveAseByDirection(direction);
if (!ase) continue; // device has no active ASEs in this direction
do {
/* No common range check */
if (ase->qos_preferences.pres_delay_min > delay_max ||
ase->qos_preferences.pres_delay_max < delay_min)
return false;
if (ase->qos_preferences.pres_delay_min > delay_min)
delay_min = ase->qos_preferences.pres_delay_min;
if (ase->qos_preferences.pres_delay_max < delay_max)
delay_max = ase->qos_preferences.pres_delay_max;
if (ase->qos_preferences.preferred_pres_delay_min > preferred_delay_min)
preferred_delay_min = ase->qos_preferences.preferred_pres_delay_min;
if (ase->qos_preferences.preferred_pres_delay_max < preferred_delay_max &&
ase->qos_preferences.preferred_pres_delay_max !=
types::kPresDelayNoPreference)
preferred_delay_max = ase->qos_preferences.preferred_pres_delay_max;
} while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)));
} while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
if (preferred_delay_min <= preferred_delay_max &&
preferred_delay_min > delay_min && preferred_delay_min < delay_max) {
*delay = preferred_delay_min;
} else {
*delay = delay_min;
}
return true;
}
uint16_t LeAudioDeviceGroup::GetRemoteDelay(uint8_t direction) const {
uint16_t remote_delay_ms = 0;
uint32_t presentation_delay;
if (!GetPresentationDelay(&presentation_delay, direction)) {
/* This should never happens at stream request time but to be safe return
* some sample value to not break streaming
*/
return 100;
}
/* us to ms */
remote_delay_ms = presentation_delay / 1000;
remote_delay_ms += GetTransportLatencyUs(direction) / 1000;
return remote_delay_ms;
}
bool LeAudioDeviceGroup::UpdateAudioContextAvailability(void) {
log::debug("{}", group_id_);
auto old_contexts = GetAvailableContexts();
SetAvailableContexts(GetLatestAvailableContexts());
return old_contexts != GetAvailableContexts();
}
bool LeAudioDeviceGroup::UpdateAudioSetConfigurationCache(
LeAudioContextType ctx_type) {
auto new_conf = CodecManager::GetInstance()->GetCodecConfig(
ctx_type, std::bind(&LeAudioDeviceGroup::FindFirstSupportedConfiguration,
this, std::placeholders::_1, std::placeholders::_2));
auto update_config = true;
if (context_to_configuration_cache_map.count(ctx_type) != 0) {
auto& [is_valid, existing_conf] =
context_to_configuration_cache_map.at(ctx_type);
update_config = (new_conf.get() != existing_conf.get());
/* Just mark it as still valid */
if (!update_config && !is_valid) {
context_to_configuration_cache_map.at(ctx_type).first = true;
return false;
}
}
if (update_config) {
log::info("config: {} -> {}", ToHexString(ctx_type),
(new_conf ? new_conf->name.c_str() : "(none)"));
context_to_configuration_cache_map.erase(ctx_type);
if (new_conf)
context_to_configuration_cache_map.insert(
std::make_pair(ctx_type, std::make_pair(true, std::move(new_conf))));
}
return update_config;
}
void LeAudioDeviceGroup::InvalidateCachedConfigurations(void) {
log::info("Group id: {}", group_id_);
context_to_configuration_cache_map.clear();
}
types::BidirectionalPair<AudioContexts>
LeAudioDeviceGroup::GetLatestAvailableContexts() const {
types::BidirectionalPair<AudioContexts> contexts;
for (const auto& device : leAudioDevices_) {
auto shared_ptr = device.lock();
if (shared_ptr &&
shared_ptr->GetConnectionState() == DeviceConnectState::CONNECTED) {
contexts.sink |=
shared_ptr->GetAvailableContexts(types::kLeAudioDirectionSink);
contexts.source |=
shared_ptr->GetAvailableContexts(types::kLeAudioDirectionSource);
}
}
return contexts;
}
bool LeAudioDeviceGroup::ReloadAudioLocations(void) {
AudioLocations updated_snk_audio_locations_ =
codec_spec_conf::kLeAudioLocationNotAllowed;
AudioLocations updated_src_audio_locations_ =
codec_spec_conf::kLeAudioLocationNotAllowed;
for (const auto& device : leAudioDevices_) {
if (device.expired() || (device.lock().get()->GetConnectionState() !=
DeviceConnectState::CONNECTED))
continue;
updated_snk_audio_locations_ |= device.lock().get()->snk_audio_locations_;
updated_src_audio_locations_ |= device.lock().get()->src_audio_locations_;
}
/* Nothing has changed */
if ((updated_snk_audio_locations_ == snk_audio_locations_) &&
(updated_src_audio_locations_ == src_audio_locations_))
return false;
snk_audio_locations_ = updated_snk_audio_locations_;
src_audio_locations_ = updated_src_audio_locations_;
return true;
}
bool LeAudioDeviceGroup::ReloadAudioDirections(void) {
uint8_t updated_audio_directions = 0x00;
for (const auto& device : leAudioDevices_) {
if (device.expired() || (device.lock().get()->GetConnectionState() !=
DeviceConnectState::CONNECTED))
continue;
updated_audio_directions |= device.lock().get()->audio_directions_;
}
/* Nothing has changed */
if (updated_audio_directions == audio_directions_) return false;
audio_directions_ = updated_audio_directions;
return true;
}
bool LeAudioDeviceGroup::IsInTransition(void) const { return in_transition_; }
bool LeAudioDeviceGroup::IsStreaming(void) const {
return current_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING;
}
bool LeAudioDeviceGroup::IsReleasingOrIdle(void) const {
return (target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) ||
(current_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
}
bool LeAudioDeviceGroup::IsGroupStreamReady(void) const {
bool is_device_ready = false;
/* All connected devices must be ready */
for (auto& weak : leAudioDevices_) {
auto dev = weak.lock();
if (!dev) return false;
/* We are interested here in devices which are connected on profile level
* and devices which are configured (meaning, have actived ASE(s))*/
if (dev->GetConnectionState() == DeviceConnectState::CONNECTED &&
dev->HaveActiveAse()) {
if (!dev->IsReadyToStream()) {
return false;
}
is_device_ready = true;
}
}
return is_device_ready;
}
bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) const {
for (auto const dev : leAudioDevices_) {
if (dev.expired()) continue;
if (dev.lock().get()->HaveAnyCisConnected()) return false;
}
return true;
}
uint8_t LeAudioDeviceGroup::CigConfiguration::GetFirstFreeCisId(
CisType cis_type) const {
log::info("Group: {}, group_id: {} cis_type: {}", fmt::ptr(group_),
group_->group_id_, static_cast<int>(cis_type));
for (size_t id = 0; id < cises.size(); id++) {
if (cises[id].addr.IsEmpty() && cises[id].type == cis_type) {
return id;
}
}
return kInvalidCisId;
}
types::LeAudioConfigurationStrategy
LeAudioDeviceGroup::GetGroupSinkStrategyFromPacs(
int expected_group_size) const {
/* Simple strategy picker */
log::debug("Group {} size {}", group_id_, expected_group_size);
if (expected_group_size > 1) {
return types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE;
}
log::debug("audio location 0x{:04x}", snk_audio_locations_.to_ulong());
if (!(snk_audio_locations_.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyLeft) ||
!(snk_audio_locations_.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyRight)) {
return types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE;
}
auto device = GetFirstDevice();
/* Note: Currently, the audio channel counts LTV is only mandatory for LC3. */
auto channel_count_bitmap =
device->GetSupportedAudioChannelCounts(types::kLeAudioDirectionSink);
log::debug("Supported channel counts for group {} (device {}) is {}",
group_id_, ADDRESS_TO_LOGGABLE_CSTR(device->address_),
channel_count_bitmap);
if (channel_count_bitmap == 1) {
return types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE;
}
return types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE;
}
types::LeAudioConfigurationStrategy LeAudioDeviceGroup::GetGroupSinkStrategy()
const {
/* Update the strategy if not set yet or was invalidated */
if (!strategy_) {
int expected_group_size = Size();
/* Choose the group configuration strategy based on PAC records */
strategy_ = GetGroupSinkStrategyFromPacs(expected_group_size);
LOG_INFO("Group strategy set to: %s", [](types::LeAudioConfigurationStrategy
strategy) {
switch (strategy) {
case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
return "MONO_ONE_CIS_PER_DEVICE";
case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
return "STEREO_TWO_CISES_PER_DEVICE";
case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE:
return "STEREO_ONE_CIS_PER_DEVICE";
default:
return "RFU";
}
}(*strategy_));
}
return *strategy_;
}
int LeAudioDeviceGroup::GetAseCount(uint8_t direction) const {
int result = 0;
for (const auto& device_iter : leAudioDevices_) {
result += device_iter.lock()->GetAseCount(direction);
}
return result;
}
void LeAudioDeviceGroup::CigConfiguration::GenerateCisIds(
LeAudioContextType context_type) {
log::info("Group {}, group_id: {}, context_type: {}", fmt::ptr(group_),
group_->group_id_, bluetooth::common::ToString(context_type));
if (cises.size() > 0) {
log::info("CIS IDs already generated");
return;
}
uint8_t cis_count_bidir = 0;
uint8_t cis_count_unidir_sink = 0;
uint8_t cis_count_unidir_source = 0;
int csis_group_size = 0;
if (bluetooth::csis::CsisClient::IsCsisClientRunning()) {
csis_group_size =
bluetooth::csis::CsisClient::Get()->GetDesiredSize(group_->group_id_);
}
/* If this is CSIS group, the csis_group_size will be > 0, otherwise -1.
* If the last happen it means, group size is 1 */
int group_size = csis_group_size > 0 ? csis_group_size : 1;
set_configurations::get_cis_count(
context_type, group_size, group_->GetGroupSinkStrategy(),
group_->GetAseCount(types::kLeAudioDirectionSink),
group_->GetAseCount(types::kLeAudioDirectionSource), cis_count_bidir,
cis_count_unidir_sink, cis_count_unidir_source);
uint8_t idx = 0;
while (cis_count_bidir > 0) {
struct bluetooth::le_audio::types::cis cis_entry = {
.id = idx,
.type = CisType::CIS_TYPE_BIDIRECTIONAL,
.conn_handle = 0,
.addr = RawAddress::kEmpty,
};
cises.push_back(cis_entry);
cis_count_bidir--;
idx++;
}
while (cis_count_unidir_sink > 0) {
struct bluetooth::le_audio::types::cis cis_entry = {
.id = idx,
.type = CisType::CIS_TYPE_UNIDIRECTIONAL_SINK,
.conn_handle = 0,
.addr = RawAddress::kEmpty,
};
cises.push_back(cis_entry);
cis_count_unidir_sink--;
idx++;
}
while (cis_count_unidir_source > 0) {
struct bluetooth::le_audio::types::cis cis_entry = {
.id = idx,
.type = CisType::CIS_TYPE_UNIDIRECTIONAL_SOURCE,
.conn_handle = 0,
.addr = RawAddress::kEmpty,
};
cises.push_back(cis_entry);
cis_count_unidir_source--;
idx++;
}
}
bool LeAudioDeviceGroup::CigConfiguration::AssignCisIds(
LeAudioDevice* leAudioDevice) {
ASSERT_LOG(leAudioDevice, "invalid device");
log::info("device: {}", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
struct ase* ase = leAudioDevice->GetFirstActiveAse();
if (!ase) {
log::error("Device {} shouldn't be called without an active ASE",
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
return false;
}
for (; ase != nullptr; ase = leAudioDevice->GetNextActiveAse(ase)) {
uint8_t cis_id = kInvalidCisId;
/* CIS ID already set */
if (ase->cis_id != kInvalidCisId) {
log::info("ASE ID: {}, is already assigned CIS ID: {}, type {}", ase->id,
ase->cis_id, cises[ase->cis_id].type);
if (!cises[ase->cis_id].addr.IsEmpty()) {
log::info("Bi-Directional CIS already assigned");
continue;
}
/* Reuse existing CIS ID if available*/
cis_id = ase->cis_id;
}
/* First check if we have bidirectional ASEs. If so, assign same CIS ID.*/
struct ase* matching_bidir_ase =
leAudioDevice->GetNextActiveAseWithDifferentDirection(ase);
for (; matching_bidir_ase != nullptr;
matching_bidir_ase = leAudioDevice->GetNextActiveAseWithSameDirection(
matching_bidir_ase)) {
if ((matching_bidir_ase->cis_id != kInvalidCisId) &&
(matching_bidir_ase->cis_id != cis_id)) {
log::info("Bi-Directional CIS is already used. ASE Id: {} cis_id={}",
matching_bidir_ase->id, matching_bidir_ase->cis_id);
continue;
}
break;
}
if (matching_bidir_ase) {
if (cis_id == kInvalidCisId) {
cis_id = GetFirstFreeCisId(CisType::CIS_TYPE_BIDIRECTIONAL);
}
if (cis_id != kInvalidCisId) {
ase->cis_id = cis_id;
matching_bidir_ase->cis_id = cis_id;
cises[cis_id].addr = leAudioDevice->address_;
log::info(
"ASE ID: {} and ASE ID: {}, assigned Bi-Directional CIS ID: {}",
ase->id, matching_bidir_ase->id, ase->cis_id);
continue;
}
log::warn(
"ASE ID: {}, unable to get free Bi-Directional CIS ID but maybe "
"thats fine. Try using unidirectional.",
ase->id);
}
if (ase->direction == types::kLeAudioDirectionSink) {
if (cis_id == kInvalidCisId) {
cis_id = GetFirstFreeCisId(CisType::CIS_TYPE_UNIDIRECTIONAL_SINK);
}
if (cis_id == kInvalidCisId) {
log::warn(
"Unable to get free Uni-Directional Sink CIS ID - maybe there is "
"bi-directional available");
/* This could happen when scenarios for given context type allows for
* Sink and Source configuration but also only Sink configuration.
*/
cis_id = GetFirstFreeCisId(CisType::CIS_TYPE_BIDIRECTIONAL);
if (cis_id == kInvalidCisId) {
log::error("Unable to get free Uni-Directional Sink CIS ID");
return false;
}
}
ase->cis_id = cis_id;
cises[cis_id].addr = leAudioDevice->address_;
log::info("ASE ID: {}, assigned Uni-Directional Sink CIS ID: {}", ase->id,
ase->cis_id);
continue;
}
/* Source direction */
ASSERT_LOG(ase->direction == types::kLeAudioDirectionSource,
"Expected Source direction, actual=%d", ase->direction);
if (cis_id == kInvalidCisId) {
cis_id = GetFirstFreeCisId(CisType::CIS_TYPE_UNIDIRECTIONAL_SOURCE);
}
if (cis_id == kInvalidCisId) {
/* This could happen when scenarios for given context type allows for
* Sink and Source configuration but also only Sink configuration.
*/
log::warn(
"Unable to get free Uni-Directional Source CIS ID - maybe there is "
"bi-directional available");
cis_id = GetFirstFreeCisId(CisType::CIS_TYPE_BIDIRECTIONAL);
if (cis_id == kInvalidCisId) {
log::error("Unable to get free Uni-Directional Source CIS ID");
return false;
}
}
ase->cis_id = cis_id;
cises[cis_id].addr = leAudioDevice->address_;
log::info("ASE ID: {}, assigned Uni-Directional Source CIS ID: {}", ase->id,
ase->cis_id);
}
return true;
}
void LeAudioDeviceGroup::CigConfiguration::AssignCisConnHandles(
const std::vector<uint16_t>& conn_handles) {
log::info("num of cis handles {}", static_cast<int>(conn_handles.size()));
for (size_t i = 0; i < cises.size(); i++) {
cises[i].conn_handle = conn_handles[i];
log::info("assigning cis[{}] conn_handle: {}", cises[i].id,
cises[i].conn_handle);
}
}
void LeAudioDeviceGroup::AssignCisConnHandlesToAses(
LeAudioDevice* leAudioDevice) {
ASSERT_LOG(leAudioDevice, "Invalid device");
log::info("group: {}, group_id: {}, device: {}", fmt::ptr(this), group_id_,
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
/* Assign all CIS connection handles to ases */
struct bluetooth::le_audio::types::ase* ase =
leAudioDevice->GetFirstActiveAseByCisAndDataPathState(
CisState::IDLE, DataPathState::IDLE);
if (!ase) {
log::warn("No active ASE with Cis and Data path state set to IDLE");
return;
}
for (; ase != nullptr;
ase = leAudioDevice->GetFirstActiveAseByCisAndDataPathState(
CisState::IDLE, DataPathState::IDLE)) {
auto ases_pair = leAudioDevice->GetAsesByCisId(ase->cis_id);
if (ases_pair.sink && ases_pair.sink->active) {
ases_pair.sink->cis_conn_hdl = cig.cises[ase->cis_id].conn_handle;
ases_pair.sink->cis_state = CisState::ASSIGNED;
}
if (ases_pair.source && ases_pair.source->active) {
ases_pair.source->cis_conn_hdl = cig.cises[ase->cis_id].conn_handle;
ases_pair.source->cis_state = CisState::ASSIGNED;
}
}
}
void LeAudioDeviceGroup::AssignCisConnHandlesToAses(void) {
LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
ASSERT_LOG(leAudioDevice, "Shouldn't be called without an active device.");
log::info("Group {}, group_id {}", fmt::ptr(this), group_id_);
/* Assign all CIS connection handles to ases */
for (; leAudioDevice != nullptr;
leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
AssignCisConnHandlesToAses(leAudioDevice);
}
}
void LeAudioDeviceGroup::CigConfiguration::UnassignCis(
LeAudioDevice* leAudioDevice) {
ASSERT_LOG(leAudioDevice, "Invalid device");
log::info("Group {}, group_id {}, device: {}", fmt::ptr(group_),
group_->group_id_,
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
for (struct bluetooth::le_audio::types::cis& cis_entry : cises) {
if (cis_entry.addr == leAudioDevice->address_) {
cis_entry.addr = RawAddress::kEmpty;
}
}
}
bool CheckIfStrategySupported(types::LeAudioConfigurationStrategy strategy,
const set_configurations::AseConfiguration& conf,
uint8_t direction, const LeAudioDevice& device) {
/* Check direction and if audio location allows to create more cises to a
* single device.
*/
types::AudioLocations audio_locations =
(direction == types::kLeAudioDirectionSink) ? device.snk_audio_locations_
: device.src_audio_locations_;
log::debug("strategy: {}, locations: {}", (int)strategy,
audio_locations.to_ulong());
switch (strategy) {
case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
return audio_locations.any();
case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
if ((audio_locations.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyLeft) &&
(audio_locations.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyRight))
return true;
else
return false;
case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE: {
if (!(audio_locations.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyLeft) ||
!(audio_locations.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyRight))
return false;
auto channel_count_mask =
device.GetSupportedAudioChannelCounts(direction);
auto requested_channel_count = conf.codec.params.GetAsCoreCodecConfig()
.GetChannelCountPerIsoStream();
log::debug("Requested channel count: {}, supp. channel counts: {}",
requested_channel_count, loghex(channel_count_mask));
/* Return true if requested channel count is set in the supported channel
* counts. In the channel_count_mask, bit 0 is set when 1 channel is
* supported.
*/
return ((1 << (requested_channel_count - 1)) & channel_count_mask);
}
default:
return false;
}
return false;
}
/* This method check if group support given audio configuration
* requirement for connected devices in the group and available ASEs
* (no matter on the ASE state) and for given context type
*/
bool LeAudioDeviceGroup::IsAudioSetConfigurationSupported(
const set_configurations::AudioSetConfiguration* audio_set_conf,
LeAudioContextType context_type,
types::LeAudioConfigurationStrategy required_snk_strategy) const {
/* When at least one device supports the configuration context, configure
* for these devices only. Otherwise configure for all devices - we will
* not put this context into the metadata if not supported.
*/
auto num_of_connected = NumOfConnected(context_type);
if (num_of_connected == 0) {
num_of_connected = NumOfConnected();
}
if (!set_configurations::check_if_may_cover_scenario(audio_set_conf,
num_of_connected)) {
log::debug("cannot cover scenario {}, num. of connected: {}",
bluetooth::common::ToString(context_type), num_of_connected);
return false;
}
/* TODO For now: set ase if matching with first pac.
* 1) We assume as well that devices will match requirements in order
* e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc.
* 2) ASEs should be active only if best (according to priority list) full
* scenarion will be covered.
* 3) ASEs should be filled according to performance profile.
*/
for (auto direction :
{types::kLeAudioDirectionSink, types::kLeAudioDirectionSource}) {
LOG_DEBUG("Looking for configuration: %s - %s",
audio_set_conf->name.c_str(),
(direction == types::kLeAudioDirectionSink ? "Sink" : "Source"));
auto const& ase_confs = audio_set_conf->confs.get(direction);
ASSERT_LOG(
audio_set_conf->topology_info.has_value(),
"No topology info, which is required to properly configure the ASEs");
auto const strategy =
audio_set_conf->topology_info->strategy.get(direction);
auto const device_cnt =
audio_set_conf->topology_info->device_count.get(direction);
auto const ase_cnt = ase_confs.size();
if (ase_cnt == 0) {
LOG_ERROR("ASE count is 0");
continue;
}
if (device_cnt == 0) {
LOG_ERROR("Device count is 0");
continue;
}
uint8_t const max_required_ase_per_dev =
ase_cnt / device_cnt + (ase_cnt % device_cnt);
uint8_t required_device_cnt = device_cnt;
uint8_t active_ase_cnt = 0;
log::debug(
"Number of devices: {}, number of ASEs: {}, Max ASE per device: {} "
"Strategy: {}",
required_device_cnt, ase_cnt, max_required_ase_per_dev,
static_cast<int>(strategy));
if (direction == types::kLeAudioDirectionSink &&
strategy != required_snk_strategy) {
log::debug("Sink strategy mismatch group!=cfg.entry ({}!={})",
static_cast<int>(required_snk_strategy),
static_cast<int>(strategy));
return false;
}
for (auto* device = GetFirstDevice();
device != nullptr && required_device_cnt > 0;
device = GetNextDevice(device)) {
if (device->ases_.empty()) {
LOG_ERROR("Device has no ASEs.");
continue;
}
int needed_ase_per_dev =
std::min(static_cast<int>(max_required_ase_per_dev),
static_cast<int>(ase_cnt - active_ase_cnt));
for (auto const& ent : ase_confs) {
if (!device->GetCodecConfigurationSupportedPac(direction, ent.codec)) {
LOG_DEBUG("Insufficient PAC");
continue;
}
if (!CheckIfStrategySupported(strategy, ent, direction, *device)) {
LOG_DEBUG("Strategy not supported");
continue;
}
for (auto& ase : device->ases_) {
if (ase.direction != direction) continue;
active_ase_cnt++;
needed_ase_per_dev--;
if (needed_ase_per_dev == 0) break;
}
}
if (needed_ase_per_dev > 0) {
LOG_DEBUG("Not enough ASEs on the device (needs %d more).",
needed_ase_per_dev);
return false;
}
required_device_cnt--;
}
if (required_device_cnt > 0) {
/* Don't left any active devices if requirements are not met */
LOG_DEBUG(
"Could not configure all the devices for direction: %s",
(direction == types::kLeAudioDirectionSink ? "Sink" : "Source"));
return false;
}
}
/* when disabling 32k dual mic, for later join case, we need to
* make sure the device is always choosing the config that its
* sampling rate matches with the sampling rate which is used
* when all devices in the group are connected.
*/
bool dual_bidirection_swb_supported_ =
CodecManager::GetInstance()->IsDualBiDirSwbSupported();
if (Size() > 1 && CodecManager::GetInstance()->CheckCodecConfigIsBiDirSwb(
*audio_set_conf)) {
if (!dual_bidirection_swb_supported_) {
return false;
}
}
log::debug("Chosen ASE Configuration for group: {}, configuration: {}",
this->group_id_, audio_set_conf->name);
return true;
}
/* This method should choose aproperiate ASEs to be active and set a cached
* configuration for codec and qos.
*/
bool LeAudioDeviceGroup::ConfigureAses(
const set_configurations::AudioSetConfiguration* audio_set_conf,
LeAudioContextType context_type,
const types::BidirectionalPair<AudioContexts>& metadata_context_types,
const types::BidirectionalPair<std::vector<uint8_t>>& ccid_lists) {
/* When at least one device supports the configuration context, configure
* for these devices only. Otherwise configure for all devices - we will
* not put this context into the metadata if not supported.
*/
auto num_of_connected = NumOfConnected(context_type);
if (num_of_connected == 0) {
num_of_connected = NumOfConnected();
}
if (!set_configurations::check_if_may_cover_scenario(audio_set_conf,
num_of_connected)) {
return false;
}
bool reuse_cis_id =
GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED;
/* TODO For now: set ase if matching with first pac.
* 1) We assume as well that devices will match requirements in order
* e.g. 1 Device - 1 Requirement, 2 Device - 2 Requirement etc.
* 2) ASEs should be active only if best (according to priority list) full
* scenarion will be covered.
* 3) ASEs should be filled according to performance profile.
*/
// WARNING: This may look like the results stored here are unused, but it
// actually shares the intermediate values between the multiple
// configuration calls within the configuration loop.
BidirectionalPair<types::AudioLocations> group_audio_locations_memo = {
.sink = 0, .source = 0};
for (auto direction :
{types::kLeAudioDirectionSink, types::kLeAudioDirectionSource}) {
auto direction_str =
(direction == types::kLeAudioDirectionSink ? "Sink" : "Source");
LOG_DEBUG("%s: Looking for requirements: %s", direction_str,
audio_set_conf->name.c_str());
if (audio_set_conf->confs.get(direction).empty()) {
LOG_WARN("No %s configuration available.", direction_str);
continue;
}
auto required_device_cnt = NumOfConnected();
uint8_t active_ase_cnt = 0;
auto configuration_closure = [&](LeAudioDevice* dev) -> void {
/* For the moment, we configure only connected devices and when it is
* ready to stream i.e. All ASEs are discovered and dev is reported as
* connected
*/
if (dev->GetConnectionState() != DeviceConnectState::CONNECTED) {
log::warn("Device {}, in the state {}",
ADDRESS_TO_LOGGABLE_CSTR(dev->address_),
bluetooth::common::ToString(dev->GetConnectionState()));
return;
}
if (!dev->ConfigureAses(audio_set_conf, direction, context_type,
&active_ase_cnt, group_audio_locations_memo,
metadata_context_types, ccid_lists,
reuse_cis_id)) {
return;
}
required_device_cnt--;
};
// First use the devices claiming proper support
for (auto* device = GetFirstDeviceWithAvailableContext(context_type);
device != nullptr && required_device_cnt > 0;
device = GetNextDeviceWithAvailableContext(device, context_type)) {
configuration_closure(device);
}
// In case some devices do not support this scenario - us them anyway if
// they are required for the scenario - we will not put this context into
// their metadata anyway
if (required_device_cnt > 0) {
for (auto* device = GetFirstDevice();
device != nullptr && required_device_cnt > 0;
device = GetNextDevice(device)) {
configuration_closure(device);
}
}
if (required_device_cnt > 0) {
/* Don't left any active devices if requirements are not met */
log::error("could not configure all the devices");
Deactivate();
return false;
}
}
log::info("Choosed ASE Configuration for group: {}, configuration: {}",
group_id_, audio_set_conf->name);
configuration_context_type_ = context_type;
metadata_context_type_ = metadata_context_types;
return true;
}
std::shared_ptr<const set_configurations::AudioSetConfiguration>
LeAudioDeviceGroup::GetCachedConfiguration(
LeAudioContextType context_type) const {
if (context_to_configuration_cache_map.count(context_type) != 0) {
return context_to_configuration_cache_map.at(context_type).second;
}
return nullptr;
}
std::shared_ptr<const set_configurations::AudioSetConfiguration>
LeAudioDeviceGroup::GetActiveConfiguration(void) const {
return GetCachedConfiguration(configuration_context_type_);
}
std::shared_ptr<const set_configurations::AudioSetConfiguration>
LeAudioDeviceGroup::GetConfiguration(LeAudioContextType context_type) {
if (context_type == LeAudioContextType::UNINITIALIZED) {
return nullptr;
}
const set_configurations::AudioSetConfiguration* conf = nullptr;
bool is_valid = false;
/* Refresh the cache if there is no valid configuration */
if (context_to_configuration_cache_map.count(context_type) != 0) {
auto& valid_config_pair =
context_to_configuration_cache_map.at(context_type);
is_valid = valid_config_pair.first;
conf = valid_config_pair.second.get();
}
if (!is_valid || (conf == nullptr)) {
UpdateAudioSetConfigurationCache(context_type);
}
return GetCachedConfiguration(context_type);
}
std::optional<LeAudioCodecConfiguration>
LeAudioDeviceGroup::GetCachedCodecConfigurationByDirection(
LeAudioContextType context_type, uint8_t direction) const {
auto audio_set_conf = GetCachedConfiguration(context_type);
if (!audio_set_conf) return std::nullopt;
LeAudioCodecConfiguration group_config = {0, 0, 0, 0};
for (const auto& conf : audio_set_conf->confs.get(direction)) {
if (group_config.sample_rate != 0 &&
conf.codec.GetSamplingFrequencyHz() != group_config.sample_rate) {
log::warn(
"stream configuration could not be determined (sampling frequency "
"differs) for direction: {}",
loghex(direction));
return std::nullopt;
}
group_config.sample_rate = conf.codec.GetSamplingFrequencyHz();
if (group_config.data_interval_us != 0 &&
conf.codec.GetDataIntervalUs() != group_config.data_interval_us) {
log::warn(
"stream configuration could not be determined (data interval "
"differs) for direction: {}",
loghex(direction));
return std::nullopt;
}
group_config.data_interval_us = conf.codec.GetDataIntervalUs();
if (group_config.bits_per_sample != 0 &&
conf.codec.GetBitsPerSample() != group_config.bits_per_sample) {
log::warn(
"stream configuration could not be determined (bits per sample "
"differs) for direction: {}",
loghex(direction));
return std::nullopt;
}
group_config.bits_per_sample = conf.codec.GetBitsPerSample();
ASSERT_LOG(
audio_set_conf->topology_info.has_value(),
"No topology info, which is required to properly configure the ASEs");
group_config.num_channels +=
conf.codec.GetChannelCountPerIsoStream() *
audio_set_conf->topology_info->device_count.get(direction);
}
if (group_config.IsInvalid()) return std::nullopt;
return group_config;
}
std::optional<LeAudioCodecConfiguration>
LeAudioDeviceGroup::GetCodecConfigurationByDirection(
LeAudioContextType context_type, uint8_t direction) {
const set_configurations::AudioSetConfiguration* conf = nullptr;
bool is_valid = false;
/* Refresh the cache if there is no valid configuration */
if (context_to_configuration_cache_map.count(context_type) != 0) {
auto& valid_config_pair =
context_to_configuration_cache_map.at(context_type);
is_valid = valid_config_pair.first;
conf = valid_config_pair.second.get();
}
if (!is_valid || (conf == nullptr)) {
UpdateAudioSetConfigurationCache(context_type);
}
/* Return the cached value */
return GetCachedCodecConfigurationByDirection(context_type, direction);
}
bool LeAudioDeviceGroup::IsAudioSetConfigurationAvailable(
LeAudioContextType group_context_type) {
return GetConfiguration(group_context_type) != nullptr;
}
bool LeAudioDeviceGroup::IsMetadataChanged(
const BidirectionalPair<AudioContexts>& context_types,
const BidirectionalPair<std::vector<uint8_t>>& ccid_lists) const {
for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice;
leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
if (leAudioDevice->IsMetadataChanged(context_types, ccid_lists))
return true;
}
return false;
}
bool LeAudioDeviceGroup::IsCisPartOfCurrentStream(uint16_t cis_conn_hdl) const {
auto& sink_stream_locations = stream_conf.stream_params.sink.stream_locations;
auto iter = std::find_if(
sink_stream_locations.begin(), sink_stream_locations.end(),
[cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; });
if (iter != sink_stream_locations.end()) return true;
auto& source_stream_locations =
stream_conf.stream_params.source.stream_locations;
iter = std::find_if(
source_stream_locations.begin(), source_stream_locations.end(),
[cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; });
return (iter != source_stream_locations.end());
}
void LeAudioDeviceGroup::RemoveCisFromStreamIfNeeded(
LeAudioDevice* leAudioDevice, uint16_t cis_conn_hdl) {
log::info("CIS Connection Handle: {}", cis_conn_hdl);
if (!IsCisPartOfCurrentStream(cis_conn_hdl)) return;
/* Cache the old values for comparison */
auto old_sink_channels = stream_conf.stream_params.sink.num_of_channels;
auto old_source_channels = stream_conf.stream_params.source.num_of_channels;
for (auto dir :
{types::kLeAudioDirectionSink, types::kLeAudioDirectionSource}) {
auto& params = stream_conf.stream_params.get(dir);
params.stream_locations.erase(
std::remove_if(
params.stream_locations.begin(), params.stream_locations.end(),
[leAudioDevice, &cis_conn_hdl, &params, dir](auto& pair) {
if (!cis_conn_hdl) {
cis_conn_hdl = pair.first;
}
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(cis_conn_hdl);
if (ases_pair.get(dir) && cis_conn_hdl == pair.first) {
params.num_of_devices--;
params.num_of_channels -=
ases_pair.get(dir)
->codec_config.GetAsCoreCodecConfig()
.GetChannelCountPerIsoStream();
params.audio_channel_allocation &= ~pair.second;
}
return (ases_pair.get(dir) && cis_conn_hdl == pair.first);
}),
params.stream_locations.end());
}
log::info(
"Sink Number Of Devices: {}, Sink Number Of Channels: {}, Source Number "
"Of Devices: {}, Source Number Of Channels: {}",
stream_conf.stream_params.sink.num_of_devices,
stream_conf.stream_params.sink.num_of_channels,
stream_conf.stream_params.source.num_of_devices,
stream_conf.stream_params.source.num_of_channels);
if (stream_conf.stream_params.sink.num_of_channels == 0) {
ClearSinksFromConfiguration();
}
if (stream_conf.stream_params.source.num_of_channels == 0) {
ClearSourcesFromConfiguration();
}
/* Update CodecManager CIS configuration */
if (old_sink_channels > stream_conf.stream_params.sink.num_of_channels) {
CodecManager::GetInstance()->UpdateCisConfiguration(
cig.cises,
stream_conf.stream_params.get(
bluetooth::le_audio::types::kLeAudioDirectionSink),
bluetooth::le_audio::types::kLeAudioDirectionSink);
}
if (old_source_channels > stream_conf.stream_params.source.num_of_channels) {
CodecManager::GetInstance()->UpdateCisConfiguration(
cig.cises,
stream_conf.stream_params.get(
bluetooth::le_audio::types::kLeAudioDirectionSource),
bluetooth::le_audio::types::kLeAudioDirectionSource);
}
cig.UnassignCis(leAudioDevice);
}
bool LeAudioDeviceGroup::IsPendingConfiguration(void) const {
return stream_conf.pending_configuration;
}
void LeAudioDeviceGroup::SetPendingConfiguration(void) {
stream_conf.pending_configuration = true;
}
void LeAudioDeviceGroup::ClearPendingConfiguration(void) {
stream_conf.pending_configuration = false;
}
void LeAudioDeviceGroup::Disable(int gatt_if) {
is_enabled_ = false;
for (auto& device_iter : leAudioDevices_) {
if (!device_iter.lock()->autoconnect_flag_) {
continue;
}
auto connection_state = device_iter.lock()->GetConnectionState();
auto address = device_iter.lock()->address_;
btif_storage_set_leaudio_autoconnect(address, false);
device_iter.lock()->autoconnect_flag_ = false;
log::info("Group {} in state {}. Removing {} from background connect",
group_id_, bluetooth::common::ToString(GetState()),
ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_GATTC_CancelOpen(gatt_if, address, false);
if (connection_state == DeviceConnectState::CONNECTING_AUTOCONNECT) {
device_iter.lock()->SetConnectionState(DeviceConnectState::DISCONNECTED);
}
}
}
void LeAudioDeviceGroup::Enable(int gatt_if,
tBTM_BLE_CONN_TYPE reconnection_mode) {
is_enabled_ = true;
for (auto& device_iter : leAudioDevices_) {
if (device_iter.lock()->autoconnect_flag_) {
continue;
}
auto address = device_iter.lock()->address_;
auto connection_state = device_iter.lock()->GetConnectionState();
btif_storage_set_leaudio_autoconnect(address, true);
device_iter.lock()->autoconnect_flag_ = true;
log::info("Group {} in state {}. Adding {} from background connect",
group_id_, bluetooth::common::ToString(GetState()),
ADDRESS_TO_LOGGABLE_CSTR(address));
if (connection_state == DeviceConnectState::DISCONNECTED) {
BTA_GATTC_Open(gatt_if, address, reconnection_mode, false);
device_iter.lock()->SetConnectionState(
DeviceConnectState::CONNECTING_AUTOCONNECT);
}
}
}
bool LeAudioDeviceGroup::IsEnabled(void) const { return is_enabled_; };
void LeAudioDeviceGroup::AddToAllowListNotConnectedGroupMembers(int gatt_if) {
for (const auto& device_iter : leAudioDevices_) {
auto connection_state = device_iter.lock()->GetConnectionState();
if (connection_state == DeviceConnectState::CONNECTED ||
connection_state == DeviceConnectState::CONNECTING_BY_USER ||
connection_state ==
DeviceConnectState::CONNECTED_BY_USER_GETTING_READY ||
connection_state ==
DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY) {
continue;
}
auto address = device_iter.lock()->address_;
log::info("Group {} in state {}. Adding {} to allow list", group_id_,
bluetooth::common::ToString(GetState()),
ADDRESS_TO_LOGGABLE_CSTR(address));
/* When adding set members to allow list, let use direct connect first.
* When it fails (i.e. device is not advertising), it will go to background
* connect. We are doing that because for background connect, stack is using
* slow scan parameters for connection which might delay connecting
* available members.
*/
BTA_GATTC_CancelOpen(gatt_if, address, false);
BTA_GATTC_Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, false);
device_iter.lock()->SetConnectionState(
DeviceConnectState::CONNECTING_AUTOCONNECT);
}
}
void LeAudioDeviceGroup::ApplyReconnectionMode(
int gatt_if, tBTM_BLE_CONN_TYPE reconnection_mode) {
for (const auto& device_iter : leAudioDevices_) {
BTA_GATTC_CancelOpen(gatt_if, device_iter.lock()->address_, false);
BTA_GATTC_Open(gatt_if, device_iter.lock()->address_, reconnection_mode,
false);
log::info("Group {} in state {}. Adding {} to default reconnection mode",
group_id_, bluetooth::common::ToString(GetState()),
ADDRESS_TO_LOGGABLE_CSTR(device_iter.lock()->address_));
device_iter.lock()->SetConnectionState(
DeviceConnectState::CONNECTING_AUTOCONNECT);
}
}
bool LeAudioDeviceGroup::IsConfiguredForContext(
LeAudioContextType context_type) const {
/* Check if all connected group members are configured */
if (GetConfigurationContextType() != context_type) {
return false;
}
/* Check if used configuration is same as the active one.*/
return (stream_conf.conf.get() == GetActiveConfiguration().get());
}
bool LeAudioDeviceGroup::IsAudioSetConfigurationSupported(
LeAudioDevice* leAudioDevice,
const set_configurations::AudioSetConfiguration* audio_set_conf) const {
for (auto direction : {le_audio::types::kLeAudioDirectionSink,
le_audio::types::kLeAudioDirectionSource}) {
const auto& confs = audio_set_conf->confs.get(direction);
if (confs.size() == 0) continue;
LOG_INFO("Looking for requirements: %s - %s", audio_set_conf->name.c_str(),
(direction == 1 ? "snk" : "src"));
for (const auto& ent : confs) {
if (!leAudioDevice->GetCodecConfigurationSupportedPac(direction,
ent.codec)) {
LOG_INFO("Configuration is NOT supported by device %s",
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
return false;
}
}
}
LOG_INFO("Configuration is supported by device %s",
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
return true;
}
const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::FindFirstSupportedConfiguration(
LeAudioContextType context_type,
const set_configurations::AudioSetConfigurations* confs) const {
ASSERT_LOG(confs != nullptr, "confs should not be null");
log::debug("context type: {}, number of connected devices: {}",
bluetooth::common::ToString(context_type), NumOfConnected());
auto num_of_connected = NumOfConnected(context_type);
if (num_of_connected == 0) {
num_of_connected = NumOfConnected();
}
/* Filter out device set for all scenarios */
if (!set_configurations::check_if_may_cover_scenario(confs,
num_of_connected)) {
log::debug(", group is unable to cover scenario");
return nullptr;
}
/* Filter out device set for each end every scenario */
auto required_snk_strategy = GetGroupSinkStrategy();
for (const auto& conf : *confs) {
ASSERT_LOG(conf != nullptr, "confs should not be null");
if (IsAudioSetConfigurationSupported(conf, context_type,
required_snk_strategy)) {
log::debug("found: {}", conf->name);
return conf;
}
}
return nullptr;
}
/* This method should choose aproperiate ASEs to be active and set a cached
* configuration for codec and qos.
*/
bool LeAudioDeviceGroup::Configure(
LeAudioContextType context_type,
const types::BidirectionalPair<AudioContexts>& metadata_context_types,
types::BidirectionalPair<std::vector<uint8_t>> ccid_lists) {
auto conf = GetConfiguration(context_type);
if (!conf) {
log::error(
", requested context type: {} , is in mismatch with cached available "
"contexts",
bluetooth::common::ToString(context_type));
return false;
}
log::debug("setting context type: {}",
bluetooth::common::ToString(context_type));
if (!ConfigureAses(conf.get(), context_type, metadata_context_types,
ccid_lists)) {
log::error(
", requested context type: {}, is in mismatch with cached available "
"contexts",
bluetooth::common::ToString(context_type));
return false;
}
/* Store selected configuration at once it is chosen.
* It might happen it will get unavailable in some point of time
*/
stream_conf.conf = conf;
return true;
}
LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); }
void LeAudioDeviceGroup::PrintDebugState(void) const {
auto active_conf = GetActiveConfiguration();
std::stringstream debug_str;
debug_str << "\n Groupd id: " << group_id_
<< (is_enabled_ ? " enabled" : " disabled")
<< ", state: " << bluetooth::common::ToString(GetState())
<< ", target state: "
<< bluetooth::common::ToString(GetTargetState())
<< ", cig state: " << bluetooth::common::ToString(cig.GetState())
<< ", \n group supported contexts: "
<< bluetooth::common::ToString(GetSupportedContexts())
<< ", \n group available contexts: "
<< bluetooth::common::ToString(GetAvailableContexts())
<< ", \n configuration context type: "
<< bluetooth::common::ToString(GetConfigurationContextType())
<< ", \n active configuration name: "
<< (active_conf ? active_conf->name : " not set");
if (cig.cises.size() > 0) {
log::info("\n Allocated CISes: {}", static_cast<int>(cig.cises.size()));
for (auto cis : cig.cises) {
log::info("\n cis id: {}, type: {}, conn_handle {}, addr: {}", cis.id,
cis.type, cis.conn_handle, cis.addr.ToString());
}
}
if (GetFirstActiveDevice() != nullptr) {
uint32_t sink_delay = 0;
uint32_t source_delay = 0;
GetPresentationDelay(&sink_delay,
bluetooth::le_audio::types::kLeAudioDirectionSink);
GetPresentationDelay(&source_delay,
bluetooth::le_audio::types::kLeAudioDirectionSource);
auto phy_mtos =
GetPhyBitmask(bluetooth::le_audio::types::kLeAudioDirectionSink);
auto phy_stom =
GetPhyBitmask(bluetooth::le_audio::types::kLeAudioDirectionSource);
auto max_transport_latency_mtos = GetMaxTransportLatencyMtos();
auto max_transport_latency_stom = GetMaxTransportLatencyStom();
auto sdu_mts =
GetSduInterval(bluetooth::le_audio::types::kLeAudioDirectionSink);
auto sdu_stom =
GetSduInterval(bluetooth::le_audio::types::kLeAudioDirectionSource);
debug_str << "\n presentation_delay for sink (speaker): " << +sink_delay
<< " us, presentation_delay for source (microphone): "
<< +source_delay << "us, \n MtoS transport latency: "
<< +max_transport_latency_mtos
<< ", StoM transport latency: " << +max_transport_latency_stom
<< ", \n MtoS Phy: " << loghex(phy_mtos)
<< ", MtoS sdu: " << loghex(phy_stom)
<< " \n MtoS sdu: " << +sdu_mts << ", StoM sdu: " << +sdu_stom;
}
log::info("{}", debug_str.str());
for (const auto& device_iter : leAudioDevices_) {
device_iter.lock()->PrintDebugState();
}
}
void LeAudioDeviceGroup::Dump(int fd, int active_group_id) const {
bool is_active = (group_id_ == active_group_id);
std::stringstream stream, stream_pacs;
auto active_conf = GetActiveConfiguration();
stream << "\n == Group id: " << group_id_
<< (is_enabled_ ? " enabled" : " disabled")
<< " == " << (is_active ? ",\tActive\n" : ",\tInactive\n")
<< " state: " << GetState()
<< ",\ttarget state: " << GetTargetState()
<< ",\tcig state: " << cig.GetState() << "\n"
<< " group supported contexts: " << GetSupportedContexts() << "\n"
<< " group available contexts: " << GetAvailableContexts() << "\n"
<< " configuration context type: "
<< bluetooth::common::ToString(GetConfigurationContextType()).c_str()
<< "\n"
<< " active configuration name: "
<< (active_conf ? active_conf->name : " not set") << "\n"
<< " stream configuration: "
<< (stream_conf.conf != nullptr ? stream_conf.conf->name : " unknown ")
<< "\n"
<< " codec id: " << +(stream_conf.codec_id.coding_format)
<< ",\tpending_configuration: " << stream_conf.pending_configuration
<< "\n"
<< " num of devices(connected): " << Size() << "("
<< NumOfConnected() << ")\n"
<< ", num of sinks(connected): "
<< stream_conf.stream_params.sink.num_of_devices << "("
<< stream_conf.stream_params.sink.stream_locations.size() << ")\n"
<< " num of sources(connected): "
<< stream_conf.stream_params.source.num_of_devices << "("
<< stream_conf.stream_params.source.stream_locations.size() << ")\n"
<< " allocated CISes: " << static_cast<int>(cig.cises.size());
if (cig.cises.size() > 0) {
stream << "\n\t == CISes == ";
for (auto cis : cig.cises) {
stream << "\n\t cis id: " << static_cast<int>(cis.id)
<< ",\ttype: " << static_cast<int>(cis.type)
<< ",\tconn_handle: " << static_cast<int>(cis.conn_handle)
<< ",\taddr: " << ADDRESS_TO_LOGGABLE_STR(cis.addr);
}
stream << "\n\t ====";
}
if (GetFirstActiveDevice() != nullptr) {
uint32_t sink_delay;
if (GetPresentationDelay(
&sink_delay, bluetooth::le_audio::types::kLeAudioDirectionSink)) {
stream << "\n presentation_delay for sink (speaker): " << sink_delay
<< " us";
}
uint32_t source_delay;
if (GetPresentationDelay(
&source_delay,
bluetooth::le_audio::types::kLeAudioDirectionSource)) {
stream << "\n presentation_delay for source (microphone): "
<< source_delay << " us";
}
}
stream << "\n == devices: ==";
dprintf(fd, "%s", stream.str().c_str());
for (const auto& device_iter : leAudioDevices_) {
device_iter.lock()->Dump(fd);
}
for (const auto& device_iter : leAudioDevices_) {
auto device = device_iter.lock();
stream_pacs << "\n\taddress: " << device->address_;
device->DumpPacsDebugState(stream_pacs);
}
dprintf(fd, "%s", stream_pacs.str().c_str());
}
LeAudioDeviceGroup* LeAudioDeviceGroups::Add(int group_id) {
/* Get first free group id */
if (FindById(group_id)) {
log::error("group already exists, id: {}", loghex(group_id));
return nullptr;
}
return (groups_.emplace_back(std::make_unique<LeAudioDeviceGroup>(group_id)))
.get();
}
void LeAudioDeviceGroups::Remove(int group_id) {
auto iter = std::find_if(
groups_.begin(), groups_.end(),
[&group_id](auto const& group) { return group->group_id_ == group_id; });
if (iter == groups_.end()) {
log::error("no such group_id: {}", group_id);
return;
}
groups_.erase(iter);
}
LeAudioDeviceGroup* LeAudioDeviceGroups::FindById(int group_id) const {
auto iter = std::find_if(
groups_.begin(), groups_.end(),
[&group_id](auto const& group) { return group->group_id_ == group_id; });
return (iter == groups_.end()) ? nullptr : iter->get();
}
void LeAudioDeviceGroups::Cleanup(void) {
for (auto& g : groups_) {
g->Cleanup();
}
groups_.clear();
}
void LeAudioDeviceGroups::Dump(int fd, int active_group_id) const {
/* Dump first active group */
for (auto& g : groups_) {
if (g->group_id_ == active_group_id) {
g->Dump(fd, active_group_id);
break;
}
}
/* Dump non active group */
for (auto& g : groups_) {
if (g->group_id_ != active_group_id) {
g->Dump(fd, active_group_id);
}
}
}
bool LeAudioDeviceGroups::IsAnyInTransition(void) const {
for (auto& g : groups_) {
if (g->IsInTransition()) {
DLOG(INFO) << __func__ << " group: " << g->group_id_
<< " is in transition";
return true;
}
}
return false;
}
size_t LeAudioDeviceGroups::Size() const { return (groups_.size()); }
std::vector<int> LeAudioDeviceGroups::GetGroupsIds(void) const {
std::vector<int> result;
for (auto const& group : groups_) {
result.push_back(group->group_id_);
}
return result;
}
} // namespace bluetooth::le_audio