axfer: add support for non-blocking operation

In alsa-lib PCM API, snd_pcm_read[i|n]() and snd_pcm_write[i|n] can be
used with non-blocking mode. This is available when SND_PCM_NONBLOCK is
used as 'mode' argument for a call of snd_pcm_open().

This commit adds support this type of operation. To reduce CPU usage, this
commit uses 'snd_pcm_wait()' to wait for event notification.

Below lines are examples to execute:
$ axfer transfer -N -P -d 2 -D hw:0,3 /dev/urandom -f dat -vvv
$ axfer transfer -N -C -d 2 -D hw:1,0 /dev/null -r 48000 -vvv

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Sakamoto 2018-11-13 15:41:34 +09:00 committed by Takashi Iwai
parent 29c6076029
commit c46d46a436
3 changed files with 113 additions and 6 deletions

View file

@ -117,6 +117,51 @@ error:
return err; return err;
} }
static int r_process_frames_nonblocking(struct libasound_state *state,
snd_pcm_state_t status,
unsigned int *frame_count,
struct mapper_context *mapper,
struct container_context *cntrs)
{
snd_pcm_sframes_t avail;
snd_pcm_uframes_t avail_count;
int err = 0;
if (status != SND_PCM_STATE_RUNNING) {
err = snd_pcm_start(state->handle);
if (err < 0)
goto error;
}
// Wait for hardware IRQ when no available space.
err = snd_pcm_wait(state->handle, -1);
if (err < 0)
goto error;
// Check available space on the buffer.
avail = snd_pcm_avail(state->handle);
if (avail < 0) {
err = avail;
goto error;
}
avail_count = (snd_pcm_uframes_t)avail;
if (avail_count == 0) {
// Let's go to a next iteration.
err = 0;
goto error;
}
err = read_frames(state, frame_count, avail_count, mapper, cntrs);
if (err < 0)
goto error;
return 0;
error:
*frame_count = 0;
return err;
}
static int write_frames(struct libasound_state *state, static int write_frames(struct libasound_state *state,
unsigned int *frame_count, unsigned int avail_count, unsigned int *frame_count, unsigned int avail_count,
struct mapper_context *mapper, struct mapper_context *mapper,
@ -231,6 +276,48 @@ error:
return err; return err;
} }
static int w_process_frames_nonblocking(struct libasound_state *state,
snd_pcm_state_t status,
unsigned int *frame_count,
struct mapper_context *mapper,
struct container_context *cntrs)
{
snd_pcm_sframes_t avail;
unsigned int avail_count;
int err;
// Wait for hardware IRQ when no left space.
err = snd_pcm_wait(state->handle, -1);
if (err < 0)
goto error;
// Check available space on the buffer.
avail = snd_pcm_avail(state->handle);
if (avail < 0) {
err = avail;
goto error;
}
avail_count = (unsigned int)avail;
if (avail_count == 0) {
// Let's go to a next iteration.
err = 0;
goto error;
}
err = write_frames(state, frame_count, avail_count, mapper, cntrs);
if (err < 0)
goto error;
// NOTE: The substream starts automatically when the accumulated number
// of queued data frame exceeds start_threshold.
return 0;
error:
*frame_count = 0;
return err;
}
static int irq_rw_pre_process(struct libasound_state *state) static int irq_rw_pre_process(struct libasound_state *state)
{ {
struct rw_closure *closure = state->private_data; struct rw_closure *closure = state->private_data;
@ -267,10 +354,17 @@ static int irq_rw_pre_process(struct libasound_state *state)
if (err < 0) if (err < 0)
return err; return err;
if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) if (snd_pcm_stream(state->handle) == SND_PCM_STREAM_CAPTURE) {
if (state->nonblock)
closure->process_frames = r_process_frames_nonblocking;
else
closure->process_frames = r_process_frames_blocking; closure->process_frames = r_process_frames_blocking;
} else {
if (state->nonblock)
closure->process_frames = w_process_frames_nonblocking;
else else
closure->process_frames = w_process_frames_blocking; closure->process_frames = w_process_frames_blocking;
}
return 0; return 0;
} }

View file

@ -14,9 +14,10 @@ enum no_short_opts {
OPT_FATAL_ERRORS = 200, OPT_FATAL_ERRORS = 200,
}; };
#define S_OPTS "D:" #define S_OPTS "D:N"
static const struct option l_opts[] = { static const struct option l_opts[] = {
{"device", 1, 0, 'D'}, {"device", 1, 0, 'D'},
{"nonblock", 0, 0, 'N'},
// For debugging. // For debugging.
{"fatal-errors", 0, 0, OPT_FATAL_ERRORS}, {"fatal-errors", 0, 0, OPT_FATAL_ERRORS},
}; };
@ -46,6 +47,8 @@ static int xfer_libasound_parse_opt(struct xfer_context *xfer, int key,
if (key == 'D') if (key == 'D')
state->node_literal = arg_duplicate_string(optarg, &err); state->node_literal = arg_duplicate_string(optarg, &err);
else if (key == 'N')
state->nonblock = true;
else if (key == OPT_FATAL_ERRORS) else if (key == OPT_FATAL_ERRORS)
state->finish_at_xrun = true; state->finish_at_xrun = true;
else else
@ -91,10 +94,14 @@ static int set_access_hw_param(struct libasound_state *state)
static int open_handle(struct xfer_context *xfer) static int open_handle(struct xfer_context *xfer)
{ {
struct libasound_state *state = xfer->private_data; struct libasound_state *state = xfer->private_data;
int mode = 0;
int err; int err;
if (state->nonblock)
mode |= SND_PCM_NONBLOCK;
err = snd_pcm_open(&state->handle, state->node_literal, xfer->direction, err = snd_pcm_open(&state->handle, state->node_literal, xfer->direction,
0); mode);
if (err < 0) { if (err < 0) {
logging(state, "Fail to open libasound PCM node for %s: %s\n", logging(state, "Fail to open libasound PCM node for %s: %s\n",
snd_pcm_stream_name(xfer->direction), snd_pcm_stream_name(xfer->direction),
@ -378,7 +385,12 @@ static void xfer_libasound_post_process(struct xfer_context *xfer)
logging(state, "snd_pcm_drop(): %s\n", logging(state, "snd_pcm_drop(): %s\n",
snd_strerror(err)); snd_strerror(err));
} else { } else {
// TODO: this is a bug in kernel land.
if (state->nonblock)
snd_pcm_nonblock(state->handle, 0);
err = snd_pcm_drain(state->handle); err = snd_pcm_drain(state->handle);
if (state->nonblock)
snd_pcm_nonblock(state->handle, 1);
if (err < 0) if (err < 0)
logging(state, "snd_pcm_drain(): %s\n", logging(state, "snd_pcm_drain(): %s\n",
snd_strerror(err)); snd_strerror(err));

View file

@ -31,6 +31,7 @@ struct libasound_state {
char *node_literal; char *node_literal;
bool finish_at_xrun:1; bool finish_at_xrun:1;
bool nonblock:1;
}; };
// For internal use in 'libasound' module. // For internal use in 'libasound' module.