Skip to content

Commit

Permalink
Time sync for PCM IO thread
Browse files Browse the repository at this point in the history
Due to naive thinking, I thought, that sampling rate synchronization
is not required for the PCM IO thread. After all, the bluealsa keeps
everything in sync, and the FIFO provides convenient sync (blocking)
mechanism. NOPE!

Writing to the FIFO blocks, BUT only if the FIFO buffer is full. If
the ring buffer size is smaller than the FIFO buffer we will write
out all data from the ring buffer instantly, and we will try to write
even more... which will cause under-run (which is visible since the
commit b3e8ef3). So, it is clear, that sync is also required for the
PCM IO thread. Furthermore, this commit adds dynamic (sampling rate
based) ring buffer size constraints in order to further enhance CPU
utilization.
  • Loading branch information
arkq committed Apr 9, 2017
1 parent cf7aee5 commit ca72c1e
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/asound/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ libasound_module_ctl_bluealsa_la_SOURCES = \
libasound_module_pcm_bluealsa_la_SOURCES = \
../shared/ctl-client.c \
../shared/log.c \
../shared/rt.c \
bluealsa-pcm.c

asound_module_ctldir = @ALSA_PLUGIN_DIR@
Expand Down
29 changes: 22 additions & 7 deletions src/asound/bluealsa-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@
#include "shared/ctl-client.h"
#include "shared/ctl-proto.h"
#include "shared/log.h"


/* Helper macro for obtaining the size of a static array. */
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#include "shared/rt.h"


struct bluealsa_pcm {
Expand Down Expand Up @@ -100,6 +97,9 @@ static void *io_thread(void *arg) {
}
}

struct asrsync asrs;
asrsync_init(asrs, io->rate);

debug("Starting IO loop");
for (;;) {

Expand All @@ -111,6 +111,7 @@ static void *io_thread(void *arg) {
default:
debug("IO thread paused: %d", io->state);
sigwait(&sigset, &tmp);
asrsync_init(asrs, io->rate);
debug("IO thread resumed: %d", io->state);
}

Expand Down Expand Up @@ -183,6 +184,8 @@ static void *io_thread(void *arg) {
}
while (len != 0);

/* synchronize playback time */
asrsync_sync(&asrs, frames);
}

sync:
Expand Down Expand Up @@ -523,19 +526,31 @@ static int bluealsa_set_hw_constraint(struct bluealsa_pcm *pcm) {
debug("Setting constraints");

if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS,
ARRAY_SIZE(accesses), accesses)) < 0)
sizeof(accesses) / sizeof(*accesses), accesses)) < 0)
return err;

if ((err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT,
ARRAY_SIZE(formats), formats)) < 0)
sizeof(formats) / sizeof(*formats), formats)) < 0)
return err;

if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS,
2, 1024)) < 0)
return err;

/* In order to prevent audio tearing and minimize CPU utilization, we're
* going to setup buffer size constraints. These limits are derived from
* the transport sampling rate and the number of channels, so the buffer
* "time" size will be constant. The minimal period size and buffer size
* are respectively 10 ms and 200 ms. Upper limits are not constraint. */
unsigned int min_p = pcm->transport->sampling * 10 / 1000 * pcm->transport->channels * 2;
unsigned int min_b = pcm->transport->sampling * 200 / 1000 * pcm->transport->channels * 2;

if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES,
128, 1024 * 4)) < 0)
min_p, 1024 * 16)) < 0)
return err;

if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES,
min_b, 1024 * 1024 * 16)) < 0)
return err;

if ((err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS,
Expand Down
121 changes: 110 additions & 11 deletions test/test-pcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,37 @@
#include "../src/shared/log.h"


static int snd_pcm_open_bluealsa(snd_pcm_t **pcmp, snd_pcm_stream_t stream, int mode) {

static char buffer[] =
"pcm.bluealsa {\n"
" type bluealsa\n"
" interface \"hci-xxx\"\n"
" device \"12:34:56:78:9A:BC\"\n"
" profile \"a2dp\"\n"
" delay 0\n"
"}\n";

snd_config_t *conf = NULL;
snd_input_t *input = NULL;
int err;

if ((err = snd_config_top(&conf)) < 0)
goto fail;
if ((err = snd_input_buffer_open(&input, buffer, sizeof(buffer) - 1)) != 0)
goto fail;
if ((err = snd_config_load(conf, input)) != 0)
goto fail;
err = snd_pcm_open_lconf(pcmp, "bluealsa", stream, mode, conf);

fail:
if (conf != NULL)
snd_config_unref(conf);
if (input != NULL)
snd_input_close(input);
return err;
}

static int set_hw_params(snd_pcm_t *pcm, int channels, int rate,
unsigned int *buffer_time, unsigned int *period_time) {

Expand All @@ -27,23 +58,36 @@ static int set_hw_params(snd_pcm_t *pcm, int channels, int rate,
int err;

snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(pcm, params);

if ((err = snd_pcm_hw_params_any(pcm, params)) != 0)
return err;
if ((err = snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED)) != 0)
if ((err = snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED)) != 0) {
error("snd_pcm_hw_params_set_access: %s", snd_strerror(err));
return err;
if ((err = snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S16_LE)) != 0)
}
if ((err = snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S16_LE)) != 0) {
error("snd_pcm_hw_params_set_format: %s", snd_strerror(err));
return err;
if ((err = snd_pcm_hw_params_set_channels(pcm, params, channels)) != 0)
}
if ((err = snd_pcm_hw_params_set_channels(pcm, params, channels)) != 0) {
error("snd_pcm_hw_params_set_channels: %s", snd_strerror(err));
return err;
if ((err = snd_pcm_hw_params_set_rate(pcm, params, rate, 0)) != 0)
}
if ((err = snd_pcm_hw_params_set_rate(pcm, params, rate, 0)) != 0) {
error("snd_pcm_hw_params_set_rate: %s", snd_strerror(err));
return err;
if ((err = snd_pcm_hw_params_set_buffer_time_near(pcm, params, buffer_time, &dir)) != 0)
}
if ((err = snd_pcm_hw_params_set_buffer_time_near(pcm, params, buffer_time, &dir)) != 0) {
error("snd_pcm_hw_params_set_buffer_time_near: %s", snd_strerror(err));
return err;
if ((err = snd_pcm_hw_params_set_period_time_near(pcm, params, period_time, &dir)) != 0)
}
if ((err = snd_pcm_hw_params_set_period_time_near(pcm, params, period_time, &dir)) != 0) {
error("snd_pcm_hw_params_set_period_time_near: %s", snd_strerror(err));
return err;
if ((err = snd_pcm_hw_params(pcm, params)) != 0)
}
if ((err = snd_pcm_hw_params(pcm, params)) != 0) {
error("snd_pcm_hw_params: %s", snd_strerror(err));
return err;
}

return 0;
}
Expand All @@ -70,6 +114,59 @@ static int set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t buffer_size, snd_pcm_
return 0;
}

int test_hw_constraints(void) {

/* hard-coded values used in the test-server */
const unsigned int server_channels = 2;
const unsigned int server_rate = 44100;

snd_pcm_t *pcm = NULL;
snd_pcm_hw_params_t *params;
int d;

assert(snd_pcm_open_bluealsa(&pcm, SND_PCM_STREAM_PLAYBACK, 0) == 0);

snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(pcm, params);

assert(snd_pcm_hw_params_test_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED) == 0);
assert(snd_pcm_hw_params_test_access(pcm, params, SND_PCM_ACCESS_MMAP_INTERLEAVED) == 0);
assert(snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED) == 0);

snd_pcm_format_t format;
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_format_first(pcm, params, &format) == 0 && format == SND_PCM_FORMAT_S16_LE);
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_format_last(pcm, params, &format) == 0 && format == SND_PCM_FORMAT_S16_LE);

unsigned int channels;
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_channels_first(pcm, params, &channels) == 0 && channels == server_channels);
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_channels_last(pcm, params, &channels) == 0 && channels == server_channels);

unsigned int rate;
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_rate_first(pcm, params, &rate, &d) == 0 && rate == server_rate && d == 0);
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_rate_last(pcm, params, &rate, &d) == 0 && rate == server_rate && d == 0);

unsigned int periods;
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_periods_first(pcm, params, &periods, &d) == 0 && periods == 3 && d == 0);
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_periods_last(pcm, params, &periods, &d) == 0 && periods == 1024 && d == 0);

unsigned int time;
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_buffer_time_first(pcm, params, &time, &d) == 0 && time == 200000 && d == 0);
snd_pcm_hw_params_any(pcm, params);
assert(snd_pcm_hw_params_set_buffer_time_last(pcm, params, &time, &d) == 0 && time == 95108934 && d == 1);

assert(snd_pcm_close(pcm) == 0);
return 0;
}

int test_playback(void) {

int pcm_channels = 2;
Expand All @@ -82,7 +179,7 @@ int test_playback(void) {
snd_pcm_uframes_t period_size;
snd_pcm_sframes_t delay;

assert(snd_pcm_open(&pcm, "bluealsa:HCI=hci-xxx,DEV=12:34:56:78:9A:BC,DELAY=0", SND_PCM_STREAM_PLAYBACK, 0) == 0);
assert(snd_pcm_open_bluealsa(&pcm, SND_PCM_STREAM_PLAYBACK, 0) == 0);
assert(set_hw_params(pcm, pcm_channels, pcm_sampling, &pcm_buffer_time, &pcm_period_time) == 0);
assert(snd_pcm_get_params(pcm, &buffer_size, &period_size) == 0);
assert(set_sw_params(pcm, buffer_size, period_size) == 0);
Expand All @@ -103,7 +200,7 @@ int test_playback(void) {
/* check if playback was not started and if delay is correctly calculated */
assert(snd_pcm_state(pcm) == SND_PCM_STATE_PREPARED);
assert(snd_pcm_delay(pcm, &delay) == 0);
assert(delay == 21042);
assert(delay == 18375);

/* start playback - start threshold will be exceeded */
x = snd_pcm_sine_s16le(buffer, period_size * pcm_channels, pcm_channels, x, 441.0 / pcm_sampling);
Expand Down Expand Up @@ -137,6 +234,7 @@ int test_playback(void) {
}
assert(snd_pcm_state(pcm) == SND_PCM_STATE_RUNNING);

assert(snd_pcm_close(pcm) == 0);
return 0;
}

Expand All @@ -156,6 +254,7 @@ int main(int argc, char *argv[]) {
assert(posix_spawn(&ba_pid, ba_path, NULL, NULL, ba_argv, NULL) == 0);
usleep(100000);

test_run(test_hw_constraints);
test_run(test_playback);
test_run(test_capture);

Expand Down

0 comments on commit ca72c1e

Please sign in to comment.