blob: 896df262a287f55930f7c189ed21fbd5bedc06a2 [file] [log] [blame]
Emil Gydesenab87e0a2022-01-10 15:06:08 +01001/* Bluetooth Audio Broadcast Source */
2
3/*
4 * Copyright (c) 2021-2022 Nordic Semiconductor ASA
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9#include <zephyr.h>
10#include <sys/byteorder.h>
11#include <sys/check.h>
12
13#include <bluetooth/bluetooth.h>
14#include <bluetooth/conn.h>
15#include <bluetooth/gatt.h>
16#include <bluetooth/audio/audio.h>
17
18#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_AUDIO_DEBUG_BROADCAST_SOURCE)
19#define LOG_MODULE_NAME bt_audio_broadcast_source
20#include "common/log.h"
21
22#include "endpoint.h"
23
24/* internal bt_audio_base_ad* structs are primarily designed to help
25 * calculate the maximum advertising data length
26 */
27struct bt_audio_base_ad_bis_specific_data {
28 uint8_t index;
29 uint8_t codec_config_len; /* currently unused and shall always be 0 */
30} __packed;
31
32struct bt_audio_base_ad_codec_data {
33 uint8_t type;
34 uint8_t data_len;
35 uint8_t data[CONFIG_BT_CODEC_MAX_DATA_LEN];
36} __packed;
37
38struct bt_audio_base_ad_codec_metadata {
39 uint8_t type;
40 uint8_t data_len;
41 uint8_t data[CONFIG_BT_CODEC_MAX_METADATA_LEN];
42} __packed;
43
44struct bt_audio_base_ad_subgroup {
45 uint8_t bis_cnt;
46 uint8_t codec_id;
47 uint16_t company_id;
48 uint16_t vendor_id;
49 uint8_t codec_config_len;
50 struct bt_audio_base_ad_codec_data codec_config[CONFIG_BT_CODEC_MAX_DATA_COUNT];
51 uint8_t metadata_len;
52 struct bt_audio_base_ad_codec_metadata metadata[CONFIG_BT_CODEC_MAX_METADATA_COUNT];
53 struct bt_audio_base_ad_bis_specific_data bis_data[BROADCAST_STREAM_CNT];
54} __packed;
55
56struct bt_audio_base_ad {
57 uint16_t uuid_val;
58 struct bt_audio_base_ad_subgroup subgroups[BROADCAST_SUBGROUP_CNT];
59} __packed;
60
61static struct bt_audio_ep broadcast_source_eps
62 [CONFIG_BT_AUDIO_BROADCAST_SRC_COUNT][BROADCAST_STREAM_CNT];
63static struct bt_audio_broadcast_source broadcast_sources[CONFIG_BT_AUDIO_BROADCAST_SRC_COUNT];
64
65static int bt_audio_set_base(const struct bt_audio_broadcast_source *source,
66 struct bt_codec *codec);
67
68static void broadcast_source_set_ep_state(struct bt_audio_ep *ep, uint8_t state)
69{
70 uint8_t old_state;
71
72 old_state = ep->status.state;
73
74 BT_DBG("ep %p id 0x%02x %s -> %s", ep, ep->status.id,
75 bt_audio_ep_state_str(old_state),
76 bt_audio_ep_state_str(state));
77
78
79 switch (old_state) {
80 case BT_AUDIO_EP_STATE_IDLE:
81 if (state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
82 BT_DBG("Invalid broadcast sync endpoint state transition");
83 return;
84 }
85 break;
86 case BT_AUDIO_EP_STATE_QOS_CONFIGURED:
87 if (state != BT_AUDIO_EP_STATE_IDLE &&
88 state != BT_AUDIO_EP_STATE_STREAMING) {
89 BT_DBG("Invalid broadcast sync endpoint state transition");
90 return;
91 }
92 case BT_AUDIO_EP_STATE_STREAMING:
93 if (state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
94 BT_DBG("Invalid broadcast sync endpoint state transition");
95 return;
96 }
97 break;
98 default:
99 BT_ERR("Invalid broadcast sync endpoint state: %s",
100 bt_audio_ep_state_str(old_state));
101 return;
102 }
103
104 ep->status.state = state;
105
106 if (state == BT_AUDIO_EP_STATE_IDLE) {
107 struct bt_audio_stream *stream = ep->stream;
108
109 if (stream != NULL) {
110 stream->ep = NULL;
111 stream->codec = NULL;
112 ep->stream = NULL;
113 }
114 }
115}
116
117static void broadcast_source_iso_recv(struct bt_iso_chan *chan,
118 const struct bt_iso_recv_info *info,
119 struct net_buf *buf)
120{
121 struct bt_audio_ep *ep = CONTAINER_OF(chan, struct bt_audio_ep, iso);
122 struct bt_audio_stream_ops *ops = ep->stream->ops;
123
124 BT_DBG("stream %p ep %p len %zu", chan, ep, net_buf_frags_len(buf));
125
126 if (ops != NULL && ops->recv != NULL) {
127 ops->recv(ep->stream, buf);
128 }
129}
130
131static void broadcast_source_iso_connected(struct bt_iso_chan *chan)
132{
133 struct bt_audio_ep *ep = CONTAINER_OF(chan, struct bt_audio_ep, iso);
134 struct bt_audio_stream_ops *ops = ep->stream->ops;
135
136 BT_DBG("stream %p ep %p", chan, ep);
137
138 broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_STREAMING);
139
140 if (ops != NULL && ops->connected != NULL) {
141 ops->connected(ep->stream);
142 }
143}
144
145static void broadcast_source_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason)
146{
147 struct bt_audio_ep *ep = CONTAINER_OF(chan, struct bt_audio_ep, iso);
148 struct bt_audio_stream *stream = ep->stream;
149 struct bt_audio_stream_ops *ops = stream->ops;
150
151 BT_DBG("stream %p ep %p reason 0x%02x", chan, ep, reason);
152
153 broadcast_source_set_ep_state(ep, BT_AUDIO_EP_STATE_IDLE);
154
155 if (ops != NULL && ops->disconnected != NULL) {
156 ops->disconnected(stream, reason);
157 }
158}
159
160static struct bt_iso_chan_ops broadcast_source_iso_ops = {
161 .recv = broadcast_source_iso_recv,
162 .connected = broadcast_source_iso_connected,
163 .disconnected = broadcast_source_iso_disconnected,
164};
165
166bool bt_audio_ep_is_broadcast_src(const struct bt_audio_ep *ep)
167{
168 for (int i = 0; i < ARRAY_SIZE(broadcast_source_eps); i++) {
169 if (PART_OF_ARRAY(broadcast_source_eps[i], ep)) {
170 return true;
171 }
172 }
173
174 return false;
175}
176
177static void broadcast_source_ep_init(struct bt_audio_ep *ep)
178{
179 BT_DBG("ep %p", ep);
180
181 (void)memset(ep, 0, sizeof(*ep));
182 ep->iso.ops = &broadcast_source_iso_ops;
183 ep->iso.qos = &ep->iso_qos;
184 ep->iso.qos->rx = &ep->iso_rx;
185 ep->iso.qos->tx = &ep->iso_tx;
186}
187
188static struct bt_audio_ep *broadcast_source_new_ep(uint8_t index)
189{
190 struct bt_audio_ep *cache = NULL;
191 size_t size;
192
193 cache = broadcast_source_eps[index];
194 size = ARRAY_SIZE(broadcast_source_eps[index]);
195
196 for (size_t i = 0; i < ARRAY_SIZE(broadcast_source_eps[index]); i++) {
197 struct bt_audio_ep *ep = &cache[i];
198
199 /* If ep->stream is NULL the endpoint is unallocated */
200 if (ep->stream == NULL) {
201 /* Initialize - It is up to the caller to allocate the
202 * stream pointer.
203 */
204 broadcast_source_ep_init(ep);
205 return ep;
206 }
207 }
208
209 return NULL;
210}
211
212static int bt_audio_broadcast_source_setup_stream(uint8_t index,
213 struct bt_audio_stream *stream,
214 struct bt_codec *codec,
215 struct bt_codec_qos *qos)
216{
217 struct bt_audio_ep *ep;
218 int err;
219
220 if (stream->group != NULL) {
221 BT_DBG("Channel %p already in group %p", stream, stream->group);
222 return -EALREADY;
223 }
224
225 ep = broadcast_source_new_ep(index);
226 if (ep == NULL) {
227 BT_DBG("Could not allocate new broadcast endpoint");
228 return -ENOMEM;
229 }
230
231 bt_audio_stream_attach(NULL, stream, ep, codec);
232 stream->qos = qos;
233 err = bt_audio_codec_qos_to_iso_qos(stream->iso->qos, qos);
234 if (err) {
235 BT_ERR("Unable to convert codec QoS to ISO QoS");
236 return err;
237 }
238
239 return 0;
240}
241
242static void bt_audio_encode_base(const struct bt_audio_broadcast_source *source,
243 struct bt_codec *codec,
244 struct net_buf_simple *buf)
245{
246 uint8_t bis_index;
247 uint8_t *start;
248 uint8_t len;
249
250 __ASSERT(source->subgroup_count == BROADCAST_SUBGROUP_CNT,
251 "Cannot encode BASE with more than a single subgroup");
252
253 net_buf_simple_add_le16(buf, BT_UUID_BASIC_AUDIO_VAL);
254 net_buf_simple_add_le24(buf, source->pd);
255 net_buf_simple_add_u8(buf, source->subgroup_count);
256 /* TODO: The following encoding should be done for each subgroup once
257 * supported
258 */
259 net_buf_simple_add_u8(buf, source->stream_count);
260 net_buf_simple_add_u8(buf, codec->id);
261 net_buf_simple_add_le16(buf, codec->cid);
262 net_buf_simple_add_le16(buf, codec->vid);
263
264 /* Insert codec configuration data in LTV format */
265 start = net_buf_simple_add(buf, sizeof(len));
266 for (int i = 0; i < codec->data_count; i++) {
267 const struct bt_data *codec_data = &codec->data[i].data;
268
269 net_buf_simple_add_u8(buf, codec_data->data_len);
270 net_buf_simple_add_u8(buf, codec_data->type);
271 net_buf_simple_add_mem(buf, codec_data->data,
272 codec_data->data_len -
273 sizeof(codec_data->type));
274
275 }
276 /* Calcute length of codec config data */
277 len = net_buf_simple_tail(buf) - start - sizeof(len);
278 /* Update the length field */
279 *start = len;
280
281 /* Insert codec metadata in LTV format*/
282 start = net_buf_simple_add(buf, sizeof(len));
283 for (int i = 0; i < codec->meta_count; i++) {
284 const struct bt_data *metadata = &codec->meta[i].data;
285
286 net_buf_simple_add_u8(buf, metadata->data_len);
287 net_buf_simple_add_u8(buf, metadata->type);
288 net_buf_simple_add_mem(buf, metadata->data,
289 metadata->data_len -
290 sizeof(metadata->type));
291 }
292 /* Calcute length of codec config data */
293 len = net_buf_simple_tail(buf) - start - sizeof(len);
294 /* Update the length field */
295 *start = len;
296
297 /* Create BIS index bitfield */
298 bis_index = 0;
299 for (int i = 0; i < source->stream_count; i++) {
300 bis_index++;
301 net_buf_simple_add_u8(buf, bis_index);
302 net_buf_simple_add_u8(buf, 0); /* unused length field */
303 }
304
305 /* NOTE: It is also possible to have the codec configuration data per
306 * BIS index. As our API does not support such specialized BISes we
307 * currently don't do that.
308 */
309}
310
311
312static int generate_broadcast_id(struct bt_audio_broadcast_source *source)
313{
314 bool unique;
315
316 do {
317 int err;
318
319 err = bt_rand(&source->broadcast_id,
320 BT_AUDIO_BROADCAST_ID_SIZE);
321 if (err) {
322 return err;
323 }
324
325 /* Ensure uniqueness */
326 unique = true;
327 for (int i = 0; i < ARRAY_SIZE(broadcast_sources); i++) {
328 if (&broadcast_sources[i] == source) {
329 continue;
330 }
331
332 if (broadcast_sources[i].broadcast_id == source->broadcast_id) {
333 unique = false;
334 break;
335 }
336 }
337 } while (!unique);
338
339 return 0;
340}
341
342static int bt_audio_set_base(const struct bt_audio_broadcast_source *source,
343 struct bt_codec *codec)
344{
345 struct bt_data base_ad_data;
346 int err;
347
348 /* Broadcast Audio Streaming Endpoint advertising data */
349 NET_BUF_SIMPLE_DEFINE(base_buf, sizeof(struct bt_audio_base_ad));
350
351 bt_audio_encode_base(source, codec, &base_buf);
352
353 base_ad_data.type = BT_DATA_SVC_DATA16;
354 base_ad_data.data_len = base_buf.len;
355 base_ad_data.data = base_buf.data;
356
357 err = bt_le_per_adv_set_data(source->adv, &base_ad_data, 1);
358 if (err != 0) {
359 BT_DBG("Failed to set extended advertising data (err %d)", err);
360 return err;
361 }
362
363 return 0;
364}
365
366static void broadcast_source_cleanup(struct bt_audio_broadcast_source *source)
367{
368 for (size_t i = 0; i < source->stream_count; i++) {
369 struct bt_audio_stream *stream = &source->streams[i];
370
371 stream->ep->stream = NULL;
372 stream->ep = NULL;
373 stream->codec = NULL;
374 stream->qos = NULL;
375 stream->iso = NULL;
376 stream->group = NULL;
377 }
378
379 (void)memset(source, 0, sizeof(*source));
380}
381
382int bt_audio_broadcast_source_create(struct bt_audio_stream *streams,
383 uint8_t num_stream,
384 struct bt_codec *codec,
385 struct bt_codec_qos *qos,
386 struct bt_audio_broadcast_source **out_source)
387{
388 struct bt_audio_broadcast_source *source;
389 struct bt_data ad;
390 uint8_t index;
391 int err;
392
393 /* Broadcast Audio Streaming Endpoint advertising data */
394 NET_BUF_SIMPLE_DEFINE(ad_buf,
395 BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE);
396
397 /* TODO: Validate codec and qos values */
398
399 /* TODO: The API currently only requires a bt_audio_stream object from
400 * the user. We could modify the API such that the extended (and
401 * periodic advertising enabled) advertiser was provided by the user as
402 * well (similar to the ISO API), or even provide the BIG.
403 *
404 * The caveat of that type of API, instead of this, where we, the stack,
405 * control the advertiser, is that the user will be able to change the
406 * advertising data (thus making the broadcast source non-functional in
407 * terms of BAP compliance), or even stop the advertiser without
408 * stopping the BIG (which also goes against the BAP specification).
409 */
410
411 CHECKIF(out_source == NULL) {
412 BT_DBG("out_source is NULL");
413 return -EINVAL;
414 }
415 /* Set out_source to NULL until the source has actually been created */
416 *out_source = NULL;
417
418 CHECKIF(streams == NULL) {
419 BT_DBG("streams is NULL");
420 return -EINVAL;
421 }
422
423 CHECKIF(codec == NULL) {
424 BT_DBG("codec is NULL");
425 return -EINVAL;
426 }
427
428 CHECKIF(num_stream > BROADCAST_STREAM_CNT) {
429 BT_DBG("Too many streams provided: %u/%u",
430 num_stream, BROADCAST_STREAM_CNT);
431 return -EINVAL;
432 }
433
434 source = NULL;
435 for (index = 0; index < ARRAY_SIZE(broadcast_sources); index++) {
436 if (broadcast_sources[index].bis[0] == NULL) { /* Find free entry */
437 source = &broadcast_sources[index];
438 break;
439 }
440 }
441
442 if (source == NULL) {
443 BT_DBG("Could not allocate any more broadcast sources");
444 return -ENOMEM;
445 }
446
447 source->streams = streams;
448 source->stream_count = num_stream;
449 for (size_t i = 0; i < num_stream; i++) {
450 struct bt_audio_stream *stream = &streams[i];
451
452 err = bt_audio_broadcast_source_setup_stream(index, stream,
453 codec, qos);
454 if (err != 0) {
455 BT_DBG("Failed to setup streams[%zu]: %d", i, err);
456 broadcast_source_cleanup(source);
457 return err;
458 }
459
460 source->bis[i] = &stream->ep->iso;
461 }
462
463 /* Create a non-connectable non-scannable advertising set */
464 err = bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN_NAME, NULL,
465 &source->adv);
466 if (err != 0) {
467 BT_DBG("Failed to create advertising set (err %d)", err);
468 broadcast_source_cleanup(source);
469 return err;
470 }
471
472 /* Set periodic advertising parameters */
473 err = bt_le_per_adv_set_param(source->adv, BT_LE_PER_ADV_DEFAULT);
474 if (err != 0) {
475 BT_DBG("Failed to set periodic advertising parameters (err %d)",
476 err);
477 broadcast_source_cleanup(source);
478 return err;
479 }
480
481 /* TODO: If updating the API to have a user-supplied advertiser, we
482 * should simply add the data here, instead of changing all of it.
483 * Similar, if the application changes the data, we should ensure
484 * that the audio advertising data is still present, similar to how
485 * the GAP device name is added.
486 */
487 err = generate_broadcast_id(source);
488 if (err != 0) {
489 BT_DBG("Could not generate broadcast id: %d", err);
490 return err;
491 }
492 net_buf_simple_add_le16(&ad_buf, BT_UUID_BROADCAST_AUDIO_VAL);
493 net_buf_simple_add_le24(&ad_buf, source->broadcast_id);
494 ad.type = BT_DATA_SVC_DATA16;
495 ad.data_len = ad_buf.len + sizeof(ad.type);
496 ad.data = ad_buf.data;
497 err = bt_le_ext_adv_set_data(source->adv, &ad, 1, NULL, 0);
498 if (err != 0) {
499 BT_DBG("Failed to set extended advertising data (err %d)", err);
500 broadcast_source_cleanup(source);
501 return err;
502 }
503
504 source->subgroup_count = BROADCAST_SUBGROUP_CNT;
505 source->pd = qos->pd;
506 err = bt_audio_set_base(source, codec);
507 if (err != 0) {
508 BT_DBG("Failed to set base data (err %d)", err);
509 broadcast_source_cleanup(source);
510 return err;
511 }
512
513 /* Enable Periodic Advertising */
514 err = bt_le_per_adv_start(source->adv);
515 if (err != 0) {
516 BT_DBG("Failed to enable periodic advertising (err %d)", err);
517 broadcast_source_cleanup(source);
518 return err;
519 }
520
521 /* Start extended advertising */
522 err = bt_le_ext_adv_start(source->adv,
523 BT_LE_EXT_ADV_START_DEFAULT);
524 if (err != 0) {
525 BT_DBG("Failed to start extended advertising (err %d)", err);
526 broadcast_source_cleanup(source);
527 return err;
528 }
529
530 for (size_t i = 0; i < source->stream_count; i++) {
531 struct bt_audio_ep *ep = streams[i].ep;
532
533 ep->broadcast_source = source;
534 broadcast_source_set_ep_state(ep,
535 BT_AUDIO_EP_STATE_QOS_CONFIGURED);
536 }
537
538 source->qos = qos;
539
540 BT_DBG("Broadcasting with ID 0x%6X", source->broadcast_id);
541
542 *out_source = source;
543
544 return 0;
545}
546
547int bt_audio_broadcast_source_reconfig(struct bt_audio_broadcast_source *source,
548 struct bt_codec *codec,
549 struct bt_codec_qos *qos)
550{
551 struct bt_audio_stream *stream;
552 int err;
553
554 CHECKIF(source == NULL) {
555 BT_DBG("source is NULL");
556 return -EINVAL;
557 }
558
559 stream = &source->streams[0];
560
561 if (stream == NULL) {
562 BT_DBG("stream is NULL");
563 return -EINVAL;
564 }
565
566 if (stream->ep == NULL) {
567 BT_DBG("stream->ep is NULL");
568 return -EINVAL;
569 }
570
571 if (stream->ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
572 BT_DBG("Broadcast source stream %p invalid state: %u",
573 stream, stream->ep->status.state);
574 return -EBADMSG;
575 }
576
577 for (size_t i = 0; i < source->stream_count; i++) {
578 stream = &source->streams[i];
579
580 bt_audio_stream_attach(NULL, stream, stream->ep, codec);
581 }
582
583 err = bt_audio_set_base(source, codec);
584 if (err != 0) {
585 BT_DBG("Failed to set base data (err %d)", err);
586 return err;
587 }
588
589 source->qos = qos;
590
591 return 0;
592}
593
594int bt_audio_broadcast_source_start(struct bt_audio_broadcast_source *source)
595{
596 struct bt_iso_big_create_param param = { 0 };
597 struct bt_audio_stream *stream;
598 int err;
599
600 CHECKIF(source == NULL) {
601 BT_DBG("source is NULL");
602 return -EINVAL;
603 }
604
605 stream = &source->streams[0];
606
607 if (stream == NULL) {
608 BT_DBG("stream is NULL");
609 return -EINVAL;
610 }
611
612 if (stream->ep == NULL) {
613 BT_DBG("stream->ep is NULL");
614 return -EINVAL;
615 }
616
617 if (stream->ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
618 BT_DBG("Broadcast source stream %p invalid state: %u",
619 stream, stream->ep->status.state);
620 return -EBADMSG;
621 }
622
623 /* Create BIG */
624 param.num_bis = source->stream_count;
625 param.bis_channels = source->bis;
626 param.framing = source->qos->framing;
627 param.packing = 0; /* TODO: Add to QoS struct */
628 param.interval = source->qos->interval;
629 param.latency = source->qos->latency;
630
631 err = bt_iso_big_create(source->adv, &param, &source->big);
632 if (err != 0) {
633 BT_DBG("Failed to create BIG: %d", err);
634 return err;
635 }
636
637 return 0;
638}
639
640int bt_audio_broadcast_source_stop(struct bt_audio_broadcast_source *source)
641{
642 struct bt_audio_stream *stream;
643 int err;
644
645 CHECKIF(source == NULL) {
646 BT_DBG("source is NULL");
647 return -EINVAL;
648 }
649
650 stream = &source->streams[0];
651
652 if (stream == NULL) {
653 BT_DBG("stream is NULL");
654 return -EINVAL;
655 }
656
657 if (stream->ep == NULL) {
658 BT_DBG("stream->ep is NULL");
659 return -EINVAL;
660 }
661
662 if (stream->ep->status.state != BT_AUDIO_EP_STATE_STREAMING) {
663 BT_DBG("Broadcast source stream %p invalid state: %u",
664 stream, stream->ep->status.state);
665 return -EBADMSG;
666 }
667
668 if (source->big == NULL) {
669 BT_DBG("Source is not started");
670 return -EALREADY;
671 }
672
673 err = bt_iso_big_terminate(source->big);
674 if (err) {
675 BT_DBG("Failed to terminate BIG (err %d)", err);
676 return err;
677 }
678
679 source->big = NULL;
680
681 return 0;
682}
683
684int bt_audio_broadcast_source_delete(struct bt_audio_broadcast_source *source)
685{
686 struct bt_audio_stream *stream;
687 struct bt_le_ext_adv *adv;
688 int err;
689
690 CHECKIF(source == NULL) {
691 BT_DBG("source is NULL");
692 return -EINVAL;
693 }
694
695 stream = &source->streams[0];
696
697 if (stream != NULL) {
698 if (stream->ep == NULL) {
699 BT_DBG("stream->ep is NULL");
700 return -EINVAL;
701 }
702
703 if (stream->ep->status.state != BT_AUDIO_EP_STATE_QOS_CONFIGURED) {
704 BT_DBG("Broadcast source stream %p invalid state: %u",
705 stream, stream->ep->status.state);
706 return -EBADMSG;
707 }
708 }
709
710 adv = source->adv;
711
712 __ASSERT(adv != NULL, "source %p adv is NULL", source);
713
714 /* Stop periodic advertising */
715 err = bt_le_per_adv_stop(adv);
716 if (err != 0) {
717 BT_DBG("Failed to stop periodic advertising (err %d)", err);
718 return err;
719 }
720
721 /* Stop extended advertising */
722 err = bt_le_ext_adv_stop(adv);
723 if (err != 0) {
724 BT_DBG("Failed to stop extended advertising (err %d)", err);
725 return err;
726 }
727
728 /* Delete extended advertising set */
729 err = bt_le_ext_adv_delete(adv);
730 if (err != 0) {
731 BT_DBG("Failed to delete extended advertising set (err %d)", err);
732 return err;
733 }
734
735 /* Reset the broadcast source */
736 broadcast_source_cleanup(source);
737
738 return 0;
739}