| /* |
| * Copyright 2019 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. |
| */ |
| |
| #pragma once |
| |
| #include <bluetooth/log.h> |
| |
| #include <array> |
| #include <map> |
| #include <optional> |
| #include <ostream> |
| #include <vector> |
| |
| #include "raw_address.h" |
| |
| namespace bluetooth { |
| namespace le_audio { |
| |
| enum class LeAudioHealthBasedAction { |
| NONE = 0, |
| DISABLE, |
| CONSIDER_DISABLING, |
| INACTIVATE_GROUP, |
| }; |
| |
| inline std::ostream& operator<<(std::ostream& os, |
| const LeAudioHealthBasedAction action) { |
| switch (action) { |
| case LeAudioHealthBasedAction::NONE: |
| os << "NONE"; |
| break; |
| case LeAudioHealthBasedAction::DISABLE: |
| os << "DISABLE"; |
| break; |
| case LeAudioHealthBasedAction::CONSIDER_DISABLING: |
| os << "CONSIDER_DISABLING"; |
| break; |
| case LeAudioHealthBasedAction::INACTIVATE_GROUP: |
| os << "INACTIVATE_GROUP"; |
| break; |
| default: |
| os << "UNKNOWN"; |
| break; |
| } |
| return os; |
| } |
| |
| enum class ConnectionState { |
| DISCONNECTED = 0, |
| CONNECTING, |
| CONNECTED, |
| DISCONNECTING |
| }; |
| |
| enum class GroupStatus { |
| INACTIVE = 0, |
| ACTIVE, |
| TURNED_IDLE_DURING_CALL, |
| }; |
| |
| enum class GroupStreamStatus { |
| IDLE = 0, |
| STREAMING, |
| RELEASING, |
| SUSPENDING, |
| SUSPENDED, |
| CONFIGURED_AUTONOMOUS, |
| CONFIGURED_BY_USER, |
| DESTROYED, |
| }; |
| |
| enum class GroupNodeStatus { |
| ADDED = 1, |
| REMOVED, |
| }; |
| |
| enum class UnicastMonitorModeStatus { |
| STREAMING_REQUESTED = 0, |
| STREAMING, |
| STREAMING_SUSPENDED, |
| STREAMING_REQUESTED_NO_CONTEXT_VALIDATE, |
| }; |
| |
| typedef enum { |
| LE_AUDIO_CODEC_INDEX_SOURCE_LC3 = 0, |
| LE_AUDIO_CODEC_INDEX_SOURCE_INVALID = 1000 * 1000, |
| } btle_audio_codec_index_t; |
| |
| typedef enum { QUALITY_STANDARD = 0, QUALITY_HIGH } btle_audio_quality_t; |
| |
| typedef enum { |
| LE_AUDIO_SAMPLE_RATE_INDEX_NONE = 0, |
| LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ = 0x01 << 0, |
| LE_AUDIO_SAMPLE_RATE_INDEX_11025HZ = 0x01 << 1, |
| LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ = 0x01 << 2, |
| LE_AUDIO_SAMPLE_RATE_INDEX_22050HZ = 0x01 << 3, |
| LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ = 0x01 << 4, |
| LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ = 0x01 << 5, |
| LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ = 0x01 << 6, |
| LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ = 0x01 << 7, |
| LE_AUDIO_SAMPLE_RATE_INDEX_88200HZ = 0x01 << 8, |
| LE_AUDIO_SAMPLE_RATE_INDEX_96000HZ = 0x01 << 9, |
| LE_AUDIO_SAMPLE_RATE_INDEX_176400HZ = 0x01 << 10, |
| LE_AUDIO_SAMPLE_RATE_INDEX_192000HZ = 0x01 << 11, |
| LE_AUDIO_SAMPLE_RATE_INDEX_384000HZ = 0x01 << 12 |
| } btle_audio_sample_rate_index_t; |
| |
| typedef enum { |
| LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE = 0, |
| LE_AUDIO_BITS_PER_SAMPLE_INDEX_16 = 0x01 << 0, |
| LE_AUDIO_BITS_PER_SAMPLE_INDEX_24 = 0x01 << 1, |
| LE_AUDIO_BITS_PER_SAMPLE_INDEX_32 = 0x01 << 3 |
| } btle_audio_bits_per_sample_index_t; |
| |
| typedef enum { |
| LE_AUDIO_CHANNEL_COUNT_INDEX_NONE = 0, |
| LE_AUDIO_CHANNEL_COUNT_INDEX_1 = 0x01 << 0, |
| LE_AUDIO_CHANNEL_COUNT_INDEX_2 = 0x01 << 1 |
| } btle_audio_channel_count_index_t; |
| |
| typedef enum { |
| LE_AUDIO_FRAME_DURATION_INDEX_NONE = 0, |
| LE_AUDIO_FRAME_DURATION_INDEX_7500US = 0x01 << 0, |
| LE_AUDIO_FRAME_DURATION_INDEX_10000US = 0x01 << 1 |
| } btle_audio_frame_duration_index_t; |
| |
| typedef struct btle_audio_codec_config { |
| btle_audio_codec_index_t codec_type = LE_AUDIO_CODEC_INDEX_SOURCE_INVALID; |
| btle_audio_sample_rate_index_t sample_rate = LE_AUDIO_SAMPLE_RATE_INDEX_NONE; |
| btle_audio_bits_per_sample_index_t bits_per_sample = |
| LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE; |
| btle_audio_channel_count_index_t channel_count = |
| LE_AUDIO_CHANNEL_COUNT_INDEX_NONE; |
| btle_audio_frame_duration_index_t frame_duration = |
| LE_AUDIO_FRAME_DURATION_INDEX_NONE; |
| uint16_t octets_per_frame = 0; |
| int32_t codec_priority = 0; |
| |
| bool operator!=(const btle_audio_codec_config& other) const { |
| if (codec_type != other.codec_type) return true; |
| if (sample_rate != other.sample_rate) return true; |
| if (bits_per_sample != other.bits_per_sample) return true; |
| if (channel_count != other.channel_count) return true; |
| if (frame_duration != other.frame_duration) return true; |
| if (octets_per_frame != other.octets_per_frame) return true; |
| if (codec_priority != other.codec_priority) return true; |
| return false; |
| }; |
| bool operator==(const btle_audio_codec_config& other) const { |
| return !(*this != other); |
| }; |
| |
| std::string ToString() const { |
| std::string codec_name_str; |
| std::string sample_rate_str; |
| std::string bits_per_sample_str; |
| std::string channel_count_str; |
| std::string frame_duration_str; |
| std::string octets_per_frame_str; |
| std::string codec_priority_str; |
| |
| switch (codec_type) { |
| case LE_AUDIO_CODEC_INDEX_SOURCE_LC3: |
| codec_name_str = "LC3"; |
| break; |
| default: |
| codec_name_str = "Unknown LE codec " + std::to_string(codec_type); |
| break; |
| } |
| |
| switch (sample_rate) { |
| case LE_AUDIO_SAMPLE_RATE_INDEX_NONE: |
| sample_rate_str = "none"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_8000HZ: |
| sample_rate_str = "8000 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_11025HZ: |
| sample_rate_str = "11025 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_16000HZ: |
| sample_rate_str = "16000 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_22050HZ: |
| sample_rate_str = "22050 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_24000HZ: |
| sample_rate_str = "24000 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_32000HZ: |
| sample_rate_str = "32000 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_44100HZ: |
| sample_rate_str = "44100 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_48000HZ: |
| sample_rate_str = "48000 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_88200HZ: |
| sample_rate_str = "88200 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_96000HZ: |
| sample_rate_str = "96000 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_176400HZ: |
| sample_rate_str = "176400 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_192000HZ: |
| sample_rate_str = "192000 hz"; |
| break; |
| case LE_AUDIO_SAMPLE_RATE_INDEX_384000HZ: |
| sample_rate_str = "384000 hz"; |
| break; |
| default: |
| sample_rate_str = |
| "Unknown LE sample rate " + std::to_string(sample_rate); |
| break; |
| } |
| |
| switch (bits_per_sample) { |
| case LE_AUDIO_BITS_PER_SAMPLE_INDEX_NONE: |
| bits_per_sample_str = "none"; |
| break; |
| case LE_AUDIO_BITS_PER_SAMPLE_INDEX_16: |
| bits_per_sample_str = "16"; |
| break; |
| case LE_AUDIO_BITS_PER_SAMPLE_INDEX_24: |
| bits_per_sample_str = "24"; |
| break; |
| case LE_AUDIO_BITS_PER_SAMPLE_INDEX_32: |
| bits_per_sample_str = "32"; |
| break; |
| default: |
| bits_per_sample_str = |
| "Unknown LE bits per sample " + std::to_string(bits_per_sample); |
| break; |
| } |
| |
| switch (channel_count) { |
| case LE_AUDIO_CHANNEL_COUNT_INDEX_NONE: |
| channel_count_str = "none"; |
| break; |
| case LE_AUDIO_CHANNEL_COUNT_INDEX_1: |
| channel_count_str = "1"; |
| break; |
| case LE_AUDIO_CHANNEL_COUNT_INDEX_2: |
| channel_count_str = "2"; |
| break; |
| default: |
| channel_count_str = |
| "Unknown LE channel count " + std::to_string(channel_count); |
| break; |
| } |
| |
| switch (frame_duration) { |
| case LE_AUDIO_FRAME_DURATION_INDEX_NONE: |
| frame_duration_str = "none"; |
| break; |
| case LE_AUDIO_FRAME_DURATION_INDEX_7500US: |
| frame_duration_str = "7500 us"; |
| break; |
| case LE_AUDIO_FRAME_DURATION_INDEX_10000US: |
| frame_duration_str = "10000 us"; |
| break; |
| default: |
| frame_duration_str = |
| "Unknown LE frame duration " + std::to_string(frame_duration); |
| break; |
| } |
| |
| if (octets_per_frame < 0) { |
| octets_per_frame_str = |
| "Unknown LE octets per frame " + std::to_string(octets_per_frame); |
| } else { |
| octets_per_frame_str = std::to_string(octets_per_frame); |
| } |
| |
| if (codec_priority < -1) { |
| codec_priority_str = |
| "Unknown LE codec priority " + std::to_string(codec_priority); |
| } else { |
| codec_priority_str = std::to_string(codec_priority); |
| } |
| |
| return "codec: " + codec_name_str + ", sample rate: " + sample_rate_str + |
| ", bits per sample: " + bits_per_sample_str + |
| ", channel count: " + channel_count_str + |
| ", frame duration: " + frame_duration_str + |
| ", octets per frame: " + octets_per_frame_str + |
| ", codec priroty: " + codec_priority_str; |
| } |
| |
| } btle_audio_codec_config_t; |
| |
| class LeAudioClientCallbacks { |
| public: |
| virtual ~LeAudioClientCallbacks() = default; |
| |
| /* Callback to notify Java that stack is ready */ |
| virtual void OnInitialized(void) = 0; |
| |
| /** Callback for profile connection state change */ |
| virtual void OnConnectionState(ConnectionState state, |
| const RawAddress& address) = 0; |
| |
| /* Callback with group status update */ |
| virtual void OnGroupStatus(int group_id, GroupStatus group_status) = 0; |
| |
| /* Callback with node status update */ |
| virtual void OnGroupNodeStatus(const RawAddress& bd_addr, int group_id, |
| GroupNodeStatus node_status) = 0; |
| /* Callback for newly recognized or reconfigured existing le audio group */ |
| virtual void OnAudioConf(uint8_t direction, int group_id, |
| uint32_t snk_audio_location, |
| uint32_t src_audio_location, |
| uint16_t avail_cont) = 0; |
| /* Callback for sink audio location recognized */ |
| virtual void OnSinkAudioLocationAvailable(const RawAddress& address, |
| uint32_t snk_audio_locations) = 0; |
| /* Callback with local codec capabilities */ |
| virtual void OnAudioLocalCodecCapabilities( |
| std::vector<btle_audio_codec_config_t> local_input_capa_codec_conf, |
| std::vector<btle_audio_codec_config_t> local_output_capa_codec_conf) = 0; |
| /* Callback with current group codec configurations. Should change when PACs |
| * changes */ |
| virtual void OnAudioGroupCurrentCodecConf( |
| int group_id, btle_audio_codec_config_t input_codec_conf, |
| btle_audio_codec_config_t output_codec_conf) = 0; |
| /* Callback with selectable group codec configurations. Should change when |
| * context changes */ |
| virtual void OnAudioGroupSelectableCodecConf( |
| int group_id, |
| std::vector<btle_audio_codec_config_t> input_selectable_codec_conf, |
| std::vector<btle_audio_codec_config_t> output_selectable_codec_conf) = 0; |
| virtual void OnHealthBasedRecommendationAction( |
| const RawAddress& address, LeAudioHealthBasedAction action) = 0; |
| virtual void OnHealthBasedGroupRecommendationAction( |
| int group_id, LeAudioHealthBasedAction action) = 0; |
| |
| virtual void OnUnicastMonitorModeStatus(uint8_t direction, |
| UnicastMonitorModeStatus status) = 0; |
| |
| /* Callback with group stream status update */ |
| virtual void OnGroupStreamStatus(int group_id, |
| GroupStreamStatus group_stream_status) = 0; |
| }; |
| |
| class LeAudioClientInterface { |
| public: |
| virtual ~LeAudioClientInterface() = default; |
| |
| /* Register the LeAudio callbacks */ |
| virtual void Initialize( |
| LeAudioClientCallbacks* callbacks, |
| const std::vector<btle_audio_codec_config_t>& offloading_preference) = 0; |
| |
| /** Connect to LEAudio */ |
| virtual void Connect(const RawAddress& address) = 0; |
| |
| /** Disconnect from LEAudio */ |
| virtual void Disconnect(const RawAddress& address) = 0; |
| |
| /* Set enable/disable State for the LeAudio device */ |
| virtual void SetEnableState(const RawAddress& address, bool enabled) = 0; |
| |
| /* Cleanup the LeAudio */ |
| virtual void Cleanup(void) = 0; |
| |
| /* Called when LeAudio is unbonded. */ |
| virtual void RemoveDevice(const RawAddress& address) = 0; |
| |
| /* Attach le audio node to group */ |
| virtual void GroupAddNode(int group_id, const RawAddress& addr) = 0; |
| |
| /* Detach le audio node from a group */ |
| virtual void GroupRemoveNode(int group_id, const RawAddress& addr) = 0; |
| |
| /* Set active le audio group */ |
| virtual void GroupSetActive(int group_id) = 0; |
| |
| /* Set codec config preference */ |
| virtual void SetCodecConfigPreference( |
| int group_id, btle_audio_codec_config_t input_codec_config, |
| btle_audio_codec_config_t output_codec_config) = 0; |
| |
| /* Set Ccid for context type */ |
| virtual void SetCcidInformation(int ccid, int context_type) = 0; |
| |
| /* Set In call flag */ |
| virtual void SetInCall(bool in_call) = 0; |
| |
| /* Set Sink listening mode flag */ |
| virtual void SetUnicastMonitorMode(uint8_t direction, bool enable) = 0; |
| |
| /* Sends a preferred audio profiles change */ |
| virtual void SendAudioProfilePreferences( |
| int group_id, bool is_output_preference_le_audio, |
| bool is_duplex_preference_le_audio) = 0; |
| |
| /* Set allowed to stream context */ |
| virtual void SetGroupAllowedContextMask(int group_id, int sink_context_types, |
| int source_context_types) = 0; |
| }; |
| |
| /* Represents the broadcast source state. */ |
| enum class BroadcastState { |
| STOPPED = 0, |
| CONFIGURING, |
| CONFIGURED, |
| STOPPING, |
| STREAMING, |
| }; |
| |
| using BroadcastId = uint32_t; |
| static constexpr BroadcastId kBroadcastIdInvalid = 0x00000000; |
| using BroadcastCode = std::array<uint8_t, 16>; |
| |
| /* Content Metadata LTV Types */ |
| constexpr uint8_t kLeAudioMetadataTypePreferredAudioContext = 0x01; |
| constexpr uint8_t kLeAudioMetadataTypeStreamingAudioContext = 0x02; |
| constexpr uint8_t kLeAudioMetadataTypeProgramInfo = 0x03; |
| constexpr uint8_t kLeAudioMetadataTypeLanguage = 0x04; |
| constexpr uint8_t kLeAudioMetadataTypeCcidList = 0x05; |
| |
| /* Codec specific LTV Types */ |
| constexpr uint8_t kLeAudioLtvTypeSamplingFreq = 0x01; |
| constexpr uint8_t kLeAudioLtvTypeFrameDuration = 0x02; |
| constexpr uint8_t kLeAudioLtvTypeAudioChannelAllocation = 0x03; |
| constexpr uint8_t kLeAudioLtvTypeOctetsPerCodecFrame = 0x04; |
| constexpr uint8_t kLeAudioLtvTypeCodecFrameBlocksPerSdu = 0x05; |
| |
| /* Audio quality configuration in public broadcast announcement */ |
| constexpr uint8_t kLeAudioQualityStandard = 0x1 << 1; |
| constexpr uint8_t kLeAudioQualityHigh = 0x1 << 2; |
| |
| /* Unknown RSSI value 0x7F - 127 */ |
| constexpr uint8_t kLeAudioSourceRssiUnknown = 0x7F; |
| |
| struct BasicAudioAnnouncementCodecConfig { |
| /* 5 octets for the Codec ID */ |
| uint8_t codec_id; |
| uint16_t vendor_company_id; |
| uint16_t vendor_codec_id; |
| |
| /* Codec params - series of LTV formatted triplets */ |
| std::map<uint8_t, std::vector<uint8_t>> codec_specific_params; |
| std::optional<std::vector<uint8_t>> vendor_codec_specific_params; |
| }; |
| |
| struct BasicAudioAnnouncementBisConfig { |
| std::map<uint8_t, std::vector<uint8_t>> codec_specific_params; |
| std::optional<std::vector<uint8_t>> vendor_codec_specific_params; |
| |
| uint8_t bis_index; |
| }; |
| |
| struct BasicAudioAnnouncementSubgroup { |
| /* Subgroup specific codec configuration and metadata */ |
| BasicAudioAnnouncementCodecConfig codec_config; |
| // Content metadata |
| std::map<uint8_t, std::vector<uint8_t>> metadata; |
| // Broadcast channel configuration |
| std::vector<BasicAudioAnnouncementBisConfig> bis_configs; |
| }; |
| |
| struct BasicAudioAnnouncementData { |
| /* Announcement Header fields */ |
| uint32_t presentation_delay_us; |
| |
| /* Subgroup specific configurations */ |
| std::vector<BasicAudioAnnouncementSubgroup> subgroup_configs; |
| }; |
| |
| struct PublicBroadcastAnnouncementData { |
| // Public Broadcast Announcement features bitmap |
| uint8_t features; |
| // Metadata |
| std::map<uint8_t, std::vector<uint8_t>> metadata; |
| }; |
| |
| struct BroadcastMetadata { |
| bool is_public; |
| uint16_t pa_interval; |
| RawAddress addr; |
| uint8_t addr_type; |
| uint8_t adv_sid; |
| |
| BroadcastId broadcast_id; |
| std::string broadcast_name; |
| std::optional<BroadcastCode> broadcast_code; |
| |
| PublicBroadcastAnnouncementData public_announcement; |
| /* Presentation delay and subgroup configurations */ |
| BasicAudioAnnouncementData basic_audio_announcement; |
| }; |
| |
| class LeAudioBroadcasterCallbacks { |
| public: |
| virtual ~LeAudioBroadcasterCallbacks() = default; |
| /* Callback for the newly created broadcast event. */ |
| virtual void OnBroadcastCreated(uint32_t broadcast_id, bool success) = 0; |
| |
| /* Callback for the destroyed broadcast event. */ |
| virtual void OnBroadcastDestroyed(uint32_t broadcast_id) = 0; |
| /* Callback for the broadcast source state event. */ |
| virtual void OnBroadcastStateChanged(uint32_t broadcast_id, |
| BroadcastState state) = 0; |
| /* Callback for the broadcast metadata change. */ |
| virtual void OnBroadcastMetadataChanged( |
| uint32_t broadcast_id, const BroadcastMetadata& broadcast_metadata) = 0; |
| }; |
| |
| class LeAudioBroadcasterInterface { |
| public: |
| virtual ~LeAudioBroadcasterInterface() = default; |
| /* Register the LeAudio Broadcaster callbacks */ |
| virtual void Initialize(LeAudioBroadcasterCallbacks* callbacks) = 0; |
| /* Stop the LeAudio Broadcaster and all active broadcasts */ |
| virtual void Stop(void) = 0; |
| /* Cleanup the LeAudio Broadcaster */ |
| virtual void Cleanup(void) = 0; |
| /* Create Broadcast instance */ |
| virtual void CreateBroadcast( |
| bool is_public, std::string broadcast_name, |
| std::optional<BroadcastCode> broadcast_code, |
| std::vector<uint8_t> public_metadata, |
| std::vector<uint8_t> subgroup_quality, |
| std::vector<std::vector<uint8_t>> subgroup_metadata) = 0; |
| /* Update the ongoing Broadcast metadata */ |
| virtual void UpdateMetadata( |
| uint32_t broadcast_id, std::string broadcast_name, |
| std::vector<uint8_t> public_metadata, |
| std::vector<std::vector<uint8_t>> subgroup_metadata) = 0; |
| |
| /* Start the existing Broadcast stream */ |
| virtual void StartBroadcast(uint32_t broadcast_id) = 0; |
| /* Pause the ongoing Broadcast stream */ |
| virtual void PauseBroadcast(uint32_t broadcast_id) = 0; |
| /* Stop the Broadcast (no stream, no periodic advertisements */ |
| virtual void StopBroadcast(uint32_t broadcast_id) = 0; |
| /* Destroy the existing Broadcast instance */ |
| virtual void DestroyBroadcast(uint32_t broadcast_id) = 0; |
| /* Get Broadcast Metadata */ |
| virtual void GetBroadcastMetadata(uint32_t broadcast_id) = 0; |
| }; |
| |
| } /* namespace le_audio */ |
| } /* namespace bluetooth */ |
| |
| namespace fmt { |
| template <> |
| struct formatter<bluetooth::le_audio::btle_audio_codec_index_t> |
| : enum_formatter<bluetooth::le_audio::btle_audio_codec_index_t> {}; |
| template <> |
| struct formatter<bluetooth::le_audio::btle_audio_sample_rate_index_t> |
| : enum_formatter<bluetooth::le_audio::btle_audio_sample_rate_index_t> {}; |
| template <> |
| struct formatter<bluetooth::le_audio::btle_audio_bits_per_sample_index_t> |
| : enum_formatter<bluetooth::le_audio::btle_audio_bits_per_sample_index_t> { |
| }; |
| template <> |
| struct formatter<bluetooth::le_audio::btle_audio_channel_count_index_t> |
| : enum_formatter<bluetooth::le_audio::btle_audio_channel_count_index_t> {}; |
| template <> |
| struct formatter<bluetooth::le_audio::btle_audio_frame_duration_index_t> |
| : enum_formatter<bluetooth::le_audio::btle_audio_frame_duration_index_t> {}; |
| template <> |
| struct formatter<bluetooth::le_audio::GroupStreamStatus> |
| : enum_formatter<bluetooth::le_audio::GroupStreamStatus> {}; |
| } // namespace fmt |