blob: a8494891ca702ddf747357aba1722940636dd504 [file] [log] [blame]
/*
* 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 "devices.h"
#include <base/strings/string_number_conversions.h>
#include <map>
#include <tuple>
#include "audio_hal_client/audio_hal_client.h"
#include "bta_csis_api.h"
#include "bta_gatt_queue.h"
#include "bta_groups.h"
#include "bta_le_audio_api.h"
#include "btif_storage.h"
#include "btm_iso_api.h"
#include "btm_iso_api_types.h"
#include "device/include/controller.h"
#include "gd/common/strings.h"
#include "le_audio_log_history.h"
#include "le_audio_set_configuration_provider.h"
#include "metrics_collector.h"
#include "osi/include/log.h"
#include "stack/include/acl_api.h"
using bluetooth::hci::kIsoCigFramingFramed;
using bluetooth::hci::kIsoCigFramingUnframed;
using bluetooth::hci::kIsoCigPackingSequential;
using bluetooth::hci::kIsoCigPhy1M;
using bluetooth::hci::kIsoCigPhy2M;
using bluetooth::hci::iso_manager::kIsoSca0To20Ppm;
using le_audio::AudioSetConfigurationProvider;
using le_audio::DeviceConnectState;
using le_audio::set_configurations::CodecCapabilitySetting;
using le_audio::types::ase;
using le_audio::types::AseState;
using le_audio::types::AudioContexts;
using le_audio::types::AudioLocations;
using le_audio::types::AudioStreamDataPathState;
using le_audio::types::BidirectionalPair;
using le_audio::types::CisType;
using le_audio::types::LeAudioCodecId;
using le_audio::types::LeAudioContextType;
using le_audio::types::LeAudioLc3Config;
namespace le_audio {
std::ostream& operator<<(std::ostream& os, const DeviceConnectState& state) {
const char* char_value_ = "UNKNOWN";
switch (state) {
case DeviceConnectState::CONNECTED:
char_value_ = "CONNECTED";
break;
case DeviceConnectState::DISCONNECTED:
char_value_ = "DISCONNECTED";
break;
case DeviceConnectState::REMOVING:
char_value_ = "REMOVING";
break;
case DeviceConnectState::DISCONNECTING:
char_value_ = "DISCONNECTING";
break;
case DeviceConnectState::DISCONNECTING_AND_RECOVER:
char_value_ = "DISCONNECTING_AND_RECOVER";
break;
case DeviceConnectState::CONNECTING_BY_USER:
char_value_ = "CONNECTING_BY_USER";
break;
case DeviceConnectState::CONNECTED_BY_USER_GETTING_READY:
char_value_ = "CONNECTED_BY_USER_GETTING_READY";
break;
case DeviceConnectState::CONNECTING_AUTOCONNECT:
char_value_ = "CONNECTING_AUTOCONNECT";
break;
case DeviceConnectState::CONNECTED_AUTOCONNECT_GETTING_READY:
char_value_ = "CONNECTED_AUTOCONNECT_GETTING_READY";
break;
}
os << char_value_ << " ("
<< "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
<< ")";
return os;
}
/* 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(
types::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 %p, group_id %d", this, group_id_);
stream_conf.sink_streams.clear();
stream_conf.sink_offloader_streams_target_allocation.clear();
stream_conf.sink_offloader_streams_current_allocation.clear();
stream_conf.sink_audio_channel_allocation = 0;
stream_conf.sink_num_of_channels = 0;
stream_conf.sink_num_of_devices = 0;
stream_conf.sink_sample_frequency_hz = 0;
stream_conf.sink_codec_frames_blocks_per_sdu = 0;
stream_conf.sink_octets_per_codec_frame = 0;
stream_conf.sink_frame_duration_us = 0;
}
void LeAudioDeviceGroup::ClearSourcesFromConfiguration(void) {
LOG_INFO("Group %p, group_id %d", this, group_id_);
stream_conf.source_streams.clear();
stream_conf.source_offloader_streams_target_allocation.clear();
stream_conf.source_offloader_streams_current_allocation.clear();
stream_conf.source_audio_channel_allocation = 0;
stream_conf.source_num_of_channels = 0;
stream_conf.source_num_of_devices = 0;
stream_conf.source_sample_frequency_hz = 0;
stream_conf.source_codec_frames_blocks_per_sdu = 0;
stream_conf.source_octets_per_codec_frame = 0;
stream_conf.source_frame_duration_us = 0;
}
void LeAudioDeviceGroup::CigClearCis(void) {
LOG_INFO("group_id: %d", group_id_);
cises_.clear();
ClearSinksFromConfiguration();
ClearSourcesFromConfiguration();
}
void LeAudioDeviceGroup::Cleanup(void) {
/* Bluetooth is off while streaming - disconnect CISes and remove CIG */
if (GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
if (!stream_conf.sink_streams.empty()) {
for (auto [cis_handle, audio_location] : stream_conf.sink_streams) {
bluetooth::hci::IsoManager::GetInstance()->DisconnectCis(
cis_handle, HCI_ERR_PEER_USER);
if (stream_conf.source_streams.empty()) {
continue;
}
uint16_t cis_hdl = cis_handle;
stream_conf.source_streams.erase(
std::remove_if(
stream_conf.source_streams.begin(),
stream_conf.source_streams.end(),
[cis_hdl](auto& pair) { return pair.first == cis_hdl; }),
stream_conf.source_streams.end());
}
}
if (!stream_conf.source_streams.empty()) {
for (auto [cis_handle, audio_location] : stream_conf.source_streams) {
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();
this->CigClearCis();
}
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;
}
}
}
le_audio::types::CigState LeAudioDeviceGroup::GetCigState(void) const {
return cig_state_;
}
void LeAudioDeviceGroup::SetCigState(le_audio::types::CigState state) {
LOG_VERBOSE("%s -> %s", bluetooth::common::ToString(cig_state_).c_str(),
bluetooth::common::ToString(state).c_str());
cig_state_ = state;
}
bool LeAudioDeviceGroup::Activate(LeAudioContextType context_type) {
bool is_activate = false;
for (auto leAudioDevice : leAudioDevices_) {
if (leAudioDevice.expired()) continue;
bool activated = leAudioDevice.lock()->ActivateConfiguredAses(context_type);
LOG_INFO("Device %s is %s",
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice.lock().get()->address_),
activated ? "activated" : " not activated");
if (activated) {
if (!CigAssignCisIds(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(
types::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,
types::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::GetFirstActiveDeviceByDataPathState(
AudioStreamDataPathState data_path_state) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&data_path_state](auto& d) {
if (d.expired()) {
return false;
}
return (((d.lock()).get())
->GetFirstActiveAseByDataPathState(
data_path_state) != nullptr);
});
if (iter == leAudioDevices_.end()) {
return nullptr;
}
return iter->lock().get();
}
LeAudioDevice* LeAudioDeviceGroup::GetNextActiveDeviceByDataPathState(
LeAudioDevice* leAudioDevice,
AudioStreamDataPathState 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(), [&data_path_state](auto& d) {
if (d.expired()) {
return false;
}
return (((d.lock()).get())
->GetFirstActiveAseByDataPathState(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->codec_config.GetFrameDurationUs();
}
return 0;
}
uint8_t LeAudioDeviceGroup::GetSCA(void) const {
uint8_t sca = 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 {
/* TODO: Decide about packing */
return kIsoCigPackingSequential;
}
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->framing == types::kFramingUnframedPduUnsupported)
return kIsoCigFramingFramed;
} while ((ase = leAudioDevice->GetNextActiveAse(ase)));
} while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
return 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)
// first assignment
max_transport_latency = ase->max_transport_latency;
else if (ase->max_transport_latency < max_transport_latency)
max_transport_latency = ase->max_transport_latency;
}
}
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) << __func__ << ", 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) << __func__ << ", 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(WARNING) << __func__ << ", Different transport latency for group: "
<< " old: " << static_cast<int>(*transport_latency_us)
<< " [us], new: " << static_cast<int>(new_transport_latency_us)
<< " [us]";
return;
}
LOG(INFO) << __func__ << ", updated group " << static_cast<int>(group_id_)
<< " transport latency: " << static_cast<int>(new_transport_latency_us)
<< " [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->retrans_nb;
} else if (ases_pair.source &&
direction == types::kLeAudioDirectionSource) {
return ases_pair.source->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->max_sdu_size;
} else if (ases_pair.source &&
direction == types::kLeAudioDirectionSource) {
return ases_pair.source->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 = kIsoCigPhy1M;
if (controller_get_interface()->supports_ble_2m_phy())
phy_bitfield |= 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->preferred_phy && (phy_bitfield & ase->preferred_phy)) {
phy_bitfield &= ase->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->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 & kIsoCigPhy2M)
return types::kTargetPhy2M;
else if (phy_bitfield & 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->pres_delay_min > delay_max || ase->pres_delay_max < delay_min)
return false;
if (ase->pres_delay_min > delay_min) delay_min = ase->pres_delay_min;
if (ase->pres_delay_max < delay_max) delay_max = ase->pres_delay_max;
if (ase->preferred_pres_delay_min > preferred_delay_min)
preferred_delay_min = ase->preferred_pres_delay_min;
if (ase->preferred_pres_delay_max < preferred_delay_max &&
ase->preferred_pres_delay_max != types::kPresDelayNoPreference)
preferred_delay_max = ase->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("%d", group_id_);
auto old_contexts = GetAvailableContexts();
SetAvailableContexts(GetLatestAvailableContexts());
return old_contexts != GetAvailableContexts();
}
bool LeAudioDeviceGroup::UpdateAudioSetConfigurationCache(
LeAudioContextType ctx_type) {
const le_audio::set_configurations::AudioSetConfiguration* new_conf =
FindFirstSupportedConfiguration(ctx_type);
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 != existing_conf);
/* 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) {
context_to_configuration_cache_map[ctx_type] = std::pair(true, new_conf);
LOG_DEBUG("config: %s -> %s", ToHexString(ctx_type).c_str(),
(new_conf ? new_conf->name.c_str() : "(none)"));
}
return update_config;
}
void LeAudioDeviceGroup::InvalidateCachedConfigurations(void) {
context_to_configuration_cache_map.clear();
}
types::BidirectionalPair<types::AudioContexts>
LeAudioDeviceGroup::GetLatestAvailableContexts() const {
types::BidirectionalPair<types::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 target_state_ != current_state_;
}
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 {
auto iter =
std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
if (d.expired())
return false;
else
return !(((d.lock()).get())->HaveAllActiveAsesCisEst());
});
return iter == leAudioDevices_.end();
}
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::GetFirstFreeCisId(void) const {
for (uint8_t id = 0; id < UINT8_MAX; id++) {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[id](auto& d) {
if (d.expired())
return false;
else
return ((d.lock()).get())->HasCisId(id);
});
if (iter == leAudioDevices_.end()) return id;
}
return kInvalidCisId;
}
uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(CisType cis_type) const {
LOG_INFO("Group: %p, group_id: %d cis_type: %d", this, 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::GetGroupStrategy(
int expected_group_size) const {
/* Simple strategy picker */
LOG_DEBUG(" Group %d size %d", group_id_, expected_group_size);
if (expected_group_size > 1) {
return types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE;
}
LOG_DEBUG("audio location 0x%04lx", 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();
auto channel_cnt =
device->GetLc3SupportedChannelCount(types::kLeAudioDirectionSink);
LOG_DEBUG("Channel count for group %d is %d (device %s)", group_id_,
channel_cnt, ADDRESS_TO_LOGGABLE_CSTR(device->address_));
if (channel_cnt == 1) {
return types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE;
}
return types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE;
}
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::CigGenerateCisIds(
types::LeAudioContextType context_type) {
LOG_INFO("Group %p, group_id: %d, context_type: %s", this, group_id_,
bluetooth::common::ToString(context_type).c_str());
if (cises_.size() > 0) {
LOG_INFO("CIS IDs already generated");
return;
}
const set_configurations::AudioSetConfigurations* confs =
AudioSetConfigurationProvider::Get()->GetConfigurations(context_type);
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_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;
get_cis_count(*confs, group_size, GetGroupStrategy(group_size),
GetAseCount(types::kLeAudioDirectionSink),
GetAseCount(types::kLeAudioDirectionSource), cis_count_bidir,
cis_count_unidir_sink, cis_count_unidir_source);
uint8_t idx = 0;
while (cis_count_bidir > 0) {
struct le_audio::types::cis cis_entry = {
.id = idx,
.addr = RawAddress::kEmpty,
.type = CisType::CIS_TYPE_BIDIRECTIONAL,
.conn_handle = 0,
};
cises_.push_back(cis_entry);
cis_count_bidir--;
idx++;
}
while (cis_count_unidir_sink > 0) {
struct le_audio::types::cis cis_entry = {
.id = idx,
.addr = RawAddress::kEmpty,
.type = CisType::CIS_TYPE_UNIDIRECTIONAL_SINK,
.conn_handle = 0,
};
cises_.push_back(cis_entry);
cis_count_unidir_sink--;
idx++;
}
while (cis_count_unidir_source > 0) {
struct le_audio::types::cis cis_entry = {
.id = idx,
.addr = RawAddress::kEmpty,
.type = CisType::CIS_TYPE_UNIDIRECTIONAL_SOURCE,
.conn_handle = 0,
};
cises_.push_back(cis_entry);
cis_count_unidir_source--;
idx++;
}
}
bool LeAudioDeviceGroup::CigAssignCisIds(LeAudioDevice* leAudioDevice) {
ASSERT_LOG(leAudioDevice, "invalid device");
LOG_INFO("device: %s", ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
struct ase* ase = leAudioDevice->GetFirstActiveAse();
if (!ase) {
LOG_ERROR(" Device %s 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: %d, is already assigned CIS ID: %d, type %d", 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: %d cis_id=%d",
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: %d and ASE ID: %d, assigned Bi-Directional CIS ID: %d",
+ase->id, +matching_bidir_ase->id, +ase->cis_id);
continue;
}
LOG_WARN(
" ASE ID: %d, 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: %d, assigned Uni-Directional Sink CIS ID: %d", 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: %d, assigned Uni-Directional Source CIS ID: %d", ase->id,
ase->cis_id);
}
return true;
}
void LeAudioDeviceGroup::CigAssignCisConnHandles(
const std::vector<uint16_t>& conn_handles) {
LOG_INFO("num of cis handles %d", 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[%d] conn_handle: %d", cises_[i].id,
cises_[i].conn_handle);
}
}
void LeAudioDeviceGroup::CigAssignCisConnHandlesToAses(
LeAudioDevice* leAudioDevice) {
ASSERT_LOG(leAudioDevice, "Invalid device");
LOG_INFO("group: %p, group_id: %d, device: %s", this, group_id_,
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
/* Assign all CIS connection handles to ases */
struct le_audio::types::ase* ase =
leAudioDevice->GetFirstActiveAseByDataPathState(
AudioStreamDataPathState::IDLE);
if (!ase) {
LOG_WARN("No active ASE with AudioStreamDataPathState IDLE");
return;
}
for (; ase != nullptr; ase = leAudioDevice->GetFirstActiveAseByDataPathState(
AudioStreamDataPathState::IDLE)) {
auto ases_pair = leAudioDevice->GetAsesByCisId(ase->cis_id);
if (ases_pair.sink && ases_pair.sink->active) {
ases_pair.sink->cis_conn_hdl = cises_[ase->cis_id].conn_handle;
ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_ASSIGNED;
}
if (ases_pair.source && ases_pair.source->active) {
ases_pair.source->cis_conn_hdl = cises_[ase->cis_id].conn_handle;
ases_pair.source->data_path_state =
AudioStreamDataPathState::CIS_ASSIGNED;
}
}
}
void LeAudioDeviceGroup::CigAssignCisConnHandlesToAses(void) {
LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
ASSERT_LOG(leAudioDevice, "Shouldn't be called without an active device.");
LOG_INFO("Group %p, group_id %d", this, group_id_);
/* Assign all CIS connection handles to ases */
for (; leAudioDevice != nullptr;
leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
CigAssignCisConnHandlesToAses(leAudioDevice);
}
}
void LeAudioDeviceGroup::CigUnassignCis(LeAudioDevice* leAudioDevice) {
ASSERT_LOG(leAudioDevice, "Invalid device");
LOG_INFO("Group %p, group_id %d, device: %s", this, group_id_,
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
for (struct le_audio::types::cis& cis_entry : cises_) {
if (cis_entry.addr == leAudioDevice->address_) {
cis_entry.addr = RawAddress::kEmpty;
}
}
}
bool CheckIfStrategySupported(types::LeAudioConfigurationStrategy strategy,
types::AudioLocations audio_locations,
uint8_t requested_channel_count,
uint8_t channel_count_mask) {
DLOG(INFO) << __func__ << " strategy: " << (int)strategy
<< " locations: " << +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;
DLOG(INFO) << __func__ << " requested chan cnt "
<< +requested_channel_count
<< " chan mask: " << loghex(channel_count_mask);
/* Return true if requested channel count is set in the channel count
* mask. In the channel_count_mask, bit0 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,
types::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 %s, num. of connected: %d",
bluetooth::common::ToString(context_type).c_str(),
+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 (const auto& ent : (*audio_set_conf).confs) {
LOG_DEBUG(" Looking for configuration: %s - %s",
audio_set_conf->name.c_str(),
(ent.direction == types::kLeAudioDirectionSink ? "snk" : "src"));
uint8_t required_device_cnt = ent.device_cnt;
uint8_t max_required_ase_per_dev =
ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
uint8_t active_ase_num = 0;
auto strategy = ent.strategy;
LOG_DEBUG(
" Number of devices: %d, number of ASEs: %d, Max ASE per device: %d "
"strategy: %d",
+required_device_cnt, +ent.ase_cnt, +max_required_ase_per_dev,
static_cast<int>(strategy));
if (ent.direction == types::kLeAudioDirectionSink &&
strategy != required_snk_strategy) {
LOG_DEBUG(" Sink strategy mismatch group!=cfg.entry (%d!=%d)",
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)) {
/* Skip if device has ASE configured in this direction already */
if (device->ases_.empty()) continue;
if (!device->GetCodecConfigurationSupportedPac(ent.direction, ent.codec))
continue;
int needed_ase = std::min(static_cast<int>(max_required_ase_per_dev),
static_cast<int>(ent.ase_cnt - active_ase_num));
/* If we required more ASEs per device which means we would like to
* create more CISes to one device, we should also check the allocation
* if it allows us to do this.
*/
types::AudioLocations audio_locations = 0;
/* Check direction and if audio location allows to create more cise */
if (ent.direction == types::kLeAudioDirectionSink)
audio_locations = device->snk_audio_locations_;
else
audio_locations = device->src_audio_locations_;
/* TODO Make it no Lc3 specific */
if (!CheckIfStrategySupported(
strategy, audio_locations,
std::get<LeAudioLc3Config>(ent.codec.config).GetChannelCount(),
device->GetLc3SupportedChannelCount(ent.direction))) {
LOG_DEBUG(" insufficient device audio allocation: %lu",
audio_locations.to_ulong());
continue;
}
for (auto& ase : device->ases_) {
if (ase.direction != ent.direction) continue;
active_ase_num++;
needed_ase--;
if (needed_ase == 0) break;
}
if (needed_ase > 0) {
LOG_DEBUG("Device has too less ASEs. Still needed ases %d", needed_ase);
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");
return false;
}
}
LOG_DEBUG("Chosen ASE Configuration for group: %d, configuration: %s",
this->group_id_, audio_set_conf->name.c_str());
return true;
}
static uint32_t GetFirstLeft(const types::AudioLocations& audio_locations) {
uint32_t audio_location_ulong = audio_locations.to_ulong();
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeft)
return codec_spec_conf::kLeAudioLocationFrontLeft;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackLeft)
return codec_spec_conf::kLeAudioLocationBackLeft;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftOfCenter)
return codec_spec_conf::kLeAudioLocationFrontLeftOfCenter;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideLeft)
return codec_spec_conf::kLeAudioLocationSideLeft;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontLeft)
return codec_spec_conf::kLeAudioLocationTopFrontLeft;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackLeft)
return codec_spec_conf::kLeAudioLocationTopBackLeft;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideLeft)
return codec_spec_conf::kLeAudioLocationTopSideLeft;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontLeft)
return codec_spec_conf::kLeAudioLocationBottomFrontLeft;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontLeftWide)
return codec_spec_conf::kLeAudioLocationFrontLeftWide;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationLeftSurround)
return codec_spec_conf::kLeAudioLocationLeftSurround;
return 0;
}
static uint32_t GetFirstRight(const types::AudioLocations& audio_locations) {
uint32_t audio_location_ulong = audio_locations.to_ulong();
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRight)
return codec_spec_conf::kLeAudioLocationFrontRight;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBackRight)
return codec_spec_conf::kLeAudioLocationBackRight;
if (audio_location_ulong &
codec_spec_conf::kLeAudioLocationFrontRightOfCenter)
return codec_spec_conf::kLeAudioLocationFrontRightOfCenter;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationSideRight)
return codec_spec_conf::kLeAudioLocationSideRight;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopFrontRight)
return codec_spec_conf::kLeAudioLocationTopFrontRight;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopBackRight)
return codec_spec_conf::kLeAudioLocationTopBackRight;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationTopSideRight)
return codec_spec_conf::kLeAudioLocationTopSideRight;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationBottomFrontRight)
return codec_spec_conf::kLeAudioLocationBottomFrontRight;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationFrontRightWide)
return codec_spec_conf::kLeAudioLocationFrontRightWide;
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationRightSurround)
return codec_spec_conf::kLeAudioLocationRightSurround;
return 0;
}
uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy,
const types::AudioLocations& device_locations,
types::AudioLocations& group_locations) {
LOG_DEBUG("strategy: %d, locations: 0x%lx, input group locations: 0x%lx",
(int)strategy, device_locations.to_ulong(),
group_locations.to_ulong());
auto is_left_not_yet_assigned =
!(group_locations.to_ulong() & codec_spec_conf::kLeAudioLocationAnyLeft);
auto is_right_not_yet_assigned =
!(group_locations.to_ulong() & codec_spec_conf::kLeAudioLocationAnyRight);
uint32_t left_device_loc = GetFirstLeft(device_locations);
uint32_t right_device_loc = GetFirstRight(device_locations);
if (left_device_loc == 0 && right_device_loc == 0) {
LOG_WARN("Can't find device able to render left and right audio channel");
}
switch (strategy) {
case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
if (left_device_loc && is_left_not_yet_assigned) {
group_locations |= left_device_loc;
return left_device_loc;
}
if (right_device_loc && is_right_not_yet_assigned) {
group_locations |= right_device_loc;
return right_device_loc;
}
break;
case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE:
if (left_device_loc && right_device_loc) {
group_locations |= left_device_loc | right_device_loc;
return left_device_loc | right_device_loc;
}
break;
default:
LOG_ALWAYS_FATAL("%s: Unknown strategy: %hhu", __func__, strategy);
return 0;
}
LOG_ERROR(
"Can't find device for left/right channel. Strategy: %hhu, "
"device_locations: %lx, output group_locations: %lx.",
strategy, device_locations.to_ulong(), group_locations.to_ulong());
/* Return either any left or any right audio location. It might result with
* multiple devices within the group having the same location.
*/
return left_device_loc ? left_device_loc : right_device_loc;
}
bool LeAudioDevice::ConfigureAses(
const le_audio::set_configurations::SetConfiguration& ent,
types::LeAudioContextType context_type,
uint8_t* number_of_already_active_group_ase,
BidirectionalPair<AudioLocations>& group_audio_locations_memo,
const BidirectionalPair<AudioContexts>& metadata_context_types,
const BidirectionalPair<std::vector<uint8_t>>& ccid_lists,
bool reuse_cis_id) {
/* First try to use the already configured ASE */
auto ase = GetFirstActiveAseByDirection(ent.direction);
if (ase) {
LOG_INFO("Using an already active ASE id=%d", ase->id);
} else {
ase = GetFirstInactiveAse(ent.direction, reuse_cis_id);
}
if (!ase) {
LOG_ERROR("Unable to find an ASE to configure");
return false;
}
/* The number_of_already_active_group_ase keeps all the active ases
* in other devices in the group.
* This function counts active ases only for this device, and we count here
* new active ases and already active ases which we want to reuse in the
* scenario
*/
uint8_t active_ases = *number_of_already_active_group_ase;
uint8_t max_required_ase_per_dev =
ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy;
auto pac = GetCodecConfigurationSupportedPac(ent.direction, ent.codec);
if (!pac) return false;
int needed_ase = std::min((int)(max_required_ase_per_dev),
(int)(ent.ase_cnt - active_ases));
types::AudioLocations audio_locations = 0;
/* Check direction and if audio location allows to create more cise */
if (ent.direction == types::kLeAudioDirectionSink) {
audio_locations = snk_audio_locations_;
} else {
audio_locations = src_audio_locations_;
}
for (; needed_ase && ase; needed_ase--) {
ase->active = true;
ase->configured_for_context_type = context_type;
active_ases++;
/* In case of late connect, we could be here for STREAMING ase.
* in such case, it is needed to mark ase as known active ase which
* is important to validate scenario and is done already few lines above.
* Nothing more to do is needed here.
*/
if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
ase->reconfigure = true;
ase->target_latency = ent.qos.target_latency;
ase->codec_id = ent.codec.id;
/* TODO: find better way to not use LC3 explicitly */
ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config);
/*Let's choose audio channel allocation if not set */
ase->codec_config.audio_channel_allocation =
PickAudioLocation(strategy, audio_locations,
group_audio_locations_memo.get_ref(ent.direction));
/* Get default value if no requirement for specific frame blocks per sdu
*/
if (!ase->codec_config.codec_frames_blocks_per_sdu) {
ase->codec_config.codec_frames_blocks_per_sdu =
GetMaxCodecFramesPerSduFromPac(pac);
}
ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts(
*ase->codec_config.audio_channel_allocation) *
*ase->codec_config.octets_per_codec_frame *
*ase->codec_config.codec_frames_blocks_per_sdu;
ase->retrans_nb = ent.qos.retransmission_number;
ase->max_transport_latency = ent.qos.max_transport_latency;
/* Filter multidirectional audio context for each ase direction */
auto directional_audio_context =
metadata_context_types.get(ase->direction) &
GetAvailableContexts(ase->direction);
if (directional_audio_context.any()) {
ase->metadata = GetMetadata(directional_audio_context,
ccid_lists.get(ase->direction));
} else {
ase->metadata =
GetMetadata(AudioContexts(LeAudioContextType::UNSPECIFIED),
std::vector<uint8_t>());
}
}
LOG_DEBUG(
"device=%s, activated ASE id=%d, direction=%s, max_sdu_size=%d, "
"cis_id=%d, target_latency=%d",
ADDRESS_TO_LOGGABLE_CSTR(address_), ase->id,
(ent.direction == 1 ? "snk" : "src"), ase->max_sdu_size, ase->cis_id,
ent.qos.target_latency);
/* Try to use the already active ASE */
ase = GetNextActiveAseWithSameDirection(ase);
if (ase == nullptr) {
ase = GetFirstInactiveAse(ent.direction, reuse_cis_id);
}
}
*number_of_already_active_group_ase = active_ases;
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,
types::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 (const auto& ent : (*audio_set_conf).confs) {
LOG_DEBUG(" Looking for requirements: %s, - %s",
audio_set_conf->name.c_str(),
(ent.direction == 1 ? "snk" : "src"));
uint8_t required_device_cnt = ent.device_cnt;
uint8_t max_required_ase_per_dev =
ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
uint8_t active_ase_num = 0;
le_audio::types::LeAudioConfigurationStrategy strategy = ent.strategy;
LOG_DEBUG(
"Number of devices: %d number of ASEs: %d, Max ASE per device: %d "
"strategy: %d",
required_device_cnt, ent.ase_cnt, max_required_ase_per_dev,
(int)strategy);
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 %s, in the state %s",
ADDRESS_TO_LOGGABLE_CSTR(dev->address_),
bluetooth::common::ToString(dev->GetConnectionState()).c_str());
return;
}
if (!dev->ConfigureAses(ent, context_type, &active_ase_num,
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: %d, configuration: %s",
group_id_, audio_set_conf->name.c_str());
configuration_context_type_ = context_type;
metadata_context_type_ = metadata_context_types;
return true;
}
const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::GetCachedConfiguration(
types::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;
}
const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::GetActiveConfiguration(void) const {
return GetCachedConfiguration(configuration_context_type_);
}
const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::GetConfiguration(types::LeAudioContextType context_type) {
if (context_type == types::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) {
std::tie(is_valid, conf) =
context_to_configuration_cache_map.at(context_type);
}
if (!is_valid || (conf == nullptr)) {
UpdateAudioSetConfigurationCache(context_type);
}
return GetCachedConfiguration(context_type);
}
std::optional<LeAudioCodecConfiguration>
LeAudioDeviceGroup::GetCachedCodecConfigurationByDirection(
types::LeAudioContextType context_type, uint8_t direction) const {
const set_configurations::AudioSetConfiguration* 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) {
if (conf.direction != direction) continue;
if (group_config.sample_rate != 0 &&
conf.codec.GetConfigSamplingFrequency() != group_config.sample_rate) {
LOG(WARNING) << __func__
<< ", stream configuration could not be "
"determined (sampling frequency differs) for direction: "
<< loghex(direction);
return std::nullopt;
}
group_config.sample_rate = conf.codec.GetConfigSamplingFrequency();
if (group_config.data_interval_us != 0 &&
conf.codec.GetConfigDataIntervalUs() != group_config.data_interval_us) {
LOG(WARNING) << __func__
<< ", stream configuration could not be "
"determined (data interval differs) for direction: "
<< loghex(direction);
return std::nullopt;
}
group_config.data_interval_us = conf.codec.GetConfigDataIntervalUs();
if (group_config.bits_per_sample != 0 &&
conf.codec.GetConfigBitsPerSample() != group_config.bits_per_sample) {
LOG(WARNING) << __func__
<< ", stream configuration could not be "
"determined (bits per sample differs) for direction: "
<< loghex(direction);
return std::nullopt;
}
group_config.bits_per_sample = conf.codec.GetConfigBitsPerSample();
group_config.num_channels +=
conf.codec.GetConfigChannelCount() * conf.device_cnt;
}
if (group_config.IsInvalid()) return std::nullopt;
return group_config;
}
std::optional<LeAudioCodecConfiguration>
LeAudioDeviceGroup::GetCodecConfigurationByDirection(
types::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) {
std::tie(is_valid, conf) =
context_to_configuration_cache_map.at(context_type);
}
if (!is_valid || (conf == nullptr)) {
UpdateAudioSetConfigurationCache(context_type);
}
/* Return the cached value */
return GetCachedCodecConfigurationByDirection(context_type, direction);
}
bool LeAudioDeviceGroup::IsAudioSetConfigurationAvailable(
types::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 iter = std::find_if(
stream_conf.sink_streams.begin(), stream_conf.sink_streams.end(),
[cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; });
if (iter != stream_conf.sink_streams.end()) return true;
iter = std::find_if(
stream_conf.source_streams.begin(), stream_conf.source_streams.end(),
[cis_conn_hdl](auto& pair) { return cis_conn_hdl == pair.first; });
return (iter != stream_conf.source_streams.end());
}
void LeAudioDeviceGroup::StreamOffloaderUpdated(uint8_t direction) {
if (direction == le_audio::types::kLeAudioDirectionSource) {
stream_conf.source_is_initial = false;
} else {
stream_conf.sink_is_initial = false;
}
}
void LeAudioDeviceGroup::RemoveCisFromStreamIfNeeded(
LeAudioDevice* leAudioDevice, uint16_t cis_conn_hdl) {
LOG_INFO(" CIS Connection Handle: %d", cis_conn_hdl);
if (!IsCisPartOfCurrentStream(cis_conn_hdl)) return;
auto sink_channels = stream_conf.sink_num_of_channels;
auto source_channels = stream_conf.source_num_of_channels;
if (!stream_conf.sink_streams.empty() ||
!stream_conf.source_streams.empty()) {
stream_conf.sink_streams.erase(
std::remove_if(
stream_conf.sink_streams.begin(), stream_conf.sink_streams.end(),
[leAudioDevice, &cis_conn_hdl, this](auto& pair) {
if (!cis_conn_hdl) {
cis_conn_hdl = pair.first;
}
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(cis_conn_hdl);
if (ases_pair.sink && cis_conn_hdl == pair.first) {
stream_conf.sink_num_of_devices--;
stream_conf.sink_num_of_channels -=
ases_pair.sink->codec_config.channel_count;
stream_conf.sink_audio_channel_allocation &= ~pair.second;
}
return (ases_pair.sink && cis_conn_hdl == pair.first);
}),
stream_conf.sink_streams.end());
stream_conf.source_streams.erase(
std::remove_if(
stream_conf.source_streams.begin(),
stream_conf.source_streams.end(),
[leAudioDevice, &cis_conn_hdl, this](auto& pair) {
if (!cis_conn_hdl) {
cis_conn_hdl = pair.first;
}
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(cis_conn_hdl);
if (ases_pair.source && cis_conn_hdl == pair.first) {
stream_conf.source_num_of_devices--;
stream_conf.source_num_of_channels -=
ases_pair.source->codec_config.channel_count;
stream_conf.source_audio_channel_allocation &= ~pair.second;
}
return (ases_pair.source && cis_conn_hdl == pair.first);
}),
stream_conf.source_streams.end());
LOG_INFO(
" Sink Number Of Devices: %d"
", Sink Number Of Channels: %d"
", Source Number Of Devices: %d"
", Source Number Of Channels: %d",
stream_conf.sink_num_of_devices, stream_conf.sink_num_of_channels,
stream_conf.source_num_of_devices, stream_conf.source_num_of_channels);
}
if (stream_conf.sink_num_of_channels == 0) {
ClearSinksFromConfiguration();
}
if (stream_conf.source_num_of_channels == 0) {
ClearSourcesFromConfiguration();
}
/* Update offloader streams if needed */
if (sink_channels > stream_conf.sink_num_of_channels) {
CreateStreamVectorForOffloader(le_audio::types::kLeAudioDirectionSink);
}
if (source_channels > stream_conf.source_num_of_channels) {
CreateStreamVectorForOffloader(le_audio::types::kLeAudioDirectionSource);
}
CigUnassignCis(leAudioDevice);
}
void LeAudioDeviceGroup::CreateStreamVectorForOffloader(uint8_t direction) {
if (CodecManager::GetInstance()->GetCodecLocation() !=
le_audio::types::CodecLocation::ADSP) {
return;
}
CisType cis_type;
std::vector<std::pair<uint16_t, uint32_t>>* streams;
std::vector<stream_map_info>* offloader_streams_target_allocation;
std::vector<stream_map_info>* offloader_streams_current_allocation;
std::string tag;
uint32_t available_allocations = 0;
bool* changed_flag;
bool* is_initial;
if (direction == le_audio::types::kLeAudioDirectionSource) {
changed_flag = &stream_conf.source_offloader_changed;
is_initial = &stream_conf.source_is_initial;
cis_type = CisType::CIS_TYPE_UNIDIRECTIONAL_SOURCE;
streams = &stream_conf.source_streams;
offloader_streams_target_allocation =
&stream_conf.source_offloader_streams_target_allocation;
offloader_streams_current_allocation =
&stream_conf.source_offloader_streams_current_allocation;
tag = "Source";
available_allocations = AdjustAllocationForOffloader(
stream_conf.source_audio_channel_allocation);
} else {
changed_flag = &stream_conf.sink_offloader_changed;
is_initial = &stream_conf.sink_is_initial;
cis_type = CisType::CIS_TYPE_UNIDIRECTIONAL_SINK;
streams = &stream_conf.sink_streams;
offloader_streams_target_allocation =
&stream_conf.sink_offloader_streams_target_allocation;
offloader_streams_current_allocation =
&stream_conf.sink_offloader_streams_current_allocation;
tag = "Sink";
available_allocations =
AdjustAllocationForOffloader(stream_conf.sink_audio_channel_allocation);
}
if (available_allocations == 0) {
LOG_ERROR("There is no CIS connected");
return;
}
if (offloader_streams_target_allocation->size() == 0) {
*is_initial = true;
} else if (*is_initial || LeAudioHalVerifier::SupportsStreamActiveApi()) {
// As multiple CISes phone call case, the target_allocation already have the
// previous data, but the is_initial flag not be cleared. We need to clear
// here to avoid make duplicated target allocation stream map.
offloader_streams_target_allocation->clear();
}
offloader_streams_current_allocation->clear();
*changed_flag = true;
bool not_all_cises_connected = false;
if (available_allocations != codec_spec_conf::kLeAudioLocationStereo) {
not_all_cises_connected = true;
}
/* If the all cises are connected as stream started, reset changed_flag that
* the bt stack wouldn't send another audio configuration for the connection
* status */
if (*is_initial && !not_all_cises_connected) {
*changed_flag = false;
}
for (auto& cis_entry : cises_) {
if ((cis_entry.type == CisType::CIS_TYPE_BIDIRECTIONAL ||
cis_entry.type == cis_type) &&
cis_entry.conn_handle != 0) {
uint32_t target_allocation = 0;
uint32_t current_allocation = 0;
bool is_active = false;
for (const auto& s : *streams) {
if (s.first == cis_entry.conn_handle) {
is_active = true;
target_allocation = AdjustAllocationForOffloader(s.second);
current_allocation = target_allocation;
if (not_all_cises_connected) {
/* Tell offloader to mix on this CIS.*/
current_allocation = codec_spec_conf::kLeAudioLocationStereo;
}
break;
}
}
if (target_allocation == 0) {
/* Take missing allocation for that one .*/
target_allocation =
codec_spec_conf::kLeAudioLocationStereo & ~available_allocations;
}
LOG_INFO(
"%s: Cis handle 0x%04x, target allocation 0x%08x, current "
"allocation 0x%08x, active: %d",
tag.c_str(), cis_entry.conn_handle, target_allocation,
current_allocation, is_active);
if (*is_initial || LeAudioHalVerifier::SupportsStreamActiveApi()) {
offloader_streams_target_allocation->emplace_back(stream_map_info(
cis_entry.conn_handle, target_allocation, is_active));
}
offloader_streams_current_allocation->emplace_back(stream_map_info(
cis_entry.conn_handle, current_allocation, is_active));
}
}
}
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) {
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 %d in state %s. Removing %s from background connect",
group_id_, bluetooth::common::ToString(GetState()).c_str(),
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) {
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 %d in state %s. Adding %s from background connect",
group_id_, bluetooth::common::ToString(GetState()).c_str(),
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 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 %d in state %s. Adding %s to allow list ", group_id_,
bluetooth::common::ToString(GetState()).c_str(),
ADDRESS_TO_LOGGABLE_CSTR(address));
BTA_GATTC_CancelOpen(gatt_if, address, false);
BTA_GATTC_Open(gatt_if, address, BTM_BLE_BKG_CONNECT_ALLOW_LIST, false);
device_iter.lock()->SetConnectionState(
DeviceConnectState::CONNECTING_AUTOCONNECT);
}
}
bool LeAudioDeviceGroup::IsConfiguredForContext(
types::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 == GetActiveConfiguration());
}
bool LeAudioDeviceGroup::IsAudioSetConfigurationSupported(
LeAudioDevice* leAudioDevice,
const set_configurations::AudioSetConfiguration* audio_set_conf) const {
for (const auto& ent : (*audio_set_conf).confs) {
LOG_INFO("Looking for requirements: %s - %s", audio_set_conf->name.c_str(),
(ent.direction == 1 ? "snk" : "src"));
auto pac = leAudioDevice->GetCodecConfigurationSupportedPac(ent.direction,
ent.codec);
if (pac != nullptr) {
LOG_INFO("Configuration is supported by device %s",
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
return true;
}
}
LOG_INFO("Configuration is NOT supported by device %s",
ADDRESS_TO_LOGGABLE_CSTR(leAudioDevice->address_));
return false;
}
const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::FindFirstSupportedConfiguration(
LeAudioContextType context_type) const {
const set_configurations::AudioSetConfigurations* confs =
AudioSetConfigurationProvider::Get()->GetConfigurations(context_type);
LOG_DEBUG("context type: %s, number of connected devices: %d",
bluetooth::common::ToString(context_type).c_str(),
+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 = GetGroupStrategy(Size());
for (const auto& conf : *confs) {
if (IsAudioSetConfigurationSupported(conf, context_type,
required_snk_strategy)) {
LOG_DEBUG("found: %s", conf->name.c_str());
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: %s , is in mismatch with cached available "
"contexts ",
bluetooth::common::ToString(context_type).c_str());
return false;
}
LOG_DEBUG(" setting context type: %s",
bluetooth::common::ToString(context_type).c_str());
if (!ConfigureAses(conf, context_type, metadata_context_types, ccid_lists)) {
LOG_ERROR(
", requested context type: %s , is in mismatch with cached available "
"contexts",
bluetooth::common::ToString(context_type).c_str());
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_
<< (enabled_ ? " enabled" : " disabled")
<< ", state: " << bluetooth::common::ToString(GetState())
<< ", target state: "
<< bluetooth::common::ToString(GetTargetState())
<< ", cig state: " << bluetooth::common::ToString(cig_state_)
<< ", \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 (cises_.size() > 0) {
LOG_INFO("\n Allocated CISes: %d", static_cast<int>(cises_.size()));
for (auto cis : cises_) {
LOG_INFO("\n cis id: %d, type: %d, conn_handle %d, addr: %s", cis.id,
cis.type, cis.conn_handle, cis.addr.ToString().c_str());
}
}
if (GetFirstActiveDevice() != nullptr) {
uint32_t sink_delay = 0;
uint32_t source_delay = 0;
GetPresentationDelay(&sink_delay, le_audio::types::kLeAudioDirectionSink);
GetPresentationDelay(&source_delay,
le_audio::types::kLeAudioDirectionSource);
auto phy_mtos = GetPhyBitmask(le_audio::types::kLeAudioDirectionSink);
auto phy_stom = GetPhyBitmask(le_audio::types::kLeAudioDirectionSource);
auto max_transport_latency_mtos = GetMaxTransportLatencyMtos();
auto max_transport_latency_stom = GetMaxTransportLatencyStom();
auto sdu_mts = GetSduInterval(le_audio::types::kLeAudioDirectionSink);
auto sdu_stom = GetSduInterval(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("%s", debug_str.str().c_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_
<< (enabled_ ? " enabled" : " disabled")
<< " == " << (is_active ? ",\tActive\n" : ",\tInactive\n")
<< " state: " << GetState()
<< ",\ttarget state: " << GetTargetState()
<< ",\tcig state: " << cig_state_ << "\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.id.coding_format)
<< ",\tpending_configuration: " << stream_conf.pending_configuration
<< "\n"
<< " num of devices(connected): " << Size() << "("
<< NumOfConnected() << ")\n"
<< ", num of sinks(connected): " << stream_conf.sink_num_of_devices
<< "(" << stream_conf.sink_streams.size() << ")\n"
<< " num of sources(connected): "
<< stream_conf.source_num_of_devices << "("
<< stream_conf.source_streams.size() << ")\n"
<< " allocated CISes: " << static_cast<int>(cises_.size());
if (cises_.size() > 0) {
stream << "\n\t == CISes == ";
for (auto cis : 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,
le_audio::types::kLeAudioDirectionSink)) {
stream << "\n presentation_delay for sink (speaker): " << sink_delay
<< " us";
}
uint32_t source_delay;
if (GetPresentationDelay(&source_delay,
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());
}
/* LeAudioDevice Class methods implementation */
void LeAudioDevice::SetConnectionState(DeviceConnectState state) {
LOG_DEBUG("%s, %s --> %s", ADDRESS_TO_LOGGABLE_CSTR(address_),
bluetooth::common::ToString(connection_state_).c_str(),
bluetooth::common::ToString(state).c_str());
LeAudioLogHistory::Get()->AddLogHistory(
kLogConnectionTag, group_id_, address_,
bluetooth::common::ToString(connection_state_) + " -> ",
"->" + bluetooth::common::ToString(state));
connection_state_ = state;
}
DeviceConnectState LeAudioDevice::GetConnectionState(void) {
return connection_state_;
}
void LeAudioDevice::ClearPACs(void) {
snk_pacs_.clear();
src_pacs_.clear();
}
LeAudioDevice::~LeAudioDevice(void) {
alarm_free(link_quality_timer);
this->ClearPACs();
}
void LeAudioDevice::RegisterPACs(
std::vector<struct types::acs_ac_record>* pac_db,
std::vector<struct types::acs_ac_record>* pac_recs) {
/* Clear PAC database for characteristic in case if re-read, indicated */
if (!pac_db->empty()) {
DLOG(INFO) << __func__ << ", upgrade PACs for characteristic";
pac_db->clear();
}
/* TODO wrap this logging part with debug flag */
for (const struct types::acs_ac_record& pac : *pac_recs) {
LOG(INFO) << "Registering PAC"
<< "\n\tCoding format: " << loghex(pac.codec_id.coding_format)
<< "\n\tVendor codec company ID: "
<< loghex(pac.codec_id.vendor_company_id)
<< "\n\tVendor codec ID: " << loghex(pac.codec_id.vendor_codec_id)
<< "\n\tCodec spec caps:\n"
<< pac.codec_spec_caps.ToString("",
types::CodecCapabilitiesLtvFormat)
<< "\n\tMetadata: "
<< base::HexEncode(pac.metadata.data(), pac.metadata.size());
}
pac_db->insert(pac_db->begin(), pac_recs->begin(), pac_recs->end());
}
struct ase* LeAudioDevice::GetAseByValHandle(uint16_t val_hdl) {
auto iter = std::find_if(
ases_.begin(), ases_.end(),
[&val_hdl](const auto& ase) { return ase.hdls.val_hdl == val_hdl; });
return (iter == ases_.end()) ? nullptr : &(*iter);
}
int LeAudioDevice::GetAseCount(uint8_t direction) {
return std::count_if(ases_.begin(), ases_.end(), [direction](const auto& a) {
return a.direction == direction;
});
}
struct ase* LeAudioDevice::GetFirstAseWithState(uint8_t direction,
AseState state) {
auto iter = std::find_if(
ases_.begin(), ases_.end(), [direction, state](const auto& ase) {
return ((ase.direction == direction) && (ase.state == state));
});
return (iter == ases_.end()) ? nullptr : &(*iter);
}
struct ase* LeAudioDevice::GetFirstActiveAse(void) {
auto iter = std::find_if(ases_.begin(), ases_.end(),
[](const auto& ase) { return ase.active; });
return (iter == ases_.end()) ? nullptr : &(*iter);
}
struct ase* LeAudioDevice::GetFirstActiveAseByDirection(uint8_t direction) {
auto iter =
std::find_if(ases_.begin(), ases_.end(), [direction](const auto& ase) {
return (ase.active && (ase.direction == direction));
});
return (iter == ases_.end()) ? nullptr : &(*iter);
}
struct ase* LeAudioDevice::GetNextActiveAseWithSameDirection(
struct ase* base_ase) {
auto iter = std::find_if(ases_.begin(), ases_.end(),
[&base_ase](auto& ase) { return base_ase == &ase; });
/* Invalid ase given */
if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1)
return nullptr;
iter =
std::find_if(std::next(iter, 1), ases_.end(), [&iter](const auto& ase) {
return ase.active && (*iter).direction == ase.direction;
});
return (iter == ases_.end()) ? nullptr : &(*iter);
}
struct ase* LeAudioDevice::GetNextActiveAseWithDifferentDirection(
struct ase* base_ase) {
auto iter = std::find_if(ases_.begin(), ases_.end(),
[&base_ase](auto& ase) { return base_ase == &ase; });
/* Invalid ase given */
if (std::distance(iter, ases_.end()) < 1) {
LOG_DEBUG("ASE %d does not use bidirectional CIS", base_ase->id);
return nullptr;
}
iter =
std::find_if(std::next(iter, 1), ases_.end(), [&iter](const auto& ase) {
return ase.active && iter->direction != ase.direction;
});
if (iter == ases_.end()) {
return nullptr;
}
return &(*iter);
}
struct ase* LeAudioDevice::GetFirstActiveAseByDataPathState(
types::AudioStreamDataPathState state) {
auto iter =
std::find_if(ases_.begin(), ases_.end(), [state](const auto& ase) {
return (ase.active && (ase.data_path_state == state));
});
return (iter == ases_.end()) ? nullptr : &(*iter);
}
struct ase* LeAudioDevice::GetFirstInactiveAse(uint8_t direction,
bool reuse_cis_id) {
auto iter = std::find_if(ases_.begin(), ases_.end(),
[direction, reuse_cis_id](const auto& ase) {
if (ase.active || (ase.direction != direction))
return false;
if (!reuse_cis_id) return true;
return (ase.cis_id != kInvalidCisId);
});
/* If ASE is found, return it */
if (iter != ases_.end()) return &(*iter);
/* If reuse was not set, that means there is no inactive ASE available. */
if (!reuse_cis_id) return nullptr;
/* Since there is no ASE with assigned CIS ID, it means new configuration
* needs more ASEs then it was configured before.
* Let's find just inactive one */
iter = std::find_if(ases_.begin(), ases_.end(),
[direction](const auto& ase) {
if (ase.active || (ase.direction != direction))
return false;
return true;
});
return (iter == ases_.end()) ? nullptr : &(*iter);
}
struct ase* LeAudioDevice::GetNextActiveAse(struct ase* base_ase) {
auto iter = std::find_if(ases_.begin(), ases_.end(),
[&base_ase](auto& ase) { return base_ase == &ase; });
/* Invalid ase given */
if (iter == ases_.end() || std::distance(iter, ases_.end()) < 1)
return nullptr;
iter = std::find_if(std::next(iter, 1), ases_.end(),
[](const auto& ase) { return ase.active; });
return (iter == ases_.end()) ? nullptr : &(*iter);
}
struct ase* LeAudioDevice::GetAseToMatchBidirectionCis(struct ase* base_ase) {
auto iter = std::find_if(ases_.begin(), ases_.end(), [&base_ase](auto& ase) {
return (base_ase->cis_conn_hdl == ase.cis_conn_hdl) &&
(base_ase->direction != ase.direction);
});
return (iter == ases_.end()) ? nullptr : &(*iter);
}
BidirectionalPair<struct ase*> LeAudioDevice::GetAsesByCisConnHdl(
uint16_t conn_hdl) {
BidirectionalPair<struct ase*> ases = {nullptr, nullptr};
for (auto& ase : ases_) {
if (ase.cis_conn_hdl == conn_hdl) {
if (ase.direction == types::kLeAudioDirectionSink) {
ases.sink = &ase;
} else {
ases.source = &ase;
}
}
}
return ases;
}
BidirectionalPair<struct ase*> LeAudioDevice::GetAsesByCisId(uint8_t cis_id) {
BidirectionalPair<struct ase*> ases = {nullptr, nullptr};
for (auto& ase : ases_) {
if (ase.cis_id == cis_id) {
if (ase.direction == types::kLeAudioDirectionSink) {
ases.sink = &ase;
} else {
ases.source = &ase;
}
}
}
return ases;
}
bool LeAudioDevice::HaveActiveAse(void) {
auto iter = std::find_if(ases_.begin(), ases_.end(),
[](const auto& ase) { return ase.active; });
return iter != ases_.end();
}
bool LeAudioDevice::HaveAnyUnconfiguredAses(void) {
/* In configuring state when active in Idle or Configured and reconfigure */
auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
if (!ase.active) return false;
if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE ||
((ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) &&
ase.reconfigure))
return true;
return false;
});
return iter != ases_.end();
}
bool LeAudioDevice::HaveAllActiveAsesSameState(AseState state) {
auto iter = std::find_if(
ases_.begin(), ases_.end(),
[&state](const auto& ase) { return ase.active && (ase.state != state); });
return iter == ases_.end();
}
bool LeAudioDevice::IsReadyToCreateStream(void) {
auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
if (!ase.active) return false;
if (ase.direction == types::kLeAudioDirectionSink &&
(ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING &&
ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING))
return true;
if (ase.direction == types::kLeAudioDirectionSource &&
ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING)
return true;
return false;
});
return iter == ases_.end();
}
bool LeAudioDevice::IsReadyToSuspendStream(void) {
auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
if (!ase.active) return false;
if (ase.direction == types::kLeAudioDirectionSink &&
ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED)
return true;
if (ase.direction == types::kLeAudioDirectionSource &&
ase.state != AseState::BTA_LE_AUDIO_ASE_STATE_DISABLING)
return true;
return false;
});
return iter == ases_.end();
}
bool LeAudioDevice::HaveAllActiveAsesCisEst(void) {
if (ases_.empty()) {
LOG_WARN("No ases for device %s", ADDRESS_TO_LOGGABLE_CSTR(address_));
return false;
}
auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
return ase.active &&
(ase.data_path_state != AudioStreamDataPathState::CIS_ESTABLISHED);
});
return iter == ases_.end();
}
bool LeAudioDevice::HaveAnyCisConnected(void) {
/* Pending and Disconnecting is considered as connected in this function */
for (auto const ase : ases_) {
if (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED &&
ase.data_path_state != AudioStreamDataPathState::IDLE) {
return true;
}
}
return false;
}
bool LeAudioDevice::HasCisId(uint8_t id) {
struct ase* ase = GetFirstActiveAse();
while (ase) {
if (ase->cis_id == id) return true;
ase = GetNextActiveAse(ase);
}
return false;
}
uint8_t LeAudioDevice::GetMatchingBidirectionCisId(
const struct types::ase* base_ase) {
for (auto& ase : ases_) {
auto& cis = ase.cis_id;
if (!ase.active) continue;
int num_cises =
std::count_if(ases_.begin(), ases_.end(), [&cis](const auto& iter_ase) {
return iter_ase.active && iter_ase.cis_id == cis;
});
/*
* If there is only one ASE for device with unique CIS ID and opposite to
* direction - it may be bi-directional/completive.
*/
if (num_cises == 1 &&
((base_ase->direction == types::kLeAudioDirectionSink &&
ase.direction == types::kLeAudioDirectionSource) ||
(base_ase->direction == types::kLeAudioDirectionSource &&
ase.direction == types::kLeAudioDirectionSink))) {
return ase.cis_id;
}
}
return kInvalidCisId;
}
uint8_t LeAudioDevice::GetLc3SupportedChannelCount(uint8_t direction) {
auto& pacs =
direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_;
if (pacs.size() == 0) {
LOG(ERROR) << __func__ << " missing PAC for direction " << +direction;
return 0;
}
for (const auto& pac_tuple : pacs) {
/* Get PAC records from tuple as second element from tuple */
auto& pac_recs = std::get<1>(pac_tuple);
for (const auto pac : pac_recs) {
if (pac.codec_id.coding_format != types::kLeAudioCodingFormatLC3)
continue;
auto supported_channel_count_ltv = pac.codec_spec_caps.Find(
codec_spec_caps::kLeAudioCodecLC3TypeAudioChannelCounts);
if (supported_channel_count_ltv == std::nullopt ||
supported_channel_count_ltv->size() == 0L) {
return 1;
}
return VEC_UINT8_TO_UINT8(supported_channel_count_ltv.value());
};
}
return 0;
}
const struct types::acs_ac_record*
LeAudioDevice::GetCodecConfigurationSupportedPac(
uint8_t direction, const CodecCapabilitySetting& codec_capability_setting) {
auto& pacs =
direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_;
if (pacs.size() == 0) {
LOG_ERROR("missing PAC for direction %d", direction);
return nullptr;
}
/* TODO: Validate channel locations */
for (const auto& pac_tuple : pacs) {
/* Get PAC records from tuple as second element from tuple */
auto& pac_recs = std::get<1>(pac_tuple);
for (const auto& pac : pac_recs) {
if (!IsCodecCapabilitySettingSupported(pac, codec_capability_setting))
continue;
return &pac;
};
}
/* Doesn't match required configuration with any PAC */
return nullptr;
}
/**
* Returns supported PHY's bitfield
*/
uint8_t LeAudioDevice::GetPhyBitmask(void) {
uint8_t phy_bitfield = kIsoCigPhy1M;
if (BTM_IsPhy2mSupported(address_, BT_TRANSPORT_LE))
phy_bitfield |= kIsoCigPhy2M;
return phy_bitfield;
}
void LeAudioDevice::PrintDebugState(void) {
std::stringstream debug_str;
debug_str << " address: " << address_ << ", "
<< bluetooth::common::ToString(connection_state_)
<< ", conn_id: " << +conn_id_ << ", mtu: " << +mtu_
<< ", num_of_ase: " << static_cast<int>(ases_.size());
if (ases_.size() > 0) {
debug_str << "\n == ASEs == ";
for (auto& ase : ases_) {
debug_str << "\n id: " << +ase.id << ", active: " << ase.active
<< ", dir: "
<< (ase.direction == types::kLeAudioDirectionSink ? "sink"
: "source")
<< ", cis_id: " << +ase.cis_id
<< ", cis_handle: " << +ase.cis_conn_hdl << ", state: "
<< bluetooth::common::ToString(ase.data_path_state)
<< "\n ase max_latency: " << +ase.max_transport_latency
<< ", rtn: " << +ase.retrans_nb
<< ", max_sdu: " << +ase.max_sdu_size
<< ", target latency: " << +ase.target_latency;
}
}
LOG_INFO("%s", debug_str.str().c_str());
}
void LeAudioDevice::DumpPacsDebugState(std::stringstream& stream,
types::PublishedAudioCapabilities pacs) {
if (pacs.size() > 0) {
for (auto& pac : pacs) {
stream << "\n\t\tvalue handle: " << loghex(std::get<0>(pac).val_hdl)
<< " / CCC handle: " << loghex(std::get<0>(pac).ccc_hdl);
for (auto& record : std::get<1>(pac)) {
stream << "\n\n\t\tCodecId(Coding format: "
<< static_cast<int>(record.codec_id.coding_format)
<< ", Vendor company ID: "
<< static_cast<int>(record.codec_id.vendor_company_id)
<< ", Vendor codec ID: "
<< static_cast<int>(record.codec_id.vendor_codec_id) << ")";
stream << "\n\t\tCodec specific capabilities:\n"
<< record.codec_spec_caps.ToString(
"\t\t\t", types::CodecCapabilitiesLtvFormat);
stream << "\t\tMetadata: "
<< base::HexEncode(record.metadata.data(),
record.metadata.size());
}
}
}
}
void LeAudioDevice::DumpPacsDebugState(std::stringstream& stream) {
stream << "\n\tSink PACs";
DumpPacsDebugState(stream, snk_pacs_);
stream << "\n\tSource PACs";
DumpPacsDebugState(stream, src_pacs_);
}
void LeAudioDevice::Dump(int fd) {
uint16_t acl_handle = BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE);
std::string location = "unknown location";
if (snk_audio_locations_.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyLeft &&
snk_audio_locations_.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyRight) {
std::string location_left_right = "left/right";
location.swap(location_left_right);
} else if (snk_audio_locations_.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyLeft) {
std::string location_left = "left";
location.swap(location_left);
} else if (snk_audio_locations_.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyRight) {
std::string location_right = "right";
location.swap(location_right);
}
std::stringstream stream;
stream << "\n\taddress: " << ADDRESS_TO_LOGGABLE_STR(address_) << ": "
<< connection_state_ << ": "
<< (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_))
<< ", acl_handle: " << std::to_string(acl_handle) << ", " << location
<< ",\t" << (encrypted_ ? "Encrypted" : "Unecrypted")
<< ",mtu: " << std::to_string(mtu_)
<< "\n\tnumber of ases_: " << static_cast<int>(ases_.size());
if (ases_.size() > 0) {
stream << "\n\t== ASEs == \n\t";
stream
<< "id active dir cis_id cis_handle sdu latency rtn state";
for (auto& ase : ases_) {
stream << std::setfill('\x20') << "\n\t" << std::left << std::setw(4)
<< static_cast<int>(ase.id) << std::left << std::setw(7)
<< (ase.active ? "true" : "false") << std::left << std::setw(8)
<< (ase.direction == types::kLeAudioDirectionSink ? "sink"
: "source")
<< std::left << std::setw(8) << static_cast<int>(ase.cis_id)
<< std::left << std::setw(12) << ase.cis_conn_hdl << std::left
<< std::setw(5) << ase.max_sdu_size << std::left << std::setw(8)
<< ase.max_transport_latency << std::left << std::setw(5)
<< static_cast<int>(ase.retrans_nb) << std::left << std::setw(12)
<< bluetooth::common::ToString(ase.data_path_state);
}
}
stream << "\n\t====";
dprintf(fd, "%s", stream.str().c_str());
}
void LeAudioDevice::DisconnectAcl(void) {
if (conn_id_ == GATT_INVALID_CONN_ID) return;
uint16_t acl_handle =
BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE);
if (acl_handle != HCI_INVALID_HANDLE) {
acl_disconnect_from_handle(acl_handle, HCI_ERR_PEER_USER,
"bta::le_audio::client disconnect");
}
}
void LeAudioDevice::SetAvailableContexts(
types::BidirectionalPair<types::AudioContexts> contexts) {
LOG_DEBUG(
"\n\t previous_contexts_.sink: %s \n\t previous_contexts_.source: %s "
"\n\t "
"new_contexts.sink: %s \n\t new_contexts.source: %s \n\t ",
avail_contexts_.sink.to_string().c_str(),
avail_contexts_.source.to_string().c_str(),
contexts.sink.to_string().c_str(), contexts.source.to_string().c_str());
avail_contexts_.sink = contexts.sink;
avail_contexts_.source = contexts.source;
}
bool LeAudioDevice::ActivateConfiguredAses(LeAudioContextType context_type) {
if (conn_id_ == GATT_INVALID_CONN_ID) {
LOG_WARN(" Device %s is not connected ",
ADDRESS_TO_LOGGABLE_CSTR(address_));
return false;
}
bool ret = false;
LOG_INFO(" Configuring device %s", ADDRESS_TO_LOGGABLE_CSTR(address_));
for (auto& ase : ases_) {
if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED &&
ase.configured_for_context_type == context_type) {
LOG_INFO(
" conn_id: %d, ase id %d, cis id %d, cis_handle 0x%04x is activated.",
conn_id_, ase.id, ase.cis_id, ase.cis_conn_hdl);
ase.active = true;
ret = true;
}
}
return ret;
}
void LeAudioDevice::DeactivateAllAses(void) {
for (auto& ase : ases_) {
if (ase.active == false &&
ase.data_path_state != AudioStreamDataPathState::IDLE) {
LOG_WARN(
" %s, ase_id: %d, ase.cis_id: %d, cis_handle: 0x%02x, "
"ase.data_path=%s",
ADDRESS_TO_LOGGABLE_CSTR(address_), ase.id, ase.cis_id,
ase.cis_conn_hdl,
bluetooth::common::ToString(ase.data_path_state).c_str());
}
ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
ase.data_path_state = AudioStreamDataPathState::IDLE;
ase.active = false;
ase.cis_id = le_audio::kInvalidCisId;
ase.cis_conn_hdl = 0;
}
}
std::vector<uint8_t> LeAudioDevice::GetMetadata(
AudioContexts context_type, const std::vector<uint8_t>& ccid_list) {
std::vector<uint8_t> metadata;
AppendMetadataLtvEntryForStreamingContext(metadata, context_type);
AppendMetadataLtvEntryForCcidList(metadata, ccid_list);
return std::move(metadata);
}
bool LeAudioDevice::IsMetadataChanged(
const BidirectionalPair<AudioContexts>& context_types,
const BidirectionalPair<std::vector<uint8_t>>& ccid_lists) {
for (auto* ase = this->GetFirstActiveAse(); ase;
ase = this->GetNextActiveAse(ase)) {
if (this->GetMetadata(context_types.get(ase->direction),
ccid_lists.get(ase->direction)) != ase->metadata)
return true;
}
return false;
}
LeAudioDeviceGroup* LeAudioDeviceGroups::Add(int group_id) {
/* Get first free group id */
if (FindById(group_id)) {
LOG(ERROR) << __func__
<< ", 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) << __func__ << ", 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;
}
/* LeAudioDevices Class methods implementation */
void LeAudioDevices::Add(const RawAddress& address, DeviceConnectState state,
int group_id) {
auto device = FindByAddress(address);
if (device != nullptr) {
LOG(ERROR) << __func__ << ", address: " << ADDRESS_TO_LOGGABLE_STR(address)
<< " is already assigned to group: " << device->group_id_;
return;
}
leAudioDevices_.emplace_back(
std::make_shared<LeAudioDevice>(address, state, group_id));
}
void LeAudioDevices::Remove(const RawAddress& address) {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&address](auto const& leAudioDevice) {
return leAudioDevice->address_ == address;
});
if (iter == leAudioDevices_.end()) {
LOG(ERROR) << __func__ << ", no such address: "
<< ADDRESS_TO_LOGGABLE_STR(address);
return;
}
leAudioDevices_.erase(iter);
}
LeAudioDevice* LeAudioDevices::FindByAddress(const RawAddress& address) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&address](auto const& leAudioDevice) {
return leAudioDevice->address_ == address;
});
return (iter == leAudioDevices_.end()) ? nullptr : iter->get();
}
std::shared_ptr<LeAudioDevice> LeAudioDevices::GetByAddress(
const RawAddress& address) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&address](auto const& leAudioDevice) {
return leAudioDevice->address_ == address;
});
return (iter == leAudioDevices_.end()) ? nullptr : *iter;
}
LeAudioDevice* LeAudioDevices::FindByConnId(uint16_t conn_id) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&conn_id](auto const& leAudioDevice) {
return leAudioDevice->conn_id_ == conn_id;
});
return (iter == leAudioDevices_.end()) ? nullptr : iter->get();
}
LeAudioDevice* LeAudioDevices::FindByCisConnHdl(uint8_t cig_id,
uint16_t conn_hdl) const {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&conn_hdl, &cig_id](auto& d) {
LeAudioDevice* dev;
BidirectionalPair<struct ase*> ases;
dev = d.get();
if (dev->group_id_ != cig_id) {
return false;
}
ases = dev->GetAsesByCisConnHdl(conn_hdl);
if (ases.sink || ases.source)
return true;
else
return false;
});
if (iter == leAudioDevices_.end()) return nullptr;
return iter->get();
}
void LeAudioDevices::SetInitialGroupAutoconnectState(
int group_id, int gatt_if, tBTM_BLE_CONN_TYPE reconnection_mode,
bool current_dev_autoconnect_flag) {
if (!current_dev_autoconnect_flag) {
/* If current device autoconnect flag is false, check if there is other
* device in the group which is in autoconnect mode.
* If yes, assume whole group is in autoconnect.
*/
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&group_id](auto& d) {
LeAudioDevice* dev;
dev = d.get();
if (dev->group_id_ != group_id) {
return false;
}
return dev->autoconnect_flag_;
});
current_dev_autoconnect_flag = !(iter == leAudioDevices_.end());
}
if (!current_dev_autoconnect_flag) {
return;
}
/* This function is called when bluetooth started, therefore here we will
* try direct connection, if that failes, we fallback to background connection
*/
for (auto dev : leAudioDevices_) {
if ((dev->group_id_ == group_id) &&
(dev->GetConnectionState() == DeviceConnectState::DISCONNECTED)) {
dev->SetConnectionState(DeviceConnectState::CONNECTING_AUTOCONNECT);
dev->autoconnect_flag_ = true;
btif_storage_set_leaudio_autoconnect(dev->address_, true);
BTA_GATTC_Open(gatt_if, dev->address_, BTM_BLE_DIRECT_CONNECTION, false);
}
}
}
size_t LeAudioDevices::Size() const { return (leAudioDevices_.size()); }
void LeAudioDevices::Dump(int fd, int group_id) const {
std::stringstream stream, stream_pacs;
for (auto const& device : leAudioDevices_) {
if (device->group_id_ == group_id) {
device->Dump(fd);
stream_pacs << "\n\taddress: " << device->address_;
device->DumpPacsDebugState(stream_pacs);
dprintf(fd, "%s", stream_pacs.str().c_str());
}
}
}
void LeAudioDevices::Cleanup(tGATT_IF client_if) {
for (auto const& device : leAudioDevices_) {
auto connection_state = device->GetConnectionState();
if (connection_state == DeviceConnectState::DISCONNECTED ||
connection_state == DeviceConnectState::DISCONNECTING) {
continue;
}
if (connection_state == DeviceConnectState::CONNECTING_AUTOCONNECT) {
BTA_GATTC_CancelOpen(client_if, device->address_, false);
} else {
BtaGattQueue::Clean(device->conn_id_);
BTA_GATTC_Close(device->conn_id_);
device->DisconnectAcl();
}
}
leAudioDevices_.clear();
}
} // namespace le_audio