blob: 2e30615adee8992920b31233939a7e53f92b4597 [file]
/*
* Copyright 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 "le_audio_utils.h"
#include <bluetooth/log.h>
#include "common/strings.h"
#include "le_audio_types.h"
#include "os/log.h"
using bluetooth::common::ToString;
using bluetooth::le_audio::types::AudioContexts;
using bluetooth::le_audio::types::LeAudioContextType;
namespace fmt {
template <>
struct formatter<audio_usage_t> : enum_formatter<audio_usage_t> {};
template <>
struct formatter<audio_content_type_t> : enum_formatter<audio_content_type_t> {
};
template <>
struct formatter<audio_source_t> : enum_formatter<audio_source_t> {};
template <>
struct formatter<audio_devices_t> : enum_formatter<audio_devices_t> {};
} // namespace fmt
namespace bluetooth::le_audio {
namespace utils {
/* The returned LeAudioContextType should have its entry in the
* AudioSetConfigurationProvider's ContextTypeToScenario mapping table.
* Otherwise the AudioSetConfigurationProvider will fall back
* to default scenario.
*/
LeAudioContextType AudioContentToLeAudioContext(
audio_content_type_t content_type, audio_usage_t usage) {
/* Check audio attribute usage of stream */
switch (usage) {
case AUDIO_USAGE_MEDIA:
return LeAudioContextType::MEDIA;
case AUDIO_USAGE_ASSISTANT:
return LeAudioContextType::VOICEASSISTANTS;
case AUDIO_USAGE_VOICE_COMMUNICATION:
case AUDIO_USAGE_CALL_ASSISTANT:
return LeAudioContextType::CONVERSATIONAL;
case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
if (content_type == AUDIO_CONTENT_TYPE_SPEECH) {
return LeAudioContextType::CONVERSATIONAL;
}
if (content_type == AUDIO_CONTENT_TYPE_SONIFICATION) {
return LeAudioContextType::RINGTONE;
}
return LeAudioContextType::MEDIA;
case AUDIO_USAGE_GAME:
return LeAudioContextType::GAME;
case AUDIO_USAGE_NOTIFICATION:
return LeAudioContextType::NOTIFICATIONS;
case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
return LeAudioContextType::RINGTONE;
case AUDIO_USAGE_ALARM:
return LeAudioContextType::ALERTS;
case AUDIO_USAGE_EMERGENCY:
return LeAudioContextType::EMERGENCYALARM;
case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
return LeAudioContextType::INSTRUCTIONAL;
case AUDIO_USAGE_ASSISTANCE_SONIFICATION:
return LeAudioContextType::SOUNDEFFECTS;
default:
break;
}
return LeAudioContextType::MEDIA;
}
static std::string usageToString(audio_usage_t usage) {
switch (usage) {
case AUDIO_USAGE_UNKNOWN:
return "USAGE_UNKNOWN";
case AUDIO_USAGE_MEDIA:
return "USAGE_MEDIA";
case AUDIO_USAGE_VOICE_COMMUNICATION:
return "USAGE_VOICE_COMMUNICATION";
case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
return "USAGE_VOICE_COMMUNICATION_SIGNALLING";
case AUDIO_USAGE_ALARM:
return "USAGE_ALARM";
case AUDIO_USAGE_NOTIFICATION:
return "USAGE_NOTIFICATION";
case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
return "USAGE_NOTIFICATION_TELEPHONY_RINGTONE";
case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
return "USAGE_NOTIFICATION_COMMUNICATION_REQUEST";
case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
return "USAGE_NOTIFICATION_COMMUNICATION_INSTANT";
case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
return "USAGE_NOTIFICATION_COMMUNICATION_DELAYED";
case AUDIO_USAGE_NOTIFICATION_EVENT:
return "USAGE_NOTIFICATION_EVENT";
case AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY:
return "USAGE_ASSISTANCE_ACCESSIBILITY";
case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
return "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE";
case AUDIO_USAGE_ASSISTANCE_SONIFICATION:
return "USAGE_ASSISTANCE_SONIFICATION";
case AUDIO_USAGE_GAME:
return "USAGE_GAME";
case AUDIO_USAGE_ASSISTANT:
return "USAGE_ASSISTANT";
case AUDIO_USAGE_CALL_ASSISTANT:
return "USAGE_CALL_ASSISTANT";
case AUDIO_USAGE_EMERGENCY:
return "USAGE_EMERGENCY";
case AUDIO_USAGE_SAFETY:
return "USAGE_SAFETY";
case AUDIO_USAGE_VEHICLE_STATUS:
return "USAGE_VEHICLE_STATUS";
case AUDIO_USAGE_ANNOUNCEMENT:
return "USAGE_ANNOUNCEMENT";
default:
return "unknown usage ";
}
}
static std::string contentTypeToString(audio_content_type_t content_type) {
switch (content_type) {
case AUDIO_CONTENT_TYPE_UNKNOWN:
return "CONTENT_TYPE_UNKNOWN";
case AUDIO_CONTENT_TYPE_SPEECH:
return "CONTENT_TYPE_SPEECH";
case AUDIO_CONTENT_TYPE_MUSIC:
return "CONTENT_TYPE_MUSIC";
case AUDIO_CONTENT_TYPE_MOVIE:
return "CONTENT_TYPE_MOVIE";
case AUDIO_CONTENT_TYPE_SONIFICATION:
return "CONTENT_TYPE_SONIFICATION";
default:
return "unknown content type ";
}
}
static const char* audioSourceToStr(audio_source_t source) {
const char* strArr[] = {
"AUDIO_SOURCE_DEFAULT", "AUDIO_SOURCE_MIC",
"AUDIO_SOURCE_VOICE_UPLINK", "AUDIO_SOURCE_VOICE_DOWNLINK",
"AUDIO_SOURCE_VOICE_CALL", "AUDIO_SOURCE_CAMCORDER",
"AUDIO_SOURCE_VOICE_RECOGNITION", "AUDIO_SOURCE_VOICE_COMMUNICATION",
"AUDIO_SOURCE_REMOTE_SUBMIX", "AUDIO_SOURCE_UNPROCESSED",
"AUDIO_SOURCE_VOICE_PERFORMANCE"};
if (static_cast<uint32_t>(source) < (sizeof(strArr) / sizeof(strArr[0])))
return strArr[source];
return "UNKNOWN";
}
static bool isMetadataTagPresent(const char* tags, const char* tag) {
std::istringstream iss(tags);
std::string t;
while (std::getline(iss, t, AUDIO_ATTRIBUTES_TAGS_SEPARATOR)) {
log::verbose("Tag {}", t);
if (t.compare(tag) == 0) {
return true;
}
}
return false;
}
AudioContexts GetAudioContextsFromSourceMetadata(
const std::vector<struct playback_track_metadata_v7>& source_metadata) {
AudioContexts track_contexts;
for (const auto& entry : source_metadata) {
auto track = entry.base;
if (track.content_type == 0 && track.usage == 0) continue;
log::info("usage={}({}), content_type={}({}), gain={:f}, tag:{}",
usageToString(track.usage), track.usage,
contentTypeToString(track.content_type),
track.content_type, track.gain, entry.tags);
if (isMetadataTagPresent(entry.tags, "VX_AOSP_SAMPLESOUND")) {
track_contexts.set(LeAudioContextType::SOUNDEFFECTS);
} else {
track_contexts.set(
AudioContentToLeAudioContext(track.content_type, track.usage));
}
}
return track_contexts;
}
AudioContexts GetAudioContextsFromSinkMetadata(
const std::vector<struct record_track_metadata_v7>& sink_metadata) {
AudioContexts all_track_contexts;
for (const auto& entry : sink_metadata) {
auto track = entry.base;
if (track.source == AUDIO_SOURCE_INVALID) continue;
LeAudioContextType track_context;
log::debug(
"source={}(0x{:02x}), gain={:f}, destination device=0x{:08x}, "
"destination device address={:32s}",
audioSourceToStr(track.source), track.source, track.gain,
track.dest_device, track.dest_device_address);
if (track.source == AUDIO_SOURCE_MIC) {
track_context = LeAudioContextType::LIVE;
} else if (track.source == AUDIO_SOURCE_VOICE_COMMUNICATION) {
track_context = LeAudioContextType::CONVERSATIONAL;
} else {
/* Fallback to voice assistant
* This will handle also a case when the device is
* AUDIO_SOURCE_VOICE_RECOGNITION
*/
track_context = LeAudioContextType::VOICEASSISTANTS;
log::warn(
"Could not match the recording track type to group available "
"context. Using context {}.",
ToString(track_context));
}
all_track_contexts.set(track_context);
}
if (all_track_contexts.none()) {
all_track_contexts = AudioContexts(
static_cast<std::underlying_type<LeAudioContextType>::type>(
LeAudioContextType::UNSPECIFIED));
log::debug(
"Unable to find supported audio source context for the remote audio "
"sink device. This may result in voice back channel malfunction.");
}
log::info("Allowed contexts from sink metadata: {} (0x{:08x})",
bluetooth::common::ToString(all_track_contexts),
all_track_contexts.value());
return all_track_contexts;
}
bluetooth::le_audio::btle_audio_codec_index_t
translateBluetoothCodecFormatToCodecType(uint8_t codec_format) {
switch (codec_format) {
case types::kLeAudioCodingFormatLC3:
return bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3;
}
return bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_INVALID;
}
bluetooth::le_audio::btle_audio_sample_rate_index_t
translateToBtLeAudioCodecConfigSampleRate(uint32_t sample_rate) {
log::info("{}", sample_rate);
switch (sample_rate) {
case LeAudioCodecConfiguration::kSampleRate8000:
return LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ;
case LeAudioCodecConfiguration::kSampleRate16000:
return LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ;
case LeAudioCodecConfiguration::kSampleRate24000:
return LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ;
case LeAudioCodecConfiguration::kSampleRate32000:
return LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ;
case LeAudioCodecConfiguration::kSampleRate44100:
return LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ;
case LeAudioCodecConfiguration::kSampleRate48000:
return LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ;
}
return LE_AUDIO_SAMPLE_RATE_INDEX_NONE;
}
bluetooth::le_audio::btle_audio_bits_per_sample_index_t
translateToBtLeAudioCodecConfigBitPerSample(uint8_t bits_per_sample) {
switch (bits_per_sample) {
case 16:
return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_16;
case 24:
return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_24;
case 32:
return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_32;
}
return bluetooth::le_audio::LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE;
}
bluetooth::le_audio::btle_audio_channel_count_index_t
translateToBtLeAudioCodecConfigChannelCount(uint8_t channel_count) {
switch (channel_count) {
case 1:
return bluetooth::le_audio::LE_AUDIO_CHANNEL_COUNT_INDEX_1;
case 2:
return bluetooth::le_audio::LE_AUDIO_CHANNEL_COUNT_INDEX_2;
}
return bluetooth::le_audio::LE_AUDIO_CHANNEL_COUNT_INDEX_NONE;
}
bluetooth::le_audio::btle_audio_frame_duration_index_t
translateToBtLeAudioCodecConfigFrameDuration(int frame_duration) {
switch (frame_duration) {
case 7500:
return bluetooth::le_audio::LE_AUDIO_FRAME_DURATION_INDEX_7500US;
case 10000:
return bluetooth::le_audio::LE_AUDIO_FRAME_DURATION_INDEX_10000US;
}
return bluetooth::le_audio::LE_AUDIO_FRAME_DURATION_INDEX_NONE;
}
void fillStreamParamsToBtLeAudioCodecConfig(
const std::vector<struct set_configurations::AseConfiguration>& confs,
bluetooth::le_audio::btle_audio_codec_config_t& out_config) {
if (confs.size() == 0) {
log::warn("Stream params are null");
return;
}
auto config = confs.at(0).codec;
out_config.codec_type =
translateBluetoothCodecFormatToCodecType(config.id.coding_format);
if (out_config.codec_type !=
bluetooth::le_audio::LE_AUDIO_CODEC_INDEX_SOURCE_LC3) {
return;
}
out_config.sample_rate = translateToBtLeAudioCodecConfigSampleRate(
config.GetSamplingFrequencyHz());
out_config.bits_per_sample = translateToBtLeAudioCodecConfigBitPerSample(16);
out_config.frame_duration =
translateToBtLeAudioCodecConfigFrameDuration(config.GetDataIntervalUs());
out_config.octets_per_frame = config.GetOctectsPerFrame();
out_config.channel_count = translateToBtLeAudioCodecConfigChannelCount(
config.GetChannelCountPerIsoStream());
}
static bool is_known_codec(const types::LeAudioCodecId& codec_id) {
switch (codec_id.coding_format) {
case types::kLeAudioCodingFormatLC3:
return true;
}
return false;
}
static void fillRemotePacsCapabitiliesToBtLeAudioCodecConfig(
const struct types::acs_ac_record& record,
std::vector<bluetooth::le_audio::btle_audio_codec_config_t>& vec) {
if (!utils::IsCodecUsingLtvFormat(record.codec_id)) {
log::warn(
"Unknown codec capability format. Unable to report known codec "
"parameters.");
return;
}
log::assert_that(!record.codec_spec_caps.IsEmpty(),
"Codec specific capabilities are not parsed approprietly.");
const struct types::LeAudioCoreCodecCapabilities capa =
record.codec_spec_caps.GetAsCoreCodecCapabilities();
for (uint8_t freq_bit = codec_spec_conf::kLeAudioSamplingFreq8000Hz;
freq_bit <= codec_spec_conf::kLeAudioSamplingFreq384000Hz; freq_bit++) {
if (!capa.IsSamplingFrequencyConfigSupported(freq_bit)) continue;
for (uint8_t fd_bit = codec_spec_conf::kLeAudioCodecFrameDur7500us;
fd_bit <= codec_spec_conf::kLeAudioCodecFrameDur10000us; fd_bit++) {
if (!capa.IsFrameDurationConfigSupported(fd_bit)) continue;
if (!capa.HasSupportedAudioChannelCounts()) {
bluetooth::le_audio::btle_audio_codec_config_t config = {
.codec_type = utils::translateBluetoothCodecFormatToCodecType(
record.codec_id.coding_format),
.sample_rate = utils::translateToBtLeAudioCodecConfigSampleRate(
types::LeAudioCoreCodecConfig::GetSamplingFrequencyHz(
freq_bit)),
.bits_per_sample =
utils::translateToBtLeAudioCodecConfigBitPerSample(16),
.channel_count =
utils::translateToBtLeAudioCodecConfigChannelCount(1),
.frame_duration =
utils::translateToBtLeAudioCodecConfigFrameDuration(
types::LeAudioCoreCodecConfig::GetFrameDurationUs(fd_bit)),
};
vec.push_back(config);
} else {
for (int chan_bit = 1; chan_bit <= 2; chan_bit++) {
if (!capa.IsAudioChannelCountsSupported(chan_bit)) continue;
bluetooth::le_audio::btle_audio_codec_config_t config = {
.codec_type = utils::translateBluetoothCodecFormatToCodecType(
record.codec_id.coding_format),
.sample_rate = utils::translateToBtLeAudioCodecConfigSampleRate(
types::LeAudioCoreCodecConfig::GetSamplingFrequencyHz(
freq_bit)),
.bits_per_sample =
utils::translateToBtLeAudioCodecConfigBitPerSample(16),
.channel_count =
utils::translateToBtLeAudioCodecConfigChannelCount(chan_bit),
.frame_duration =
utils::translateToBtLeAudioCodecConfigFrameDuration(
types::LeAudioCoreCodecConfig::GetFrameDurationUs(
fd_bit)),
};
vec.push_back(config);
}
}
}
}
}
std::vector<bluetooth::le_audio::btle_audio_codec_config_t>
GetRemoteBtLeAudioCodecConfigFromPac(
const types::PublishedAudioCapabilities& group_pacs) {
std::vector<bluetooth::le_audio::btle_audio_codec_config_t> vec;
for (auto& [handles, pacs_record] : group_pacs) {
for (auto& pac : pacs_record) {
if (!is_known_codec(pac.codec_id)) continue;
fillRemotePacsCapabitiliesToBtLeAudioCodecConfig(pac, vec);
}
}
return vec;
}
bool IsCodecUsingLtvFormat(const types::LeAudioCodecId& codec_id) {
if (codec_id == set_configurations::LeAudioCodecIdLc3) {
return true;
}
return false;
}
::bluetooth::le_audio::LeAudioCodecConfiguration
GetAudioSessionCodecConfigFromAudioSetConfiguration(
const bluetooth::le_audio::set_configurations::AudioSetConfiguration&
audio_set_conf,
uint8_t remote_direction) {
/* Note: For now we expect that each ASE in a particular direction needs
* exactly the same audio codec parameters.
*/
LeAudioCodecConfiguration group_config = {0, 0, 0, 0};
for (const auto& conf : audio_set_conf.confs.get(remote_direction)) {
if (group_config.sample_rate != 0 &&
conf.codec.GetSamplingFrequencyHz() != group_config.sample_rate) {
log::warn(
"Stream configuration could not be determined (multiple, different "
"sampling frequencies) for remote_direction: {:#x}",
remote_direction);
break;
}
group_config.sample_rate = conf.codec.GetSamplingFrequencyHz();
if (group_config.data_interval_us != 0 &&
conf.codec.GetDataIntervalUs() != group_config.data_interval_us) {
log::warn(
"Stream configuration could not be determined (multiple, different "
"data intervals) for remote_direction: {:#x}",
remote_direction);
break;
}
group_config.data_interval_us = conf.codec.GetDataIntervalUs();
if (group_config.bits_per_sample != 0 &&
conf.codec.GetBitsPerSample() != group_config.bits_per_sample) {
log::warn(
"Stream configuration could not be determined (multiple, different "
"bits per sample) for remote_direction: {:#x}",
remote_direction);
break;
}
group_config.bits_per_sample = conf.codec.GetBitsPerSample();
group_config.num_channels += conf.codec.GetChannelCountPerIsoStream();
}
if (group_config.num_channels > 2) group_config.num_channels = 2;
return group_config;
}
types::LeAudioConfigurationStrategy GetStrategyForAseConfig(
const std::vector<le_audio::set_configurations::AseConfiguration>& cfgs,
uint8_t device_cnt) {
if (cfgs.size() == 0) {
return types::LeAudioConfigurationStrategy::RFU;
}
/* Banded headphones or the Classic TWS style topology (a single device) */
if (device_cnt == 1) {
if (cfgs.at(0).codec.GetChannelCountPerIsoStream() == 1) {
/* One mono ASE - could be a single channel microphone */
if (cfgs.size() == 1) {
return types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE;
}
/* Each channel on a dedicated ASE - TWS style split channel re-routing */
return types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE;
}
/* Banded headphones with 1 ASE - requires two channels per CIS */
return types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE;
}
// We need at least 2 ASEs in the group config to set up more than one device
if (cfgs.size() == 1) {
return types::LeAudioConfigurationStrategy::RFU;
}
/* The common one channel per device topology */
return types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE;
}
static bool IsCodecConfigSupported(const types::LeAudioLtvMap& pacs,
const types::LeAudioLtvMap& reqs,
uint8_t channel_cnt_per_ase) {
auto caps = pacs.GetAsCoreCodecCapabilities();
auto config = reqs.GetAsCoreCodecConfig();
/* Sampling frequency */
if (!caps.HasSupportedSamplingFrequencies() || !config.sampling_frequency) {
log::debug("Missing supported sampling frequencies capability");
return false;
}
if (!caps.IsSamplingFrequencyConfigSupported(
config.sampling_frequency.value())) {
log::debug("Cfg: SamplingFrequency= {:#x}",
config.sampling_frequency.value());
log::debug("Cap: SupportedSamplingFrequencies= {:#x}",
caps.supported_sampling_frequencies.value());
log::debug("Sampling frequency not supported");
return false;
}
/* Channel counts */
if (!caps.IsAudioChannelCountsSupported(channel_cnt_per_ase)) {
log::debug("Cfg: Allocated channel count= {:#x}", channel_cnt_per_ase);
log::debug("Cap: Supported channel counts= {:#x}",
caps.supported_audio_channel_counts.value_or(1));
log::debug("Channel count not supported");
return false;
}
/* Frame duration */
if (!caps.HasSupportedFrameDurations() || !config.frame_duration) {
log::debug("Missing supported frame durations capability");
return false;
}
if (!caps.IsFrameDurationConfigSupported(config.frame_duration.value())) {
log::debug("Cfg: FrameDuration= {:#x}", config.frame_duration.value());
log::debug("Cap: SupportedFrameDurations= {:#x}",
caps.supported_frame_durations.value());
log::debug("Frame duration not supported");
return false;
}
/* Octets per frame */
if (!caps.HasSupportedOctetsPerCodecFrame() ||
!config.octets_per_codec_frame) {
log::debug("Missing supported octets per codec frame");
return false;
}
if (!caps.IsOctetsPerCodecFrameConfigSupported(
config.octets_per_codec_frame.value())) {
log::debug("Cfg: Octets per frame={}",
config.octets_per_codec_frame.value());
log::debug("Cap: Min octets per frame={}",
caps.supported_min_octets_per_codec_frame.value());
log::debug("Cap: Max octets per frame={}",
caps.supported_max_octets_per_codec_frame.value());
log::debug("Octets per codec frame outside the capabilities");
return false;
}
return true;
}
static bool IsCodecConfigSettingSupported(
const types::acs_ac_record& pac,
const set_configurations::CodecConfigSetting& codec_config_setting) {
const auto& codec_id = codec_config_setting.id;
if (codec_id != pac.codec_id) return false;
log::debug(": Settings for format: 0x{:#02x} ", codec_id.coding_format);
if (utils::IsCodecUsingLtvFormat(codec_id)) {
log::assert_that(
!pac.codec_spec_caps.IsEmpty(),
"Codec specific capabilities are not parsed approprietly.");
return IsCodecConfigSupported(
pac.codec_spec_caps, codec_config_setting.params,
codec_config_setting.GetChannelCountPerIsoStream());
}
log::error("Codec {}, seems to be not supported here.",
bluetooth::common::ToString(codec_id));
return false;
}
const struct types::acs_ac_record* GetConfigurationSupportedPac(
const types::PublishedAudioCapabilities& pacs,
const set_configurations::CodecConfigSetting& codec_config_setting) {
for (const auto& pac_tuple : pacs) {
for (const auto& pac : std::get<1>(pac_tuple)) {
if (utils::IsCodecConfigSettingSupported(pac, codec_config_setting))
return &pac;
};
}
/* Doesn't match required configuration with any PAC */
if (pacs.size() == 0) log::error("No PAC records");
return nullptr;
}
} // namespace utils
} // namespace bluetooth::le_audio