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