Introduction
In this previous post on broadcast sources, we covered how to create a simple BAP broadcast source using Zephyr RTOS and a Nordic Semiconductor nRF52840 Dongle, broadcasting a single 16 kHz mono audio stream.
In this post, we will explore how to extend that to a more complex source, broadcasting several "tracks" in parallel.
BASE (Broadcast Audio Source Endpoint)
The Bluetooth Basic Audio Profile (BAP) spec, section 3.7.2.2 describes how to create a BASE (Broadcast Audio Source Endpoint) for the..
- Broadcast source (Level 1 - BIG), which can contain..
- One or more parallel groups (Level 2 - Subgroups) containing..
- One or more streaming channels (Level 3 - BIS)
...with the possibility to specify various combinations of codec configuration and metadata on level 2 and 3.
The most common case, would be e.g. a TV or phone, broadcasting a simple stereo signal (one subgroup containing two streams) with some added metadata, e.g. the title of what's currently being broadcasted (in the ProgramInfo metadata field of the subgroup).
This is fine for simple streaming, but what if we could utilize the flexibility of the BASE structure to create broadcast sources covering some more interesting cases?
Multiple subgroups
One obvious case that could be interesting to look at, is a multi-language source, e.g. to be used for tourist attractions.
In this case, mono-channel audio (for each language) with 16 kHz sample rate should be sufficient and this would also allow for 5-7 "stream tracks" (subgroups with language metadata containing one mono stream each) to fit both in the BASE structure and airtime wise (the amount of data possible to send in a given timeframe has physical limitations).
Another interesting case, could be to have two subgroups with different sample rates, but otherwise same streaming content. This would allow for high-powered receivers to receive and playback high quality audio (e.g. 48 kHz), while more resource constrained devices (like hearing aids) to receive and playback standard quality audio (e.g. 24 kHz) - at the same time, from the same broadcast source.
Building with Zephyr
In order to explore these cases, a variant of the simple broadcast application used in a previous post was created: https://github.com/AstraeusLabs/multi-source
Different from the simple broadcast application, this one utilizes a range of pre-encoded, 1s sine wave audio snippets in 16/24/48 kHz sample rate and for each sample rate, various sine frequencies. This allows us to create a wide variety of combinations of subgroups and streams from the same application source.
5 different language tracks
In order to explore the first case with multiple languages, a customized source is created, containing 5 subgroups (distinguishable by different Language specified in the metadata for each subgroup), each containing a single mono channel stream.
Some highlights from the code to setup the broadcast source include the languages set for subgroups (English, German, French, Spanish and Italian):
...
struct bt_audio_codec_cfg subgroup_codec_cfg[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT];
char *lang[] = {"eng","deu","fra","spa","ita"};
static int setup_broadcast_source(struct bt_bap_broadcast_source **source)
{
...
struct bt_bap_broadcast_source_stream_param
stream_params[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT];
struct bt_bap_broadcast_source_subgroup_param
subgroup_param[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT];
struct bt_bap_broadcast_source_param create_param = {0};
int err;
for (size_t i = 0U; i < ARRAY_SIZE(subgroup_param); i++) {
memcpy(&subgroup_codec_cfg[i], &preset_16_mono.codec_cfg,
sizeof(struct bt_audio_codec_cfg));
bt_audio_codec_cfg_meta_set_lang(&subgroup_codec_cfg[i], lang[i]);
/* MONO is implicit if omitted */
bt_audio_codec_cfg_unset_val(&subgroup_codec_cfg[i], BT_AUDIO_CODEC_CFG_CHAN_ALLOC);
subgroup_param[i].params_count = 1;
subgroup_param[i].params = &stream_params[i];
subgroup_param[i].codec_cfg = &subgroup_codec_cfg[i];
}
...
Also assignment of different 1s, 16 kHz sample rate, pre-encoded sine waves of varying frequency to each mono stream:
...
for (size_t j = 0U; j < ARRAY_SIZE(stream_params); j++) {
stream_params[j].data = NULL;
stream_params[j].data_len = 0;
samples_per_frame = 160;
sdu = preset_16_mono.qos.sdu;
switch(j) {
case 0:
streams[j].data_ptr = (uint8_t *)lc3_sine_0200_16;
break;
case 1:
streams[j].data_ptr = (uint8_t *)lc3_sine_0320_16;
break;
case 2:
streams[j].data_ptr = (uint8_t *)lc3_sine_0800_16;
break;
case 3:
streams[j].data_ptr = (uint8_t *)lc3_sine_1000_16;
break;
case 4:
streams[j].data_ptr = (uint8_t *)lc3_sine_1600_16;
break;
}
printk("Reading LC3 header (%p)\n", streams[j].data_ptr);
printk("======================\n");
ret = lc3bin_read_header(&streams[j].data_ptr, &frame_us, &srate_hz, &nchannels, &nsamples);
printk("Frame size: %dus\n", frame_us);
printk("Sample rate: %dHz\n", srate_hz);
printk("Number of channels: %d\n", nchannels);
printk("Number of samples: %d\n", nsamples);
/* Store position of start and end+1 of frame blocks */
streams[j].start_data_ptr = streams[j].data_ptr;
streams[j].end_data_ptr = streams[j].data_ptr + (nsamples / samples_per_frame) *
(sdu + 2); // TBD
streams[j].sdu = sdu;
stream_params[j].stream = &streams[j].stream;
bt_bap_stream_cb_register(stream_params[j].stream, &stream_ops);
}
...
The full source for this configuration can be found here
Capturing the streams with blueSpy
As in the previous post, let's try to capture the broadcast source streams using the RFcreations mini-moreph together with the blueSpy software.
Immediately after starting the capture and selecting the broadcast device in the Filter Devices panel, it's clear that we have successfully built a broadcast source with 5 streams of 16 kHz audio:
Looking closer at the Timeline it's also clear that each stream contains slightly different frequency wave data:
Using the live playback function in the Audio Export for each stream also confirms this.
Selecting one of the entries with BASE information in the Summary panel and looking at the Details tab on the right side of the screen also shows, that the BASE contains 5 subgroups with different language indications in the metadata:
Conclusion
Even though the first Auracast capable devices available in the market, currently only seem to support the simple case of "1 subgroup with stereo audio", it's still worth exploring other use cases like the ones mentioned above.
Also, it's really great to see that the RFcreations mini-moreph and blueSpy software was able to capture and render this slightly more advanced source and that it was possible to build using Zephyr RTOS and the nRF52840 Dongle.
The full repo containing a bunch of different source configurations can be found here.
Enjoy ;)
Top comments (0)