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