blob: 67a8a582766590b03c2c46809ba794622335c78a [file]
/*
* 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.
*/
/*
* This file contains definitions for Basic Audio Profile / Audio Stream Control
* and Published Audio Capabilities definitions, structures etc.
*/
#include "le_audio_types.h"
#include <base/strings/string_number_conversions.h>
#include <bluetooth/log.h>
#include "audio_hal_client/audio_hal_client.h"
#include "common/strings.h"
#include "internal_include/bt_trace.h"
#include "le_audio_utils.h"
#include "stack/include/bt_types.h"
namespace bluetooth::le_audio {
using types::acs_ac_record;
using types::LeAudioContextType;
namespace set_configurations {
using set_configurations::CodecConfigSetting;
using types::CodecLocation;
using types::kLeAudioCodingFormatLC3;
using types::kLeAudioDirectionSink;
using types::kLeAudioDirectionSource;
using types::LeAudioCoreCodecConfig;
void get_cis_count(LeAudioContextType context_type, int expected_device_cnt,
types::LeAudioConfigurationStrategy strategy,
int avail_group_ase_snk_cnt, int avail_group_ase_src_count,
uint8_t& out_cis_count_bidir,
uint8_t& out_cis_count_unidir_sink,
uint8_t& out_cis_count_unidir_source) {
log::info(
"{} strategy {}, group avail sink ases: {}, group avail source ases {} "
"expected_device_count {}",
bluetooth::common::ToString(context_type), static_cast<int>(strategy),
avail_group_ase_snk_cnt, avail_group_ase_src_count, expected_device_cnt);
bool is_bidirectional = types::kLeAudioContextAllBidir.test(context_type);
switch (strategy) {
case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
/* This strategy is for the CSIS topology, e.g. two earbuds which are both
* connected with a Phone
*/
case types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE:
/* This strategy is for e.g. the banded headphones */
if (is_bidirectional) {
if ((avail_group_ase_snk_cnt > 0) && (avail_group_ase_src_count) > 0) {
/* Prepare CIG to enable all microphones */
out_cis_count_bidir = expected_device_cnt;
} else {
if (avail_group_ase_snk_cnt > 0) {
out_cis_count_unidir_sink = expected_device_cnt;
} else if (avail_group_ase_src_count > 0) {
out_cis_count_unidir_source = expected_device_cnt;
}
}
} else {
out_cis_count_unidir_sink = expected_device_cnt;
}
break;
case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
/* This strategy is for the old TWS topology. e.g. one earbud connected to
* the Phone but each channel is carried in separate CIS
*/
if (is_bidirectional) {
if ((avail_group_ase_snk_cnt > 0) && (avail_group_ase_src_count) > 0) {
/* Prepare CIG to enable all microphones per device */
/* TODO: Support TWS style device with two source ASEs - two
* bidirectional CISes
*/
out_cis_count_bidir = expected_device_cnt;
out_cis_count_unidir_sink = expected_device_cnt;
} else {
if (avail_group_ase_snk_cnt > 0) {
out_cis_count_unidir_sink = 2 * expected_device_cnt;
} else if (avail_group_ase_src_count > 0) {
out_cis_count_unidir_source = 2 * expected_device_cnt;
}
}
} else {
out_cis_count_unidir_sink = 2 * expected_device_cnt;
}
break;
case types::LeAudioConfigurationStrategy::RFU:
log::error("Should not happen;");
break;
}
log::info(
"Required cis count: Bi-Directional: {}, Uni-Directional Sink: {}, "
"Uni-Directional Source: {}",
out_cis_count_bidir, out_cis_count_unidir_sink,
out_cis_count_unidir_source);
}
uint16_t CodecConfigSetting::GetOctectsPerFrame() const {
switch (id.coding_format) {
case kLeAudioCodingFormatLC3:
return params.GetAsCoreCodecConfig().GetOctectsPerFrame();
default:
log::warn(", invalid codec id: 0x{:02x}", id.coding_format);
return 0;
}
};
uint32_t CodecConfigSetting::GetSamplingFrequencyHz() const {
// We also mandate the sampling frequency parameter for vendor spec. codecs
return params.GetAsCoreCodecConfig().GetSamplingFrequencyHz();
};
uint32_t CodecConfigSetting::GetDataIntervalUs() const {
switch (id.coding_format) {
case kLeAudioCodingFormatLC3:
return params.GetAsCoreCodecConfig().GetFrameDurationUs();
default:
log::warn(", invalid codec id: 0x{:02x}", id.coding_format);
return 0;
}
};
uint8_t CodecConfigSetting::GetBitsPerSample() const {
switch (id.coding_format) {
case kLeAudioCodingFormatLC3:
/* XXX LC3 supports 16, 24, 32 */
return 16;
default:
log::warn(", invalid codec id: 0x{:02x}", id.coding_format);
return 0;
}
};
std::ostream& operator<<(std::ostream& os, const QosConfigSetting& config) {
os << "QosConfigSetting{";
os << "targetLatency: " << (int)config.target_latency;
os << ", retransmissionNum: " << (int)config.retransmission_number;
os << ", maxTransportLatency: " << (int)config.max_transport_latency;
os << ", sduIntervalUs: " << (int)config.sduIntervalUs;
os << ", maxSdu: " << (int)config.maxSdu;
os << "}";
return os;
}
std::ostream& operator<<(std::ostream& os, const AseConfiguration& config) {
os << "AseConfiguration{";
os << "dataPath: " << config.data_path_configuration;
os << ", codec: " << config.codec;
os << ", qos: " << config.qos;
os << "}";
return os;
}
std::ostream& operator<<(std::ostream& os,
const AudioSetConfiguration& config) {
os << "AudioSetConfiguration{";
os << "name: " << config.name;
os << ", packing: " << (int)config.packing;
os << ", sinkConfs: [";
for (auto const& conf : config.confs.sink) {
os << conf;
os << ", ";
}
os << "], sourceConfs: [";
for (auto const& conf : config.confs.source) {
os << conf;
os << ", ";
}
os << "]}";
return os;
}
std::ostream& operator<<(std::ostream& os, const CodecConfigSetting& config) {
os << "CodecConfigSetting{";
os << ", id: " << config.id;
os << ", codecSpecParams: " << config.params.GetAsCoreCodecConfig();
os << ", bitsPerSample: " << (int)config.GetBitsPerSample();
os << ", channelCountPerIsoStream: "
<< (int)config.GetChannelCountPerIsoStream();
if (!config.vendor_params.empty()) {
os << ", vendorParams: "
<< base::HexEncode(config.vendor_params.data(),
config.vendor_params.size());
}
os << "}";
return os;
}
} // namespace set_configurations
namespace types {
/* Helper map for matching various frequency notations */
const std::map<uint8_t, uint32_t> LeAudioCoreCodecConfig::sampling_freq_map = {
{codec_spec_conf::kLeAudioSamplingFreq8000Hz,
LeAudioCodecConfiguration::kSampleRate8000},
{codec_spec_conf::kLeAudioSamplingFreq16000Hz,
LeAudioCodecConfiguration::kSampleRate16000},
{codec_spec_conf::kLeAudioSamplingFreq24000Hz,
LeAudioCodecConfiguration::kSampleRate24000},
{codec_spec_conf::kLeAudioSamplingFreq32000Hz,
LeAudioCodecConfiguration::kSampleRate32000},
{codec_spec_conf::kLeAudioSamplingFreq44100Hz,
LeAudioCodecConfiguration::kSampleRate44100},
{codec_spec_conf::kLeAudioSamplingFreq48000Hz,
LeAudioCodecConfiguration::kSampleRate48000}};
/* Helper map for matching various frequency notations */
const std::map<uint32_t, uint8_t> LeAudioCoreCodecConfig::sample_rate_map = {
{LeAudioCodecConfiguration::kSampleRate8000,
codec_spec_conf::kLeAudioSamplingFreq8000Hz},
{LeAudioCodecConfiguration::kSampleRate16000,
codec_spec_conf::kLeAudioSamplingFreq16000Hz},
{LeAudioCodecConfiguration::kSampleRate24000,
codec_spec_conf::kLeAudioSamplingFreq24000Hz},
{LeAudioCodecConfiguration::kSampleRate32000,
codec_spec_conf::kLeAudioSamplingFreq32000Hz},
{LeAudioCodecConfiguration::kSampleRate44100,
codec_spec_conf::kLeAudioSamplingFreq44100Hz},
{LeAudioCodecConfiguration::kSampleRate48000,
codec_spec_conf::kLeAudioSamplingFreq48000Hz},
};
/* Helper map for matching various frame durations notations */
const std::map<uint8_t, uint32_t> LeAudioCoreCodecConfig::frame_duration_map = {
{codec_spec_conf::kLeAudioCodecFrameDur7500us,
LeAudioCodecConfiguration::kInterval7500Us},
{codec_spec_conf::kLeAudioCodecFrameDur10000us,
LeAudioCodecConfiguration::kInterval10000Us}};
/* Helper map for matching various frame durations notations */
const std::map<uint32_t, uint8_t> LeAudioCoreCodecConfig::data_interval_map = {
{LeAudioCodecConfiguration::kInterval7500Us,
codec_spec_conf::kLeAudioCodecFrameDur7500us},
{LeAudioCodecConfiguration::kInterval10000Us,
codec_spec_conf::kLeAudioCodecFrameDur10000us},
};
std::string CapabilityTypeToStr(const uint8_t& type) {
switch (type) {
case codec_spec_caps::kLeAudioLtvTypeSupportedSamplingFrequencies:
return "Supported Sampling Frequencies";
case codec_spec_caps::kLeAudioLtvTypeSupportedFrameDurations:
return "Supported Frame Durations";
case codec_spec_caps::kLeAudioLtvTypeSupportedAudioChannelCounts:
return "Supported Audio Channel Count";
case codec_spec_caps::kLeAudioLtvTypeSupportedOctetsPerCodecFrame:
return "Supported Octets Per Codec Frame";
case codec_spec_caps::kLeAudioLtvTypeSupportedMaxCodecFramesPerSdu:
return "Supported Max Codec Frames Per SDU";
default:
return "Unknown";
}
}
std::string CapabilityValueToStr(const uint8_t& type,
const std::vector<uint8_t>& value) {
std::string string = "";
switch (type) {
case codec_spec_conf::kLeAudioLtvTypeSamplingFreq: {
if (value.size() != 2) {
return "Invalid size";
}
uint16_t u16_val = VEC_UINT8_TO_UINT16(value);
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq8000Hz) {
string += "8";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq11025Hz) {
string += std::string((string.empty() ? "" : "|")) + "11.025";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq16000Hz) {
string += std::string((string.empty() ? "" : "|")) + "16";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq22050Hz) {
string += std::string((string.empty() ? "" : "|")) + "22.050";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq24000Hz) {
string += std::string((string.empty() ? "" : "|")) + "24";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq32000Hz) {
string += std::string((string.empty() ? "" : "|")) + "32";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq44100Hz) {
string += std::string((string.empty() ? "" : "|")) + "44.1";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq48000Hz) {
string += std::string((string.empty() ? "" : "|")) + "48";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq88200Hz) {
string += std::string((string.empty() ? "" : "|")) + "88.2";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq96000Hz) {
string += std::string((string.empty() ? "" : "|")) + "96";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq176400Hz) {
string += std::string((string.empty() ? "" : "|")) + "176.4";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq192000Hz) {
string += std::string((string.empty() ? "" : "|")) + "192";
}
if (u16_val & codec_spec_caps::kLeAudioSamplingFreq384000Hz) {
string += std::string((string.empty() ? "" : "|")) + "384";
}
return string += " [kHz]\n";
}
case codec_spec_conf::kLeAudioLtvTypeFrameDuration: {
if (value.size() != 1) {
return "Invalid size";
}
uint8_t u8_val = VEC_UINT8_TO_UINT8(value);
if (u8_val & codec_spec_caps::kLeAudioCodecFrameDur7500us) {
string += "7.5";
}
if (u8_val & codec_spec_caps::kLeAudioCodecFrameDur10000us) {
string += std::string((string.empty() ? "" : "|")) + "10";
}
if (u8_val & codec_spec_caps::kLeAudioCodecFrameDurPrefer7500us) {
string += std::string((string.empty() ? "" : "|")) + "7.5 preferred";
}
if (u8_val & codec_spec_caps::kLeAudioCodecFrameDurPrefer10000us) {
string += std::string((string.empty() ? "" : "|")) + "10 preferred";
}
return string += " [ms]\n";
}
case codec_spec_conf::kLeAudioLtvTypeAudioChannelAllocation: {
if (value.size() != 1) {
return "Invalid size";
}
uint8_t u8_val = VEC_UINT8_TO_UINT8(value);
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountNone) {
string += "0";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountSingleChannel) {
string += std::string((string.empty() ? "" : "|")) + "1";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountTwoChannel) {
string += std::string((string.empty() ? "" : "|")) + "2";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountThreeChannel) {
string += std::string((string.empty() ? "" : "|")) + "3";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountFourChannel) {
string += std::string((string.empty() ? "" : "|")) + "4";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountFiveChannel) {
string += std::string((string.empty() ? "" : "|")) + "5";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountSixChannel) {
string += std::string((string.empty() ? "" : "|")) + "6";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountSevenChannel) {
string += std::string((string.empty() ? "" : "|")) + "7";
}
if (u8_val & codec_spec_caps::kLeAudioCodecChannelCountEightChannel) {
string += std::string((string.empty() ? "" : "|")) + "8";
}
return string += " channel/s\n";
}
case codec_spec_conf::kLeAudioLtvTypeOctetsPerCodecFrame: {
if (value.size() != 4) {
return "Invalid size";
}
uint16_t u16_min_number_of_octets = VEC_UINT8_TO_UINT16(value);
uint16_t u16_max_number_of_octets =
OFF_VEC_UINT8_TO_UINT16(value, sizeof(u16_min_number_of_octets));
string += "Minimum: " + std::to_string(u16_min_number_of_octets);
string += ", Maximum: " + std::to_string(u16_max_number_of_octets) + "\n";
return string;
}
case codec_spec_conf::kLeAudioLtvTypeCodecFrameBlocksPerSdu: {
if (value.size() != 1) {
return "Invalid size";
}
uint8_t u8_val = VEC_UINT8_TO_UINT8(value);
string += std::to_string(u8_val) + " frame/s\n";
return string;
}
default:
return base::HexEncode(value.data(), value.size()) + "\n";
}
}
std::string CodecCapabilitiesLtvFormat(const uint8_t& type,
const std::vector<uint8_t>& value) {
std::string string = "";
string += CapabilityTypeToStr(type) + ": ";
string += CapabilityValueToStr(type, value);
return string;
}
std::optional<std::vector<uint8_t>> LeAudioLtvMap::Find(uint8_t type) const {
auto iter =
std::find_if(values.cbegin(), values.cend(),
[type](const auto& value) { return value.first == type; });
if (iter == values.cend()) return std::nullopt;
return iter->second;
}
uint8_t* LeAudioLtvMap::RawPacket(uint8_t* p_buf) const {
for (auto const& value : values) {
UINT8_TO_STREAM(p_buf, value.second.size() + 1);
UINT8_TO_STREAM(p_buf, value.first);
ARRAY_TO_STREAM(p_buf, value.second.data(),
static_cast<int>(value.second.size()));
}
return p_buf;
}
std::vector<uint8_t> LeAudioLtvMap::RawPacket() const {
std::vector<uint8_t> data(RawPacketSize());
RawPacket(data.data());
return data;
}
void LeAudioLtvMap::Append(const LeAudioLtvMap& other) {
/* This will override values for the already existing keys */
for (auto& el : other.values) {
values[el.first] = el.second;
}
invalidate();
}
LeAudioLtvMap LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len,
bool& success) {
LeAudioLtvMap ltv_map;
success = ltv_map.Parse(p_value, len);
if (!success) {
log::error("Error parsing LTV map");
}
return ltv_map;
}
bool LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len) {
if (len > 0) {
const auto p_value_end = p_value + len;
while ((p_value_end - p_value) > 0) {
uint8_t ltv_len;
STREAM_TO_UINT8(ltv_len, p_value);
// Unusual, but possible case
if (ltv_len == 0) continue;
if (p_value_end < (p_value + ltv_len)) {
log::error("Invalid ltv_len: {}", static_cast<int>(ltv_len));
invalidate();
return false;
}
uint8_t ltv_type;
STREAM_TO_UINT8(ltv_type, p_value);
ltv_len -= sizeof(ltv_type);
const auto p_temp = p_value;
p_value += ltv_len;
std::vector<uint8_t> ltv_value(p_temp, p_value);
values.emplace(ltv_type, std::move(ltv_value));
}
}
invalidate();
return true;
}
size_t LeAudioLtvMap::RawPacketSize() const {
size_t bytes = 0;
for (auto const& value : values) {
bytes += (/* ltv_len + ltv_type */ 2 + value.second.size());
}
return bytes;
}
std::string LeAudioLtvMap::ToString(
const std::string& indent_string,
std::string (*format)(const uint8_t&, const std::vector<uint8_t>&)) const {
std::string debug_str;
for (const auto& value : values) {
std::stringstream sstream;
if (format == nullptr) {
sstream << indent_string + "type: " << std::to_string(value.first)
<< "\tlen: " << std::to_string(value.second.size()) << "\tdata: "
<< base::HexEncode(value.second.data(), value.second.size()) +
"\n";
} else {
sstream << indent_string + format(value.first, value.second);
}
debug_str += sstream.str();
}
return debug_str;
}
const struct LeAudioCoreCodecConfig& LeAudioLtvMap::GetAsCoreCodecConfig()
const {
log::assert_that(!core_capabilities,
"LTVs were already parsed for capabilities!");
log::assert_that(!metadata, "LTVs were already parsed for metadata!");
if (!core_config) {
core_config = LtvMapToCoreCodecConfig(*this);
}
return *core_config;
}
const struct LeAudioCoreCodecCapabilities&
LeAudioLtvMap::GetAsCoreCodecCapabilities() const {
log::assert_that(!core_config,
"LTVs were already parsed for configurations!");
log::assert_that(!metadata, "LTVs were already parsed for metadata!");
if (!core_capabilities) {
core_capabilities = LtvMapToCoreCodecCapabilities(*this);
}
return *core_capabilities;
}
const struct LeAudioMetadata& LeAudioLtvMap::GetAsLeAudioMetadata() const {
log::assert_that(!core_config,
"LTVs were already parsed for configurations!");
log::assert_that(!core_capabilities,
"LTVs were already parsed for capabilities!");
if (!metadata) {
metadata = LtvMapToMetadata(*this);
}
return *metadata;
}
void LeAudioLtvMap::RemoveAllTypes(const LeAudioLtvMap& other) {
for (auto const& [key, _] : other.values) {
Remove(key);
}
}
LeAudioLtvMap LeAudioLtvMap::GetIntersection(const LeAudioLtvMap& other) const {
LeAudioLtvMap result;
for (auto const& [key, value] : values) {
auto entry = other.Find(key);
if (entry->size() != value.size()) continue;
if (memcmp(entry->data(), value.data(), value.size()) == 0) {
result.Add(key, value);
}
}
return result;
}
} // namespace types
void AppendMetadataLtvEntryForCcidList(std::vector<uint8_t>& metadata,
const std::vector<uint8_t>& ccid_list) {
if (ccid_list.size() == 0) {
log::warn("Empty CCID list.");
return;
}
metadata.push_back(
static_cast<uint8_t>(types::kLeAudioMetadataTypeLen + ccid_list.size()));
metadata.push_back(static_cast<uint8_t>(types::kLeAudioMetadataTypeCcidList));
metadata.insert(metadata.end(), ccid_list.begin(), ccid_list.end());
}
void AppendMetadataLtvEntryForStreamingContext(
std::vector<uint8_t>& metadata, types::AudioContexts context_type) {
std::vector<uint8_t> streaming_context_ltv_entry;
streaming_context_ltv_entry.resize(
types::kLeAudioMetadataTypeLen + types::kLeAudioMetadataLenLen +
types::kLeAudioMetadataStreamingAudioContextLen);
uint8_t* streaming_context_ltv_entry_buf = streaming_context_ltv_entry.data();
UINT8_TO_STREAM(streaming_context_ltv_entry_buf,
types::kLeAudioMetadataTypeLen +
types::kLeAudioMetadataStreamingAudioContextLen);
UINT8_TO_STREAM(streaming_context_ltv_entry_buf,
types::kLeAudioMetadataTypeStreamingAudioContext);
UINT16_TO_STREAM(streaming_context_ltv_entry_buf, context_type.value());
metadata.insert(metadata.end(), streaming_context_ltv_entry.begin(),
streaming_context_ltv_entry.end());
}
uint8_t GetMaxCodecFramesPerSduFromPac(const acs_ac_record* pac) {
if (utils::IsCodecUsingLtvFormat(pac->codec_id)) {
auto tlv_ent = pac->codec_spec_caps.Find(
codec_spec_caps::kLeAudioLtvTypeSupportedMaxCodecFramesPerSdu);
if (tlv_ent) return VEC_UINT8_TO_UINT8(tlv_ent.value());
}
return 1;
}
namespace types {
std::ostream& operator<<(std::ostream& os, const CisState& state) {
static const char* char_value_[5] = {"IDLE", "ASSIGNED", "CONNECTING",
"CONNECTED", "DISCONNECTING"};
os << char_value_[static_cast<uint8_t>(state)] << " ("
<< "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
<< ")";
return os;
}
std::ostream& operator<<(std::ostream& os, const DataPathState& state) {
static const char* char_value_[4] = {"IDLE", "CONFIGURING", "CONFIGURED",
"REMOVING"};
os << char_value_[static_cast<uint8_t>(state)] << " ("
<< "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
<< ")";
return os;
}
std::ostream& operator<<(std::ostream& os, const types::CigState& state) {
static const char* char_value_[5] = {"NONE", "CREATING", "CREATED",
"REMOVING", "RECOVERING"};
os << char_value_[static_cast<uint8_t>(state)] << " ("
<< "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
<< ")";
return os;
}
std::ostream& operator<<(std::ostream& os, const types::AseState& state) {
static const char* char_value_[7] = {
"IDLE", "CODEC_CONFIGURED", "QOS_CONFIGURED", "ENABLING",
"STREAMING", "DISABLING", "RELEASING",
};
os << char_value_[static_cast<uint8_t>(state)] << " ("
<< "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
<< ")";
return os;
}
std::ostream& operator<<(std::ostream& os, const LeAudioCodecId& codec_id) {
os << "LeAudioCodecId{CodingFormat: " << loghex(codec_id.coding_format)
<< ", CompanyId: " << loghex(codec_id.vendor_company_id)
<< ", CodecId: " << loghex(codec_id.vendor_codec_id) << "}";
return os;
}
std::ostream& operator<<(std::ostream& os,
const types::LeAudioCoreCodecConfig& config) {
os << "LeAudioCoreCodecConfig{SamplFreq: "
<< loghex(*config.sampling_frequency)
<< ", FrameDur: " << loghex(*config.frame_duration)
<< ", OctetsPerFrame: " << int(*config.octets_per_codec_frame)
<< ", CodecFramesBlocksPerSDU: "
<< int(*config.codec_frames_blocks_per_sdu)
<< ", AudioChanLoc: " << loghex(*config.audio_channel_allocation) << "}";
return os;
}
std::string contextTypeToStr(const LeAudioContextType& context) {
switch (context) {
case LeAudioContextType::UNINITIALIZED:
return "UNINITIALIZED";
case LeAudioContextType::UNSPECIFIED:
return "UNSPECIFIED";
case LeAudioContextType::CONVERSATIONAL:
return "CONVERSATIONAL";
case LeAudioContextType::MEDIA:
return "MEDIA";
case LeAudioContextType::GAME:
return "GAME";
case LeAudioContextType::INSTRUCTIONAL:
return "INSTRUCTIONAL";
case LeAudioContextType::VOICEASSISTANTS:
return "VOICEASSISTANTS";
case LeAudioContextType::LIVE:
return "LIVE";
case LeAudioContextType::SOUNDEFFECTS:
return "SOUNDEFFECTS";
case LeAudioContextType::NOTIFICATIONS:
return "NOTIFICATIONS";
case LeAudioContextType::RINGTONE:
return "RINGTONE";
case LeAudioContextType::ALERTS:
return "ALERTS";
case LeAudioContextType::EMERGENCYALARM:
return "EMERGENCYALARM";
default:
return "UNKNOWN";
}
}
std::ostream& operator<<(std::ostream& os, const LeAudioContextType& context) {
os << contextTypeToStr(context);
return os;
}
AudioContexts operator|(std::underlying_type<LeAudioContextType>::type lhs,
const LeAudioContextType rhs) {
using T = std::underlying_type<LeAudioContextType>::type;
return AudioContexts(lhs | static_cast<T>(rhs));
}
AudioContexts& operator|=(AudioContexts& lhs, AudioContexts const& rhs) {
lhs = AudioContexts(lhs.value() | rhs.value());
return lhs;
}
AudioContexts& operator&=(AudioContexts& lhs, AudioContexts const& rhs) {
lhs = AudioContexts(lhs.value() & rhs.value());
return lhs;
}
std::string ToHexString(const LeAudioContextType& value) {
using T = std::underlying_type<LeAudioContextType>::type;
return bluetooth::common::ToHexString(static_cast<T>(value));
}
std::string AudioContexts::to_string() const {
std::stringstream s;
for (auto ctx : bluetooth::le_audio::types::kLeAudioContextAllTypesArray) {
if (test(ctx)) {
if (s.tellp() != 0) s << " | ";
s << ctx;
}
}
s << " (" << bluetooth::common::ToHexString(mValue) << ")";
return s.str();
}
std::ostream& operator<<(std::ostream& os, const AudioContexts& contexts) {
os << contexts.to_string();
return os;
}
template <typename T>
const T& BidirectionalPair<T>::get(uint8_t direction) const {
log::assert_that(direction < types::kLeAudioDirectionBoth,
"Unsupported complex direction. Consider using "
"get_bidirectional<>() instead.");
return (direction == types::kLeAudioDirectionSink) ? sink : source;
}
template <typename T>
T& BidirectionalPair<T>::get(uint8_t direction) {
log::assert_that(direction < types::kLeAudioDirectionBoth,
"Unsupported complex direction. Reference to a single "
"complex direction value is not supported.");
return (direction == types::kLeAudioDirectionSink) ? sink : source;
}
/* Bidirectional getter trait for AudioContexts bidirectional pair */
template <>
AudioContexts get_bidirectional(BidirectionalPair<AudioContexts> p) {
return p.sink | p.source;
}
template <>
std::vector<uint8_t> get_bidirectional(
BidirectionalPair<std::vector<uint8_t>> bidir) {
std::vector<uint8_t> res = bidir.sink;
res.insert(std::end(res), std::begin(bidir.source), std::end(bidir.source));
return res;
}
template <>
AudioLocations get_bidirectional(BidirectionalPair<AudioLocations> bidir) {
return bidir.sink | bidir.source;
}
std::ostream& operator<<(
std::ostream& os, const le_audio::types::IsoDataPathConfiguration& config) {
os << "IsoDataPathCfg{codecId: " << config.codecId
<< ", isTransparent: " << config.isTransparent
<< ", controllerDelayUs: " << config.controllerDelayUs
<< ", configuration.size: " << config.configuration.size() << "}";
return os;
}
std::ostream& operator<<(std::ostream& os,
const le_audio::types::DataPathConfiguration& config) {
os << "DataPathCfg{datapathId: " << +config.dataPathId
<< ", dataPathCfg.size: " << +config.dataPathConfig.size()
<< ", isoDataPathCfg: " << config.isoDataPathConfig << "}";
return os;
}
std::ostream& operator<<(std::ostream& os, const LeAudioMetadata& config) {
os << "LeAudioMetadata{";
if (config.preferred_audio_context) {
os << "preferred_audio_context: ";
os << AudioContexts(config.preferred_audio_context.value());
}
if (config.streaming_audio_context) {
os << ", streaming_audio_context: ";
os << AudioContexts(config.streaming_audio_context.value());
}
if (config.program_info) {
os << ", program_info: ";
os << config.program_info.value();
}
if (config.language) {
os << ", language: ";
os << config.language.value();
}
if (config.ccid_list) {
os << ", ccid_list: ";
os << base::HexEncode(config.ccid_list.value().data(),
config.ccid_list.value().size());
}
if (config.parental_rating) {
os << ", parental_rating: ";
os << (int)config.parental_rating.value();
}
if (config.program_info_uri) {
os << ", program_info_uri: ";
os << config.program_info_uri.value();
}
if (config.extended_metadata) {
os << ", extended_metadata: ";
os << base::HexEncode(config.extended_metadata.value().data(),
config.extended_metadata.value().size());
}
if (config.vendor_specific) {
os << ", vendor_specific: ";
os << base::HexEncode(config.vendor_specific.value().data(),
config.vendor_specific.value().size());
}
if (config.audio_active_state) {
os << ", audio_active_state: ";
os << config.audio_active_state.value();
}
if (config.broadcast_audio_immediate_rendering) {
os << ", broadcast_audio_immediate_rendering: ";
os << config.broadcast_audio_immediate_rendering.value();
}
os << "}";
return os;
}
template struct BidirectionalPair<AudioContexts>;
template struct BidirectionalPair<AudioLocations>;
template struct BidirectionalPair<CisType>;
template struct BidirectionalPair<LeAudioConfigurationStrategy>;
template struct BidirectionalPair<ase*>;
template struct BidirectionalPair<std::string>;
template struct BidirectionalPair<std::vector<uint8_t>>;
template struct BidirectionalPair<stream_configuration>;
template struct BidirectionalPair<stream_parameters>;
template struct BidirectionalPair<uint16_t>;
template struct BidirectionalPair<uint8_t>;
template struct BidirectionalPair<bool>;
template struct BidirectionalPair<int>;
template struct BidirectionalPair<
std::vector<set_configurations::AseConfiguration>>;
template struct BidirectionalPair<set_configurations::QosConfigSetting>;
} // namespace types
} // namespace bluetooth::le_audio