From 65e76442879710536c8282d7434f54986ceda717 Mon Sep 17 00:00:00 2001 From: Matthias Ringwald Date: Wed, 25 Sep 2024 16:06:51 +0200 Subject: [PATCH] example/le_audio_demo_util_source: support recording and 5-channel sine --- example/le_audio_demo_util_source.c | 246 ++++++++++++++++++---------- example/le_audio_demo_util_source.h | 3 +- 2 files changed, 161 insertions(+), 88 deletions(-) diff --git a/example/le_audio_demo_util_source.c b/example/le_audio_demo_util_source.c index f16ed2f32..661f389fd 100644 --- a/example/le_audio_demo_util_source.c +++ b/example/le_audio_demo_util_source.c @@ -39,10 +39,14 @@ #include "le_audio_demo_util_source.h" -#include "btstack_bool.h" -#include "btstack_config.h" -#include #include +#include +#include + +#include "btstack_config.h" +#include "btstack_bool.h" +#include "btstack_debug.h" +#include "btstack_ring_buffer.h" #include "hci.h" #include "btstack_audio.h" @@ -52,78 +56,29 @@ #include "hxcmod.h" #include "mods/mod.h" -#ifdef HAVE_POSIX_FILE_IO -#include "wav_util.h" -#include "btstack_ring_buffer.h" - -#endif - -//#define DEBUG_PLC -#ifdef DEBUG_PLC -#define printf_plc(...) printf(__VA_ARGS__) -#else -#define printf_plc(...) (void)(0); -#endif - -#define MAX_CHANNELS 2 -#define MAX_SAMPLES_PER_FRAME 480 +#define MAX_CHANNELS 5 +#define MAX_SAMPLES_PER_10MS_FRAME 480 #define MAX_LC3_FRAME_BYTES 155 -// playback -#define MAX_NUM_LC3_FRAMES 15 -#define MAX_BYTES_PER_SAMPLE 4 -#define PLAYBACK_BUFFER_SIZE (MAX_NUM_LC3_FRAMES * MAX_SAMPLES_PER_FRAME * MAX_BYTES_PER_SAMPLE) -#define PLAYBACK_START_MS (MAX_NUM_LC3_FRAMES * 20 / 3) - -#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" - -// analysis -#define PACKET_PREFIX_LEN 10 +// live recording +#define RECORDING_PREBUFFER__MS 20 +#define RECORDING_BUFFER_MS 40 // SOURCE - -// input signal: pre-computed int16 sine wave, 96000 Hz at 300 Hz -static const int16_t sine_int16[] = { - 0, 643, 1286, 1929, 2571, 3212, 3851, 4489, 5126, 5760, - 6393, 7022, 7649, 8273, 8894, 9512, 10126, 10735, 11341, 11943, - 12539, 13131, 13718, 14300, 14876, 15446, 16011, 16569, 17121, 17666, - 18204, 18736, 19260, 19777, 20286, 20787, 21280, 21766, 22242, 22710, - 23170, 23620, 24062, 24494, 24916, 25329, 25732, 26126, 26509, 26882, - 27245, 27597, 27938, 28269, 28589, 28898, 29196, 29482, 29757, 30021, - 30273, 30513, 30742, 30958, 31163, 31356, 31537, 31705, 31862, 32006, - 32137, 32257, 32364, 32458, 32540, 32609, 32666, 32710, 32742, 32761, - 32767, 32761, 32742, 32710, 32666, 32609, 32540, 32458, 32364, 32257, - 32137, 32006, 31862, 31705, 31537, 31356, 31163, 30958, 30742, 30513, - 30273, 30021, 29757, 29482, 29196, 28898, 28589, 28269, 27938, 27597, - 27245, 26882, 26509, 26126, 25732, 25329, 24916, 24494, 24062, 23620, - 23170, 22710, 22242, 21766, 21280, 20787, 20286, 19777, 19260, 18736, - 18204, 17666, 17121, 16569, 16011, 15446, 14876, 14300, 13718, 13131, - 12539, 11943, 11341, 10735, 10126, 9512, 8894, 8273, 7649, 7022, - 6393, 5760, 5126, 4489, 3851, 3212, 2571, 1929, 1286, 643, - 0, -643, -1286, -1929, -2571, -3212, -3851, -4489, -5126, -5760, - -6393, -7022, -7649, -8273, -8894, -9512, -10126, -10735, -11341, -11943, - -12539, -13131, -13718, -14300, -14876, -15446, -16011, -16569, -17121, -17666, - -18204, -18736, -19260, -19777, -20286, -20787, -21280, -21766, -22242, -22710, - -23170, -23620, -24062, -24494, -24916, -25329, -25732, -26126, -26509, -26882, - -27245, -27597, -27938, -28269, -28589, -28898, -29196, -29482, -29757, -30021, - -30273, -30513, -30742, -30958, -31163, -31356, -31537, -31705, -31862, -32006, - -32137, -32257, -32364, -32458, -32540, -32609, -32666, -32710, -32742, -32761, - -32767, -32761, -32742, -32710, -32666, -32609, -32540, -32458, -32364, -32257, - -32137, -32006, -31862, -31705, -31537, -31356, -31163, -30958, -30742, -30513, - -30273, -30021, -29757, -29482, -29196, -28898, -28589, -28269, -27938, -27597, - -27245, -26882, -26509, -26126, -25732, -25329, -24916, -24494, -24062, -23620, - -23170, -22710, -22242, -21766, -21280, -20787, -20286, -19777, -19260, -18736, - -18204, -17666, -17121, -16569, -16011, -15446, -14876, -14300, -13718, -13131, - -12539, -11943, -11341, -10735, -10126, -9512, -8894, -8273, -7649, -7022, - -6393, -5760, -5126, -4489, -3851, -3212, -2571, -1929, -1286, -643, +static float sine_frequencies[MAX_CHANNELS] = { + 261.63, // C-4 + 311.13, // Es-4 + 369.99, // G-4 + 493.88, // B-4 + 587.33, // D-5 }; +// 48000 / 261.6 = 183.46 +#define SINE_MAX_SAMPLES_AT_48KHZ 185 +#define PI 3.14159265358979323846 + +static int16_t sine_table[MAX_CHANNELS * SINE_MAX_SAMPLES_AT_48KHZ]; + static uint16_t le_audio_demo_source_octets_per_frame; static uint16_t le_audio_demo_source_packet_sequence_numbers[MAX_CHANNELS]; static uint8_t le_audio_demo_source_iso_frame_counter; @@ -140,10 +95,10 @@ static uint8_t le_audio_demo_source_iso_payload[MAX_CHANNELS * MAX_LC3_FRAME_BYT // lc3 encoder static const btstack_lc3_encoder_t * le_audio_demo_source_lc3_encoder; static btstack_lc3_encoder_google_t le_audio_demo_source_encoder_contexts[MAX_CHANNELS]; -static int16_t le_audio_demo_source_pcm[MAX_CHANNELS * MAX_SAMPLES_PER_FRAME]; +static int16_t le_audio_demo_source_pcm[MAX_CHANNELS * MAX_SAMPLES_PER_10MS_FRAME]; // sine generator -static uint8_t le_audio_demo_source_sine_step; +static uint16_t le_audio_demo_source_sine_samples[MAX_CHANNELS]; static uint16_t le_audio_demo_source_sine_phases[MAX_CHANNELS]; // mod player @@ -151,7 +106,53 @@ static bool le_audio_demo_source_hxcmod_initialized; static modcontext le_audio_demo_source_hxcmod_context; static tracker_buffer_state le_audio_demo_source_hxcmod_trkbuf; -void le_audio_demo_util_source_init(void){ +// recording / portaudio +#define SAMPLES_PER_CHANNEL_RECORDING (MAX_SAMPLES_PER_10MS_FRAME * RECORDING_BUFFER_MS / 10) +static uint8_t recording_storage[MAX_CHANNELS * 2 * SAMPLES_PER_CHANNEL_RECORDING]; +static btstack_ring_buffer_t le_audio_demo_source_recording_buffer; +static uint16_t le_audio_demo_source_recording_stored_samples; +static uint16_t le_audio_demo_source_recording_prebuffer_samples; +static bool le_audio_demo_source_recording_streaming; +static btstack_audio_source_t const * le_audio_demo_audio_source; + +// generation method +static le_audio_demo_source_generator le_audio_demo_util_source_generator = AUDIO_SOURCE_SINE; + +// recording callback has channels interleaved, collect per channel +static void le_audio_util_source_recording_callback(const int16_t * buffer, uint16_t num_samples){ + log_info("store %u samples per channel", num_samples); + uint32_t bytes_to_store = le_audio_demo_source_num_channels * num_samples * 2; + if (bytes_to_store < btstack_ring_buffer_bytes_free(&le_audio_demo_source_recording_buffer)){ + btstack_ring_buffer_write(&le_audio_demo_source_recording_buffer, (uint8_t *)buffer, bytes_to_store); + le_audio_demo_source_recording_stored_samples += num_samples; + } +} + +void le_audio_demo_util_source_init(void) { + le_audio_demo_audio_source = btstack_audio_source_get_instance(); +} + +static bool le_audio_demo_util_source_recording_start(void){ + bool ok = false; + if (le_audio_demo_audio_source != NULL){ + int init_ok = le_audio_demo_audio_source->init(le_audio_demo_source_num_channels, le_audio_demo_source_sampling_frequency_hz, + &le_audio_util_source_recording_callback); + log_info("recording initialized, ok %u", init_ok); + if (init_ok){ + btstack_ring_buffer_init(&le_audio_demo_source_recording_buffer, recording_storage, sizeof(recording_storage)); + le_audio_demo_source_recording_prebuffer_samples = le_audio_demo_source_num_channels * le_audio_demo_source_sampling_frequency_hz * RECORDING_PREBUFFER__MS / 1000; + le_audio_demo_source_recording_streaming = false; + le_audio_demo_source_recording_stored_samples = 0; + le_audio_demo_audio_source->start_stream(); + log_info("recording start, %u prebuffer samples per channel", le_audio_demo_source_recording_prebuffer_samples / le_audio_demo_source_num_channels); + ok = true; + } + } + return ok; +} + +static void le_audio_demo_util_source_recording_stop(void) { + le_audio_demo_audio_source->stop_stream(); } static void le_audio_demo_source_setup_lc3_encoder(void){ @@ -162,7 +163,7 @@ static void le_audio_demo_source_setup_lc3_encoder(void){ le_audio_demo_source_lc3_encoder->configure(context, le_audio_demo_source_sampling_frequency_hz, le_audio_demo_source_frame_duration, le_audio_demo_source_octets_per_frame); } - printf("LC3 Encoder config: %u hz, frame duration %s ms, num samples %u, num octets %u\n", + printf("LC3 Encoder config: %" PRIu32 " hz, frame duration %s ms, num samples %u, num octets %u\n", le_audio_demo_source_sampling_frequency_hz, le_audio_demo_source_frame_duration == BTSTACK_LC3_FRAME_DURATION_7500US ? "7.5" : "10", le_audio_demo_source_num_samples_per_frame, le_audio_demo_source_octets_per_frame); } @@ -177,6 +178,23 @@ static void le_audio_demo_source_setup_mod_player(void){ hxcmod_load(&le_audio_demo_source_hxcmod_context, (void *) &mod_data, mod_len); } +static void le_audio_demo_source_setup_sine_generator(void){ + // pre-compute sine for all channels + uint8_t channel; + for (channel = 0; channel < MAX_CHANNELS ; channel++){ + float frequency_hz = (float) sine_frequencies[channel]; + float samplerate_hz = (float) le_audio_demo_source_sampling_frequency_hz; + float period_samples =samplerate_hz / frequency_hz; + le_audio_demo_source_sine_samples[channel] = (uint16_t) period_samples; + le_audio_demo_source_sine_phases[channel] = 0; + uint16_t i; + for (i=0;i 0) || (le_audio_demo_source_num_channels <= MAX_CHANNELS)); le_audio_demo_source_num_samples_per_frame = btstack_lc3_samples_per_frame(sampling_frequency_hz, frame_duration); - btstack_assert(le_audio_demo_source_num_samples_per_frame <= MAX_SAMPLES_PER_FRAME); + btstack_assert(le_audio_demo_source_num_samples_per_frame <= MAX_SAMPLES_PER_10MS_FRAME); // setup encoder le_audio_demo_source_setup_lc3_encoder(); // setup sine generator - if (sampling_frequency_hz == 44100){ - le_audio_demo_source_sine_step = 2; - } else { - le_audio_demo_source_sine_step = 96000 / sampling_frequency_hz; - } + le_audio_demo_source_setup_sine_generator(); // setup mod player le_audio_demo_source_setup_mod_player(); -}; +} void le_audio_demo_util_source_generate_iso_frame(le_audio_demo_source_generator generator) { btstack_assert(le_audio_demo_source_octets_per_frame != 0); uint16_t sample; bool encode_pcm = true; + + // lazy init of btstack_audio, fallback to sine + if ((generator == AUDIO_SOURCE_RECORDING) && (le_audio_demo_util_source_generator != AUDIO_SOURCE_RECORDING)){ + bool ok = le_audio_demo_util_source_recording_start(); + if (ok) { + le_audio_demo_util_source_generator = generator; + } else { + generator = AUDIO_SOURCE_SINE; + } + } + + // stop recording + if ((generator != AUDIO_SOURCE_RECORDING) && (le_audio_demo_util_source_generator == AUDIO_SOURCE_RECORDING)) { + le_audio_demo_util_source_recording_stop(); + } + switch (generator){ case AUDIO_SOURCE_COUNTER: encode_pcm = false; memset(le_audio_demo_source_iso_payload, le_audio_demo_source_iso_frame_counter++, sizeof(le_audio_demo_source_iso_payload)); break; case AUDIO_SOURCE_SINE: - // generate sine wave for all channels + // use pre-computed sine for all channels for (sample = 0 ; sample < le_audio_demo_source_num_samples_per_frame ; sample++){ uint8_t i; for (i = 0; i < le_audio_demo_source_num_channels; i++) { - int16_t value = sine_int16[le_audio_demo_source_sine_phases[i]] / 4; + int16_t value = sine_table[i*SINE_MAX_SAMPLES_AT_48KHZ + le_audio_demo_source_sine_phases[i]]; le_audio_demo_source_pcm[sample * le_audio_demo_source_num_channels + i] = value; - le_audio_demo_source_sine_phases[i] += le_audio_demo_source_sine_step * (1 + i); // second channel, double frequency - if (le_audio_demo_source_sine_phases[i] >= (sizeof(sine_int16) / sizeof(int16_t))) { + le_audio_demo_source_sine_phases[i] += 1; + if (le_audio_demo_source_sine_phases[i] >= le_audio_demo_source_sine_samples[i]) { le_audio_demo_source_sine_phases[i] = 0; } } @@ -231,13 +261,50 @@ void le_audio_demo_util_source_generate_iso_frame(le_audio_demo_source_generator case AUDIO_SOURCE_MODPLAYER: // mod player configured for stereo hxcmod_fillbuffer(&le_audio_demo_source_hxcmod_context, (unsigned short *) le_audio_demo_source_pcm, le_audio_demo_source_num_samples_per_frame, &le_audio_demo_source_hxcmod_trkbuf); + // stereo -> mono if (le_audio_demo_source_num_channels == 1) { - // stereo -> mono uint16_t i; for (i=0;i 2) { + int16_t i; + for (i = le_audio_demo_source_num_samples_per_frame - 1; i >= 0; i--) { + uint16_t channel_dst; + for (channel_dst=0; channel_dst < le_audio_demo_source_num_channels; channel_dst++){ + uint16_t channel_src = channel_dst & 1; + le_audio_demo_source_pcm[i * le_audio_demo_source_num_channels + channel_dst] = + le_audio_demo_source_pcm[i * 2 + channel_src]; + } + } + } + break; + case AUDIO_SOURCE_RECORDING: + if (le_audio_demo_source_recording_streaming == false){ + if (le_audio_demo_source_recording_stored_samples >= le_audio_demo_source_recording_prebuffer_samples){ + // start streaming audio + le_audio_demo_source_recording_streaming = true; + log_info("Streaming started"); + + } + } + if (le_audio_demo_source_recording_streaming){ + uint32_t bytes_needed = le_audio_demo_source_num_channels * le_audio_demo_source_num_samples_per_frame * 2; + if (btstack_ring_buffer_bytes_available(&le_audio_demo_source_recording_buffer) >= bytes_needed){ + uint32_t bytes_read; + btstack_ring_buffer_read(&le_audio_demo_source_recording_buffer, (uint8_t*) le_audio_demo_source_pcm, bytes_needed, &bytes_read); + btstack_assert(bytes_needed == bytes_needed); + log_info("Read %u samples per channel", le_audio_demo_source_num_samples_per_frame); + le_audio_demo_source_recording_stored_samples -= le_audio_demo_source_num_samples_per_frame; + } else { + le_audio_demo_source_recording_streaming = false; + log_info("Streaming underrun"); + } + } else { + memset(le_audio_demo_source_pcm, 0, sizeof(le_audio_demo_source_pcm)); + } break; default: btstack_unreachable(); @@ -250,7 +317,9 @@ void le_audio_demo_util_source_generate_iso_frame(le_audio_demo_source_generator le_audio_demo_source_lc3_encoder->encode_signed_16(&le_audio_demo_source_encoder_contexts[i], &le_audio_demo_source_pcm[i], le_audio_demo_source_num_channels, &le_audio_demo_source_iso_payload[i * MAX_LC3_FRAME_BYTES]); } } -}; + + le_audio_demo_util_source_generator = generator; +} void le_audio_demo_util_source_send(uint8_t stream_index, hci_con_handle_t con_handle){ btstack_assert(le_audio_demo_source_octets_per_frame != 0); @@ -282,4 +351,7 @@ void le_audio_demo_util_source_send(uint8_t stream_index, hci_con_handle_t con_h } void le_audio_demo_util_source_close(void){ + if (le_audio_demo_audio_source != NULL){ + le_audio_demo_audio_source->close(); + } } diff --git a/example/le_audio_demo_util_source.h b/example/le_audio_demo_util_source.h index c7425a962..d5426da4c 100644 --- a/example/le_audio_demo_util_source.h +++ b/example/le_audio_demo_util_source.h @@ -48,7 +48,8 @@ extern "C" { typedef enum { AUDIO_SOURCE_COUNTER, AUDIO_SOURCE_SINE, - AUDIO_SOURCE_MODPLAYER + AUDIO_SOURCE_MODPLAYER, + AUDIO_SOURCE_RECORDING, } le_audio_demo_source_generator; /**