1 /* 2 * Copyright 2019 HIMSA II K/S - www.himsa.com. Represented by EHIMA - 3 * www.ehima.com 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #pragma once 19 20 #include <bluetooth/log.h> 21 22 #include <array> 23 #include <map> 24 #include <optional> 25 #include <ostream> 26 #include <vector> 27 28 #include "types/raw_address.h" 29 30 namespace bluetooth { 31 namespace le_audio { 32 33 enum class LeAudioHealthBasedAction { 34 NONE = 0, 35 DISABLE, 36 CONSIDER_DISABLING, 37 INACTIVATE_GROUP, 38 }; 39 40 inline std::ostream& operator<<(std::ostream& os, const LeAudioHealthBasedAction action) { 41 switch (action) { 42 case LeAudioHealthBasedAction::NONE: 43 os << "NONE"; 44 break; 45 case LeAudioHealthBasedAction::DISABLE: 46 os << "DISABLE"; 47 break; 48 case LeAudioHealthBasedAction::CONSIDER_DISABLING: 49 os << "CONSIDER_DISABLING"; 50 break; 51 case LeAudioHealthBasedAction::INACTIVATE_GROUP: 52 os << "INACTIVATE_GROUP"; 53 break; 54 default: 55 os << "UNKNOWN"; 56 break; 57 } 58 return os; 59 } 60 61 enum class ConnectionState { DISCONNECTED = 0, CONNECTING, CONNECTED, DISCONNECTING }; 62 63 enum class GroupStatus { 64 INACTIVE = 0, 65 ACTIVE, 66 TURNED_IDLE_DURING_CALL, 67 }; 68 69 enum class GroupStreamStatus { 70 IDLE = 0, 71 STREAMING, 72 RELEASING, 73 SUSPENDING, 74 SUSPENDED, 75 CONFIGURED_AUTONOMOUS, 76 CONFIGURED_BY_USER, 77 DESTROYED, 78 }; 79 80 enum class GroupNodeStatus { 81 ADDED = 1, 82 REMOVED, 83 }; 84 85 enum class UnicastMonitorModeStatus { 86 STREAMING_REQUESTED = 0, 87 STREAMING, 88 STREAMING_SUSPENDED, 89 STREAMING_REQUESTED_NO_CONTEXT_VALIDATE, 90 }; 91 92 typedef enum { 93 LE_AUDIO_CODEC_INDEX_SOURCE_LC3 = 0, 94 LE_AUDIO_CODEC_INDEX_SOURCE_INVALID = 1000 * 1000, 95 } btle_audio_codec_index_t; 96 97 typedef enum { QUALITY_STANDARD = 0, QUALITY_HIGH } btle_audio_quality_t; 98 99 typedef enum { 100 LE_AUDIO_SAMPLE_RATE_INDEX_NONE = 0, 101 LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ = 0x01 << 0, 102 LE_AUDIO_SAMPLE_RATE_INDEX_11025HZ = 0x01 << 1, 103 LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ = 0x01 << 2, 104 LE_AUDIO_SAMPLE_RATE_INDEX_22050HZ = 0x01 << 3, 105 LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ = 0x01 << 4, 106 LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ = 0x01 << 5, 107 LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ = 0x01 << 6, 108 LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ = 0x01 << 7, 109 LE_AUDIO_SAMPLE_RATE_INDEX_88200HZ = 0x01 << 8, 110 LE_AUDIO_SAMPLE_RATE_INDEX_96000HZ = 0x01 << 9, 111 LE_AUDIO_SAMPLE_RATE_INDEX_176400HZ = 0x01 << 10, 112 LE_AUDIO_SAMPLE_RATE_INDEX_192000HZ = 0x01 << 11, 113 LE_AUDIO_SAMPLE_RATE_INDEX_384000HZ = 0x01 << 12 114 } btle_audio_sample_rate_index_t; 115 116 typedef enum { 117 LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE = 0, 118 LE_AUDIO_BITS_PER_SAMPLE_INDEX_16 = 0x01 << 0, 119 LE_AUDIO_BITS_PER_SAMPLE_INDEX_24 = 0x01 << 1, 120 LE_AUDIO_BITS_PER_SAMPLE_INDEX_32 = 0x01 << 3 121 } btle_audio_bits_per_sample_index_t; 122 123 typedef enum { 124 LE_AUDIO_CHANNEL_COUNT_INDEX_NONE = 0, 125 LE_AUDIO_CHANNEL_COUNT_INDEX_1 = 0x01 << 0, 126 LE_AUDIO_CHANNEL_COUNT_INDEX_2 = 0x01 << 1 127 } btle_audio_channel_count_index_t; 128 129 typedef enum { 130 LE_AUDIO_FRAME_DURATION_INDEX_NONE = 0, 131 LE_AUDIO_FRAME_DURATION_INDEX_7500US = 0x01 << 0, 132 LE_AUDIO_FRAME_DURATION_INDEX_10000US = 0x01 << 1 133 } btle_audio_frame_duration_index_t; 134 135 typedef struct btle_audio_codec_config { 136 btle_audio_codec_index_t codec_type = LE_AUDIO_CODEC_INDEX_SOURCE_INVALID; 137 btle_audio_sample_rate_index_t sample_rate = LE_AUDIO_SAMPLE_RATE_INDEX_NONE; 138 btle_audio_bits_per_sample_index_t bits_per_sample = LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE; 139 btle_audio_channel_count_index_t channel_count = LE_AUDIO_CHANNEL_COUNT_INDEX_NONE; 140 btle_audio_frame_duration_index_t frame_duration = LE_AUDIO_FRAME_DURATION_INDEX_NONE; 141 uint16_t octets_per_frame = 0; 142 int32_t codec_priority = 0; 143 144 bool operator!=(const btle_audio_codec_config& other) const { 145 if (codec_type != other.codec_type) { 146 return true; 147 } 148 if (sample_rate != other.sample_rate) { 149 return true; 150 } 151 if (bits_per_sample != other.bits_per_sample) { 152 return true; 153 } 154 if (channel_count != other.channel_count) { 155 return true; 156 } 157 if (frame_duration != other.frame_duration) { 158 return true; 159 } 160 if (octets_per_frame != other.octets_per_frame) { 161 return true; 162 } 163 if (codec_priority != other.codec_priority) { 164 return true; 165 } 166 return false; 167 } 168 bool operator==(const btle_audio_codec_config& other) const { return !(*this != other); } 169 ToStringbtle_audio_codec_config170 std::string ToString() const { 171 std::string codec_name_str; 172 std::string sample_rate_str; 173 std::string bits_per_sample_str; 174 std::string channel_count_str; 175 std::string frame_duration_str; 176 std::string octets_per_frame_str; 177 std::string codec_priority_str; 178 179 switch (codec_type) { 180 case LE_AUDIO_CODEC_INDEX_SOURCE_LC3: 181 codec_name_str = "LC3"; 182 break; 183 default: 184 codec_name_str = "Unknown LE codec " + std::to_string(codec_type); 185 break; 186 } 187 188 switch (sample_rate) { 189 case LE_AUDIO_SAMPLE_RATE_INDEX_NONE: 190 sample_rate_str = "none"; 191 break; 192 case LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ: 193 sample_rate_str = "8000 hz"; 194 break; 195 case LE_AUDIO_SAMPLE_RATE_INDEX_11025HZ: 196 sample_rate_str = "11025 hz"; 197 break; 198 case LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ: 199 sample_rate_str = "16000 hz"; 200 break; 201 case LE_AUDIO_SAMPLE_RATE_INDEX_22050HZ: 202 sample_rate_str = "22050 hz"; 203 break; 204 case LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ: 205 sample_rate_str = "24000 hz"; 206 break; 207 case LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ: 208 sample_rate_str = "32000 hz"; 209 break; 210 case LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ: 211 sample_rate_str = "44100 hz"; 212 break; 213 case LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ: 214 sample_rate_str = "48000 hz"; 215 break; 216 case LE_AUDIO_SAMPLE_RATE_INDEX_88200HZ: 217 sample_rate_str = "88200 hz"; 218 break; 219 case LE_AUDIO_SAMPLE_RATE_INDEX_96000HZ: 220 sample_rate_str = "96000 hz"; 221 break; 222 case LE_AUDIO_SAMPLE_RATE_INDEX_176400HZ: 223 sample_rate_str = "176400 hz"; 224 break; 225 case LE_AUDIO_SAMPLE_RATE_INDEX_192000HZ: 226 sample_rate_str = "192000 hz"; 227 break; 228 case LE_AUDIO_SAMPLE_RATE_INDEX_384000HZ: 229 sample_rate_str = "384000 hz"; 230 break; 231 default: 232 sample_rate_str = "Unknown LE sample rate " + std::to_string(sample_rate); 233 break; 234 } 235 236 switch (bits_per_sample) { 237 case LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE: 238 bits_per_sample_str = "none"; 239 break; 240 case LE_AUDIO_BITS_PER_SAMPLE_INDEX_16: 241 bits_per_sample_str = "16"; 242 break; 243 case LE_AUDIO_BITS_PER_SAMPLE_INDEX_24: 244 bits_per_sample_str = "24"; 245 break; 246 case LE_AUDIO_BITS_PER_SAMPLE_INDEX_32: 247 bits_per_sample_str = "32"; 248 break; 249 default: 250 bits_per_sample_str = "Unknown LE bits per sample " + std::to_string(bits_per_sample); 251 break; 252 } 253 254 switch (channel_count) { 255 case LE_AUDIO_CHANNEL_COUNT_INDEX_NONE: 256 channel_count_str = "none"; 257 break; 258 case LE_AUDIO_CHANNEL_COUNT_INDEX_1: 259 channel_count_str = "1"; 260 break; 261 case LE_AUDIO_CHANNEL_COUNT_INDEX_2: 262 channel_count_str = "2"; 263 break; 264 default: 265 channel_count_str = "Unknown LE channel count " + std::to_string(channel_count); 266 break; 267 } 268 269 switch (frame_duration) { 270 case LE_AUDIO_FRAME_DURATION_INDEX_NONE: 271 frame_duration_str = "none"; 272 break; 273 case LE_AUDIO_FRAME_DURATION_INDEX_7500US: 274 frame_duration_str = "7500 us"; 275 break; 276 case LE_AUDIO_FRAME_DURATION_INDEX_10000US: 277 frame_duration_str = "10000 us"; 278 break; 279 default: 280 frame_duration_str = "Unknown LE frame duration " + std::to_string(frame_duration); 281 break; 282 } 283 284 if (octets_per_frame < 0) { 285 octets_per_frame_str = "Unknown LE octets per frame " + std::to_string(octets_per_frame); 286 } else { 287 octets_per_frame_str = std::to_string(octets_per_frame); 288 } 289 290 if (codec_priority < -1) { 291 codec_priority_str = "Unknown LE codec priority " + std::to_string(codec_priority); 292 } else { 293 codec_priority_str = std::to_string(codec_priority); 294 } 295 296 return "codec: " + codec_name_str + ", sample rate: " + sample_rate_str + 297 ", bits per sample: " + bits_per_sample_str + ", channel count: " + channel_count_str + 298 ", frame duration: " + frame_duration_str + 299 ", octets per frame: " + octets_per_frame_str + ", codec priroty: " + codec_priority_str; 300 } 301 } btle_audio_codec_config_t; 302 303 class LeAudioClientCallbacks { 304 public: 305 virtual ~LeAudioClientCallbacks() = default; 306 307 /* Callback to notify Java that stack is ready */ 308 virtual void OnInitialized(void) = 0; 309 310 /** Callback for profile connection state change */ 311 virtual void OnConnectionState(ConnectionState state, const RawAddress& address) = 0; 312 313 /* Callback with group status update */ 314 virtual void OnGroupStatus(int group_id, GroupStatus group_status) = 0; 315 316 /* Callback with node status update */ 317 virtual void OnGroupNodeStatus(const RawAddress& bd_addr, int group_id, 318 GroupNodeStatus node_status) = 0; 319 /* Callback for newly recognized or reconfigured existing le audio group */ 320 virtual void OnAudioConf(uint8_t direction, int group_id, uint32_t snk_audio_location, 321 uint32_t src_audio_location, uint16_t avail_cont) = 0; 322 /* Callback for sink audio location recognized */ 323 virtual void OnSinkAudioLocationAvailable(const RawAddress& address, 324 uint32_t snk_audio_locations) = 0; 325 /* Callback with local codec capabilities */ 326 virtual void OnAudioLocalCodecCapabilities( 327 std::vector<btle_audio_codec_config_t> local_input_capa_codec_conf, 328 std::vector<btle_audio_codec_config_t> local_output_capa_codec_conf) = 0; 329 /* Callback with current group codec configurations. Should change when PACs 330 * changes */ 331 virtual void OnAudioGroupCurrentCodecConf(int group_id, 332 btle_audio_codec_config_t input_codec_conf, 333 btle_audio_codec_config_t output_codec_conf) = 0; 334 /* Callback with selectable group codec configurations. Should change when 335 * context changes */ 336 virtual void OnAudioGroupSelectableCodecConf( 337 int group_id, std::vector<btle_audio_codec_config_t> input_selectable_codec_conf, 338 std::vector<btle_audio_codec_config_t> output_selectable_codec_conf) = 0; 339 virtual void OnHealthBasedRecommendationAction(const RawAddress& address, 340 LeAudioHealthBasedAction action) = 0; 341 virtual void OnHealthBasedGroupRecommendationAction(int group_id, 342 LeAudioHealthBasedAction action) = 0; 343 344 virtual void OnUnicastMonitorModeStatus(uint8_t direction, UnicastMonitorModeStatus status) = 0; 345 346 /* Callback with group stream status update */ 347 virtual void OnGroupStreamStatus(int group_id, GroupStreamStatus group_stream_status) = 0; 348 }; 349 350 class LeAudioClientInterface { 351 public: 352 virtual ~LeAudioClientInterface() = default; 353 354 /* Register the LeAudio callbacks */ 355 virtual void Initialize(LeAudioClientCallbacks* callbacks, 356 const std::vector<btle_audio_codec_config_t>& offloading_preference) = 0; 357 358 /** Connect to LEAudio */ 359 virtual void Connect(const RawAddress& address) = 0; 360 361 /** Disconnect from LEAudio */ 362 virtual void Disconnect(const RawAddress& address) = 0; 363 364 /* Set enable/disable State for the LeAudio device */ 365 virtual void SetEnableState(const RawAddress& address, bool enabled) = 0; 366 367 /* Cleanup the LeAudio */ 368 virtual void Cleanup(void) = 0; 369 370 /* Called when LeAudio is unbonded. */ 371 virtual void RemoveDevice(const RawAddress& address) = 0; 372 373 /* Attach le audio node to group */ 374 virtual void GroupAddNode(int group_id, const RawAddress& addr) = 0; 375 376 /* Detach le audio node from a group */ 377 virtual void GroupRemoveNode(int group_id, const RawAddress& addr) = 0; 378 379 /* Set active le audio group */ 380 virtual void GroupSetActive(int group_id) = 0; 381 382 /* Set codec config preference */ 383 virtual void SetCodecConfigPreference(int group_id, btle_audio_codec_config_t input_codec_config, 384 btle_audio_codec_config_t output_codec_config) = 0; 385 386 /* Set Ccid for context type */ 387 virtual void SetCcidInformation(int ccid, int context_type) = 0; 388 389 /* Set In call flag */ 390 virtual void SetInCall(bool in_call) = 0; 391 392 /* Set Sink listening mode flag */ 393 virtual void SetUnicastMonitorMode(uint8_t direction, bool enable) = 0; 394 395 /* Sends a preferred audio profiles change */ 396 virtual void SendAudioProfilePreferences(int group_id, bool is_output_preference_le_audio, 397 bool is_duplex_preference_le_audio) = 0; 398 399 /* Set allowed to stream context */ 400 virtual void SetGroupAllowedContextMask(int group_id, int sink_context_types, 401 int source_context_types) = 0; 402 }; 403 404 /* Represents the broadcast source state. */ 405 enum class BroadcastState { 406 STOPPED = 0, 407 CONFIGURING, 408 CONFIGURED, 409 ENABLING, 410 DISABLING, 411 STOPPING, 412 STREAMING, 413 }; 414 415 using BroadcastId = uint32_t; 416 static constexpr BroadcastId kBroadcastIdInvalid = 0x00000000; 417 using BroadcastCode = std::array<uint8_t, 16>; 418 419 /* Content Metadata LTV Types */ 420 constexpr uint8_t kLeAudioMetadataTypePreferredAudioContext = 0x01; 421 constexpr uint8_t kLeAudioMetadataTypeStreamingAudioContext = 0x02; 422 constexpr uint8_t kLeAudioMetadataTypeProgramInfo = 0x03; 423 constexpr uint8_t kLeAudioMetadataTypeLanguage = 0x04; 424 constexpr uint8_t kLeAudioMetadataTypeCcidList = 0x05; 425 426 /* Codec specific LTV Types */ 427 constexpr uint8_t kLeAudioLtvTypeSamplingFreq = 0x01; 428 constexpr uint8_t kLeAudioLtvTypeFrameDuration = 0x02; 429 constexpr uint8_t kLeAudioLtvTypeAudioChannelAllocation = 0x03; 430 constexpr uint8_t kLeAudioLtvTypeOctetsPerCodecFrame = 0x04; 431 constexpr uint8_t kLeAudioLtvTypeCodecFrameBlocksPerSdu = 0x05; 432 433 /* Audio quality configuration in public broadcast announcement */ 434 constexpr uint8_t kLeAudioQualityStandard = 0x1 << 1; 435 constexpr uint8_t kLeAudioQualityHigh = 0x1 << 2; 436 437 /* Unknown RSSI value 0x7F - 127 */ 438 constexpr uint8_t kLeAudioSourceRssiUnknown = 0x7F; 439 440 struct BasicAudioAnnouncementCodecConfig { 441 /* 5 octets for the Codec ID */ 442 uint8_t codec_id; 443 uint16_t vendor_company_id; 444 uint16_t vendor_codec_id; 445 446 /* Codec params - series of LTV formatted triplets */ 447 std::map<uint8_t, std::vector<uint8_t>> codec_specific_params; 448 std::optional<std::vector<uint8_t>> vendor_codec_specific_params; 449 }; 450 451 struct BasicAudioAnnouncementBisConfig { 452 std::map<uint8_t, std::vector<uint8_t>> codec_specific_params; 453 std::optional<std::vector<uint8_t>> vendor_codec_specific_params; 454 455 uint8_t bis_index; 456 }; 457 458 struct BasicAudioAnnouncementSubgroup { 459 /* Subgroup specific codec configuration and metadata */ 460 BasicAudioAnnouncementCodecConfig codec_config; 461 // Content metadata 462 std::map<uint8_t, std::vector<uint8_t>> metadata; 463 // Broadcast channel configuration 464 std::vector<BasicAudioAnnouncementBisConfig> bis_configs; 465 }; 466 467 struct BasicAudioAnnouncementData { 468 /* Announcement Header fields */ 469 uint32_t presentation_delay_us; 470 471 /* Subgroup specific configurations */ 472 std::vector<BasicAudioAnnouncementSubgroup> subgroup_configs; 473 }; 474 475 struct PublicBroadcastAnnouncementData { 476 // Public Broadcast Announcement features bitmap 477 uint8_t features; 478 // Metadata 479 std::map<uint8_t, std::vector<uint8_t>> metadata; 480 }; 481 482 struct BroadcastMetadata { 483 bool is_public; 484 uint16_t pa_interval; 485 RawAddress addr; 486 uint8_t addr_type; 487 uint8_t adv_sid; 488 489 BroadcastId broadcast_id; 490 std::string broadcast_name; 491 std::optional<BroadcastCode> broadcast_code; 492 493 PublicBroadcastAnnouncementData public_announcement; 494 /* Presentation delay and subgroup configurations */ 495 BasicAudioAnnouncementData basic_audio_announcement; 496 }; 497 498 class LeAudioBroadcasterCallbacks { 499 public: 500 virtual ~LeAudioBroadcasterCallbacks() = default; 501 /* Callback for the newly created broadcast event. */ 502 virtual void OnBroadcastCreated(uint32_t broadcast_id, bool success) = 0; 503 504 /* Callback for the destroyed broadcast event. */ 505 virtual void OnBroadcastDestroyed(uint32_t broadcast_id) = 0; 506 /* Callback for the broadcast source state event. */ 507 virtual void OnBroadcastStateChanged(uint32_t broadcast_id, BroadcastState state) = 0; 508 /* Callback for the broadcast metadata change. */ 509 virtual void OnBroadcastMetadataChanged(uint32_t broadcast_id, 510 const BroadcastMetadata& broadcast_metadata) = 0; 511 /* Callback for broadcast audio session create event. */ 512 virtual void OnBroadcastAudioSessionCreated(bool success) = 0; 513 }; 514 515 class LeAudioBroadcasterInterface { 516 public: 517 virtual ~LeAudioBroadcasterInterface() = default; 518 /* Register the LeAudio Broadcaster callbacks */ 519 virtual void Initialize(LeAudioBroadcasterCallbacks* callbacks) = 0; 520 /* Stop the LeAudio Broadcaster and all active broadcasts */ 521 virtual void Stop(void) = 0; 522 /* Cleanup the LeAudio Broadcaster */ 523 virtual void Cleanup(void) = 0; 524 /* Create Broadcast instance */ 525 virtual void CreateBroadcast(bool is_public, std::string broadcast_name, 526 std::optional<BroadcastCode> broadcast_code, 527 std::vector<uint8_t> public_metadata, 528 std::vector<uint8_t> subgroup_quality, 529 std::vector<std::vector<uint8_t>> subgroup_metadata) = 0; 530 /* Update the ongoing Broadcast metadata */ 531 virtual void UpdateMetadata(uint32_t broadcast_id, std::string broadcast_name, 532 std::vector<uint8_t> public_metadata, 533 std::vector<std::vector<uint8_t>> subgroup_metadata) = 0; 534 535 /* Start the existing Broadcast stream */ 536 virtual void StartBroadcast(uint32_t broadcast_id) = 0; 537 /* Pause the ongoing Broadcast stream */ 538 virtual void PauseBroadcast(uint32_t broadcast_id) = 0; 539 /* Stop the Broadcast (no stream, no periodic advertisements */ 540 virtual void StopBroadcast(uint32_t broadcast_id) = 0; 541 /* Destroy the existing Broadcast instance */ 542 virtual void DestroyBroadcast(uint32_t broadcast_id) = 0; 543 /* Get Broadcast Metadata */ 544 virtual void GetBroadcastMetadata(uint32_t broadcast_id) = 0; 545 }; 546 547 } /* namespace le_audio */ 548 } /* namespace bluetooth */ 549 550 namespace std { 551 template <> 552 struct formatter<bluetooth::le_audio::btle_audio_codec_index_t> 553 : enum_formatter<bluetooth::le_audio::btle_audio_codec_index_t> {}; 554 template <> 555 struct formatter<bluetooth::le_audio::btle_audio_sample_rate_index_t> 556 : enum_formatter<bluetooth::le_audio::btle_audio_sample_rate_index_t> {}; 557 template <> 558 struct formatter<bluetooth::le_audio::btle_audio_bits_per_sample_index_t> 559 : enum_formatter<bluetooth::le_audio::btle_audio_bits_per_sample_index_t> {}; 560 template <> 561 struct formatter<bluetooth::le_audio::btle_audio_channel_count_index_t> 562 : enum_formatter<bluetooth::le_audio::btle_audio_channel_count_index_t> {}; 563 template <> 564 struct formatter<bluetooth::le_audio::btle_audio_frame_duration_index_t> 565 : enum_formatter<bluetooth::le_audio::btle_audio_frame_duration_index_t> {}; 566 template <> 567 struct formatter<bluetooth::le_audio::GroupStreamStatus> 568 : enum_formatter<bluetooth::le_audio::GroupStreamStatus> {}; 569 } // namespace std 570