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