From b6481930e453306221e736e087be634268a5ee6f Mon Sep 17 00:00:00 2001 From: borine <32966433+borine@users.noreply.github.com> Date: Thu, 7 Dec 2023 12:31:20 +0000 Subject: [PATCH] Support ALSA silence software parameters --- src/asound/bluealsa-pcm.c | 197 ++++++++++++++++++++++++++++++++++---- test/test-alsa-pcm.c | 55 +++++++++++ 2 files changed, 236 insertions(+), 16 deletions(-) diff --git a/src/asound/bluealsa-pcm.c b/src/asound/bluealsa-pcm.c index f376f1cca..9608b2e96 100644 --- a/src/asound/bluealsa-pcm.c +++ b/src/asound/bluealsa-pcm.c @@ -86,7 +86,8 @@ struct bluealsa_pcm { int event_fd; /* virtual hardware - ring buffer */ - char * _Atomic io_hw_buffer; + uint8_t *io_hw_buffer; + snd_pcm_channel_area_t *channel_areas; /* The IO thread is responsible for maintaining the hardware pointer * (pcm->io_hw_ptr), the application is responsible for the application * pointer (io->appl_ptr). These pointers should be atomic as they are @@ -99,6 +100,14 @@ struct bluealsa_pcm { atomic_int io_period_event; /* Permit the application to modify XRUN behavior. */ _Atomic snd_pcm_uframes_t io_stop_threshold; + _Atomic snd_pcm_uframes_t io_silence_threshold; + _Atomic snd_pcm_uframes_t io_silence_size; + + /* When the silence_size sw param is enabled, we maintain a region of + * silence in the ring buffer, defined by its start offset and length */ + snd_pcm_uframes_t silence_start; + snd_pcm_uframes_t silence_len; + pthread_t io_thread; bool io_started; @@ -228,6 +237,50 @@ static void io_thread_update_delay(struct bluealsa_pcm *pcm, } +static void bluealsa_update_silence_region(snd_pcm_ioplug_t *io, snd_pcm_uframes_t appl_ptr) { + struct bluealsa_pcm *pcm = io->private_data; + if (appl_ptr != pcm->silence_start) { + snd_pcm_sframes_t silence_lost = appl_ptr - pcm->silence_start; + if (silence_lost < 0) + silence_lost += pcm->io_hw_boundary; + if (silence_lost < (snd_pcm_sframes_t)pcm->silence_len) + pcm->silence_len -= silence_lost; + else + pcm->silence_len = 0; + + pcm->silence_start = appl_ptr; + } +} + +/** + * Overwrite a section of the ring buffer with silence. */ +static void bluealsa_silence(snd_pcm_ioplug_t *io, snd_pcm_uframes_t appl_ptr, snd_pcm_uframes_t hw_ptr) { + struct bluealsa_pcm *pcm = io->private_data; + snd_pcm_uframes_t frames = 0; + + if (pcm->io_silence_threshold > 0) { + snd_pcm_sframes_t hw_avail = appl_ptr - hw_ptr; + if (hw_avail < 0) + hw_avail += pcm->io_hw_boundary; + snd_pcm_uframes_t noise_distance = hw_avail + pcm->silence_len; + if (noise_distance < pcm->io_silence_threshold) { + frames = pcm->io_silence_threshold - noise_distance; + if (frames > pcm->io_silence_size) + frames = pcm->io_silence_size; + } + } + else { + frames = io->buffer_size + hw_ptr - appl_ptr; + if (frames > io->buffer_size) + frames %= io->buffer_size; + } + + snd_pcm_uframes_t offset = (pcm->silence_start + pcm->silence_len) % io->buffer_size; + pthread_mutex_lock(&pcm->mutex); + snd_pcm_areas_silence(pcm->channel_areas, offset, io->channels, frames, io->format); + pthread_mutex_unlock(&pcm->mutex); +} + /** * IO thread, which facilitates ring buffer. */ static void *io_thread(snd_pcm_ioplug_t *io) { @@ -289,7 +342,8 @@ static void *io_thread(snd_pcm_ioplug_t *io) { io_hw_ptr = pcm->io_hw_ptr; } - snd_pcm_uframes_t appl_avail = snd_pcm_ioplug_avail(io, io_hw_ptr, io->appl_ptr); + snd_pcm_sframes_t appl_ptr = io->appl_ptr; + snd_pcm_uframes_t appl_avail = snd_pcm_ioplug_avail(io, io_hw_ptr, appl_ptr); snd_pcm_uframes_t avail = appl_avail < io->buffer_size ? io->buffer_size - appl_avail : io->period_size; snd_pcm_uframes_t stop_threshold = io->state == SND_PCM_STATE_DRAINING ? io->buffer_size : pcm->io_stop_threshold; @@ -341,7 +395,7 @@ static void *io_thread(snd_pcm_ioplug_t *io) { /* IO operation size in bytes */ size_t len = chunk * pcm->frame_size; - char *head = pcm->io_hw_buffer + offset * pcm->frame_size; + uint8_t *head = pcm->io_hw_buffer + offset * pcm->frame_size; ssize_t ret = 0; if (io->stream == SND_PCM_STREAM_CAPTURE) { @@ -369,6 +423,8 @@ static void *io_thread(snd_pcm_ioplug_t *io) { } else { + uint8_t *start = head; + /* Perform atomic write - see the explanation above. */ do { if ((ret = write(pcm->ba_pcm_fd, head, len)) == -1) { @@ -383,6 +439,11 @@ static void *io_thread(snd_pcm_ioplug_t *io) { len -= ret; } while (len != 0); + if (pcm->io_silence_size >= pcm->io_hw_boundary) { + /* Special case: fill just-written buffer frames with silence.*/ + snd_pcm_format_set_silence(io->format, start, frames * io->channels); + } + } frames_transfered += chunk; @@ -393,10 +454,17 @@ static void *io_thread(snd_pcm_ioplug_t *io) { io_thread_update_delay(pcm, io_hw_ptr); - /* synchronize playback time */ - if (io->stream == SND_PCM_STREAM_PLAYBACK) + if (io->stream == SND_PCM_STREAM_PLAYBACK) { + /* synchronize playback time */ asrsync_sync(&asrs, frames); + /* Apply silence sw parameter settings */ + if (io->state == SND_PCM_STATE_RUNNING && + pcm->io_silence_size > 0 && + pcm->io_silence_size < pcm->io_hw_boundary) + bluealsa_silence(io, appl_ptr, io_hw_ptr); + } + /* Make the new HW pointer value visible to the ioplug. */ pcm->io_hw_ptr = io_hw_ptr; @@ -438,6 +506,15 @@ static int bluealsa_start(snd_pcm_ioplug_t *io) { if (io->stream == SND_PCM_STREAM_PLAYBACK && io->appl_ptr == 0 && pcm->io_stop_threshold < pcm->io_hw_boundary) return -EPIPE; + /* Special case: fill unused portion of buffer with silence.*/ + if (io->stream == SND_PCM_STREAM_PLAYBACK && + pcm->io_silence_size >= pcm->io_hw_boundary && + pcm->io_silence_threshold == 0) { + const size_t offset = io->appl_ptr * pcm->frame_size; + unsigned int samples = (io->buffer_size - io->appl_ptr) * io->channels; + snd_pcm_format_set_silence(io->format, pcm->io_hw_buffer + offset, samples); + } + /* If the IO thread is already started, skip thread creation. Otherwise, * we might end up with a bunch of IO threads reading or writing to the * same FIFO simultaneously. Instead, just send resume signal. */ @@ -510,11 +587,14 @@ static snd_pcm_sframes_t bluealsa_pointer(snd_pcm_ioplug_t *io) { if (!pcm->connected) snd_pcm_ioplug_set_state(io, SND_PCM_STATE_DISCONNECTED); -#ifndef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA - if (pcm->io_hw_ptr != -1) - return pcm->io_hw_ptr % io->buffer_size; -#endif - return pcm->io_hw_ptr; + snd_pcm_sframes_t hw_ptr = pcm->io_hw_ptr; + if (hw_ptr == -1) + return hw_ptr; + + #ifndef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA + return hw_ptr % io->buffer_size; + #endif + return hw_ptr; } static int bluealsa_close(snd_pcm_ioplug_t *io) { @@ -626,6 +706,18 @@ static int bluealsa_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) pcm->frame_size = (snd_pcm_format_physical_width(io->format) * io->channels) / 8; + if ((pcm->io_hw_buffer = malloc(io->buffer_size * pcm->frame_size)) == NULL) + return -ENOMEM; + + pcm->channel_areas = malloc(sizeof(snd_pcm_channel_area_t) * io->channels); + snd_pcm_channel_area_t *area = pcm->channel_areas; + unsigned channel; + for (channel = 0; channel < io->channels; ++channel, ++area) { + area->addr = pcm->io_hw_buffer; + area->first = channel * snd_pcm_format_physical_width(io->format); + area->step = pcm->frame_size * 8; + } + DBusError err = DBUS_ERROR_INIT; if (!ba_dbus_pcm_open(&pcm->dbus_ctx, pcm->ba_pcm.pcm_path, &pcm->ba_pcm_fd, &pcm->ba_pcm_ctrl_fd, &err)) { @@ -679,6 +771,10 @@ static int bluealsa_hw_free(snd_pcm_ioplug_t *io) { pcm->ba_pcm_ctrl_fd = -1; pcm->connected = false; + free(pcm->io_hw_buffer); + free(pcm->channel_areas); + pcm->io_hw_buffer = NULL; + pcm->channel_areas = NULL; return rv == 0 ? 0 : -errno; } @@ -690,12 +786,38 @@ static int bluealsa_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) snd_pcm_sw_params_get_boundary(params, &boundary); pcm->io_hw_boundary = boundary; + snd_pcm_uframes_t silence_threshold; + snd_pcm_sw_params_get_silence_threshold(params, &silence_threshold); + snd_pcm_uframes_t silence_size; + snd_pcm_sw_params_get_silence_size(params, &silence_size); + if (silence_size >= boundary) { + if (silence_threshold != 0) + return -EINVAL; + } + else if (silence_size > silence_threshold) + return -EINVAL; + + if (silence_threshold != pcm->io_silence_threshold) { + debug2("Changing SW silence threshold: %zu -> %zu", pcm->io_silence_threshold, silence_threshold); + pcm->io_silence_threshold = silence_threshold; + } + if (silence_size != pcm->io_silence_size) { + debug2("Changing SW silence size: %zu -> %zu", pcm->io_silence_size, silence_size); + pcm->io_silence_size = silence_size; + if (io->stream == SND_PCM_STREAM_PLAYBACK && pcm->io_silence_size >= pcm->io_hw_boundary) { + snd_pcm_sframes_t appl_ptr = io->appl_ptr; + bluealsa_update_silence_region(io, appl_ptr); + bluealsa_silence(io, appl_ptr, pcm->io_hw_ptr); + } + } + snd_pcm_uframes_t avail_min; snd_pcm_sw_params_get_avail_min(params, &avail_min); if (avail_min != pcm->io_avail_min) { debug2("Changing SW avail min: %zu -> %zu", pcm->io_avail_min, avail_min); pcm->io_avail_min = avail_min; } + snd_pcm_uframes_t stop_threshold; snd_pcm_sw_params_get_stop_threshold(params, &stop_threshold); if (stop_threshold >= pcm->io_hw_boundary) @@ -727,12 +849,10 @@ static int bluealsa_prepare(snd_pcm_ioplug_t *io) { /* initialize ring buffer */ pcm->io_hw_ptr = 0; - /* The ioplug allocates and configures its channel area buffer when the - * HW parameters are fixed, but after calling bluealsa_hw_params(). So, - * this is the earliest opportunity for us to safely cache the ring - * buffer start address. */ - const snd_pcm_channel_area_t *areas = snd_pcm_ioplug_mmap_areas(io); - pcm->io_hw_buffer = (char *)areas->addr + areas->first / 8; + if (pcm->io_silence_size >= pcm->io_hw_boundary) { + /* Special case: fill buffer with silence.*/ + snd_pcm_format_set_silence(io->format, pcm->io_hw_buffer, io->buffer_size * io->channels); + } /* Indicate that our PCM is ready for IO, even though is is not 100% * true - the IO thread may not be running yet. Applications using @@ -1160,6 +1280,50 @@ static int bluealsa_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfd, return -ENODEV; } +snd_pcm_sframes_t bluealsa_transfer(snd_pcm_ioplug_t *io, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t size) { + struct bluealsa_pcm *pcm = io->private_data; + int result; + + pthread_mutex_lock(&pcm->mutex); + + if (io->stream == SND_PCM_STREAM_CAPTURE) { + result = snd_pcm_areas_copy_wrap( + areas, + offset, + size + offset, + pcm->channel_areas, + io->appl_ptr % io->buffer_size, + io->buffer_size, + io->channels, + size, + io->format); + } + else { + result = snd_pcm_areas_copy_wrap( + pcm->channel_areas, + io->appl_ptr % io->buffer_size, + io->buffer_size, + areas, + offset, + size + offset, + io->channels, + size, + io->format); + + if (result > 0) { + /* This transfer may have overwritten part of our silence region */ + snd_pcm_uframes_t new_appl_ptr = io->appl_ptr + result; + if (new_appl_ptr >= pcm->io_hw_boundary) + new_appl_ptr -= pcm->io_hw_boundary; + + bluealsa_update_silence_region(io, new_appl_ptr); + } + } + pthread_mutex_unlock(&pcm->mutex); + + return result < 0 ? result : (snd_pcm_sframes_t) size; +} + static const snd_pcm_ioplug_callback_t bluealsa_callback = { .start = bluealsa_start, .stop = bluealsa_stop, @@ -1176,6 +1340,7 @@ static const snd_pcm_ioplug_callback_t bluealsa_callback = { .poll_descriptors_count = bluealsa_poll_descriptors_count, .poll_descriptors = bluealsa_poll_descriptors, .poll_revents = bluealsa_poll_revents, + .transfer = bluealsa_transfer, }; static int str2bdaddr(const char *str, bdaddr_t *ba) { diff --git a/test/test-alsa-pcm.c b/test/test-alsa-pcm.c index 59558b013..d0a1666e0 100644 --- a/test/test-alsa-pcm.c +++ b/test/test-alsa-pcm.c @@ -1297,6 +1297,60 @@ CK_START_TEST(test_playback_stop_threshold) { } CK_END_TEST +CK_START_TEST(test_playback_silence) { + + unsigned int buffer_time = 200000; + unsigned int period_time = 25000; + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + struct spawn_process sp_ba_mock; + snd_pcm_t *pcm = NULL; + + ck_assert_int_eq(test_pcm_open(&sp_ba_mock, &pcm, SND_PCM_STREAM_PLAYBACK), 0); + ck_assert_int_eq(set_hw_params(pcm, pcm_format, pcm_channels, pcm_sampling, + &buffer_time, &period_time), 0); + ck_assert_int_eq(snd_pcm_get_params(pcm, &buffer_size, &period_size), 0); + ck_assert_int_eq(snd_pcm_prepare(pcm), 0); + + snd_pcm_sw_params_t *sw_params; + snd_pcm_sw_params_alloca(&sw_params); + ck_assert_int_eq(snd_pcm_sw_params_current(pcm, sw_params), 0); + + /* Set silence_size greater than silence_threshold */ + ck_assert_int_eq(snd_pcm_sw_params_set_silence_size(pcm, sw_params, 2 * period_size), 0); + ck_assert_int_eq(snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, period_size), 0); + + /* check the params are rejected */ + ck_assert_int_eq(snd_pcm_sw_params(pcm, sw_params), -EINVAL); + + /* set silence_size to boundary with silence threshold non-zero */ + snd_pcm_uframes_t boundary; + ck_assert_int_eq(snd_pcm_sw_params_get_boundary(sw_params, &boundary), 0); + ck_assert_int_eq(snd_pcm_sw_params_set_silence_size(pcm, sw_params, boundary), 0); + ck_assert_int_eq(snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, period_size), 0); + + /* check the params are rejected */ + ck_assert_int_eq(snd_pcm_sw_params(pcm, sw_params), -EINVAL); + + /* set silence_size to boundary with silence threshold zero */ + ck_assert_int_eq(snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, 0), 0); + ck_assert_int_eq(snd_pcm_sw_params_set_silence_size(pcm, sw_params, boundary), 0); + ck_assert_int_eq(snd_pcm_sw_params(pcm, sw_params), 0); + +/* test here ?? */ + + /* set silence_size and silence threshold to period_size */ + ck_assert_int_eq(snd_pcm_sw_params_set_silence_threshold(pcm, sw_params, period_size), 0); + ck_assert_int_eq(snd_pcm_sw_params_set_silence_size(pcm, sw_params, period_size), 0); + ck_assert_int_eq(snd_pcm_sw_params(pcm, sw_params), 0); + +/* test here ?? */ + + ck_assert_int_eq(test_pcm_close(&sp_ba_mock, pcm), 0); + +} CK_END_TEST + + int main(int argc, char *argv[], char *envp[]) { preload(argc, argv, envp, ".libs/aloader.so"); @@ -1389,6 +1443,7 @@ int main(int argc, char *argv[], char *envp[]) { tcase_add_test(tc, ba_test_playback_device_unplug); tcase_add_test(tc, test_playback_period_event); tcase_add_test(tc, test_playback_stop_threshold); + tcase_add_test(tc, test_playback_silence); suite_add_tcase(s, tc); }