diff --git a/test/le_audio/CMakeLists.txt b/test/le_audio/CMakeLists.txt index 4ada8b00c..85d34c62a 100644 --- a/test/le_audio/CMakeLists.txt +++ b/test/le_audio/CMakeLists.txt @@ -54,6 +54,17 @@ if (APTX_FOUND) add_compile_definitions(HAVE_APTX) endif() +# lc3plus +pkg_check_modules(LC3PLUS LC3plus) +if(LC3PLUS_FOUND) + message("HAVE_LC3PLUS") + include_directories(${LC3PLUS_INCLUDE_DIRS}) + link_directories(${LC3PLUS_LIBRARY_DIRS}) + link_libraries(${LC3PLUS_LIBRARIES}) + add_definitions(${LC3PLUS_CFLAGS}) + add_compile_definitions(HAVE_LC3PLUS) +endif() + # enable optional features add_compile_definitions(ENABLE_TESTING_SUPPORT) @@ -94,7 +105,7 @@ file(GLOB SOURCES_YXML "../../3rd-party/yxml/yxml.c") file(GLOB SOURCES_HXCMOD "../../3rd-party/hxcmod-player/*.c" "../../3rd-party/hxcmod-player/mods/*.c") file(GLOB SOURCES_RIJNDAEL "../../3rd-party/rijndael/rijndael.c") file(GLOB SOURCES_POSIX "../../platform/posix/*.c") -file(GLOB SOURCES_MAIN "main.c") +file(GLOB SOURCES_MAIN "main.c" "btstack_lc3plus_fraunhofer.c") file(GLOB SOURCES_ZEPHYR "../../chipset/zephyr/*.c") file(GLOB SOURCES_LC3_GOOGLE "../../3rd-party/lc3-google/src/*.c") diff --git a/test/le_audio/btstack_lc3plus_fraunhofer.c b/test/le_audio/btstack_lc3plus_fraunhofer.c new file mode 100644 index 000000000..99d3e8b36 --- /dev/null +++ b/test/le_audio/btstack_lc3plus_fraunhofer.c @@ -0,0 +1,206 @@ +/* + * 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. + * + * 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 BLUEKITCHEN + * GMBH 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. + * + */ + +#define BTSTACK_FILE__ "btstack_lc3_plus_fraunhofer.c" + +/** + * @title LC3 Plus Fraunhofer Adapter + */ + +#include "btstack_config.h" +#include "bluetooth.h" + +#include "btstack_lc3plus_fraunhofer.h" +#include "btstack_debug.h" +#include + +#ifdef HAVE_LC3PLUS + +static uint8_t lc3plus_farunhofer_scratch[LC3PLUS_DEC_MAX_SCRATCH_SIZE]; + +static uint16_t lc3_frame_duration_in_us(btstack_lc3_frame_duration_t frame_duration){ + switch (frame_duration) { + case BTSTACK_LC3_FRAME_DURATION_7500US: + return 7500; + case BTSTACK_LC3_FRAME_DURATION_10000US: + return 10000; + default: + return 0; + } +} + +/* Decoder implementation */ + +static uint8_t lc3plus_fraunhofer_decoder_configure(void * context, uint32_t sample_rate, btstack_lc3_frame_duration_t frame_duration){ + btstack_lc3plus_fraunhofer_decoder_t * instance = (btstack_lc3plus_fraunhofer_decoder_t *) context; + LC3PLUS_Dec * decoder = (LC3PLUS_Dec*) instance->decoder; + + // map frame duration + uint16_t duration_us = lc3_frame_duration_in_us(frame_duration); + if (duration_us == 0){ + return ERROR_CODE_INVALID_HCI_COMMAND_PARAMETERS; + } + + // store config + instance->sample_rate = sample_rate; + instance->frame_duration = frame_duration; + + LC3PLUS_Error error; + error = lc3plus_dec_init(decoder, sample_rate, 1, LC3PLUS_PLC_ADVANCED, 0); + btstack_assert(error == LC3PLUS_OK); + + error = lc3plus_dec_set_frame_dms(decoder, duration_us / 100); + btstack_assert(error == LC3PLUS_OK); + + return ERROR_CODE_SUCCESS; +} + +static uint16_t lc3plus_fraunhofer_decoder_get_number_octets_for_bitrate(void * context, uint32_t bitrate){ + btstack_assert(false); + return 0; +} + +static uint16_t lc3plus_fraunhofer_decoder_get_number_samples_per_frame(void * context){ + btstack_lc3plus_fraunhofer_decoder_t * instance = (btstack_lc3plus_fraunhofer_decoder_t *) context; + LC3PLUS_Dec * decoder = (LC3PLUS_Dec*) instance->decoder; + return lc3plus_dec_get_output_samples(decoder); +} + +static uint8_t lc3plus_fraunhofer_decoder_decode_signed_16(void * context, const uint8_t *bytes, uint16_t byte_count, uint8_t BFI, int16_t* pcm_out, uint16_t stride, uint8_t * BEC_detect){ + btstack_lc3plus_fraunhofer_decoder_t * instance = (btstack_lc3plus_fraunhofer_decoder_t *) context; + LC3PLUS_Dec * decoder = (LC3PLUS_Dec*) instance->decoder; + + // output_samples: array of channel buffers. + int16_t * output_samples[1]; + output_samples[0] = pcm_out; + + // trigger plc if BFI by passing 0 valid input bytes + if (BFI != 0){ + byte_count = 0; + } + + LC3PLUS_Error error = lc3plus_dec16(decoder, (void*) bytes, byte_count, output_samples, lc3plus_farunhofer_scratch, BFI); + + // map error + switch (error){ + case LC3PLUS_OK: + // success + return ERROR_CODE_SUCCESS; + case LC3PLUS_DECODE_ERROR: + // PLC enagaged + *BEC_detect = 1; + return ERROR_CODE_SUCCESS; + default: + return ERROR_CODE_INVALID_HCI_COMMAND_PARAMETERS; + } +} + +static uint8_t lc3plus_fraunhofer_decoder_decode_signed_24(void * context, const uint8_t *bytes, uint16_t byte_count, uint8_t BFI, int32_t* pcm_out, uint16_t stride, uint8_t * BEC_detect) { + btstack_lc3plus_fraunhofer_decoder_t * instance = (btstack_lc3plus_fraunhofer_decoder_t *) context; + LC3PLUS_Dec * decoder = (LC3PLUS_Dec*) instance->decoder; + + // output_samples: array of channel buffers. + int32_t * output_samples[1]; + output_samples[0] = pcm_out; + + // trigger plc if BFI by passing 0 valid input bytes + if (BFI != 0){ + byte_count = 0; + } + + LC3PLUS_Error error = lc3plus_dec24(decoder, (void *) bytes, byte_count, output_samples, lc3plus_farunhofer_scratch, BFI); + + // map error + switch (error){ + case LC3PLUS_OK: + // success + return ERROR_CODE_SUCCESS; + case LC3PLUS_DECODE_ERROR: + // PLC enagaged + *BEC_detect = 1; + return ERROR_CODE_SUCCESS; + default: + return ERROR_CODE_INVALID_HCI_COMMAND_PARAMETERS; + } +} + +static const btstack_lc3_decoder_t btstack_l3cplus_fraunhofer_decoder_instance = { + lc3plus_fraunhofer_decoder_configure, + lc3plus_fraunhofer_decoder_get_number_octets_for_bitrate, + lc3plus_fraunhofer_decoder_get_number_samples_per_frame, + lc3plus_fraunhofer_decoder_decode_signed_16, + lc3plus_fraunhofer_decoder_decode_signed_24 +}; + +const btstack_lc3_decoder_t * btstack_lc3plus_fraunhofer_decoder_init_instance(btstack_lc3plus_fraunhofer_decoder_t * context){ + memset(context, 0, sizeof(btstack_lc3plus_fraunhofer_decoder_t)); + return &btstack_l3cplus_fraunhofer_decoder_instance; +} + +/* Encoder implementation */ + +static uint8_t lc3plus_fraunhofer_encoder_configure(void * context, uint32_t sample_rate, btstack_lc3_frame_duration_t frame_duration){ + btstack_lc3plus_fraunhofer_encoder_t * instance = (btstack_lc3plus_fraunhofer_encoder_t *) context; + return ERROR_CODE_COMMAND_DISALLOWED; +} + +static uint32_t lc3plus_fraunhofer_encoder_get_bitrate_for_number_of_octets(void * context, uint16_t number_of_octets){ + btstack_assert(false); + return 0; +} + +static uint16_t lc3plus_fraunhofer_encoder_get_number_samples_per_frame(void * context){ + btstack_assert(false); + return 0; +} + +static uint8_t lc3plus_fraunhofer_encoder_encode_signed_16(void * context, const int16_t* pcm_in, uint16_t stride, uint8_t *bytes, uint16_t byte_count){ + return ERROR_CODE_COMMAND_DISALLOWED; +} + +static uint8_t lc3plus_fraunhofer_encoder_encode_signed_24(void * context, const int32_t* pcm_in, uint16_t stride, uint8_t *bytes, uint16_t byte_count) { + return ERROR_CODE_COMMAND_DISALLOWED; +} + +static const btstack_lc3_encoder_t btstack_l3cplus_fraunhofer_encoder_instance = { + lc3plus_fraunhofer_encoder_configure, + lc3plus_fraunhofer_encoder_get_bitrate_for_number_of_octets, + lc3plus_fraunhofer_encoder_get_number_samples_per_frame, + lc3plus_fraunhofer_encoder_encode_signed_16, + lc3plus_fraunhofer_encoder_encode_signed_24 +}; + +const btstack_lc3_encoder_t * btstack_lc3plus_fraunhofer_encoder_init_instance(btstack_lc3plus_fraunhofer_encoder_t * context){ + memset(context, 0, sizeof(btstack_lc3plus_fraunhofer_encoder_t)); + return &btstack_l3cplus_fraunhofer_encoder_instance; +} + +#endif /* HAVE_LC3PLUS */ diff --git a/test/le_audio/btstack_lc3plus_fraunhofer.h b/test/le_audio/btstack_lc3plus_fraunhofer.h new file mode 100644 index 000000000..a2331e92b --- /dev/null +++ b/test/le_audio/btstack_lc3plus_fraunhofer.h @@ -0,0 +1,84 @@ +/* + * 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. + * + * 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 BLUEKITCHEN + * GMBH 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. + * + */ + +/** + * @title Adapter for Fraunhofer LC3plus Coddec + * only uses suitable subset for lc3 testing + */ + +#ifndef BTSTACK_LC3PLUS_FRAUNHOFER_H +#define BTSTACK_LC3PLUS_FRAUNHOFER_H + +#include +#include "btstack_lc3.h" + +#if defined __cplusplus +extern "C" { +#endif + +#ifdef HAVE_LC3PLUS + +#include "LC3plus/lc3.h" + +/* API_START */ + +typedef struct { + uint32_t sample_rate; + btstack_lc3_frame_duration_t frame_duration; + uint8_t decoder[LC3PLUS_DEC_MAX_SIZE]; +} btstack_lc3plus_fraunhofer_decoder_t; + +typedef struct { + uint32_t sample_rate; + btstack_lc3_frame_duration_t frame_duration; + uint8_t encoder[LC3PLUS_ENC_MAX_SIZE]; +} btstack_lc3plus_fraunhofer_encoder_t; + +/** + * Init LC3 Decoder Instance + * @param context for Fraunhofer LC3plus decoder + */ +const btstack_lc3_decoder_t * btstack_lc3plus_fraunhofer_decoder_init_instance(btstack_lc3plus_fraunhofer_decoder_t * context); + +/** + * Init LC3 Encoder Instance + * @param context for Fraunhofer LC3plus encoder + */ +const btstack_lc3_encoder_t * btstack_lc3plus_fraunhofer_encoder_init_instance(btstack_lc3plus_fraunhofer_encoder_t * context); + +/* API_END */ + +#endif /* HAVE_LC3PLUS */ + +#if defined __cplusplus +} +#endif +#endif // BTSTACK_LC3_PLUS_FRAUNHOFER_H diff --git a/test/le_audio/lc3_test.c b/test/le_audio/lc3_test.c index b585a783f..6f42cce0e 100644 --- a/test/le_audio/lc3_test.c +++ b/test/le_audio/lc3_test.c @@ -65,6 +65,8 @@ #include "wav_util.h" #endif +#include "btstack_lc3plus_fraunhofer.h" + // max config #define MAX_SAMPLES_PER_FRAME 480 #define MAX_NUM_BIS 2 @@ -82,10 +84,16 @@ static btstack_lc3_encoder_google_t encoder_contexts[MAX_NUM_BIS]; static int16_t pcm[MAX_NUM_BIS * MAX_SAMPLES_PER_FRAME]; // lc3 decoder +static bool use_lc3plus_decoder = false; static const btstack_lc3_decoder_t * lc3_decoder; -static btstack_lc3_decoder_google_t decoder_contexts[MAX_NUM_BIS]; 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]; + // PLC static uint16_t plc_frame_counter; static uint16_t plc_dopped_frame_interval; @@ -208,14 +216,15 @@ static const int16_t sine_int16[] = { static void show_usage(void); static void print_config(void) { - printf("Config '%s_%u': %u, %s ms, %u octets - %s, drop frame interval %u\n", + printf("Config '%s_%u': %u, %s ms, %u octets - %s, drop frame interval %u, decoder %s\n", codec_configurations[menu_sampling_frequency].variants[menu_variant].name, num_bis, codec_configurations[menu_sampling_frequency].samplingrate_hz, codec_configurations[menu_sampling_frequency].variants[menu_variant].frame_duration == BTSTACK_LC3_FRAME_DURATION_7500US ? "7.5" : "10", codec_configurations[menu_sampling_frequency].variants[menu_variant].octets_per_frame, audio_source == AUDIO_SOURCE_SINE ? "Sine" : "Modplayer", - plc_dopped_frame_interval); + plc_dopped_frame_interval, + use_lc3plus_decoder ? "LC3plus" : "LC3"); } static void setup_lc3_encoder(void){ @@ -234,12 +243,24 @@ static void setup_lc3_encoder(void){ static void setup_lc3_decoder(void){ uint8_t channel; - for (channel = 0 ; channel < num_bis ; channel++){ - btstack_lc3_decoder_google_t * decoder_context = &decoder_contexts[channel]; - lc3_decoder = btstack_lc3_decoder_google_init_instance(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]); + 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); } @@ -323,27 +344,14 @@ static void test_encoder(){ BFI = 1; } -#ifdef HAVE_POSIX_FILE_IO - if (BFI){ - // insert silence before for analysis in audacity - memset(pcm, 0, sizeof(pcm)); - wav_writer_write_int16(number_samples_per_frame, pcm); - } -#endif - // decode codec frame uint8_t tmp_BEC_detect; - (void) lc3_decoder->decode_signed_16(&decoder_contexts[0], buffer, octets_per_frame, BFI, pcm, 1, &tmp_BEC_detect); + (void) lc3_decoder->decode_signed_16(decoder_contexts[0], buffer, octets_per_frame, BFI, pcm, 1, &tmp_BEC_detect); uint32_t block_decoded_ms = btstack_run_loop_get_time_ms(); #ifdef HAVE_POSIX_FILE_IO wav_writer_write_int16(number_samples_per_frame, pcm); - if (BFI){ - // insert silence after for analysis in audacity - memset(pcm, 0, sizeof(pcm)); - wav_writer_write_int16(number_samples_per_frame, pcm); - } #endif // summary @@ -368,6 +376,9 @@ static void show_usage(void){ printf("v - next codec variant\n"); printf("t - toggle sine / modplayer\n"); printf("p - simulated dropped frames\n"); +#ifdef HAVE_LC3PLUS + printf("q - use LC3plus\n"); +#endif printf("s - start test\n"); printf("---\n"); } @@ -378,6 +389,16 @@ static void stdin_process(char c){ plc_dopped_frame_interval = 16 - plc_dopped_frame_interval; print_config(); break; +#ifdef HAVE_LC3PLUS + case 'q': + use_lc3plus_decoder = true; + // enforce 10ms as 7.5ms is not supported + if ((menu_variant & 1) == 0){ + menu_variant++; + } + print_config(); + break; +#endif case 'f': menu_sampling_frequency++; if (menu_sampling_frequency >= 6){