/* * Copyright (C) 2022 BlueKitchen GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * 4. Any redistribution, use, or modification is done solely for * personal benefit and not for any commercial purpose or for * monetary gain. * * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Please inquire about commercial licensing options at * contact@bluekitchen-gmbh.com * */ #define BTSTACK_FILE__ "le_audio_broadcast_sink.c" /* * LE Audio Broadcast Sink */ #include "btstack_config.h" #include #include #include #include #include #include #include // open #include #include "ad_parser.h" #include "bluetooth_data_types.h" #include "bluetooth_gatt.h" #include "btstack_debug.h" #include "btstack_audio.h" #include "btstack_event.h" #include "btstack_run_loop.h" #include "btstack_ring_buffer.h" #include "btstack_stdin.h" #include "btstack_util.h" #include "gap.h" #include "hci.h" #include "hci_cmd.h" #include "btstack_lc3.h" #include "btstack_lc3_google.h" #include "btstack_lc3plus_fraunhofer.h" #ifdef HAVE_POSIX_FILE_IO #include "wav_util.h" #endif // max config #define MAX_NUM_BIS 2 #define MAX_SAMPLES_PER_FRAME 480 #define DUMP_LEN_LC3_FRAMES 10000 // playback #define MAX_NUM_LC3_FRAMES 5 #define MAX_BYTES_PER_SAMPLE 4 #define PLAYBACK_BUFFER_SIZE (MAX_NUM_LC3_FRAMES * MAX_SAMPLES_PER_FRAME * MAX_BYTES_PER_SAMPLE) // analysis #define PACKET_PREFIX_LEN 10 #define ANSI_COLOR_RED "\x1b[31m" #define ANSI_COLOR_GREEN "\x1b[32m" #define ANSI_COLOR_YELLOW "\x1b[33m" #define ANSI_COLOR_BLUE "\x1b[34m" #define ANSI_COLOR_MAGENTA "\x1b[35m" #define ANSI_COLOR_CYAN "\x1b[36m" #define ANSI_COLOR_RESET "\x1b[0m" static void show_usage(void); static const char * filename_lc3 = "le_audio_broadcast_sink.lc3"; static const char * filename_wav = "le_audio_broadcast_sink.wav"; static enum { APP_W4_WORKING, APP_W4_BROADCAST_ADV, APP_W4_PA_AND_BIG_INFO, APP_W4_BIG_SYNC_ESTABLISHED, APP_STREAMING, APP_IDLE } app_state = APP_W4_WORKING; // static btstack_packet_callback_registration_t hci_event_callback_registration; static bool have_base; static bool have_big_info; uint32_t last_samples_report_ms; uint16_t samples_received; uint16_t samples_dropped; uint16_t frames_per_second[MAX_NUM_BIS]; // remote info static char remote_name[20]; static bd_addr_t remote; static bd_addr_type_t remote_type; static uint8_t remote_sid; static bool count_mode; static bool pts_mode; static bool nrf5340_audio_demo; // broadcast info static const uint8_t big_handle = 1; static hci_con_handle_t sync_handle; static hci_con_handle_t bis_con_handles[MAX_NUM_BIS]; static unsigned int next_bis_index; // analysis static uint16_t last_packet_sequence[MAX_NUM_BIS]; static uint32_t last_packet_time_ms[MAX_NUM_BIS]; static uint8_t last_packet_prefix[MAX_NUM_BIS * PACKET_PREFIX_LEN]; // BIG Sync static le_audio_big_sync_t big_sync_storage; static le_audio_big_sync_params_t big_sync_params; // lc3 writer static int dump_file; static uint32_t lc3_frames; // lc3 codec config static uint16_t sampling_frequency_hz; static btstack_lc3_frame_duration_t frame_duration; static uint16_t number_samples_per_frame; static uint16_t octets_per_frame; static uint8_t num_bis; // lc3 decoder static bool request_lc3plus_decoder = false; static bool use_lc3plus_decoder = false; static const btstack_lc3_decoder_t * lc3_decoder; static int16_t pcm[MAX_NUM_BIS * MAX_SAMPLES_PER_FRAME]; static btstack_lc3_decoder_google_t google_decoder_contexts[MAX_NUM_BIS]; #ifdef HAVE_LC3PLUS static btstack_lc3plus_fraunhofer_decoder_t fraunhofer_decoder_contexts[MAX_NUM_BIS]; #endif static void * decoder_contexts[MAX_NR_BIS]; // playback static uint8_t playback_buffer_storage[PLAYBACK_BUFFER_SIZE]; static btstack_ring_buffer_t playback_buffer; // PLC state #define PLC_GUARD_MS 1 static btstack_timer_source_t next_packet_timer[MAX_NUM_BIS]; static uint16_t cached_iso_sdu_len; static bool have_pcm[MAX_NUM_BIS]; static void le_audio_broadcast_sink_playback(int16_t * buffer, uint16_t num_samples){ // called from lower-layer but guaranteed to be on main thread uint32_t bytes_needed = num_samples * num_bis * 2; static bool underrun = true; log_info("Playback: need %u, have %u", num_samples, btstack_ring_buffer_bytes_available(&playback_buffer) / ( num_bis * 2)); if (bytes_needed > btstack_ring_buffer_bytes_available(&playback_buffer)){ memset(buffer, 0, bytes_needed); if (underrun == false){ log_info("Playback underrun"); underrun = true; } return; } if (underrun){ underrun = false; log_info("Playback started"); } uint32_t bytes_read; btstack_ring_buffer_read(&playback_buffer, (uint8_t *) buffer, bytes_needed, &bytes_read); btstack_assert(bytes_read == bytes_needed); } #ifdef HAVE_POSIX_FILE_IO static void open_lc3_file(void) { // open lc3 file int oflags = O_WRONLY | O_CREAT | O_TRUNC; dump_file = open(filename_lc3, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (dump_file < 0) { printf("failed to open file %s, errno = %d\n", filename_lc3, errno); return; } printf("LC3 binary file: %s\n", filename_lc3); // calc bps uint16_t frame_duration_100us = (frame_duration == BTSTACK_LC3_FRAME_DURATION_7500US) ? 75 : 100; uint32_t bits_per_second = (uint32_t) octets_per_frame * num_bis * 8 * 10000 / frame_duration_100us; // write header for floating point implementation uint8_t header[18]; little_endian_store_16(header, 0, 0xcc1c); little_endian_store_16(header, 2, sizeof(header)); little_endian_store_16(header, 4, sampling_frequency_hz / 100); little_endian_store_16(header, 6, bits_per_second / 100); little_endian_store_16(header, 8, num_bis); little_endian_store_16(header, 10, frame_duration_100us * 10); little_endian_store_16(header, 12, 0); little_endian_store_32(header, 14, DUMP_LEN_LC3_FRAMES * number_samples_per_frame); write(dump_file, header, sizeof(header)); } #endif static void setup_lc3_decoder(void){ uint8_t channel; for (channel = 0 ; channel < num_bis ; channel++){ // pick decoder void * decoder_context = NULL; #ifdef HAVE_LC3PLUS if (use_lc3plus_decoder){ decoder_context = &fraunhofer_decoder_contexts[channel]; lc3_decoder = btstack_lc3plus_fraunhofer_decoder_init_instance(decoder_context); } else #endif { decoder_context = &google_decoder_contexts[channel]; lc3_decoder = btstack_lc3_decoder_google_init_instance(decoder_context); } decoder_contexts[channel] = decoder_context; lc3_decoder->configure(decoder_context, sampling_frequency_hz, frame_duration); } number_samples_per_frame = lc3_decoder->get_number_samples_per_frame(decoder_contexts[0]); btstack_assert(number_samples_per_frame <= MAX_SAMPLES_PER_FRAME); } static void close_files(void){ #ifdef HAVE_POSIX_FILE_IO printf("Close files\n"); close(dump_file); wav_writer_close(); #endif } static void handle_periodic_advertisement(const uint8_t * packet, uint16_t size){ // nRF534_audio quirk - no BASE in periodic advertisement if (nrf5340_audio_demo){ // hard coded config LC3 // default: mono bitrate 96000, 10 ms with USB audio source, 120 octets per frame count_mode = 0; pts_mode = 0; num_bis = 1; sampling_frequency_hz = 48000; frame_duration = BTSTACK_LC3_FRAME_DURATION_10000US; octets_per_frame = 120; have_base = true; return; } // periodic advertisement contains the BASE // TODO: BASE might be split across multiple advertisements const uint8_t * adv_data = hci_subevent_le_periodic_advertising_report_get_data(packet); uint16_t adv_size = hci_subevent_le_periodic_advertising_report_get_data_length(packet); uint8_t adv_status = hci_subevent_le_periodic_advertising_report_get_data_status(packet); if (adv_status != 0) { printf("Periodic Advertisement (status %u): ", adv_status); printf_hexdump(adv_data, adv_size); return; } ad_context_t context; for (ad_iterator_init(&context, adv_size, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)) { uint8_t data_type = ad_iterator_get_data_type(&context); // TODO: avoid out-of-bounds read // uint8_t data_size = ad_iterator_get_data_len(&context); const uint8_t * data = ad_iterator_get_data(&context); uint16_t uuid; switch (data_type){ case BLUETOOTH_DATA_TYPE_SERVICE_DATA_16_BIT_UUID: uuid = little_endian_read_16(data, 0); if (uuid == ORG_BLUETOOTH_SERVICE_BASIC_AUDIO_ANNOUNCEMENT_SERVICE){ have_base = true; // Level 1: Group Level const uint8_t * base_data = &data[2]; // TODO: avoid out-of-bounds read // uint16_t base_len = data_size - 2; printf("BASE:\n"); uint32_t presentation_delay = little_endian_read_24(base_data, 0); printf("- presentation delay: %"PRIu32" us\n", presentation_delay); uint8_t num_subgroups = base_data[3]; printf("- num subgroups: %u\n", num_subgroups); uint8_t i; uint16_t offset = 4; for (i=0;i 1) && pts_mode){ playback_speed = sampling_frequency_hz / num_bis; printf("PTS workaround: playback at %u hz\n", playback_speed); } else { playback_speed = sampling_frequency_hz; }; sink->init(num_bis, sampling_frequency_hz, le_audio_broadcast_sink_playback); sink->start_stream(); } big_sync_params.big_handle = big_handle; big_sync_params.sync_handle = sync_handle; big_sync_params.encryption = 0; memset(big_sync_params.broadcast_code, 0, 16); big_sync_params.mse = 0; big_sync_params.big_sync_timeout_10ms = 100; big_sync_params.num_bis = num_bis; uint8_t i; printf("BIG Create Sync for BIS: "); for (i=0;istop_stream(); sink->close(); } } // start over start_scanning(); break; default: break; } break; case HCI_EVENT_META_GAP: switch (hci_event_gap_meta_get_subevent_code(packet)){ case GAP_SUBEVENT_BIG_SYNC_CREATED: { printf("BIG Sync created with BIS Connection handles: "); uint8_t i; for (i=0;i= bytes_to_store) { btstack_ring_buffer_write(&playback_buffer, (uint8_t *) pcm, bytes_to_store); } else { printf("Samples dropped\n"); samples_dropped += number_samples_per_frame; } // reset for (bis_channel = 0; bis_channel < num_bis ; bis_channel++){ have_pcm[bis_channel] = false; } } static void plc_timeout(btstack_timer_source_t * timer) { uint8_t bis_channel = (uint8_t) (uintptr_t) btstack_run_loop_get_timer_context(timer); log_info("PLC channel %u", bis_channel); // set timer again uint32_t frame_duration_ms = frame_duration == BTSTACK_LC3_FRAME_DURATION_7500US ? 8 : 10; btstack_run_loop_set_timer(&next_packet_timer[bis_channel], frame_duration_ms); btstack_run_loop_set_timer_handler(&next_packet_timer[bis_channel], plc_timeout); btstack_run_loop_add_timer(&next_packet_timer[bis_channel]); // inject packet uint8_t tmp_BEC_detect; uint8_t BFI = 1; (void) lc3_decoder->decode_signed_16(decoder_contexts[bis_channel], NULL, cached_iso_sdu_len, BFI, &pcm[bis_channel], num_bis, &tmp_BEC_detect); have_pcm[bis_channel] = true; store_samples_in_ringbuffer(); } static void iso_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ uint16_t header = little_endian_read_16(packet, 0); hci_con_handle_t con_handle = header & 0x0fff; uint8_t pb_flag = (header >> 12) & 3; uint8_t ts_flag = (header >> 14) & 1; uint16_t iso_load_len = little_endian_read_16(packet, 2); uint16_t offset = 4; uint32_t time_stamp = 0; if (ts_flag){ uint32_t time_stamp = little_endian_read_32(packet, offset); offset += 4; } uint32_t receive_time_ms = btstack_run_loop_get_time_ms(); uint16_t packet_sequence_number = little_endian_read_16(packet, offset); offset += 2; uint16_t header_2 = little_endian_read_16(packet, offset); uint16_t iso_sdu_length = header_2 & 0x3fff; uint8_t packet_status_flag = (uint8_t) (header_2 >> 14); offset += 2; if (iso_sdu_length == 0) return; // infer channel from con handle - only works for up to 2 channels uint8_t bis_channel = (con_handle == bis_con_handles[0]) ? 0 : 1; if (count_mode){ // check for missing packet uint16_t last_seq_no = last_packet_sequence[bis_channel]; uint32_t now = btstack_run_loop_get_time_ms(); bool packet_missed = (last_seq_no != 0) && ((last_seq_no + 1) != packet_sequence_number); if (packet_missed){ // print last packet printf("\n"); printf("%04x %10"PRIu32" %u ", last_seq_no, last_packet_time_ms[bis_channel], bis_channel); printf_hexdump(&last_packet_prefix[num_bis*PACKET_PREFIX_LEN], PACKET_PREFIX_LEN); last_seq_no++; printf(ANSI_COLOR_RED); while (last_seq_no < packet_sequence_number){ printf("%04x %u MISSING\n", last_seq_no, bis_channel); last_seq_no++; } printf(ANSI_COLOR_RESET); // print current packet printf("%04x %10"PRIu32" %u ", packet_sequence_number, now, bis_channel); printf_hexdump(&packet[offset], PACKET_PREFIX_LEN); } // cache current packet last_packet_time_ms[bis_channel] = now; last_packet_sequence[bis_channel] = packet_sequence_number; memcpy(&last_packet_prefix[num_bis*PACKET_PREFIX_LEN], &packet[offset], PACKET_PREFIX_LEN); } else { if ((packet_sequence_number & 0x7c) == 0) { printf("%04x %10"PRIu32" %u ", packet_sequence_number, btstack_run_loop_get_time_ms(), bis_channel); printf_hexdump(&packet[offset], iso_sdu_length); } if (lc3_frames < DUMP_LEN_LC3_FRAMES) { // store len header only for first bis if (bis_channel == 0) { uint8_t len_header[2]; little_endian_store_16(len_header, 0, num_bis * iso_sdu_length); write(dump_file, len_header, 2); } // store single channel codec frame write(dump_file, &packet[offset], iso_sdu_length); } // decode codec frame uint8_t tmp_BEC_detect; uint8_t BFI = 0; (void) lc3_decoder->decode_signed_16(decoder_contexts[bis_channel], &packet[offset], iso_sdu_length, BFI, &pcm[bis_channel], num_bis, &tmp_BEC_detect); have_pcm[bis_channel] = true; store_samples_in_ringbuffer(); lc3_frames++; frames_per_second[bis_channel]++; // PLC cached_iso_sdu_len = iso_sdu_length; uint32_t frame_duration_ms = frame_duration == BTSTACK_LC3_FRAME_DURATION_7500US ? 8 : 10; btstack_run_loop_remove_timer(&next_packet_timer[bis_channel]); btstack_run_loop_set_timer(&next_packet_timer[bis_channel], frame_duration_ms + PLC_GUARD_MS); btstack_run_loop_set_timer_context(&next_packet_timer[bis_channel], (void *) (uintptr_t) bis_channel); btstack_run_loop_set_timer_handler(&next_packet_timer[bis_channel], plc_timeout); btstack_run_loop_add_timer(&next_packet_timer[bis_channel]); uint32_t time_ms = btstack_run_loop_get_time_ms(); if (btstack_time_delta(time_ms, last_samples_report_ms) >= 1000){ last_samples_report_ms = time_ms; printf("LC3 Frames: %4u - ", (int) (lc3_frames / num_bis)); uint8_t i; for (i=0;i