blob: 71c0de613c56d963de5827220ea3fc85a542fe9e [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 "bta_gatt_queue.h"
#include "bta_groups.h"
#include "bta_le_audio_api.h"
#include "btm_iso_api.h"
#include "btm_iso_api_types.h"
#include "client_audio.h"
#include "device/include/controller.h"
#include "stack/btm/btm_int.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::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::BidirectAsesPair;
using le_audio::types::LeAudioCodecId;
using le_audio::types::LeAudioContextType;
using le_audio::types::LeAudioLc3Config;
namespace le_audio {
/* 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));
}
void LeAudioDeviceGroup::RemoveNode(
const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
leAudioDevice->group_id_ = bluetooth::groups::kGroupUnknown;
leAudioDevices_.erase(
std::remove_if(
leAudioDevices_.begin(), leAudioDevices_.end(),
[&leAudioDevice](auto& d) { return d.lock() == leAudioDevice; }),
leAudioDevices_.end());
}
bool LeAudioDeviceGroup::IsEmpty(void) { return leAudioDevices_.size() == 0; }
bool LeAudioDeviceGroup::IsAnyDeviceConnected(void) {
return (NumOfConnected() != 0);
}
int LeAudioDeviceGroup::Size(void) { return leAudioDevices_.size(); }
int LeAudioDeviceGroup::NumOfConnected(types::LeAudioContextType context_type) {
if (leAudioDevices_.empty()) return 0;
bool check_context_type = (context_type != LeAudioContextType::RFU);
AudioContexts type_set = static_cast<uint16_t>(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) {
if (iter.expired()) return false;
if (iter.lock()->conn_id_ == GATT_INVALID_CONN_ID) return false;
if (!check_context_type) return true;
return (iter.lock()->GetAvailableContexts() & type_set).any();
});
}
void LeAudioDeviceGroup::Cleanup(void) { leAudioDevices_.clear(); }
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;
}
}
}
LeAudioDevice* LeAudioDeviceGroup::GetFirstDevice(void) {
return (leAudioDevices_.front().lock()).get();
}
LeAudioDevice* LeAudioDeviceGroup::GetFirstDeviceWithActiveContext(
types::LeAudioContextType context_type) {
AudioContexts type_set = static_cast<uint16_t>(context_type);
auto iter = std::find_if(
leAudioDevices_.begin(), leAudioDevices_.end(), [&type_set](auto& iter) {
if (iter.expired()) return false;
return (iter.lock()->GetAvailableContexts() & type_set).any();
});
if ((iter == leAudioDevices_.end()) || (iter->expired())) return nullptr;
return (iter->lock()).get();
}
LeAudioDevice* LeAudioDeviceGroup::GetNextDevice(LeAudioDevice* leAudioDevice) {
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::GetNextDeviceWithActiveContext(
LeAudioDevice* leAudioDevice, types::LeAudioContextType context_type) {
AudioContexts type_set = static_cast<uint16_t>(context_type);
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(), [&type_set](auto& d) {
if (d.expired())
return false;
else
return (d.lock()->GetAvailableContexts() & type_set).any();
;
});
return (iter == leAudioDevices_.end()) ? nullptr : (iter->lock()).get();
}
bool LeAudioDeviceGroup::IsDeviceInTheGroup(LeAudioDevice* leAudioDevice) {
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::HaveAllActiveDevicesAsesTheSameState(AseState state) {
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) {
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) {
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();
}
bool LeAudioDeviceGroup::SetContextType(LeAudioContextType context_type) {
/* XXX: group context policy ? / may it disallow to change type ?) */
context_type_ = context_type;
return true;
}
LeAudioContextType LeAudioDeviceGroup::GetContextType(void) {
return context_type_;
}
uint32_t LeAudioDeviceGroup::GetSduInterval(uint8_t direction) {
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) {
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) {
/* TODO: Decide about packing */
return kIsoCigPackingSequential;
}
uint8_t LeAudioDeviceGroup::GetFraming(void) {
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;
}
uint8_t LeAudioDeviceGroup::GetTargetLatency(void) {
/* TODO: Decide about target latency */
return types::kTargetLatencyBalancedLatencyReliability;
}
/* TODO: Preferred parameter may be other than minimum */
static uint16_t find_max_transport_latency(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) {
return find_max_transport_latency(this, types::kLeAudioDirectionSource);
}
uint16_t LeAudioDeviceGroup::GetMaxTransportLatencyMtos(void) {
return find_max_transport_latency(this, types::kLeAudioDirectionSink);
}
uint16_t LeAudioDeviceGroup::GetTransportLatency(uint8_t direction) {
if (direction == types::kLeAudioDirectionSink) {
return transport_latency_mtos_;
} else if (direction == types::kLeAudioDirectionSource) {
return transport_latency_stom_;
} else {
LOG(ERROR) << __func__ << ", invalid direction";
return 0;
}
}
void LeAudioDeviceGroup::SetTransportLatency(uint8_t direction,
uint16_t new_transport_latency) {
uint16_t* transport_latency;
if (direction == types::kLeAudioDirectionSink) {
transport_latency = &transport_latency_mtos_;
} else if (direction == types::kLeAudioDirectionSource) {
transport_latency = &transport_latency_stom_;
} else {
LOG(ERROR) << __func__ << ", invalid direction";
return;
}
if (*transport_latency == new_transport_latency) return;
if ((*transport_latency != 0) &&
(*transport_latency != new_transport_latency)) {
LOG(WARNING) << __func__ << ", Different transport latency for group: "
<< " old: " << static_cast<int>(*transport_latency)
<< " [ms], new: " << static_cast<int>(new_transport_latency)
<< " [ms]";
return;
}
LOG(INFO) << __func__ << ", updated group " << static_cast<int>(group_id_)
<< " transport latency: " << static_cast<int>(new_transport_latency)
<< " [ms]";
*transport_latency = new_transport_latency;
}
uint8_t LeAudioDeviceGroup::GetPhyBitmask(uint8_t direction) {
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;
}
} while ((ase = leAudioDevice->GetNextActiveAseWithSameDirection(ase)));
} while ((leAudioDevice = GetNextActiveDevice(leAudioDevice)));
return phy_bitfield;
}
uint8_t LeAudioDeviceGroup::GetTargetPhy(uint8_t direction) {
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) {
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) {
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 += GetTransportLatency(direction) / 1000;
return remote_delay_ms;
}
/* This method returns AudioContext value if support for any type has changed */
std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap(void) {
DLOG(INFO) << __func__ << " group id: " << group_id_ << " active contexts: "
<< loghex(active_contexts_mask_.to_ulong());
return UpdateActiveContextsMap(active_contexts_mask_);
}
/* This method returns AudioContext value if support for any type has changed */
std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap(
AudioContexts update_contexts) {
AudioContexts contexts = 0x0000;
bool active_contexts_has_been_modified = false;
for (LeAudioContextType ctx_type : types::kLeAudioContextAllTypesArray) {
AudioContexts type_set = static_cast<uint16_t>(ctx_type);
if ((type_set & update_contexts).none()) {
/* Fill context bitset for possible returned value if updated */
if (active_context_to_configuration_map.count(ctx_type) > 0)
contexts |= type_set;
continue;
}
auto new_conf = FindFirstSupportedConfiguration(ctx_type);
/* Check if support for context type has changed */
if (active_context_to_configuration_map.count(ctx_type) == 0 ||
active_context_to_configuration_map[ctx_type] == nullptr) {
/* Current configuration for context type is empty */
if (new_conf == nullptr) {
/* Configuration remains empty */
continue;
} else {
/* Configuration changes from empty to some */
contexts |= type_set;
active_contexts_has_been_modified = true;
}
} else {
/* Current configuration for context type is not empty */
if (new_conf == nullptr) {
/* Configuration changed to empty */
contexts &= ~type_set;
active_contexts_has_been_modified = true;
} else if (new_conf != active_context_to_configuration_map[ctx_type]) {
/* Configuration changed to any other */
contexts |= type_set;
active_contexts_has_been_modified = true;
} else {
/* Configuration is the same */
contexts |= type_set;
continue;
}
}
LOG(INFO) << __func__ << ", updated context: " << loghex(int(ctx_type))
<< ", "
<< (active_context_to_configuration_map[ctx_type] != nullptr
? active_context_to_configuration_map[ctx_type]->name
: "empty")
<< " -> " << (new_conf != nullptr ? new_conf->name : "empty");
active_context_to_configuration_map[ctx_type] = new_conf;
}
/* Some contexts have changed, return new active context bitset */
if (active_contexts_has_been_modified) {
active_contexts_mask_ = contexts;
return contexts;
}
/* Nothing has changed */
return std::nullopt;
}
bool LeAudioDeviceGroup::ReloadAudioLocations(void) {
AudioLocations updated_snk_audio_locations_ =
codec_spec_conf::kLeAudioLocationMonoUnspecified;
AudioLocations updated_src_audio_locations_ =
codec_spec_conf::kLeAudioLocationMonoUnspecified;
for (const auto& device : leAudioDevices_) {
if (device.expired()) 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::IsInTransition(void) {
return target_state_ != current_state_;
}
bool LeAudioDeviceGroup::IsReleasing(void) {
return target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
}
bool LeAudioDeviceGroup::IsGroupStreamReady(void) {
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::HaveAllActiveDevicesCisDisc(void) {
auto iter =
std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
if (d.expired())
return false;
else
return !(((d.lock()).get())->HaveAllAsesCisDisc());
});
return iter == leAudioDevices_.end();
}
uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) {
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;
}
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::IsConfigurationSupported(
const set_configurations::AudioSetConfiguration* audio_set_conf,
types::LeAudioContextType context_type) {
if (!set_configurations::check_if_may_cover_scenario(
audio_set_conf, NumOfConnected(context_type))) {
DLOG(INFO) << __func__ << " cannot cover scenario "
<< static_cast<int>(context_type)
<< " size of for context type: "
<< +NumOfConnected(context_type);
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) {
DLOG(INFO) << __func__
<< " Looking for configuration: " << audio_set_conf->name
<< " - "
<< (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;
DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt
<< " number of ASEs: " << +ent.ase_cnt
<< " Max ASE per device: " << +max_required_ase_per_dev
<< " strategy: " << static_cast<int>(strategy);
for (auto* device = GetFirstDeviceWithActiveContext(context_type);
device != nullptr && required_device_cnt > 0;
device = GetNextDeviceWithActiveContext(device, context_type)) {
/* Skip if device has ASE configured in this direction already */
if (device->ases_.empty()) continue;
if (!device->IsCodecConfigurationSupported(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))) {
DLOG(INFO) << __func__ << " insufficient device audio allocation: "
<< audio_locations;
continue;
}
for (auto& ase : device->ases_) {
if (ase.direction != ent.direction) continue;
active_ase_num++;
needed_ase--;
if (needed_ase == 0) break;
}
required_device_cnt--;
}
if (required_device_cnt > 0) {
/* Don't left any active devices if requirements are not met */
DLOG(INFO) << __func__ << " could not configure all the devices";
return false;
}
}
DLOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_
<< " configuration: " << audio_set_conf->name;
return true;
}
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;
LOG_ASSERT(0) << __func__ << " shall not happen";
return 0;
}
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;
LOG_ASSERT(0) << __func__ << " shall not happen";
return 0;
}
uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy,
types::AudioLocations audio_locations,
types::AudioLocations* group_audio_locations) {
DLOG(INFO) << __func__ << " strategy: " << (int)strategy
<< " locations: " << +audio_locations.to_ulong()
<< " group locations: " << +group_audio_locations->to_ulong();
switch (strategy) {
case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
if ((audio_locations.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyLeft) &&
!(group_audio_locations->to_ulong() &
codec_spec_conf::kLeAudioLocationAnyLeft)) {
uint32_t left_location = GetFirstLeft(audio_locations);
*group_audio_locations |= left_location;
return left_location;
}
if ((audio_locations.to_ulong() &
codec_spec_conf::kLeAudioLocationAnyRight) &&
!(group_audio_locations->to_ulong() &
codec_spec_conf::kLeAudioLocationAnyRight)) {
uint32_t right_location = GetFirstRight(audio_locations);
*group_audio_locations |= right_location;
return right_location;
}
break;
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)) {
uint32_t left_location = GetFirstLeft(audio_locations);
uint32_t right_location = GetFirstRight(audio_locations);
*group_audio_locations |= left_location | right_location;
return left_location | right_location;
}
break;
default:
LOG_ASSERT(0) << " Shall never happen ";
return 0;
}
LOG_ASSERT(0) << " Shall never happen ";
return 0;
}
bool LeAudioDevice::ConfigureAses(
const le_audio::set_configurations::SetConfiguration& ent,
types::LeAudioContextType context_type,
uint8_t* number_of_already_active_group_ase,
types::AudioLocations& group_snk_audio_locations,
types::AudioLocations& group_src_audio_locations, bool reconnect) {
struct ase* ase = GetFirstInactiveAse(ent.direction, reconnect);
if (!ase) return false;
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;
bool is_codec_supported =
IsCodecConfigurationSupported(ent.direction, ent.codec);
if (!is_codec_supported) return false;
int needed_ase = std::min((int)(max_required_ase_per_dev),
(int)(ent.ase_cnt - active_ases));
types::AudioLocations audio_locations = 0;
types::AudioLocations* group_audio_locations;
/* Check direction and if audio location allows to create more cise */
if (ent.direction == types::kLeAudioDirectionSink) {
audio_locations = snk_audio_locations_;
group_audio_locations = &group_snk_audio_locations;
} else {
audio_locations = src_audio_locations_;
group_audio_locations = &group_src_audio_locations;
}
for (; needed_ase && ase; needed_ase--) {
ase->active = true;
active_ases++;
if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
ase->reconfigure = true;
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);
ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts(
ase->codec_config.audio_channel_allocation) *
ase->codec_config.octets_per_codec_frame;
ase->metadata = GetMetadata(context_type);
DLOG(INFO) << __func__ << " device=" << address_
<< ", activated ASE id=" << +ase->id
<< ", direction=" << +ase->direction
<< ", max_sdu_size=" << +ase->max_sdu_size
<< ", cis_id=" << +ase->cis_id;
ase = GetFirstInactiveAse(ent.direction, reconnect);
}
*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) {
if (!set_configurations::check_if_may_cover_scenario(
audio_set_conf, NumOfConnected(context_type)))
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.
*/
types::AudioLocations group_snk_audio_locations = 0;
types::AudioLocations group_src_audio_locations = 0;
for (const auto& ent : (*audio_set_conf).confs) {
DLOG(INFO) << __func__
<< " Looking for requirements: " << audio_set_conf->name << " - "
<< (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;
DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt
<< " number of ASEs: " << +ent.ase_cnt
<< " Max ASE per device: " << +max_required_ase_per_dev
<< " strategy: " << (int)strategy;
for (auto* device = GetFirstDeviceWithActiveContext(context_type);
device != nullptr && required_device_cnt > 0;
device = GetNextDeviceWithActiveContext(device, context_type)) {
/* Skip if device has ASE configured in this direction already */
if (device->GetFirstActiveAseByDirection(ent.direction)) continue;
if (!device->ConfigureAses(ent, context_type, &active_ase_num,
group_snk_audio_locations,
group_src_audio_locations))
continue;
required_device_cnt--;
}
if (required_device_cnt > 0) {
/* Don't left any active devices if requirements are not met */
LOG(ERROR) << __func__ << " could not configure all the devices";
Deactivate();
return false;
}
}
LOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_
<< " configuration: " << audio_set_conf->name;
active_context_type_ = context_type;
return true;
}
const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::GetActiveConfiguration(void) {
return active_context_to_configuration_map[active_context_type_];
}
AudioContexts LeAudioDeviceGroup::GetActiveContexts(void) {
return active_contexts_mask_;
}
std::optional<LeAudioCodecConfiguration>
LeAudioDeviceGroup::GetCodecConfigurationByDirection(
types::LeAudioContextType group_context_type, uint8_t direction) {
const set_configurations::AudioSetConfiguration* audio_set_conf =
active_context_to_configuration_map[group_context_type];
LeAudioCodecConfiguration group_config = {0, 0, 0, 0};
if (!audio_set_conf) return std::nullopt;
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;
}
bool LeAudioDeviceGroup::IsMetadataChanged(
types::LeAudioContextType context_type) {
for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice;
leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
if (leAudioDevice->IsMetadataChanged(context_type)) return true;
}
return false;
}
types::LeAudioContextType LeAudioDeviceGroup::GetCurrentContextType(void) {
return active_context_type_;
}
const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::FindFirstSupportedConfiguration(
LeAudioContextType context_type) {
const set_configurations::AudioSetConfigurations* confs =
set_configurations::get_confs_by_type(context_type);
DLOG(INFO) << __func__ << " context type: " << (int)context_type
<< " number of connected devices: " << NumOfConnected();
/* Filter out device set for all scenarios */
if (!set_configurations::check_if_may_cover_scenario(confs,
NumOfConnected())) {
LOG(ERROR) << __func__ << ", group is unable to cover scenario";
return nullptr;
}
/* Filter out device set for each end every scenario */
for (const auto& conf : *confs) {
if (IsConfigurationSupported(conf, context_type)) {
DLOG(INFO) << __func__ << " found: " << conf->name;
return conf;
}
}
return nullptr;
}
/* This method should choose aproperiate ASEs to be active and set a cached
* configuration for codec and qos.
*/
bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type) {
const set_configurations::AudioSetConfiguration* conf =
active_context_to_configuration_map[context_type];
DLOG(INFO) << __func__;
if (!conf) {
LOG(ERROR) << __func__ << ", requested context type: "
<< loghex(static_cast<uint16_t>(context_type))
<< ", is in mismatch with cached active contexts";
return false;
}
DLOG(INFO) << __func__ << " setting context type: " << int(context_type);
if (!ConfigureAses(conf, context_type)) {
LOG(ERROR) << __func__ << ", requested pick ASE config context type: "
<< loghex(static_cast<uint16_t>(context_type))
<< ", is in mismatch with cached active contexts";
return false;
}
return true;
}
LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); }
void LeAudioDeviceGroup::Dump(int fd) {
std::stringstream stream;
auto* active_conf = GetActiveConfiguration();
stream << " == Group id: " << group_id_ << " == \n"
<< " state: " << GetState() << "\n"
<< " target state: " << GetTargetState() << "\n"
<< " number of devices: " << Size() << "\n"
<< " number of connected devices: " << NumOfConnected() << "\n"
<< " active context types: "
<< loghex(GetActiveContexts().to_ulong()) << "\n"
<< " current context type: "
<< static_cast<int>(GetCurrentContextType()) << "\n"
<< " active stream configuration name: "
<< (active_conf ? active_conf->name : " not set") << "\n"
<< " Last used stream configuration: \n"
<< " valid: " << (stream_conf.valid ? " Yes " : " No") << "\n"
<< " codec id : " << +(stream_conf.id.coding_format) << "\n"
<< " name: "
<< (stream_conf.conf != nullptr ? stream_conf.conf->name : " null ")
<< "\n"
<< " number of sinks in the configuration "
<< stream_conf.sink_num_of_devices << "\n"
<< " number of sink_streams connected: "
<< stream_conf.sink_streams.size() << "\n"
<< " number of sources in the configuration "
<< stream_conf.source_num_of_devices << "\n"
<< " number of source_streams connected: "
<< stream_conf.source_streams.size() << "\n"
<< " === devices: ===\n";
dprintf(fd, "%s", stream.str().c_str());
for (const auto& device_iter : leAudioDevices_) {
device_iter.lock()->Dump(fd);
}
}
/* LeAudioDevice Class methods implementation */
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() << "\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);
}
struct ase* LeAudioDevice::GetFirstInactiveAseWithState(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::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 reconnect) {
auto iter = std::find_if(ases_.begin(), ases_.end(),
[direction, reconnect](const auto& ase) {
if (ase.active || (ase.direction != direction))
return false;
if (!reconnect) return true;
return (ase.cis_id != kInvalidCisId);
});
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);
}
BidirectAsesPair LeAudioDevice::GetAsesByCisConnHdl(uint16_t conn_hdl) {
BidirectAsesPair ases;
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;
}
BidirectAsesPair LeAudioDevice::GetAsesByCisId(uint8_t cis_id) {
BidirectAsesPair ases;
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) {
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::HaveAllAsesCisDisc(void) {
auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
return ase.active &&
(ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED);
});
return iter == ases_.end();
}
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);
return VEC_UINT8_TO_UINT8(supported_channel_count_ltv.value());
};
}
return 0;
}
bool LeAudioDevice::IsCodecConfigurationSupported(
uint8_t direction, const CodecCapabilitySetting& codec_capability_setting) {
auto& pacs =
direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_;
if (pacs.size() == 0) {
LOG(ERROR) << __func__ << " missing PAC for direction " << +direction;
return false;
}
/* 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 true;
};
}
/* Doesn't match required configuration with any PAC */
return false;
}
/**
* 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::SetSupportedContexts(AudioContexts snk_contexts,
AudioContexts src_contexts) {
supp_snk_context_ = snk_contexts;
supp_src_context_ = src_contexts;
}
void LeAudioDevice::Dump(int fd) {
std::stringstream stream;
stream << " address: " << address_ << "\n"
<< (conn_id_ == GATT_INVALID_CONN_ID ? " Not connected "
: " Connected conn_id =")
<< (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_))
<< "\n"
<< " set member: " << (csis_member_ ? " Yes" : " No") << "\n"
<< " known_service_handles_: " << known_service_handles_
<< "\n"
<< " notify_connected_after_read_: "
<< notify_connected_after_read_ << "\n"
<< " removing_device_: " << removing_device_ << "\n"
<< " first_connection_: " << first_connection_ << "\n"
<< " encrypted_: " << encrypted_ << "\n"
<< " connecting_actively_: " << connecting_actively_ << "\n";
dprintf(fd, "%s", stream.str().c_str());
}
AudioContexts LeAudioDevice::GetAvailableContexts(void) {
return avail_snk_contexts_ | avail_src_contexts_;
}
/* Returns XOR of updated sink and source bitset context types */
AudioContexts LeAudioDevice::SetAvailableContexts(AudioContexts snk_contexts,
AudioContexts src_contexts) {
AudioContexts updated_contexts;
updated_contexts = snk_contexts ^ avail_snk_contexts_;
updated_contexts |= src_contexts ^ avail_src_contexts_;
DLOG(INFO) << __func__
<< "\n\t avail_snk_contexts_: " << avail_snk_contexts_.to_string()
<< "\n\t avail_src_contexts_: " << avail_src_contexts_.to_string()
<< "\n\t snk_contexts:" << snk_contexts.to_string()
<< "\n\t src_contexts: " << src_contexts.to_string()
<< "\n\t updated_contexts: " << updated_contexts.to_string();
avail_snk_contexts_ = snk_contexts;
avail_src_contexts_ = src_contexts;
return updated_contexts;
}
void LeAudioDevice::DeactivateAllAses(void) {
/* Just clear states and keep previous configuration for use
* in case device will get reconnected
*/
for (auto& ase : ases_) {
if (ase.active) {
ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
ase.data_path_state = AudioStreamDataPathState::IDLE;
ase.active = false;
}
}
}
std::vector<uint8_t> LeAudioDevice::GetMetadata(
LeAudioContextType context_type) {
std::vector<uint8_t> metadata;
AppendMetadataLtvEntryForStreamingContext(metadata, context_type);
AppendMetadataLtvEntryForCcidList(metadata, context_type);
return std::move(metadata);
}
bool LeAudioDevice::IsMetadataChanged(types::LeAudioContextType context_type) {
for (auto* ase = this->GetFirstActiveAse(); ase;
ase = this->GetNextActiveAse(ase)) {
if (this->GetMetadata(context_type) != 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) {
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) {
for (auto& g : groups_) {
g->Dump(fd);
}
}
bool LeAudioDeviceGroups::IsAnyInTransition(void) {
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() { return (groups_.size()); }
std::vector<int> LeAudioDeviceGroups::GetGroupsIds(void) {
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, bool first_connection,
int group_id) {
auto device = FindByAddress(address);
if (device != nullptr) {
LOG(ERROR) << __func__ << ", address: " << address
<< " is already assigned to group: " << device->group_id_;
return;
}
leAudioDevices_.emplace_back(
std::make_shared<LeAudioDevice>(address, first_connection, 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;
return;
}
leAudioDevices_.erase(iter);
}
LeAudioDevice* LeAudioDevices::FindByAddress(const RawAddress& address) {
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) {
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) {
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(const uint16_t conn_hdl) {
auto iter = std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(),
[&conn_hdl](auto& d) {
LeAudioDevice* dev;
BidirectAsesPair ases;
dev = d.get();
ases = dev->GetAsesByCisConnHdl(conn_hdl);
if (ases.sink || ases.source)
return true;
else
return false;
});
if (iter == leAudioDevices_.end()) return nullptr;
return iter->get();
}
size_t LeAudioDevices::Size() { return (leAudioDevices_.size()); }
void LeAudioDevices::Dump(int fd, int group_id) {
for (auto const& device : leAudioDevices_) {
if (device->group_id_ == group_id) {
device->Dump(fd);
}
}
}
void LeAudioDevices::Cleanup(void) { leAudioDevices_.clear(); }
} // namespace le_audio