blob: 2a9b2635db97f6b99dd2a23bb8d829007cf4e62c [file]
/*
* Copyright (c) 2022 The Android Open Source Project
*
* 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 <bluetooth/log.h>
#include <mutex>
#include <string>
#include <string_view>
#include "audio_hal_client/audio_hal_client.h"
#include "audio_set_configurations_generated.h"
#include "audio_set_scenarios_generated.h"
#include "btm_iso_api_types.h"
#include "flatbuffers/idl.h"
#include "flatbuffers/util.h"
#include "le_audio/le_audio_types.h"
#include "le_audio_set_configuration_provider.h"
#include "os/log.h"
#include "osi/include/osi.h"
#include "osi/include/properties.h"
using bluetooth::le_audio::set_configurations::AseConfiguration;
using bluetooth::le_audio::set_configurations::AudioSetConfiguration;
using bluetooth::le_audio::set_configurations::AudioSetConfigurations;
using bluetooth::le_audio::set_configurations::CodecConfigSetting;
using bluetooth::le_audio::set_configurations::LeAudioCodecIdLc3;
using bluetooth::le_audio::set_configurations::QosConfigSetting;
using bluetooth::le_audio::types::LeAudioContextType;
namespace bluetooth::le_audio {
#ifdef __ANDROID__
static const std::vector<
std::pair<const char* /*schema*/, const char* /*content*/>>
kLeAudioSetConfigs = {
{"/apex/com.android.btservices/etc/bluetooth/le_audio/"
"audio_set_configurations.bfbs",
"/apex/com.android.btservices/etc/bluetooth/le_audio/"
"audio_set_configurations.json"}};
static const std::vector<
std::pair<const char* /*schema*/, const char* /*content*/>>
kLeAudioSetScenarios = {{"/apex/com.android.btservices/etc/bluetooth/"
"le_audio/audio_set_scenarios.bfbs",
"/apex/com.android.btservices/etc/bluetooth/"
"le_audio/audio_set_scenarios.json"}};
#elif defined(TARGET_FLOSS)
static const std::vector<
std::pair<const char* /*schema*/, const char* /*content*/>>
kLeAudioSetConfigs = {
{"/etc/bluetooth/le_audio/audio_set_configurations.bfbs",
"/etc/bluetooth/le_audio/audio_set_configurations.json"}};
static const std::vector<
std::pair<const char* /*schema*/, const char* /*content*/>>
kLeAudioSetScenarios = {
{"/etc/bluetooth/le_audio/audio_set_scenarios.bfbs",
"/etc/bluetooth/le_audio/audio_set_scenarios.json"}};
#else
static const std::vector<
std::pair<const char* /*schema*/, const char* /*content*/>>
kLeAudioSetConfigs = {
{"audio_set_configurations.bfbs", "audio_set_configurations.json"}};
static const std::vector<
std::pair<const char* /*schema*/, const char* /*content*/>>
kLeAudioSetScenarios = {
{"audio_set_scenarios.bfbs", "audio_set_scenarios.json"}};
#endif
/** Provides a set configurations for the given context type */
struct AudioSetConfigurationProviderJson {
static constexpr auto kDefaultScenario = "Media";
AudioSetConfigurationProviderJson(types::CodecLocation location) {
log::assert_that(
LoadContent(kLeAudioSetConfigs, kLeAudioSetScenarios, location),
": Unable to load le audio set configuration files.");
}
/* Use the same scenario configurations for different contexts to avoid
* internal reconfiguration and handover that produces time gap. When using
* the same scenario for different contexts, quality and configuration remains
* the same while changing to same scenario based context type.
*/
static auto ScenarioToContextTypes(const std::string& scenario) {
static const std::multimap<std::string,
::bluetooth::le_audio::types::LeAudioContextType>
scenarios = {
{"Media", types::LeAudioContextType::ALERTS},
{"Media", types::LeAudioContextType::INSTRUCTIONAL},
{"Media", types::LeAudioContextType::NOTIFICATIONS},
{"Media", types::LeAudioContextType::EMERGENCYALARM},
{"Media", types::LeAudioContextType::UNSPECIFIED},
{"Media", types::LeAudioContextType::MEDIA},
{"Conversational", types::LeAudioContextType::RINGTONE},
{"Conversational", types::LeAudioContextType::CONVERSATIONAL},
{"Live", types::LeAudioContextType::LIVE},
{"Game", types::LeAudioContextType::GAME},
{"VoiceAssistants", types::LeAudioContextType::VOICEASSISTANTS},
};
return scenarios.equal_range(scenario);
}
static std::string ContextTypeToScenario(
::bluetooth::le_audio::types::LeAudioContextType context_type) {
switch (context_type) {
case types::LeAudioContextType::ALERTS:
FALLTHROUGH_INTENDED;
case types::LeAudioContextType::INSTRUCTIONAL:
FALLTHROUGH_INTENDED;
case types::LeAudioContextType::NOTIFICATIONS:
FALLTHROUGH_INTENDED;
case types::LeAudioContextType::EMERGENCYALARM:
FALLTHROUGH_INTENDED;
case types::LeAudioContextType::UNSPECIFIED:
FALLTHROUGH_INTENDED;
case types::LeAudioContextType::SOUNDEFFECTS:
FALLTHROUGH_INTENDED;
case types::LeAudioContextType::MEDIA:
return "Media";
case types::LeAudioContextType::RINGTONE:
FALLTHROUGH_INTENDED;
case types::LeAudioContextType::CONVERSATIONAL:
return "Conversational";
case types::LeAudioContextType::LIVE:
return "Live";
case types::LeAudioContextType::GAME:
return "Game";
case types::LeAudioContextType::VOICEASSISTANTS:
return "VoiceAssistants";
default:
return kDefaultScenario;
}
}
const AudioSetConfigurations* GetConfigurationsByContextType(
LeAudioContextType context_type) const {
if (context_configurations_.count(context_type))
return &context_configurations_.at(context_type);
log::warn(": No predefined scenario for the context {} was found.",
(int)context_type);
auto [it_begin, it_end] = ScenarioToContextTypes(kDefaultScenario);
if (it_begin != it_end) {
log::warn(": Using '{}' scenario by default.", kDefaultScenario);
return &context_configurations_.at(it_begin->second);
}
log::error(
": No valid configuration for the default '{}' scenario, or no audio "
"set configurations loaded at all.",
kDefaultScenario);
return nullptr;
};
private:
/* Codec configurations */
std::map<std::string, const AudioSetConfiguration> configurations_;
/* Maps of context types to a set of configuration structs */
std::map<::bluetooth::le_audio::types::LeAudioContextType,
AudioSetConfigurations>
context_configurations_;
static CodecConfigSetting CodecConfigSettingFromFlat(
const fbs::le_audio::CodecId* flat_codec_id,
const flatbuffers::Vector<
flatbuffers::Offset<fbs::le_audio::CodecSpecificConfiguration>>*
flat_codec_specific_params) {
CodecConfigSetting codec;
/* Cache the bluetooth::le_audio::types::CodecId type value */
codec.id = types::LeAudioCodecId({
.coding_format = flat_codec_id->coding_format(),
.vendor_company_id = flat_codec_id->vendor_company_id(),
.vendor_codec_id = flat_codec_id->vendor_codec_id(),
});
/* Cache all the codec specific parameters */
for (auto const& param : *flat_codec_specific_params) {
auto const value = param->compound_value()->value();
codec.params.Add(
param->type(),
std::vector<uint8_t>(value->data(), value->data() + value->size()));
}
return codec;
}
void SetConfigurationFromFlatSubconfig(
const fbs::le_audio::AudioSetSubConfiguration* flat_subconfig,
QosConfigSetting qos, std::vector<AseConfiguration>& subconfigs,
types::CodecLocation location) {
auto codec_config = CodecConfigSettingFromFlat(
flat_subconfig->codec_id(), flat_subconfig->codec_configuration());
// Fill in the remaining params
codec_config.channel_count_per_iso_stream =
flat_subconfig->ase_channel_cnt();
auto config = AseConfiguration(codec_config, qos);
// Note that these parameters are set here since for now, we are using the
// common configuration source for all the codec locations.
switch (location) {
case types::CodecLocation::ADSP:
config.data_path_configuration.dataPathId =
bluetooth::hci::iso_manager::kIsoDataPathPlatformDefault;
config.data_path_configuration.dataPathConfig = {};
config.data_path_configuration.isoDataPathConfig.isTransparent = true;
config.data_path_configuration.isoDataPathConfig.controllerDelayUs = 0;
config.data_path_configuration.isoDataPathConfig.codecId = {
.coding_format = bluetooth::hci::kIsoCodingFormatTransparent,
.vendor_company_id = 0,
.vendor_codec_id = 0};
config.data_path_configuration.isoDataPathConfig.configuration = {};
break;
case types::CodecLocation::HOST:
config.data_path_configuration.dataPathId =
bluetooth::hci::iso_manager::kIsoDataPathHci;
config.data_path_configuration.dataPathConfig = {};
config.data_path_configuration.isoDataPathConfig.isTransparent = true;
config.data_path_configuration.isoDataPathConfig.codecId = {
.coding_format = bluetooth::hci::kIsoCodingFormatTransparent,
.vendor_company_id = 0,
.vendor_codec_id = 0};
config.data_path_configuration.isoDataPathConfig.controllerDelayUs = 0;
config.data_path_configuration.isoDataPathConfig.configuration = {};
break;
case types::CodecLocation::CONTROLLER:
config.data_path_configuration.dataPathId =
bluetooth::hci::iso_manager::kIsoDataPathPlatformDefault;
config.data_path_configuration.dataPathConfig = {};
config.data_path_configuration.isoDataPathConfig.isTransparent = false;
// Note: The data path codecId matches the used codec, but for now there
// is no support for the custom path configuration data buffer.
config.data_path_configuration.isoDataPathConfig.codecId =
codec_config.id;
config.data_path_configuration.isoDataPathConfig.controllerDelayUs = 0;
config.data_path_configuration.isoDataPathConfig.configuration = {};
break;
}
// Store each ASE configuration
for (auto i = flat_subconfig->ase_cnt(); i; --i) {
subconfigs.push_back(std::move(config));
}
}
static uint8_t ValidateTargetLatency(int flat_target_latency) {
auto target_latency_int = static_cast<int>(flat_target_latency);
bool valid_target_latency =
(target_latency_int >= (int)types::kTargetLatencyLower &&
target_latency_int <= (int)types::kTargetLatencyHigherReliability);
return valid_target_latency
? static_cast<uint8_t>(target_latency_int)
: types::kTargetLatencyBalancedLatencyReliability;
}
AudioSetConfiguration AudioSetConfigurationFromFlat(
const fbs::le_audio::AudioSetConfiguration* flat_cfg,
std::vector<const fbs::le_audio::CodecConfiguration*>* codec_cfgs,
std::vector<const fbs::le_audio::QosConfiguration*>* qos_cfgs,
types::CodecLocation location) {
log::assert_that(flat_cfg != nullptr, "flat_cfg cannot be null");
std::string codec_config_key = flat_cfg->codec_config_name()->str();
auto* qos_config_key_array = flat_cfg->qos_config_name();
constexpr std::string_view default_qos = "QoS_Config_Balanced_Reliability";
std::string qos_sink_key(default_qos);
std::string qos_source_key(default_qos);
/* We expect maximum two QoS settings. First for Sink and second for Source
*/
if (qos_config_key_array->size() > 0) {
qos_sink_key = qos_config_key_array->Get(0)->str();
if (qos_config_key_array->size() > 1) {
qos_source_key = qos_config_key_array->Get(1)->str();
} else {
qos_source_key = qos_sink_key;
}
}
log::info(
"Audio set config {}: codec config {}, qos_sink {}, qos_source {}",
flat_cfg->name()->c_str(), codec_config_key, qos_sink_key,
qos_source_key);
const fbs::le_audio::QosConfiguration* qos_sink_cfg = nullptr;
for (auto i = qos_cfgs->begin(); i != qos_cfgs->end(); ++i) {
if ((*i)->name()->str() == qos_sink_key) {
qos_sink_cfg = *i;
break;
}
}
const fbs::le_audio::QosConfiguration* qos_source_cfg = nullptr;
for (auto i = qos_cfgs->begin(); i != qos_cfgs->end(); ++i) {
if ((*i)->name()->str() == qos_source_key) {
qos_source_cfg = *i;
break;
}
}
types::BidirectionalPair<QosConfigSetting> qos;
if (qos_sink_cfg != nullptr) {
qos.sink.target_latency =
ValidateTargetLatency(qos_sink_cfg->target_latency());
qos.sink.retransmission_number = qos_sink_cfg->retransmission_number();
qos.sink.max_transport_latency = qos_sink_cfg->max_transport_latency();
} else {
log::error("No qos config matching key {} found", qos_sink_key);
}
if (qos_source_cfg != nullptr) {
qos.source.target_latency =
ValidateTargetLatency(qos_source_cfg->target_latency());
qos.source.retransmission_number =
qos_source_cfg->retransmission_number();
qos.source.max_transport_latency =
qos_source_cfg->max_transport_latency();
} else {
log::error("No qos config matching key {} found", qos_source_key);
}
const fbs::le_audio::CodecConfiguration* codec_cfg = nullptr;
for (auto i = codec_cfgs->begin(); i != codec_cfgs->end(); ++i) {
if ((*i)->name()->str() == codec_config_key) {
codec_cfg = *i;
break;
}
}
types::BidirectionalPair<std::vector<AseConfiguration>> subconfigs;
if (codec_cfg != nullptr && codec_cfg->subconfigurations()) {
/* Load subconfigurations */
for (auto subconfig : *codec_cfg->subconfigurations()) {
auto direction = subconfig->direction();
processSubconfig(*subconfig, qos.get(direction),
subconfigs.get(direction), location);
}
} else {
if (codec_cfg == nullptr) {
log::error("No codec config matching key {} found", codec_config_key);
} else {
log::error("Configuration '{}' has no valid subconfigurations.",
flat_cfg->name()->c_str());
}
}
return {
.name = flat_cfg->name()->c_str(),
.packing = bluetooth::hci::kIsoCigPackingSequential,
.confs = std::move(subconfigs),
};
}
void processSubconfig(
const fbs::le_audio::AudioSetSubConfiguration& subconfig,
const QosConfigSetting& qos_setting,
std::vector<AseConfiguration>& subconfigs,
types::CodecLocation location) {
SetConfigurationFromFlatSubconfig(&subconfig, qos_setting, subconfigs,
location);
// Recalculate some qos params based on the Core Codec Configuration
for (auto& subconfig : subconfigs) {
const auto& core_config = subconfig.codec.params.GetAsCoreCodecConfig();
subconfig.qos.maxSdu =
subconfig.codec.GetChannelCountPerIsoStream() *
core_config.octets_per_codec_frame.value_or(0) *
core_config.codec_frames_blocks_per_sdu.value_or(1);
subconfig.qos.sduIntervalUs = core_config.GetFrameDurationUs();
}
}
bool LoadConfigurationsFromFiles(const char* schema_file,
const char* content_file,
types::CodecLocation location) {
flatbuffers::Parser configurations_parser_;
std::string configurations_schema_binary_content;
bool ok = flatbuffers::LoadFile(schema_file, true,
&configurations_schema_binary_content);
if (!ok) return ok;
/* Load the binary schema */
ok = configurations_parser_.Deserialize(
(uint8_t*)configurations_schema_binary_content.c_str(),
configurations_schema_binary_content.length());
if (!ok) return ok;
/* Load the content from JSON */
std::string configurations_json_content;
ok = flatbuffers::LoadFile(content_file, false,
&configurations_json_content);
if (!ok) return ok;
/* Parse */
ok = configurations_parser_.Parse(configurations_json_content.c_str());
if (!ok) return ok;
/* Import from flatbuffers */
auto configurations_root = fbs::le_audio::GetAudioSetConfigurations(
configurations_parser_.builder_.GetBufferPointer());
if (!configurations_root) return false;
auto flat_qos_configs = configurations_root->qos_configurations();
if ((flat_qos_configs == nullptr) || (flat_qos_configs->size() == 0))
return false;
log::debug(": Updating {} qos config entries.", flat_qos_configs->size());
std::vector<const fbs::le_audio::QosConfiguration*> qos_cfgs;
for (auto const& flat_qos_cfg : *flat_qos_configs) {
qos_cfgs.push_back(flat_qos_cfg);
}
auto flat_codec_configs = configurations_root->codec_configurations();
if ((flat_codec_configs == nullptr) || (flat_codec_configs->size() == 0))
return false;
log::debug(": Updating {} codec config entries.",
flat_codec_configs->size());
std::vector<const fbs::le_audio::CodecConfiguration*> codec_cfgs;
for (auto const& flat_codec_cfg : *flat_codec_configs) {
codec_cfgs.push_back(flat_codec_cfg);
}
auto flat_configs = configurations_root->configurations();
if ((flat_configs == nullptr) || (flat_configs->size() == 0)) return false;
log::debug(": Updating {} config entries.", flat_configs->size());
for (auto const& flat_cfg : *flat_configs) {
auto configuration = AudioSetConfigurationFromFlat(flat_cfg, &codec_cfgs,
&qos_cfgs, location);
if (!configuration.confs.sink.empty() ||
!configuration.confs.source.empty()) {
configurations_.insert({flat_cfg->name()->str(), configuration});
}
}
return true;
}
AudioSetConfigurations AudioSetConfigurationsFromFlatScenario(
const fbs::le_audio::AudioSetScenario* const flat_scenario) {
AudioSetConfigurations items;
if (!flat_scenario->configurations()) return items;
for (auto config_name : *flat_scenario->configurations()) {
if (configurations_.count(config_name->str()) == 0) continue;
auto& cfg = configurations_.at(config_name->str());
items.push_back(&cfg);
}
return items;
}
bool LoadScenariosFromFiles(const char* schema_file,
const char* content_file) {
flatbuffers::Parser scenarios_parser_;
std::string scenarios_schema_binary_content;
bool ok = flatbuffers::LoadFile(schema_file, true,
&scenarios_schema_binary_content);
if (!ok) return ok;
/* Load the binary schema */
ok = scenarios_parser_.Deserialize(
(uint8_t*)scenarios_schema_binary_content.c_str(),
scenarios_schema_binary_content.length());
if (!ok) return ok;
/* Load the content from JSON */
std::string scenarios_json_content;
ok = flatbuffers::LoadFile(content_file, false, &scenarios_json_content);
if (!ok) return ok;
/* Parse */
ok = scenarios_parser_.Parse(scenarios_json_content.c_str());
if (!ok) return ok;
/* Import from flatbuffers */
auto scenarios_root = fbs::le_audio::GetAudioSetScenarios(
scenarios_parser_.builder_.GetBufferPointer());
if (!scenarios_root) return false;
auto flat_scenarios = scenarios_root->scenarios();
if ((flat_scenarios == nullptr) || (flat_scenarios->size() == 0))
return false;
log::debug(": Updating {} scenarios.", flat_scenarios->size());
for (auto const& scenario : *flat_scenarios) {
log::debug("Scenario {} configs:", scenario->name()->c_str());
auto configs = AudioSetConfigurationsFromFlatScenario(scenario);
for (auto& config : configs) {
log::debug("\t\t Audio set config: {}", config->name);
}
auto [it_begin, it_end] =
ScenarioToContextTypes(scenario->name()->c_str());
for (auto it = it_begin; it != it_end; ++it) {
context_configurations_.insert_or_assign(
it->second, AudioSetConfigurationsFromFlatScenario(scenario));
}
}
return true;
}
bool LoadContent(
std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
config_files,
std::vector<std::pair<const char* /*schema*/, const char* /*content*/>>
scenario_files,
types::CodecLocation location) {
for (auto [schema, content] : config_files) {
if (!LoadConfigurationsFromFiles(schema, content, location)) return false;
}
for (auto [schema, content] : scenario_files) {
if (!LoadScenariosFromFiles(schema, content)) return false;
}
return true;
}
};
struct AudioSetConfigurationProvider::impl {
impl(const AudioSetConfigurationProvider& config_provider)
: config_provider_(config_provider) {}
void Initialize(types::CodecLocation location) {
log::assert_that(!config_provider_impl_, "Config provider not available.");
config_provider_impl_ =
std::make_unique<AudioSetConfigurationProviderJson>(location);
}
void Cleanup() {
log::assert_that(config_provider_impl_ != nullptr,
"Config provider not available.");
config_provider_impl_.reset();
}
bool IsRunning() { return config_provider_impl_ ? true : false; }
void Dump(int fd) {
std::stringstream stream;
for (LeAudioContextType context : types::kLeAudioContextAllTypesArray) {
auto confs = Get()->GetConfigurations(context);
stream << "\n === Configurations for context type: " << (int)context
<< ", num: " << (confs == nullptr ? 0 : confs->size()) << " \n";
if (confs && confs->size() > 0) {
for (const auto& conf : *confs) {
stream << " name: " << conf->name << " \n";
for (const auto direction :
{types::kLeAudioDirectionSink, types::kLeAudioDirectionSource}) {
stream << " ASE configs for direction: "
<< (direction == types::kLeAudioDirectionSink
? "Sink (speaker)\n"
: "Source (microphone)\n");
for (const auto& ent : conf->confs.get(direction)) {
stream << " ASE config: "
<< " qos->target latency: " << +ent.qos.target_latency
<< " \n"
<< " qos->retransmission_number: "
<< +ent.qos.retransmission_number << " \n"
<< " qos->max_transport_latency: "
<< +ent.qos.max_transport_latency << " \n"
<< " channel count per ISO stream: "
<< +ent.codec.GetChannelCountPerIsoStream() << "\n";
}
}
}
}
}
dprintf(fd, "%s", stream.str().c_str());
}
const AudioSetConfigurationProvider& config_provider_;
std::unique_ptr<AudioSetConfigurationProviderJson> config_provider_impl_;
};
static std::unique_ptr<AudioSetConfigurationProvider> config_provider;
std::mutex instance_mutex;
AudioSetConfigurationProvider::AudioSetConfigurationProvider()
: pimpl_(std::make_unique<AudioSetConfigurationProvider::impl>(*this)) {}
void AudioSetConfigurationProvider::Initialize(types::CodecLocation location) {
std::scoped_lock<std::mutex> lock(instance_mutex);
if (!config_provider)
config_provider = std::make_unique<AudioSetConfigurationProvider>();
if (!config_provider->pimpl_->IsRunning())
config_provider->pimpl_->Initialize(location);
}
void AudioSetConfigurationProvider::DebugDump(int fd) {
std::scoped_lock<std::mutex> lock(instance_mutex);
if (!config_provider || !config_provider->pimpl_->IsRunning()) {
dprintf(
fd,
"\n AudioSetConfigurationProvider not initialized: config provider: "
"%d, pimpl: %d \n",
config_provider != nullptr,
(config_provider == nullptr ? 0
: config_provider->pimpl_->IsRunning()));
return;
}
dprintf(fd, "\n AudioSetConfigurationProvider: \n");
config_provider->pimpl_->Dump(fd);
}
void AudioSetConfigurationProvider::Cleanup() {
std::scoped_lock<std::mutex> lock(instance_mutex);
if (!config_provider) return;
if (config_provider->pimpl_->IsRunning()) config_provider->pimpl_->Cleanup();
config_provider.reset();
}
AudioSetConfigurationProvider* AudioSetConfigurationProvider::Get() {
return config_provider.get();
}
const set_configurations::AudioSetConfigurations*
AudioSetConfigurationProvider::GetConfigurations(
::bluetooth::le_audio::types::LeAudioContextType content_type) const {
if (pimpl_->IsRunning())
return pimpl_->config_provider_impl_->GetConfigurationsByContextType(
content_type);
return nullptr;
}
bool AudioSetConfigurationProvider::CheckConfigurationIsBiDirSwb(
const set_configurations::AudioSetConfiguration& set_configuration) const {
uint8_t dir = 0;
for (auto direction : {le_audio::types::kLeAudioDirectionSink,
le_audio::types::kLeAudioDirectionSource}) {
for (const auto& conf : set_configuration.confs.get(direction)) {
if (conf.codec.GetSamplingFrequencyHz() >=
bluetooth::le_audio::LeAudioCodecConfiguration::kSampleRate32000) {
dir |= direction;
}
}
}
return dir == bluetooth::le_audio::types::kLeAudioDirectionBoth;
}
bool AudioSetConfigurationProvider::CheckConfigurationIsDualBiDirSwb(
const set_configurations::AudioSetConfiguration& set_configuration) const {
types::BidirectionalPair<uint8_t> swb_direction_counter = {0, 0};
for (auto direction : {le_audio::types::kLeAudioDirectionSink,
le_audio::types::kLeAudioDirectionSource}) {
auto const& confs = set_configuration.confs.get(direction);
swb_direction_counter.get(direction) +=
std::count_if(confs.begin(), confs.end(), [](auto const& cfg) {
return cfg.codec.GetSamplingFrequencyHz() >=
bluetooth::le_audio::LeAudioCodecConfiguration::
kSampleRate32000;
});
}
return (swb_direction_counter.sink > 1) && (swb_direction_counter.source > 1);
}
} // namespace bluetooth::le_audio