• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - 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 #include "broadcaster_types.h"
19 
20 #include <vector>
21 
22 #include "bt_types.h"
23 #include "bta_le_audio_broadcaster_api.h"
24 #include "btm_ble_api_types.h"
25 #include "embdrv/lc3/include/lc3.h"
26 #include "internal_include/stack_config.h"
27 #include "osi/include/properties.h"
28 
29 using bluetooth::le_audio::BasicAudioAnnouncementBisConfig;
30 using bluetooth::le_audio::BasicAudioAnnouncementCodecConfig;
31 using bluetooth::le_audio::BasicAudioAnnouncementData;
32 using bluetooth::le_audio::BasicAudioAnnouncementSubgroup;
33 using le_audio::types::LeAudioContextType;
34 
35 namespace le_audio {
36 namespace broadcaster {
37 
EmitHeader(const BasicAudioAnnouncementData & announcement_data,std::vector<uint8_t> & data)38 static void EmitHeader(const BasicAudioAnnouncementData& announcement_data,
39                        std::vector<uint8_t>& data) {
40   size_t old_size = data.size();
41   data.resize(old_size + 3);
42 
43   // Set the cursor behind the old data
44   uint8_t* p_value = data.data() + old_size;
45 
46   UINT24_TO_STREAM(p_value, announcement_data.presentation_delay);
47 }
48 
EmitCodecConfiguration(const BasicAudioAnnouncementCodecConfig & config,std::vector<uint8_t> & data,const BasicAudioAnnouncementCodecConfig * lower_lvl_config)49 static void EmitCodecConfiguration(
50     const BasicAudioAnnouncementCodecConfig& config, std::vector<uint8_t>& data,
51     const BasicAudioAnnouncementCodecConfig* lower_lvl_config) {
52   size_t old_size = data.size();
53 
54   // Add 5 for full, or 1 for short Codec ID
55   uint8_t codec_config_length = 5;
56 
57   auto ltv = types::LeAudioLtvMap(config.codec_specific_params);
58   auto ltv_raw_sz = ltv.RawPacketSize();
59 
60   // Add 1 for the codec spec. config length + config spec. data itself
61   codec_config_length += 1 + ltv_raw_sz;
62 
63   // Resize and set the cursor behind the old data
64   data.resize(old_size + codec_config_length);
65   uint8_t* p_value = data.data() + old_size;
66 
67   // Codec ID
68   UINT8_TO_STREAM(p_value, config.codec_id);
69   UINT16_TO_STREAM(p_value, config.vendor_company_id);
70   UINT16_TO_STREAM(p_value, config.vendor_codec_id);
71 
72   // Codec specific config length and data
73   UINT8_TO_STREAM(p_value, ltv_raw_sz);
74   p_value = ltv.RawPacket(p_value);
75 }
76 
EmitMetadata(const std::map<uint8_t,std::vector<uint8_t>> & metadata,std::vector<uint8_t> & data)77 static void EmitMetadata(
78     const std::map<uint8_t, std::vector<uint8_t>>& metadata,
79     std::vector<uint8_t>& data) {
80   auto ltv = types::LeAudioLtvMap(metadata);
81   auto ltv_raw_sz = ltv.RawPacketSize();
82 
83   size_t old_size = data.size();
84   data.resize(old_size + ltv_raw_sz + 1);
85 
86   // Set the cursor behind the old data
87   uint8_t* p_value = data.data() + old_size;
88 
89   UINT8_TO_STREAM(p_value, ltv_raw_sz);
90   if (ltv_raw_sz > 0) {
91     p_value = ltv.RawPacket(p_value);
92   }
93 }
94 
EmitBisConfigs(const std::vector<BasicAudioAnnouncementBisConfig> & bis_configs,std::vector<uint8_t> & data)95 static void EmitBisConfigs(
96     const std::vector<BasicAudioAnnouncementBisConfig>& bis_configs,
97     std::vector<uint8_t>& data) {
98   // Emit each BIS config - that's the level 3 data
99   for (auto const& bis_config : bis_configs) {
100     auto ltv = types::LeAudioLtvMap(bis_config.codec_specific_params);
101     auto ltv_raw_sz = ltv.RawPacketSize();
102 
103     size_t old_size = data.size();
104     data.resize(old_size + ltv_raw_sz + 2);
105 
106     // Set the cursor behind the old data
107     auto* p_value = data.data() + old_size;
108 
109     // BIS_index[i[k]]
110     UINT8_TO_STREAM(p_value, bis_config.bis_index);
111 
112     // Per BIS Codec Specific Params[i[k]]
113     UINT8_TO_STREAM(p_value, ltv_raw_sz);
114     if (ltv_raw_sz > 0) {
115       p_value = ltv.RawPacket(p_value);
116     }
117   }
118 }
119 
EmitSubgroup(const BasicAudioAnnouncementSubgroup & subgroup_config,std::vector<uint8_t> & data)120 static void EmitSubgroup(const BasicAudioAnnouncementSubgroup& subgroup_config,
121                          std::vector<uint8_t>& data) {
122   // That's the level 2 data
123 
124   // Resize for the num_bis
125   size_t initial_offset = data.size();
126   data.resize(initial_offset + 1);
127 
128   // Set the cursor behind the old data and adds the level 2 Num_BIS[i]
129   uint8_t* p_value = data.data() + initial_offset;
130   UINT8_TO_STREAM(p_value, subgroup_config.bis_configs.size());
131 
132   EmitCodecConfiguration(subgroup_config.codec_config, data, nullptr);
133   EmitMetadata(subgroup_config.metadata, data);
134 
135   // This adds the level 3 data
136   EmitBisConfigs(subgroup_config.bis_configs, data);
137 }
138 
ToRawPacket(BasicAudioAnnouncementData const & in,std::vector<uint8_t> & data)139 bool ToRawPacket(BasicAudioAnnouncementData const& in,
140                  std::vector<uint8_t>& data) {
141   EmitHeader(in, data);
142 
143   // Set the cursor behind the old data and resize
144   size_t old_size = data.size();
145   data.resize(old_size + 1);
146   uint8_t* p_value = data.data() + old_size;
147 
148   // Emit the subgroup size and each subgroup
149   // That's the level 1 Num_Subgroups
150   UINT8_TO_STREAM(p_value, in.subgroup_configs.size());
151   for (const auto& subgroup_config : in.subgroup_configs) {
152     // That's the level 2 and higher level data
153     EmitSubgroup(subgroup_config, data);
154   }
155 
156   return true;
157 }
158 
PrepareAdvertisingData(bluetooth::le_audio::BroadcastId & broadcast_id,std::vector<uint8_t> & periodic_data)159 void PrepareAdvertisingData(bluetooth::le_audio::BroadcastId& broadcast_id,
160                             std::vector<uint8_t>& periodic_data) {
161   periodic_data.resize(7);
162   uint8_t* data_ptr = periodic_data.data();
163   UINT8_TO_STREAM(data_ptr, 6);
164   UINT8_TO_STREAM(data_ptr, BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE);
165   UINT16_TO_STREAM(data_ptr, kBroadcastAudioAnnouncementServiceUuid);
166   UINT24_TO_STREAM(data_ptr, broadcast_id)
167 };
168 
PreparePeriodicData(const BasicAudioAnnouncementData & announcement,std::vector<uint8_t> & periodic_data)169 void PreparePeriodicData(const BasicAudioAnnouncementData& announcement,
170                          std::vector<uint8_t>& periodic_data) {
171   /* Account for AD Type + Service UUID */
172   periodic_data.resize(4);
173   /* Skip the data length field until the full content is generated */
174   uint8_t* data_ptr = periodic_data.data() + 1;
175   UINT8_TO_STREAM(data_ptr, BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE);
176   UINT16_TO_STREAM(data_ptr, kBasicAudioAnnouncementServiceUuid);
177 
178   /* Append the announcement */
179   ToRawPacket(announcement, periodic_data);
180 
181   /* Update the length field accordingly */
182   data_ptr = periodic_data.data();
183   UINT8_TO_STREAM(data_ptr, periodic_data.size() - 1);
184 }
185 
186 constexpr types::LeAudioCodecId kLeAudioCodecIdLc3 = {
187     .coding_format = types::kLeAudioCodingFormatLC3,
188     .vendor_company_id = types::kLeAudioVendorCompanyIdUndefined,
189     .vendor_codec_id = types::kLeAudioVendorCodecIdUndefined};
190 
191 static const BroadcastCodecWrapper lc3_mono_16_2 = BroadcastCodecWrapper(
192     kLeAudioCodecIdLc3,
193     // LeAudioCodecConfiguration
194     {.num_channels = LeAudioCodecConfiguration::kChannelNumberMono,
195      .sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
196      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
197      .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
198     // Bitrate
199     32000,
200     // Frame len.
201     40);
202 
203 static const BroadcastCodecWrapper lc3_stereo_16_2 = BroadcastCodecWrapper(
204     kLeAudioCodecIdLc3,
205     // LeAudioCodecConfiguration
206     {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
207      .sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
208      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
209      .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
210     // Bitrate
211     32000,
212     // Frame len.
213     40);
214 
215 static const BroadcastCodecWrapper lc3_stereo_24_2 = BroadcastCodecWrapper(
216     kLeAudioCodecIdLc3,
217     // LeAudioCodecConfiguration
218     {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
219      .sample_rate = LeAudioCodecConfiguration::kSampleRate24000,
220      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
221      .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
222     // Bitrate
223     48000,
224     // Frame len.
225     60);
226 
227 static const BroadcastCodecWrapper lc3_stereo_48_1 = BroadcastCodecWrapper(
228     kLeAudioCodecIdLc3,
229     // LeAudioCodecConfiguration
230     {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
231      .sample_rate = LeAudioCodecConfiguration::kSampleRate48000,
232      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
233      .data_interval_us = LeAudioCodecConfiguration::kInterval7500Us},
234     // Bitrate
235     80000,
236     // Frame len.
237     75);
238 
239 static const BroadcastCodecWrapper lc3_stereo_48_2 = BroadcastCodecWrapper(
240     kLeAudioCodecIdLc3,
241     // LeAudioCodecConfiguration
242     {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
243      .sample_rate = LeAudioCodecConfiguration::kSampleRate48000,
244      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
245      .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
246     // Bitrate
247     80000,
248     // Frame len.
249     100);
250 
251 static const BroadcastCodecWrapper lc3_stereo_48_3 = BroadcastCodecWrapper(
252     kLeAudioCodecIdLc3,
253     // LeAudioCodecConfiguration
254     {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
255      .sample_rate = LeAudioCodecConfiguration::kSampleRate48000,
256      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
257      .data_interval_us = LeAudioCodecConfiguration::kInterval7500Us},
258     // Bitrate
259     96000,
260     // Frame len.
261     90);
262 
263 static const BroadcastCodecWrapper lc3_stereo_48_4 = BroadcastCodecWrapper(
264     kLeAudioCodecIdLc3,
265     // LeAudioCodecConfiguration
266     {.num_channels = LeAudioCodecConfiguration::kChannelNumberStereo,
267      .sample_rate = LeAudioCodecConfiguration::kSampleRate48000,
268      .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
269      .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
270     // Bitrate
271     96000,
272     // Frame len.
273     120);
274 
275 const std::map<uint32_t, uint8_t> sample_rate_to_sampling_freq_map = {
276     {LeAudioCodecConfiguration::kSampleRate8000,
277      codec_spec_conf::kLeAudioSamplingFreq8000Hz},
278     {LeAudioCodecConfiguration::kSampleRate16000,
279      codec_spec_conf::kLeAudioSamplingFreq16000Hz},
280     {LeAudioCodecConfiguration::kSampleRate24000,
281      codec_spec_conf::kLeAudioSamplingFreq24000Hz},
282     {LeAudioCodecConfiguration::kSampleRate32000,
283      codec_spec_conf::kLeAudioSamplingFreq32000Hz},
284     {LeAudioCodecConfiguration::kSampleRate44100,
285      codec_spec_conf::kLeAudioSamplingFreq44100Hz},
286     {LeAudioCodecConfiguration::kSampleRate48000,
287      codec_spec_conf::kLeAudioSamplingFreq48000Hz},
288 };
289 
290 const std::map<uint32_t, uint8_t> data_interval_ms_to_frame_duration = {
291     {LeAudioCodecConfiguration::kInterval7500Us,
292      codec_spec_conf::kLeAudioCodecLC3FrameDur7500us},
293     {LeAudioCodecConfiguration::kInterval10000Us,
294      codec_spec_conf::kLeAudioCodecLC3FrameDur10000us},
295 };
296 
GetBisCodecSpecData(uint8_t bis_idx) const297 types::LeAudioLtvMap BroadcastCodecWrapper::GetBisCodecSpecData(
298     uint8_t bis_idx) const {
299   /* For a single channel this will be set at the subgroup lvl. */
300   if (source_codec_config.num_channels == 1) return {};
301 
302   switch (bis_idx) {
303     case 1:
304       return types::LeAudioLtvMap(
305           {{codec_spec_conf::kLeAudioCodecLC3TypeAudioChannelAllocation,
306             UINT32_TO_VEC_UINT8(codec_spec_conf::kLeAudioLocationFrontLeft)}});
307     case 2:
308       return types::LeAudioLtvMap(
309           {{codec_spec_conf::kLeAudioCodecLC3TypeAudioChannelAllocation,
310             UINT32_TO_VEC_UINT8(codec_spec_conf::kLeAudioLocationFrontRight)}});
311       break;
312     default:
313       return {};
314   }
315 }
316 
GetSubgroupCodecSpecData() const317 types::LeAudioLtvMap BroadcastCodecWrapper::GetSubgroupCodecSpecData() const {
318   LOG_ASSERT(
319       sample_rate_to_sampling_freq_map.count(source_codec_config.sample_rate))
320       << "Invalid sample_rate";
321   LOG_ASSERT(data_interval_ms_to_frame_duration.count(
322       source_codec_config.data_interval_us))
323       << "Invalid data_interval";
324 
325   std::map<uint8_t, std::vector<uint8_t>> codec_spec_ltvs = {
326       {codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq,
327        UINT8_TO_VEC_UINT8(sample_rate_to_sampling_freq_map.at(
328            source_codec_config.sample_rate))},
329       {codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration,
330        UINT8_TO_VEC_UINT8(data_interval_ms_to_frame_duration.at(
331            source_codec_config.data_interval_us))},
332   };
333 
334   if (codec_id.coding_format == kLeAudioCodecIdLc3.coding_format) {
335     uint16_t bc =
336         lc3_frame_bytes(source_codec_config.data_interval_us, codec_bitrate);
337     codec_spec_ltvs[codec_spec_conf::kLeAudioCodecLC3TypeOctetPerFrame] =
338         UINT16_TO_VEC_UINT8(bc);
339   }
340 
341   if (source_codec_config.num_channels == 1) {
342     codec_spec_ltvs
343         [codec_spec_conf::kLeAudioCodecLC3TypeAudioChannelAllocation] =
344             UINT32_TO_VEC_UINT8(codec_spec_conf::kLeAudioLocationFrontCenter);
345   }
346 
347   return types::LeAudioLtvMap(codec_spec_ltvs);
348 }
349 
operator <<(std::ostream & os,const le_audio::broadcaster::BroadcastCodecWrapper & config)350 std::ostream& operator<<(
351     std::ostream& os,
352     const le_audio::broadcaster::BroadcastCodecWrapper& config) {
353   os << " BroadcastCodecWrapper=[";
354   os << "CodecID="
355      << "{" << +config.GetLeAudioCodecId().coding_format << ":"
356      << +config.GetLeAudioCodecId().vendor_company_id << ":"
357      << +config.GetLeAudioCodecId().vendor_codec_id << "}";
358   os << ", LeAudioCodecConfiguration="
359      << "{NumChannels=" << +config.GetNumChannels()
360      << ", SampleRate=" << +config.GetSampleRate()
361      << ", BitsPerSample=" << +config.GetBitsPerSample()
362      << ", DataIntervalUs=" << +config.GetDataIntervalUs() << "}";
363   os << ", Bitrate=" << +config.GetBitrate();
364   os << "]";
365   return os;
366 }
367 
368 static const BroadcastQosConfig qos_config_2_10 = BroadcastQosConfig(2, 10);
369 
370 static const BroadcastQosConfig qos_config_4_50 = BroadcastQosConfig(4, 50);
371 
372 static const BroadcastQosConfig qos_config_4_60 = BroadcastQosConfig(4, 60);
373 
374 static const BroadcastQosConfig qos_config_4_65 = BroadcastQosConfig(4, 65);
375 
operator <<(std::ostream & os,const le_audio::broadcaster::BroadcastQosConfig & config)376 std::ostream& operator<<(
377     std::ostream& os, const le_audio::broadcaster::BroadcastQosConfig& config) {
378   os << " BroadcastQosConfig=[";
379   os << "RTN=" << +config.getRetransmissionNumber();
380   os << ", MaxTransportLatency=" << config.getMaxTransportLatency();
381   os << "]";
382   return os;
383 }
384 
385 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
386     lc3_mono_16_2_1 = {lc3_mono_16_2, qos_config_2_10};
387 
388 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
389     lc3_mono_16_2_2 = {lc3_mono_16_2, qos_config_4_60};
390 
391 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
392     lc3_stereo_16_2_2 = {lc3_stereo_16_2, qos_config_4_60};
393 
394 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
395     lc3_stereo_24_2_1 = {lc3_stereo_24_2, qos_config_2_10};
396 
397 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
398     lc3_stereo_24_2_2 = {lc3_stereo_24_2, qos_config_4_60};
399 
400 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
401     lc3_stereo_48_1_2 = {lc3_stereo_48_1, qos_config_4_50};
402 
403 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
404     lc3_stereo_48_2_2 = {lc3_stereo_48_2, qos_config_4_65};
405 
406 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
407     lc3_stereo_48_3_2 = {lc3_stereo_48_3, qos_config_4_50};
408 
409 static const std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
410     lc3_stereo_48_4_2 = {lc3_stereo_48_4, qos_config_4_65};
411 
412 std::pair<const BroadcastCodecWrapper&, const BroadcastQosConfig&>
getStreamConfigForContext(types::AudioContexts context)413 getStreamConfigForContext(types::AudioContexts context) {
414   const std::string* options =
415       stack_config_get_interface()->get_pts_broadcast_audio_config_options();
416   if (options) {
417     if (!options->compare("lc3_stereo_48_1_2")) return lc3_stereo_48_1_2;
418     if (!options->compare("lc3_stereo_48_2_2")) return lc3_stereo_48_2_2;
419     if (!options->compare("lc3_stereo_48_3_2")) return lc3_stereo_48_3_2;
420     if (!options->compare("lc3_stereo_48_4_2")) return lc3_stereo_48_4_2;
421   }
422   // High quality, Low Latency
423   if (context.test_any(LeAudioContextType::GAME | LeAudioContextType::LIVE))
424     return lc3_stereo_24_2_1;
425 
426   // Low quality, Low Latency
427   if (context.test(LeAudioContextType::INSTRUCTIONAL)) return lc3_mono_16_2_1;
428 
429   // Low quality, High Reliability
430   if (context.test_any(LeAudioContextType::SOUNDEFFECTS |
431                        LeAudioContextType::UNSPECIFIED))
432     return lc3_stereo_16_2_2;
433 
434   if (context.test_any(LeAudioContextType::ALERTS |
435                        LeAudioContextType::NOTIFICATIONS |
436                        LeAudioContextType::EMERGENCYALARM))
437     return lc3_mono_16_2_2;
438 
439   // High quality, High Reliability
440   if (context.test(LeAudioContextType::MEDIA)) return lc3_stereo_24_2_2;
441 
442   // Defaults: Low quality, High Reliability
443   return lc3_mono_16_2_2;
444 }
445 
446 } /* namespace broadcaster */
447 } /* namespace le_audio */
448 
449 /* Helper functions for comparing BroadcastAnnouncements */
450 namespace bluetooth {
451 namespace le_audio {
452 
isMetadataSame(std::map<uint8_t,std::vector<uint8_t>> m1,std::map<uint8_t,std::vector<uint8_t>> m2)453 static bool isMetadataSame(std::map<uint8_t, std::vector<uint8_t>> m1,
454                            std::map<uint8_t, std::vector<uint8_t>> m2) {
455   if (m1.size() != m2.size()) return false;
456 
457   for (auto& m1pair : m1) {
458     if (m2.count(m1pair.first) == 0) return false;
459 
460     auto& m2val = m2.at(m1pair.first);
461     if (m1pair.second.size() != m2val.size()) return false;
462 
463     if (m1pair.second.size() != 0) {
464       if (memcmp(m1pair.second.data(), m2val.data(), m2val.size()) != 0)
465         return false;
466     }
467   }
468   return true;
469 }
470 
operator ==(const BasicAudioAnnouncementData & lhs,const BasicAudioAnnouncementData & rhs)471 bool operator==(const BasicAudioAnnouncementData& lhs,
472                 const BasicAudioAnnouncementData& rhs) {
473   if (lhs.presentation_delay != rhs.presentation_delay) return false;
474 
475   if (lhs.subgroup_configs.size() != rhs.subgroup_configs.size()) return false;
476 
477   for (auto i = 0lu; i < lhs.subgroup_configs.size(); ++i) {
478     auto& lhs_subgroup = lhs.subgroup_configs[i];
479     auto& rhs_subgroup = rhs.subgroup_configs[i];
480 
481     if (lhs_subgroup.codec_config.codec_id !=
482         rhs_subgroup.codec_config.codec_id)
483       return false;
484 
485     if (lhs_subgroup.codec_config.vendor_company_id !=
486         rhs_subgroup.codec_config.vendor_company_id)
487       return false;
488 
489     if (lhs_subgroup.codec_config.vendor_codec_id !=
490         rhs_subgroup.codec_config.vendor_codec_id)
491       return false;
492 
493     if (!isMetadataSame(lhs_subgroup.codec_config.codec_specific_params,
494                         rhs_subgroup.codec_config.codec_specific_params))
495       return false;
496 
497     if (!isMetadataSame(lhs_subgroup.metadata, rhs_subgroup.metadata))
498       return false;
499 
500     for (auto j = 0lu; j < lhs_subgroup.bis_configs.size(); ++j) {
501       auto& lhs_bis_config = lhs_subgroup.bis_configs[i];
502       auto& rhs_bis_config = rhs_subgroup.bis_configs[i];
503       if (lhs_bis_config.bis_index != rhs_bis_config.bis_index) return false;
504 
505       if (!isMetadataSame(lhs_bis_config.codec_specific_params,
506                           rhs_bis_config.codec_specific_params))
507         return false;
508     }
509   }
510 
511   return true;
512 }
513 }  // namespace le_audio
514 }  // namespace bluetooth
515