From dcc88ffaa7eca209b39b0ae5f8be67105020c283 Mon Sep 17 00:00:00 2001 From: Abramo Bagnara Date: Wed, 11 Oct 2000 12:37:27 +0000 Subject: [PATCH] Added support for async. Added error callback (and begun to use it). First implementation of pcm_share --- aserver/aserver.c | 3 + include/aserver.h | 9 +- include/pcm.h | 3 + src/pcm/Makefile.am | 3 +- src/pcm/pcm.c | 20 +- src/pcm/pcm_adpcm.c | 1 + src/pcm/pcm_alaw.c | 1 + src/pcm/pcm_client.c | 17 + src/pcm/pcm_file.c | 7 + src/pcm/pcm_hw.c | 143 +++++- src/pcm/pcm_linear.c | 1 + src/pcm/pcm_local.h | 7 + src/pcm/pcm_mulaw.c | 1 + src/pcm/pcm_multi.c | 8 + src/pcm/pcm_plug.c | 7 + src/pcm/pcm_plugin.c | 6 + src/pcm/pcm_plugin.h | 1 + src/pcm/pcm_rate.c | 1 + src/pcm/pcm_route.c | 1 + src/pcm/pcm_share.c | 1155 ++++++++++++++++++++++++++++++++++++++++++ 20 files changed, 1363 insertions(+), 32 deletions(-) create mode 100644 src/pcm/pcm_share.c diff --git a/aserver/aserver.c b/aserver/aserver.c index eef9a675..7f54885e 100644 --- a/aserver/aserver.c +++ b/aserver/aserver.c @@ -353,6 +353,9 @@ int pcm_shm_cmd(client_t *client) ctrl->cmd = 0; pcm = client->device.pcm.handle; switch (cmd) { + case SND_PCM_IOCTL_ASYNC: + ctrl->result = snd_pcm_async(pcm, ctrl->u.async.sig, ctrl->u.async.pid); + break; case SND_PCM_IOCTL_INFO: ctrl->result = snd_pcm_info(pcm, &ctrl->u.info); break; diff --git a/include/aserver.h b/include/aserver.h index 138534f5..d48caf52 100644 --- a/include/aserver.h +++ b/include/aserver.h @@ -26,14 +26,19 @@ #define SND_PCM_IOCTL_MUNMAP_DATA _IO ('A', 0xf4) #define SND_PCM_IOCTL_MUNMAP_CONTROL _IO ('A', 0xf5) #define SND_PCM_IOCTL_MUNMAP_STATUS _IO ('A', 0xf6) -#define SND_PCM_IOCTL_MMAP_FORWARD _IOW('A', 0xf7, size_t) +#define SND_PCM_IOCTL_MMAP_FORWARD _IO ('A', 0xf7) #define SND_PCM_IOCTL_AVAIL_UPDATE _IO ('A', 0xf8) -#define SND_PCM_IOCTL_CLOSE _IO ('A', 0xf9) +#define SND_PCM_IOCTL_ASYNC _IO ('A', 0xf9) +#define SND_PCM_IOCTL_CLOSE _IO ('A', 0xfa) typedef struct { long result; int cmd; union { + struct { + int sig; + pid_t pid; + } async; snd_pcm_info_t info; snd_pcm_params_t params; snd_pcm_params_info_t params_info; diff --git a/include/pcm.h b/include/pcm.h index 55c2c7ce..1671d9e2 100644 --- a/include/pcm.h +++ b/include/pcm.h @@ -6,6 +6,7 @@ ****************************************************************************/ #define SND_PCM_NONBLOCK 0x0001 +#define SND_PCM_ASYNC 0x0002 #ifdef __cplusplus extern "C" { @@ -117,6 +118,7 @@ typedef enum { SND_PCM_TYPE_LBSERVER, } snd_pcm_type_t; +extern void (*snd_pcm_error)(const char *file, int line, const char *function, const char *fmt, ...); int snd_pcm_open(snd_pcm_t **handle, char *name, int stream, int mode); @@ -136,6 +138,7 @@ snd_pcm_type_t snd_pcm_type(snd_pcm_t *pcm); int snd_pcm_close(snd_pcm_t *pcm); int snd_pcm_poll_descriptor(snd_pcm_t *pcm); int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock); +int snd_pcm_async(snd_pcm_t *pcm, int sig, pid_t pid); int snd_pcm_info(snd_pcm_t *pcm, snd_pcm_info_t *info); int snd_pcm_params_info(snd_pcm_t *pcm, snd_pcm_params_info_t *info); int snd_pcm_params(snd_pcm_t *pcm, snd_pcm_params_t *params); diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am index 82da9063..e932d519 100644 --- a/src/pcm/Makefile.am +++ b/src/pcm/Makefile.am @@ -3,7 +3,8 @@ EXTRA_LTLIBRARIES = libpcm.la libpcm_la_SOURCES = pcm.c pcm_hw.c pcm_plugin.c pcm_linear.c pcm_route.c \ pcm_mulaw.c pcm_alaw.c pcm_adpcm.c pcm_rate.c pcm_plug.c \ - pcm_misc.c pcm_mmap.c pcm_multi.c pcm_client.c pcm_file.c + pcm_misc.c pcm_mmap.c pcm_multi.c pcm_client.c pcm_file.c \ + pcm_share.c noinst_HEADERS = pcm_local.h pcm_plugin.h all: libpcm.la diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c index e40e5e57..4288d3d5 100644 --- a/src/pcm/pcm.c +++ b/src/pcm/pcm.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -83,7 +84,7 @@ int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) { int err; assert(pcm); - if ((err = pcm->ops->nonblock(pcm->fast_op_arg, nonblock)) < 0) + if ((err = pcm->ops->nonblock(pcm->op_arg, nonblock)) < 0) return err; if (nonblock) pcm->mode |= SND_PCM_NONBLOCK; @@ -92,6 +93,12 @@ int snd_pcm_nonblock(snd_pcm_t *pcm, int nonblock) return 0; } +int snd_pcm_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + assert(pcm); + return pcm->ops->async(pcm->op_arg, sig, pid); +} + int snd_pcm_info(snd_pcm_t *pcm, snd_pcm_info_t *info) { assert(pcm && info); @@ -1037,3 +1044,14 @@ ssize_t snd_pcm_write_areas(snd_pcm_t *pcm, snd_pcm_channel_area_t *areas, return err; } +void snd_pcm_error_default(const char *file, int line, const char *function, const char *fmt, ...) +{ + va_list arg; + va_start(arg, fmt); + fprintf(stderr, "ALSA PCM lib %s:%i:(%s) ", file, line, function); + vfprintf(stderr, fmt, arg); + putc('\n', stderr); + va_end(arg); +} + +void (*snd_pcm_error)(const char *file, int line, const char *function, const char *fmt, ...) = snd_pcm_error_default; diff --git a/src/pcm/pcm_adpcm.c b/src/pcm/pcm_adpcm.c index 4c916ab3..56d9e76f 100644 --- a/src/pcm/pcm_adpcm.c +++ b/src/pcm/pcm_adpcm.c @@ -544,6 +544,7 @@ struct snd_pcm_ops snd_pcm_adpcm_ops = { channel_setup: snd_pcm_plugin_channel_setup, dump: snd_pcm_adpcm_dump, nonblock: snd_pcm_plugin_nonblock, + async: snd_pcm_plugin_async, mmap_status: snd_pcm_plugin_mmap_status, mmap_control: snd_pcm_plugin_mmap_control, mmap_data: snd_pcm_plugin_mmap_data, diff --git a/src/pcm/pcm_alaw.c b/src/pcm/pcm_alaw.c index 27f616e6..7fae5be0 100644 --- a/src/pcm/pcm_alaw.c +++ b/src/pcm/pcm_alaw.c @@ -412,6 +412,7 @@ struct snd_pcm_ops snd_pcm_alaw_ops = { channel_setup: snd_pcm_plugin_channel_setup, dump: snd_pcm_alaw_dump, nonblock: snd_pcm_plugin_nonblock, + async: snd_pcm_plugin_async, mmap_status: snd_pcm_plugin_mmap_status, mmap_control: snd_pcm_plugin_mmap_control, mmap_data: snd_pcm_plugin_mmap_data, diff --git a/src/pcm/pcm_client.c b/src/pcm/pcm_client.c index 2652ab1a..0222891e 100644 --- a/src/pcm/pcm_client.c +++ b/src/pcm/pcm_client.c @@ -175,6 +175,22 @@ static int snd_pcm_client_shm_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonb return 0; } +static int snd_pcm_client_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_client_t *client = pcm->private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_ASYNC; + ctrl->u.async.sig = sig; + if (pid == 0) + pid = getpid(); + ctrl->u.async.pid = pid; + err = snd_pcm_client_shm_action(pcm); + if (err < 0) + return err; + return ctrl->result; +} + static int snd_pcm_client_shm_info(snd_pcm_t *pcm, snd_pcm_info_t * info) { snd_pcm_client_t *client = pcm->private; @@ -550,6 +566,7 @@ struct snd_pcm_ops snd_pcm_client_ops = { channel_setup: snd_pcm_client_shm_channel_setup, dump: snd_pcm_client_dump, nonblock: snd_pcm_client_shm_nonblock, + async: snd_pcm_client_async, mmap_status: snd_pcm_client_shm_mmap_status, mmap_control: snd_pcm_client_shm_mmap_control, mmap_data: snd_pcm_client_shm_mmap_data, diff --git a/src/pcm/pcm_file.c b/src/pcm/pcm_file.c index 3e7ea2a7..0391554b 100644 --- a/src/pcm/pcm_file.c +++ b/src/pcm/pcm_file.c @@ -50,6 +50,12 @@ static int snd_pcm_file_nonblock(snd_pcm_t *pcm, int nonblock) return snd_pcm_nonblock(file->slave, nonblock); } +static int snd_pcm_file_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_file_t *file = pcm->private; + return snd_pcm_async(file->slave, sig, pid); +} + static int snd_pcm_file_info(snd_pcm_t *pcm, snd_pcm_info_t * info) { snd_pcm_file_t *file = pcm->private; @@ -342,6 +348,7 @@ struct snd_pcm_ops snd_pcm_file_ops = { channel_setup: snd_pcm_file_channel_setup, dump: snd_pcm_file_dump, nonblock: snd_pcm_file_nonblock, + async: snd_pcm_file_async, mmap_status: snd_pcm_file_mmap_status, mmap_control: snd_pcm_file_mmap_control, mmap_data: snd_pcm_file_mmap_data, diff --git a/src/pcm/pcm_hw.c b/src/pcm/pcm_hw.c index 9eb56579..30fd3750 100644 --- a/src/pcm/pcm_hw.c +++ b/src/pcm/pcm_hw.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,10 @@ #include #include "pcm_local.h" +#ifndef F_SETSIG +#define F_SETSIG 10 +#endif + typedef struct { int fd; int card, device, subdevice; @@ -44,9 +49,10 @@ static int snd_pcm_hw_close(snd_pcm_t *pcm) snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; free(hw); - if (fd >= 0) - if (close(fd)) - return -errno; + if (close(fd)) { + ERR("close failed\n"); + return -errno; + } return 0; } @@ -56,14 +62,53 @@ static int snd_pcm_hw_nonblock(snd_pcm_t *pcm, int nonblock) snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if ((flags = fcntl(fd, F_GETFL)) < 0) + if ((flags = fcntl(fd, F_GETFL)) < 0) { + ERR("F_GETFL failed"); return -errno; + } if (nonblock) flags |= O_NONBLOCK; else flags &= ~O_NONBLOCK; - if (fcntl(fd, F_SETFL, flags) < 0) + if (fcntl(fd, F_SETFL, flags) < 0) { + ERR("F_SETFL for O_NONBLOCK failed"); + return -errno; + } + return 0; +} + +static int snd_pcm_hw_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + long flags; + snd_pcm_hw_t *hw = pcm->private; + int fd = hw->fd; + + if ((flags = fcntl(fd, F_GETFL)) < 0) { + ERR("F_GETFL failed"); + return -errno; + } + if (sig >= 0) + flags |= O_ASYNC; + else + flags &= ~O_ASYNC; + if (fcntl(fd, F_SETFL, flags) < 0) { + ERR("F_SETFL for O_ASYNC failed"); return -errno; + } + if (sig < 0) + return 0; + if (sig == 0) + sig = SIGIO; + if (fcntl(fd, F_SETSIG, sig) < 0) { + ERR("F_SETSIG failed"); + return -errno; + } + if (pid == 0) + pid = getpid(); + if (fcntl(fd, F_SETOWN, pid) < 0) { + ERR("F_SETOWN failed"); + return -errno; + } return 0; } @@ -71,8 +116,10 @@ static int snd_pcm_hw_info(snd_pcm_t *pcm, snd_pcm_info_t * info) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_INFO, info) < 0) + if (ioctl(fd, SND_PCM_IOCTL_INFO, info) < 0) { + ERR("SND_PCM_IOCTL_INFO failed"); return -errno; + } return 0; } @@ -80,8 +127,10 @@ static int snd_pcm_hw_params_info(snd_pcm_t *pcm, snd_pcm_params_info_t * info) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_PARAMS_INFO, info) < 0) + if (ioctl(fd, SND_PCM_IOCTL_PARAMS_INFO, info) < 0) { + ERR("SND_PCM_IOCTL_PARAMS_INFO failed"); return -errno; + } return 0; } @@ -89,8 +138,10 @@ static int snd_pcm_hw_params(snd_pcm_t *pcm, snd_pcm_params_t * params) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_PARAMS, params) < 0) + if (ioctl(fd, SND_PCM_IOCTL_PARAMS, params) < 0) { + ERR("SND_PCM_IOCTL_PARAMS failed"); return -errno; + } return 0; } @@ -98,8 +149,10 @@ static int snd_pcm_hw_setup(snd_pcm_t *pcm, snd_pcm_setup_t * setup) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_SETUP, setup) < 0) + if (ioctl(fd, SND_PCM_IOCTL_SETUP, setup) < 0) { + ERR("SND_PCM_IOCTL_SETUP failed"); return -errno; + } if (setup->mmap_shape == SND_PCM_MMAP_UNSPECIFIED) { if (setup->xfer_mode == SND_PCM_XFER_INTERLEAVED) setup->mmap_shape = SND_PCM_MMAP_INTERLEAVED; @@ -115,8 +168,10 @@ static int snd_pcm_hw_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_CHANNEL_INFO, info) < 0) + if (ioctl(fd, SND_PCM_IOCTL_CHANNEL_INFO, info) < 0) { + ERR("SND_PCM_IOCTL_CHANNEL_INFO failed"); return -errno; + } return 0; } @@ -124,8 +179,10 @@ static int snd_pcm_hw_channel_params(snd_pcm_t *pcm, snd_pcm_channel_params_t * { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_CHANNEL_PARAMS, params) < 0) + if (ioctl(fd, SND_PCM_IOCTL_CHANNEL_PARAMS, params) < 0) { + ERR("SND_PCM_IOCTL_CHANNEL_PARAMS failed"); return -errno; + } return 0; } @@ -133,8 +190,10 @@ static int snd_pcm_hw_channel_setup(snd_pcm_t *pcm, snd_pcm_channel_setup_t * se { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_CHANNEL_SETUP, setup) < 0) + if (ioctl(fd, SND_PCM_IOCTL_CHANNEL_SETUP, setup) < 0) { + ERR("SND_PCM_IOCTL_CHANNEL_SETUP failed"); return -errno; + } if (hw->mmap_emulation) { if (pcm->setup.mmap_shape == SND_PCM_MMAP_INTERLEAVED) { setup->running_area.addr = pcm->mmap_data; @@ -157,8 +216,10 @@ static int snd_pcm_hw_status(snd_pcm_t *pcm, snd_pcm_status_t * status) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_STATUS, status) < 0) + if (ioctl(fd, SND_PCM_IOCTL_STATUS, status) < 0) { + ERR("SND_PCM_IOCTL_STATUS failed"); return -errno; + } return 0; } @@ -171,8 +232,10 @@ static int snd_pcm_hw_delay(snd_pcm_t *pcm, ssize_t *delayp) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_DELAY, delayp) < 0) + if (ioctl(fd, SND_PCM_IOCTL_DELAY, delayp) < 0) { + ERR("SND_PCM_IOCTL_DELAY failed"); return -errno; + } return 0; } @@ -180,8 +243,10 @@ static int snd_pcm_hw_prepare(snd_pcm_t *pcm) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_PREPARE) < 0) + if (ioctl(fd, SND_PCM_IOCTL_PREPARE) < 0) { + ERR("SND_PCM_IOCTL_PREPARE failed"); return -errno; + } return 0; } @@ -189,8 +254,10 @@ static int snd_pcm_hw_start(snd_pcm_t *pcm) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_START) < 0) + if (ioctl(fd, SND_PCM_IOCTL_START) < 0) { + ERR("SND_PCM_IOCTL_START failed"); return -errno; + } return 0; } @@ -198,8 +265,10 @@ static int snd_pcm_hw_drop(snd_pcm_t *pcm) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_DROP) < 0) + if (ioctl(fd, SND_PCM_IOCTL_DROP) < 0) { + ERR("SND_PCM_IOCTL_DROP failed"); return -errno; + } return 0; } @@ -207,8 +276,10 @@ static int snd_pcm_hw_drain(snd_pcm_t *pcm) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_DRAIN) < 0) + if (ioctl(fd, SND_PCM_IOCTL_DRAIN) < 0) { + ERR("SND_PCM_IOCTL_DRAIN failed"); return -errno; + } return 0; } @@ -216,8 +287,10 @@ static int snd_pcm_hw_pause(snd_pcm_t *pcm, int enable) { snd_pcm_hw_t *hw = pcm->private; int fd = hw->fd; - if (ioctl(fd, SND_PCM_IOCTL_PAUSE, enable) < 0) + if (ioctl(fd, SND_PCM_IOCTL_PAUSE, enable) < 0) { + ERR("SND_PCM_IOCTL_PAUSE failed"); return -errno; + } return 0; } @@ -301,8 +374,10 @@ static int snd_pcm_hw_mmap_status(snd_pcm_t *pcm) void *ptr; ptr = mmap(NULL, sizeof(snd_pcm_mmap_status_t), PROT_READ, MAP_FILE|MAP_SHARED, hw->fd, SND_PCM_MMAP_OFFSET_STATUS); - if (ptr == MAP_FAILED || ptr == NULL) + if (ptr == MAP_FAILED || ptr == NULL) { + ERR("status mmap failed"); return -errno; + } pcm->mmap_status = ptr; return 0; } @@ -313,8 +388,10 @@ static int snd_pcm_hw_mmap_control(snd_pcm_t *pcm) void *ptr; ptr = mmap(NULL, sizeof(snd_pcm_mmap_control_t), PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, hw->fd, SND_PCM_MMAP_OFFSET_CONTROL); - if (ptr == MAP_FAILED || ptr == NULL) + if (ptr == MAP_FAILED || ptr == NULL) { + ERR("control mmap failed"); return -errno; + } pcm->mmap_control = ptr; return 0; } @@ -333,8 +410,10 @@ static int snd_pcm_hw_mmap_data(snd_pcm_t *pcm) ptr = mmap(NULL, pcm->setup.mmap_bytes, prot, MAP_FILE|MAP_SHARED, hw->fd, SND_PCM_MMAP_OFFSET_DATA); - if (ptr == MAP_FAILED || ptr == NULL) + if (ptr == MAP_FAILED || ptr == NULL) { + ERR("data mmap failed"); return -errno; + } } pcm->mmap_data = ptr; return 0; @@ -342,15 +421,19 @@ static int snd_pcm_hw_mmap_data(snd_pcm_t *pcm) static int snd_pcm_hw_munmap_status(snd_pcm_t *pcm) { - if (munmap((void*)pcm->mmap_status, sizeof(*pcm->mmap_status)) < 0) + if (munmap((void*)pcm->mmap_status, sizeof(*pcm->mmap_status)) < 0) { + ERR("status munmap failed"); return -errno; + } return 0; } static int snd_pcm_hw_munmap_control(snd_pcm_t *pcm) { - if (munmap(pcm->mmap_control, sizeof(*pcm->mmap_control)) < 0) + if (munmap(pcm->mmap_control, sizeof(*pcm->mmap_control)) < 0) { + ERR("control munmap failed"); return -errno; + } return 0; } @@ -360,8 +443,10 @@ static int snd_pcm_hw_munmap_data(snd_pcm_t *pcm) if (hw->mmap_emulation) free(pcm->mmap_data); else - if (munmap(pcm->mmap_data, pcm->setup.mmap_bytes) < 0) + if (munmap(pcm->mmap_data, pcm->setup.mmap_bytes) < 0) { + ERR("data munmap failed"); return -errno; + } return 0; } @@ -377,15 +462,14 @@ static ssize_t snd_pcm_hw_mmap_forward(snd_pcm_t *pcm, size_t size) static ssize_t snd_pcm_hw_avail_update(snd_pcm_t *pcm) { snd_pcm_hw_t *hw = pcm->private; - int fd = hw->fd; size_t avail; ssize_t err; if (pcm->setup.ready_mode == SND_PCM_READY_ASAP || pcm->setup.xrun_mode == SND_PCM_XRUN_ASAP) { ssize_t d; - int err = ioctl(fd, SND_PCM_IOCTL_DELAY, &d); + int err = snd_pcm_hw_delay(pcm, &d); if (err < 0) - return -errno; + return err; } if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { avail = snd_pcm_mmap_playback_avail(pcm); @@ -441,6 +525,7 @@ struct snd_pcm_ops snd_pcm_hw_ops = { channel_setup: snd_pcm_hw_channel_setup, dump: snd_pcm_hw_dump, nonblock: snd_pcm_hw_nonblock, + async: snd_pcm_hw_async, mmap_status: snd_pcm_hw_mmap_status, mmap_control: snd_pcm_hw_mmap_control, mmap_data: snd_pcm_hw_mmap_data, @@ -509,6 +594,8 @@ int snd_pcm_hw_open_subdevice(snd_pcm_t **handlep, int card, int device, int sub fmode = O_RDWR; if (mode & SND_PCM_NONBLOCK) fmode |= O_NONBLOCK; + if (mode & SND_PCM_ASYNC) + fmode |= O_ASYNC; if ((fd = open(filename, fmode)) < 0) { ret = -errno; goto __end; diff --git a/src/pcm/pcm_linear.c b/src/pcm/pcm_linear.c index b335445e..e92e1cca 100644 --- a/src/pcm/pcm_linear.c +++ b/src/pcm/pcm_linear.c @@ -255,6 +255,7 @@ struct snd_pcm_ops snd_pcm_linear_ops = { channel_setup: snd_pcm_plugin_channel_setup, dump: snd_pcm_linear_dump, nonblock: snd_pcm_plugin_nonblock, + async: snd_pcm_plugin_async, mmap_status: snd_pcm_plugin_mmap_status, mmap_control: snd_pcm_plugin_mmap_control, mmap_data: snd_pcm_plugin_mmap_data, diff --git a/src/pcm/pcm_local.h b/src/pcm/pcm_local.h index 7772af60..970578c9 100644 --- a/src/pcm/pcm_local.h +++ b/src/pcm/pcm_local.h @@ -26,9 +26,16 @@ #include #include "asoundlib.h" +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define ERR(...) snd_pcm_error(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) +#else +#define ERR(args...) snd_pcm_error(__FILE__, __LINE__, __FUNCTION__, ##args) +#endif + struct snd_pcm_ops { int (*close)(snd_pcm_t *pcm); int (*nonblock)(snd_pcm_t *pcm, int nonblock); + int (*async)(snd_pcm_t *pcm, int sig, pid_t pid); int (*info)(snd_pcm_t *pcm, snd_pcm_info_t *info); int (*params_info)(snd_pcm_t *pcm, snd_pcm_params_info_t *info); int (*params)(snd_pcm_t *pcm, snd_pcm_params_t *params); diff --git a/src/pcm/pcm_mulaw.c b/src/pcm/pcm_mulaw.c index 65448586..ba6dc888 100644 --- a/src/pcm/pcm_mulaw.c +++ b/src/pcm/pcm_mulaw.c @@ -429,6 +429,7 @@ struct snd_pcm_ops snd_pcm_mulaw_ops = { channel_setup: snd_pcm_plugin_channel_setup, dump: snd_pcm_mulaw_dump, nonblock: snd_pcm_plugin_nonblock, + async: snd_pcm_plugin_async, mmap_status: snd_pcm_plugin_mmap_status, mmap_control: snd_pcm_plugin_mmap_control, mmap_data: snd_pcm_plugin_mmap_data, diff --git a/src/pcm/pcm_multi.c b/src/pcm/pcm_multi.c index 9e5d7ab5..c317c112 100644 --- a/src/pcm/pcm_multi.c +++ b/src/pcm/pcm_multi.c @@ -72,6 +72,13 @@ static int snd_pcm_multi_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock return 0; } +static int snd_pcm_multi_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_multi_t *multi = pcm->private; + snd_pcm_t *slave_0 = multi->slaves[0].pcm; + return snd_pcm_async(slave_0, sig, pid); +} + static int snd_pcm_multi_info(snd_pcm_t *pcm, snd_pcm_info_t *info) { snd_pcm_multi_t *multi = pcm->private; @@ -504,6 +511,7 @@ struct snd_pcm_ops snd_pcm_multi_ops = { channel_setup: snd_pcm_multi_channel_setup, dump: snd_pcm_multi_dump, nonblock: snd_pcm_multi_nonblock, + async: snd_pcm_multi_async, mmap_status: snd_pcm_multi_mmap_status, mmap_control: snd_pcm_multi_mmap_control, mmap_data: snd_pcm_multi_mmap_data, diff --git a/src/pcm/pcm_plug.c b/src/pcm/pcm_plug.c index fd6ef7c7..36ea8930 100644 --- a/src/pcm/pcm_plug.c +++ b/src/pcm/pcm_plug.c @@ -189,6 +189,12 @@ static int snd_pcm_plug_nonblock(snd_pcm_t *pcm, int nonblock) return snd_pcm_nonblock(plug->slave, nonblock); } +static int snd_pcm_plug_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_plug_t *plug = pcm->private; + return snd_pcm_async(plug->slave, sig, pid); +} + static int snd_pcm_plug_info(snd_pcm_t *pcm, snd_pcm_info_t *info) { snd_pcm_plug_t *plug = pcm->private; @@ -658,6 +664,7 @@ struct snd_pcm_ops snd_pcm_plug_ops = { channel_setup: snd_pcm_plug_channel_setup, dump: snd_pcm_plug_dump, nonblock: snd_pcm_plug_nonblock, + async: snd_pcm_plug_async, mmap_status: snd_pcm_plug_mmap_status, mmap_control: snd_pcm_plug_mmap_control, mmap_data: snd_pcm_plug_mmap_data, diff --git a/src/pcm/pcm_plugin.c b/src/pcm/pcm_plugin.c index 6a863056..7fb5a0b4 100644 --- a/src/pcm/pcm_plugin.c +++ b/src/pcm/pcm_plugin.c @@ -39,6 +39,12 @@ int snd_pcm_plugin_nonblock(snd_pcm_t *pcm, int nonblock) return snd_pcm_nonblock(plugin->slave, nonblock); } +int snd_pcm_plugin_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_plugin_t *plugin = pcm->private; + return snd_pcm_async(plugin->slave, sig, pid); +} + int snd_pcm_plugin_info(snd_pcm_t *pcm, snd_pcm_info_t * info) { snd_pcm_plugin_t *plugin = pcm->private; diff --git a/src/pcm/pcm_plugin.h b/src/pcm/pcm_plugin.h index 25de6d2a..e86b1d06 100644 --- a/src/pcm/pcm_plugin.h +++ b/src/pcm/pcm_plugin.h @@ -32,6 +32,7 @@ typedef struct { int snd_pcm_plugin_close(snd_pcm_t *pcm); int snd_pcm_plugin_nonblock(snd_pcm_t *pcm, int nonblock); +int snd_pcm_plugin_async(snd_pcm_t *pcm, int sig, pid_t pid); int snd_pcm_plugin_info(snd_pcm_t *pcm, snd_pcm_info_t * info); int snd_pcm_plugin_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info); int snd_pcm_plugin_channel_params(snd_pcm_t *pcm, snd_pcm_channel_params_t * params); diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c index 9606c274..43e5ee87 100644 --- a/src/pcm/pcm_rate.c +++ b/src/pcm/pcm_rate.c @@ -576,6 +576,7 @@ struct snd_pcm_ops snd_pcm_rate_ops = { channel_setup: snd_pcm_plugin_channel_setup, dump: snd_pcm_rate_dump, nonblock: snd_pcm_plugin_nonblock, + async: snd_pcm_plugin_async, mmap_status: snd_pcm_plugin_mmap_status, mmap_control: snd_pcm_plugin_mmap_control, mmap_data: snd_pcm_plugin_mmap_data, diff --git a/src/pcm/pcm_route.c b/src/pcm/pcm_route.c index 341e3be6..347d83e0 100644 --- a/src/pcm/pcm_route.c +++ b/src/pcm/pcm_route.c @@ -695,6 +695,7 @@ struct snd_pcm_ops snd_pcm_route_ops = { channel_setup: snd_pcm_route_channel_setup, dump: snd_pcm_route_dump, nonblock: snd_pcm_plugin_nonblock, + async: snd_pcm_plugin_async, mmap_status: snd_pcm_plugin_mmap_status, mmap_control: snd_pcm_plugin_mmap_control, mmap_data: snd_pcm_plugin_mmap_data, diff --git a/src/pcm/pcm_share.c b/src/pcm/pcm_share.c new file mode 100644 index 00000000..0b3e50f9 --- /dev/null +++ b/src/pcm/pcm_share.c @@ -0,0 +1,1155 @@ +/* + * PCM - Share + * Copyright (c) 2000 by Abramo Bagnara + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcm_local.h" +#include "list.h" + +static LIST_HEAD(slaves); +static pthread_mutex_t slaves_mutex = PTHREAD_MUTEX_INITIALIZER; + +typedef struct { + struct list_head clients; + struct list_head list; + snd_pcm_t *pcm; + size_t channels_count; + size_t open_count; + size_t setup_count; + size_t prepared_count; + size_t running_count; + size_t safety_threshold; + pthread_t thread; + pthread_mutex_t mutex; +} snd_pcm_share_slave_t; + +typedef struct { + struct list_head list; + snd_pcm_t *pcm; + snd_pcm_share_slave_t *slave; + size_t channels_count; + int *slave_channels; + int xfer_mode; + int xrun_mode; + int async_sig; + pid_t async_pid; + struct timeval trigger_time; + size_t draining_silence; + snd_pcm_mmap_control_t mmap_control; + snd_pcm_mmap_status_t mmap_status; + int ready; + int client_socket; + int slave_socket; + void *stopped_data; +} snd_pcm_share_t; + + +static void snd_pcm_share_interrupt(snd_pcm_share_slave_t *slave) +{ + struct list_head *i; + pthread_mutex_lock(&slave->mutex); + /* Update poll status */ + for (i = slave->clients.next; i != &slave->clients; i = i->next) { + snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list); + snd_pcm_t *pcm = share->pcm; + int ready; + switch (share->mmap_status.state) { + case SND_PCM_STATE_DRAINING: + if (pcm->stream == SND_PCM_STREAM_CAPTURE) + ready = 1; + else { + if (pcm->mode & SND_PCM_ASYNC) + kill(share->async_pid, share->async_sig); + ready = 0; + } + break; + case SND_PCM_STATE_RUNNING: + if (pcm->mode & SND_PCM_ASYNC) + kill(share->async_pid, share->async_sig); + ready = (snd_pcm_mmap_avail(pcm) >= pcm->setup.avail_min); + break; + default: + ready = 1; + } + if (ready != share->ready) { + char buf[1]; + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + if (ready) + read(share->slave_socket, buf, 1); + else + write(share->client_socket, buf, 1); + } else { + if (ready) + write(share->slave_socket, buf, 1); + else + read(share->client_socket, buf, 1); + } + share->ready = ready; + } + } + pthread_mutex_unlock(&slave->mutex); +} + + +void sigio_handler(int sig ATTRIBUTE_UNUSED) +{ +} + +void *snd_pcm_share_slave_thread(void *data) +{ + snd_pcm_share_slave_t *slave = data; + int err; + struct sigaction act; + err = snd_pcm_async(slave->pcm, SIGIO, 0); + assert(err == 0); + act.sa_handler = sigio_handler; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGIO); + act.sa_flags = 0; + err = sigaction(SIGIO, &act, NULL); + assert(err == 0); + while (1) { + pause(); + snd_pcm_share_interrupt(slave); + } + return NULL; +} + +/* Warning: take the mutex before to call this */ +static void snd_pcm_share_stop(snd_pcm_t *pcm, int state) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + share->mmap_status.state = state; + gettimeofday(&share->trigger_time, 0); + slave->prepared_count--; + slave->running_count--; + if (slave->running_count == 0) { + int err = snd_pcm_drop(slave->pcm); + assert(err >= 0); + } + if (pcm->stream == SND_PCM_STREAM_CAPTURE) { + snd_pcm_areas_copy(pcm->running_areas, 0, + pcm->stopped_areas, 0, + pcm->setup.format.channels, pcm->setup.buffer_size, + pcm->setup.format.sfmt); + } + share->mmap_status.hw_ptr = slave->pcm->mmap_status->hw_ptr; + pcm->mmap_status = &share->mmap_status; +} + +/* Warning: take the mutex before to call this */ +static void snd_pcm_share_slave_forward(snd_pcm_share_slave_t *slave) +{ + struct list_head *i; + size_t buffer_size, boundary; + size_t slave_appl_ptr; + ssize_t frames, safety_frames; + size_t min_frames, max_frames; + ssize_t avail; + int err; + avail = snd_pcm_avail_update(slave->pcm); + if (avail == 0) + return; + assert(avail > 0); + boundary = slave->pcm->setup.boundary; + buffer_size = slave->pcm->setup.buffer_size; + min_frames = buffer_size; + max_frames = 0; + slave_appl_ptr = slave->pcm->mmap_control->appl_ptr; + for (i = slave->clients.next; i != &slave->clients; i = i->next) { + snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list); + snd_pcm_t *pcm = share->pcm; + switch (share->mmap_status.state) { + case SND_PCM_STATE_RUNNING: + break; + case SND_PCM_STATE_DRAINING: + { + size_t a, offset; + if (pcm->stream != SND_PCM_STREAM_PLAYBACK) + continue; + a = snd_pcm_mmap_avail(pcm); + frames = a - share->draining_silence; + offset = snd_pcm_mmap_offset(pcm); + offset += share->draining_silence; + if (offset >= buffer_size) + offset -= buffer_size; + while (frames > 0) { + size_t f = buffer_size - offset; + if (f > (size_t) frames) + f = frames; + snd_pcm_areas_silence(pcm->running_areas, offset, + pcm->setup.format.channels, + f, pcm->setup.format.sfmt); + offset += f; + if (offset == buffer_size) + offset = 0; + frames -= f; + } + share->draining_silence = a; + if (a == buffer_size) + snd_pcm_share_stop(pcm, SND_PCM_STATE_SETUP); + break; + } + default: + continue; + } + frames = share->mmap_control.appl_ptr - slave_appl_ptr; + if (frames > (ssize_t)buffer_size) + frames -= pcm->setup.boundary; + else if (frames < -(ssize_t)pcm->setup.buffer_size) + frames += pcm->setup.boundary; + if (frames < 0) { + if (snd_pcm_mmap_hw_avail(pcm) <= 0 && + pcm->setup.xrun_mode != SND_PCM_XRUN_NONE) + snd_pcm_share_stop(pcm, SND_PCM_STATE_XRUN); + continue; + } + if ((size_t)frames < min_frames) + min_frames = frames; + if ((size_t)frames > max_frames) + max_frames = frames; + } + if (max_frames == 0) + return; + frames = min_frames; + if (frames > avail) + frames = avail; + /* Slave xrun prevention */ + safety_frames = slave->safety_threshold - snd_pcm_mmap_hw_avail(slave->pcm); + if (safety_frames > 0 && + frames < (ssize_t)safety_frames) { + /* Avoid to pass over the last */ + if (max_frames < (size_t)safety_frames) + frames = max_frames; + else + frames = safety_frames; + } + if (frames > 0) { + err = snd_pcm_mmap_forward(slave->pcm, frames); + assert(err == frames); + } +} + +static int snd_pcm_share_close(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + if (share->mmap_status.state == SND_PCM_STATE_RUNNING) { + if (pcm->mode & SND_PCM_NONBLOCK) + snd_pcm_drop(pcm); + else + snd_pcm_drain(pcm); + } + pthread_mutex_lock(&slave->mutex); + if (pcm->valid_setup) + slave->setup_count--; + slave->open_count--; + if (slave->open_count == 0) { + pthread_kill(slave->thread, SIGTERM); + err = snd_pcm_close(slave->pcm); + list_del(&slave->list); + pthread_mutex_destroy(&slave->mutex); + free(slave); + } + list_del(&share->list); + pthread_mutex_unlock(&slave->mutex); + close(share->client_socket); + close(share->slave_socket); + free(share->slave_channels); + free(share); + return err; +} + +static int snd_pcm_share_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_share_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_share_t *share = pcm->private; + if (sig) + share->async_sig = sig; + else + share->async_sig = SIGIO; + if (pid) + share->async_pid = pid; + else + share->async_pid = getpid(); + return -ENOSYS; +} + +static int snd_pcm_share_info(snd_pcm_t *pcm, snd_pcm_info_t *info) +{ + snd_pcm_share_t *share = pcm->private; + return snd_pcm_info(share->slave->pcm, info); +} + +static int snd_pcm_share_params_info(snd_pcm_t *pcm, snd_pcm_params_info_t *info) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + unsigned int req_mask = info->req_mask; + unsigned int channels = info->req.format.channels; + if ((req_mask & SND_PCM_PARAMS_CHANNELS) && + channels != share->channels_count) { + info->req.fail_mask |= SND_PCM_PARAMS_CHANNELS; + info->req.fail_reason = SND_PCM_PARAMS_FAIL_INVAL; + return -EINVAL; + } + info->req_mask |= SND_PCM_PARAMS_CHANNELS; + info->req.format.channels = slave->channels_count; + err = snd_pcm_params_info(slave->pcm, info); + info->req.format.channels = channels; + info->req_mask = req_mask; + if (slave->setup_count > 1 || + (slave->setup_count == 1 && !pcm->valid_setup)) { + snd_pcm_setup_t *s = &slave->pcm->setup; + if ((req_mask & SND_PCM_PARAMS_SFMT) && + info->req.format.sfmt != s->format.sfmt) { + info->req.fail_mask |= SND_PCM_PARAMS_SFMT; + info->req.fail_reason = SND_PCM_PARAMS_FAIL_INVAL; + return -EINVAL; + } + info->formats = 1 << s->format.sfmt; + info->rates = SND_PCM_RATE_CONTINUOUS; + info->min_rate = info->max_rate = s->format.rate; + info->buffer_size = s->buffer_size; + info->min_fragment_size = info->max_fragment_size = s->frag_size; + info->min_fragments = info->max_fragments = s->frags; + info->fragment_align = s->frag_size; + info->req.fail_mask = 0; + } + + info->min_channels = info->max_channels = share->channels_count; + if (info->flags & SND_PCM_INFO_INTERLEAVED) { + info->flags &= ~SND_PCM_INFO_INTERLEAVED; + info->flags |= SND_PCM_INFO_COMPLEX; + } + return err; +} + +static int snd_pcm_share_params(snd_pcm_t *pcm, snd_pcm_params_t *params) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + unsigned int channels = params->format.channels; + int err = 0; + if (channels != share->channels_count) { + params->fail_mask = SND_PCM_PARAMS_CHANNELS; + params->fail_reason = SND_PCM_PARAMS_FAIL_INVAL; + ERR("channels requested (%d) differs from configuration (%d)", channels, share->channels_count); + return -EINVAL; + } + share->xfer_mode = params->xfer_mode; + share->xrun_mode = params->xrun_mode; + pthread_mutex_lock(&slave->mutex); + if (slave->setup_count > 1 || + (slave->setup_count == 1 && !pcm->valid_setup)) { + snd_pcm_setup_t *s = &slave->pcm->setup; + if (params->format.sfmt != s->format.sfmt) { + ERR("slave is already running with different format"); + params->fail_mask |= SND_PCM_PARAMS_SFMT; + } + if (params->fail_mask) { + params->fail_reason = SND_PCM_PARAMS_FAIL_INVAL; + err = -EINVAL; + goto _end; + } + } else { + snd_pcm_params_t sp = *params; + if (slave->pcm->mmap_data) { + err = snd_pcm_munmap_data(slave->pcm); + if (err < 0) + goto _end; + } + sp.xfer_mode = SND_PCM_XFER_UNSPECIFIED; + sp.xrun_mode = SND_PCM_XRUN_NONE; + sp.format.channels = slave->channels_count; + err = snd_pcm_params(slave->pcm, &sp); + snd_pcm_mmap_data(slave->pcm, NULL); + if (err < 0) + goto _end; + if (slave->pcm->stream == SND_PCM_STREAM_PLAYBACK) + snd_pcm_areas_silence(slave->pcm->running_areas, 0, slave->pcm->setup.format.channels, slave->pcm->setup.buffer_size, slave->pcm->setup.format.sfmt); + } + share->mmap_status.state = SND_PCM_STATE_SETUP; + slave->setup_count++; + _end: + pthread_mutex_unlock(&slave->mutex); + return err; +} + +static int snd_pcm_share_setup(snd_pcm_t *pcm, snd_pcm_setup_t *setup) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err; + err = snd_pcm_setup(slave->pcm, setup); + if (err < 0) + return err; + setup->xrun_mode = share->xrun_mode; + setup->format.channels = share->channels_count; + if (share->xfer_mode == SND_PCM_XFER_UNSPECIFIED) + setup->xfer_mode = SND_PCM_XFER_NONINTERLEAVED; + else + setup->xfer_mode = share->xfer_mode; + if (setup->mmap_shape != SND_PCM_MMAP_INTERLEAVED) + setup->mmap_shape = SND_PCM_MMAP_COMPLEX; + return 0; +} + +static int snd_pcm_share_status(snd_pcm_t *pcm, snd_pcm_status_t *status) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + ssize_t sd = 0, d = 0; + pthread_mutex_lock(&slave->mutex); + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + status->avail = snd_pcm_mmap_playback_avail(pcm); + if (share->mmap_status.state != SND_PCM_STATE_RUNNING && + share->mmap_status.state != SND_PCM_STATE_DRAINING) + goto _notrunning; + d = pcm->setup.buffer_size - status->avail; + } else { + status->avail = snd_pcm_mmap_capture_avail(pcm); + if (share->mmap_status.state != SND_PCM_STATE_RUNNING) + goto _notrunning; + d = status->avail; + } + err = snd_pcm_delay(slave->pcm, &sd); + if (err < 0) + goto _end; + _notrunning: + status->delay = sd + d; + status->state = share->mmap_status.state; + status->trigger_time = share->trigger_time; + _end: + pthread_mutex_unlock(&slave->mutex); + return err; +} + +static int snd_pcm_share_state(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + return share->mmap_status.state; +} + +static int snd_pcm_share_delay(snd_pcm_t *pcm, ssize_t *delayp) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + ssize_t sd; + pthread_mutex_lock(&slave->mutex); + switch (share->mmap_status.state) { + case SND_PCM_STATE_XRUN: + err = -EPIPE; + goto _end; + case SND_PCM_STATE_RUNNING: + break; + case SND_PCM_STATE_DRAINING: + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) + break; + /* Fall through */ + default: + err = -EBADFD; + goto _end; + } + err = snd_pcm_delay(slave->pcm, &sd); + if (err < 0) + goto _end; + *delayp = sd + snd_pcm_mmap_delay(pcm); + _end: + pthread_mutex_unlock(&slave->mutex); + return 0; +} + +static ssize_t snd_pcm_share_avail_update(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + ssize_t ret = 0; + pthread_mutex_lock(&slave->mutex); + ret = snd_pcm_avail_update(slave->pcm); + if (ret == -EPIPE) { + struct list_head *i; + for (i = slave->clients.next; i != &slave->clients; i = i->next) { + snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list); + snd_pcm_t *pcm = share->pcm; + if (share->mmap_status.state == SND_PCM_STATE_RUNNING && + pcm->setup.xrun_mode != SND_PCM_XRUN_NONE) + snd_pcm_share_stop(pcm, SND_PCM_STATE_XRUN); + } + } + pthread_mutex_unlock(&slave->mutex); + if (ret >= 0) { + ret = snd_pcm_mmap_avail(pcm); + if ((size_t)ret > pcm->setup.buffer_size) + return -EPIPE; + } + return ret; +} + +static ssize_t snd_pcm_share_mmap_forward(snd_pcm_t *pcm, size_t size) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + ssize_t ret = 0; + ssize_t frames; + pthread_mutex_lock(&slave->mutex); + if (pcm->stream == SND_PCM_STREAM_PLAYBACK && + share->mmap_status.state == SND_PCM_STATE_RUNNING) { + frames = slave->pcm->mmap_control->appl_ptr - share->mmap_control.appl_ptr; + if (frames > (ssize_t)pcm->setup.buffer_size) + frames -= pcm->setup.boundary; + else if (frames < -(ssize_t)pcm->setup.buffer_size) + frames += pcm->setup.boundary; + if (frames > 0) { + /* Latecomer PCM */ + ret = snd_pcm_rewind(slave->pcm, frames); + if (ret < 0) { + pthread_mutex_unlock(&slave->mutex); + return ret; + } + size += ret; + } + } + snd_pcm_mmap_appl_forward(pcm, size); + snd_pcm_share_slave_forward(share->slave); + pthread_mutex_unlock(&slave->mutex); + return size; +} + +static int snd_pcm_share_prepare(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + pthread_mutex_lock(&slave->mutex); + if (slave->prepared_count == 0) { + err = snd_pcm_prepare(slave->pcm); + if (err < 0) + goto _end; + } + slave->prepared_count++; + share->mmap_status.hw_ptr = 0; + share->mmap_control.appl_ptr = 0; + share->mmap_status.state = SND_PCM_STATE_PREPARED; + _end: + pthread_mutex_unlock(&slave->mutex); + return err; +} + +static int snd_pcm_share_start(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + if (share->mmap_status.state != SND_PCM_STATE_PREPARED) + return -EBADFD; + pthread_mutex_lock(&slave->mutex); + share->mmap_status.state = SND_PCM_STATE_RUNNING; + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + size_t hw_avail = snd_pcm_mmap_playback_hw_avail(pcm); + if (hw_avail == 0) { + err = -EPIPE; + goto _end; + } + if (slave->running_count) { + ssize_t sd; + err = snd_pcm_delay(slave->pcm, &sd); + if (err < 0) + goto _end; + err = snd_pcm_rewind(slave->pcm, sd); + if (err < 0) + goto _end; + } + assert(share->mmap_status.hw_ptr == 0); + /* Use the same mmap_status of slave */ + pcm->mmap_status = slave->pcm->mmap_status; + share->mmap_control.appl_ptr = slave->pcm->mmap_control->appl_ptr; + snd_pcm_areas_copy(pcm->stopped_areas, 0, + pcm->running_areas, snd_pcm_mmap_offset(pcm), + pcm->setup.format.channels, hw_avail, + pcm->setup.format.sfmt); + snd_pcm_mmap_forward(pcm, pcm->setup.buffer_size); + } + if (slave->running_count == 0) { + err = snd_pcm_start(slave->pcm); + if (err < 0) + goto _end; + } + slave->running_count++; + gettimeofday(&share->trigger_time, 0); + _end: + pthread_mutex_unlock(&slave->mutex); + return err; +} + +static int snd_pcm_share_drop(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + pthread_mutex_lock(&slave->mutex); + switch (share->mmap_status.state) { + case SND_PCM_STATE_OPEN: + err = -EBADFD; + goto _end; + case SND_PCM_STATE_SETUP: + goto _end; + case SND_PCM_STATE_DRAINING: + if (pcm->stream == SND_PCM_STREAM_CAPTURE) { + share->mmap_status.state = SND_PCM_STATE_SETUP; + break; + } + /* Fall through */ + case SND_PCM_STATE_RUNNING: + snd_pcm_share_stop(pcm, SND_PCM_STATE_SETUP); + break; + case SND_PCM_STATE_PREPARED: + case SND_PCM_STATE_XRUN: + share->mmap_status.state = SND_PCM_STATE_SETUP; + break; + } + + if (slave->running_count > 0 && + pcm->stream == SND_PCM_STREAM_PLAYBACK) { + ssize_t delay; + err = snd_pcm_delay(pcm, &delay); + if (err < 0) + goto _end; + if (delay > 0) { + err = snd_pcm_rewind(pcm, delay); + if (err < 0) + goto _end; + } + snd_pcm_areas_silence(pcm->running_areas, 0, pcm->setup.format.channels, + pcm->setup.buffer_size, pcm->setup.format.sfmt); + snd_pcm_mmap_forward(pcm, pcm->setup.buffer_size); + } + share->mmap_control.appl_ptr = share->mmap_status.hw_ptr = 0; + _end: + pthread_mutex_unlock(&slave->mutex); + return err; +} + +static int snd_pcm_share_drain(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int err = 0; + pthread_mutex_lock(&slave->mutex); + switch (share->mmap_status.state) { + case SND_PCM_STATE_OPEN: + err = -EBADFD; + goto _end; + case SND_PCM_STATE_PREPARED: + share->mmap_status.state = SND_PCM_STATE_SETUP; + break; + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_DRAINING: + goto _end; + case SND_PCM_STATE_XRUN: + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + share->mmap_status.state = SND_PCM_STATE_SETUP; + break; + } + /* Fall through */ + case SND_PCM_STATE_RUNNING: + if (snd_pcm_mmap_avail(pcm) <= 0) { + share->mmap_status.state = SND_PCM_STATE_SETUP; + break; + } + share->draining_silence = 0; + share->mmap_status.state = SND_PCM_STATE_DRAINING; + break; + } + _end: + pthread_mutex_unlock(&slave->mutex); + return err; +} + +static int snd_pcm_share_pause(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int enable ATTRIBUTE_UNUSED) +{ + return -ENOSYS; +} + +static int snd_pcm_share_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + unsigned int channel = info->channel; + int c = share->slave_channels[channel]; + int err; + info->channel = c; + err = snd_pcm_channel_info(slave->pcm, info); + info->channel = channel; + return err; +} + +static int snd_pcm_share_channel_params(snd_pcm_t *pcm, snd_pcm_channel_params_t *params) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + unsigned int channel = params->channel; + int c = share->slave_channels[channel]; + int err; + params->channel = c; + err = snd_pcm_channel_params(slave->pcm, params); + params->channel = channel; + return err; +} + +static int snd_pcm_share_channel_setup(snd_pcm_t *pcm, snd_pcm_channel_setup_t *setup) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + unsigned int channel = setup->channel; + int c = share->slave_channels[channel]; + int err; + setup->channel = c; + err = snd_pcm_channel_setup(slave->pcm, setup); + setup->channel = channel; + return err; +} + +static ssize_t snd_pcm_share_rewind(snd_pcm_t *pcm, size_t frames) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + int ret = -EBADFD; + ssize_t n; + switch (share->mmap_status.state) { + case SND_PCM_STATE_RUNNING: + break; + case SND_PCM_STATE_PREPARED: + if (pcm->stream != SND_PCM_STREAM_PLAYBACK) + return -EBADFD; + break; + case SND_PCM_STATE_DRAINING: + if (pcm->stream != SND_PCM_STREAM_CAPTURE) + return -EBADFD; + break; + case SND_PCM_STATE_XRUN: + return -EPIPE; + default: + goto _err; + } + n = snd_pcm_mmap_hw_avail(pcm); + assert(n >= 0); + if (n > 0) { + if ((size_t)n > frames) + n = frames; + frames -= n; + } + if (share->mmap_status.state == SND_PCM_STATE_RUNNING && + frames > 0) { + pthread_mutex_lock(&slave->mutex); + ret = snd_pcm_rewind(slave->pcm, frames); + pthread_mutex_unlock(&slave->mutex); + if (ret < 0) { + if (n <= 0) + return ret; + goto _end; + } + n += ret; + } + _end: + snd_pcm_mmap_appl_backward(pcm, n); + ret = n; + _err: + return ret; +} + +static int snd_pcm_share_mmap_status(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + pcm->mmap_status = &share->mmap_status; + return 0; +} + +static int snd_pcm_share_mmap_control(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + pcm->mmap_control = &share->mmap_control; + return 0; +} + +static int snd_pcm_share_mmap_data(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + pcm->mmap_data = slave->pcm->mmap_data; + return 0; +} + +static int snd_pcm_share_munmap_status(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_share_munmap_control(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_share_munmap_data(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_share_channels_mask(snd_pcm_t *pcm, bitset_t *cmask) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + unsigned int i; + bitset_t m[bitset_size(slave->channels_count)]; + int err = snd_pcm_channels_mask(slave->pcm, m); + if (err < 0) + return err; + for (i = 0; i < share->channels_count; ++i) { + if (!bitset_get(m, share->slave_channels[i])) + bitset_reset(cmask, i); + } + return 0; +} + +int snd_pcm_share_poll_descriptor(snd_pcm_t *pcm) +{ + snd_pcm_share_t *share = pcm->private; + return share->client_socket; +} + +static void snd_pcm_share_dump(snd_pcm_t *pcm, FILE *fp) +{ + snd_pcm_share_t *share = pcm->private; + snd_pcm_share_slave_t *slave = share->slave; + unsigned int k; + fprintf(fp, "Share PCM\n"); + fprintf(fp, "\nChannel bindings:\n"); + for (k = 0; k < share->channels_count; ++k) + fprintf(fp, "%d: %d\n", k, share->slave_channels[k]); + if (pcm->valid_setup) { + fprintf(fp, "\nIts setup is:\n"); + snd_pcm_dump_setup(pcm, fp); + } + fprintf(fp, "Slave: "); + snd_pcm_dump(slave->pcm, fp); +} + +struct snd_pcm_ops snd_pcm_share_ops = { + close: snd_pcm_share_close, + info: snd_pcm_share_info, + params_info: snd_pcm_share_params_info, + params: snd_pcm_share_params, + setup: snd_pcm_share_setup, + channel_info: snd_pcm_share_channel_info, + channel_params: snd_pcm_share_channel_params, + channel_setup: snd_pcm_share_channel_setup, + dump: snd_pcm_share_dump, + nonblock: snd_pcm_share_nonblock, + async: snd_pcm_share_async, + mmap_status: snd_pcm_share_mmap_status, + mmap_control: snd_pcm_share_mmap_control, + mmap_data: snd_pcm_share_mmap_data, + munmap_status: snd_pcm_share_munmap_status, + munmap_control: snd_pcm_share_munmap_control, + munmap_data: snd_pcm_share_munmap_data, +}; + +struct snd_pcm_fast_ops snd_pcm_share_fast_ops = { + status: snd_pcm_share_status, + state: snd_pcm_share_state, + delay: snd_pcm_share_delay, + prepare: snd_pcm_share_prepare, + start: snd_pcm_share_start, + drop: snd_pcm_share_drop, + drain: snd_pcm_share_drain, + pause: snd_pcm_share_pause, + writei: snd_pcm_mmap_writei, + writen: snd_pcm_mmap_writen, + readi: snd_pcm_mmap_readi, + readn: snd_pcm_mmap_readn, + rewind: snd_pcm_share_rewind, + poll_descriptor: snd_pcm_share_poll_descriptor, + channels_mask: snd_pcm_share_channels_mask, + avail_update: snd_pcm_share_avail_update, + mmap_forward: snd_pcm_share_mmap_forward, +}; + +int snd_pcm_share_open(snd_pcm_t **handlep, char *name, char *sname, + size_t schannels_count, + size_t channels_count, int *channels_map, + int stream, int mode) +{ + snd_pcm_t *handle; + snd_pcm_share_t *share; + int err; + struct list_head *i; + char slave_map[32] = { 0 }; + unsigned int k; + snd_pcm_share_slave_t *slave = NULL; + int sd[2]; + + assert(handlep); + assert(channels_count > 0 && sname && channels_map); + + for (k = 0; k < channels_count; ++k) { + if (channels_map[k] < 0 || channels_map[k] > 31) { + ERR("Invalid slave channel (%d) in binding", channels_map[k]); + return -EINVAL; + } + if (slave_map[channels_map[k]]) { + ERR("Repeated slave channel (%d) in binding", channels_map[k]); + return -EINVAL; + } + slave_map[channels_map[k]] = 1; + assert((unsigned)channels_map[k] < schannels_count); + } + + share = calloc(1, sizeof(snd_pcm_share_t)); + if (!share) + return -ENOMEM; + + share->channels_count = channels_count; + share->slave_channels = calloc(channels_count, sizeof(*share->slave_channels)); + if (!share->slave_channels) { + free(share); + return -ENOMEM; + } + memcpy(share->slave_channels, channels_map, channels_count * sizeof(*share->slave_channels)); + + handle = calloc(1, sizeof(snd_pcm_t)); + if (!handle) { + free(share->slave_channels); + free(share); + return -ENOMEM; + } + err = socketpair(AF_LOCAL, SOCK_STREAM, 0, sd); + if (err >= 0 && stream == SND_PCM_STREAM_PLAYBACK) { + int bufsize = 1; + err = setsockopt(sd[0], SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); + if (err >= 0) { + struct pollfd pfd; + pfd.fd = sd[0]; + pfd.events = POLLOUT; + while ((err = poll(&pfd, 1, 0)) == 1) { + char buf[1]; + err = write(sd[0], buf, 1); + assert(err != 0); + if (err != 1) + break; + } + } + } + if (err < 0) { + err = -errno; + free(handle); + free(share->slave_channels); + free(share); + return err; + } + + pthread_mutex_lock(&slaves_mutex); + for (i = slaves.next; i != &slaves; i = i->next) { + snd_pcm_share_slave_t *s = list_entry(i, snd_pcm_share_slave_t, list); + if (s->pcm->name && strcmp(s->pcm->name, sname)) { + slave = s; + break; + } + } + if (!slave) { + snd_pcm_t *spcm; + err = snd_pcm_open(&spcm, sname, stream, mode); + if (err < 0) { + pthread_mutex_unlock(&slaves_mutex); + close(sd[0]); + close(sd[1]); + free(handle); + free(share->slave_channels); + free(share); + return err; + } + slave = calloc(1, sizeof(*slave)); + if (!slave) { + pthread_mutex_unlock(&slaves_mutex); + snd_pcm_close(spcm); + close(sd[0]); + close(sd[1]); + free(handle); + free(share->slave_channels); + free(share); + return err; + } + INIT_LIST_HEAD(&slave->clients); + slave->pcm = spcm; + slave->channels_count = schannels_count; + pthread_mutex_init(&slave->mutex, NULL); + list_add_tail(&slave->list, &slaves); + } + pthread_mutex_unlock(&slaves_mutex); + + pthread_mutex_lock(&slave->mutex); + if (slave->open_count == 0) { + err = pthread_create(&slave->thread, NULL, snd_pcm_share_slave_thread, slave); + assert(err == 0); + } + slave->open_count++; + list_add_tail(&share->list, &slave->clients); + pthread_mutex_unlock(&slave->mutex); + + share->slave = slave; + share->pcm = handle; + share->client_socket = sd[0]; + share->slave_socket = sd[1]; + share->async_sig = SIGIO; + share->async_pid = getpid(); + + if (name) + handle->name = strdup(name); + handle->type = SND_PCM_TYPE_SHARE; + handle->stream = stream; + handle->mode = mode; + handle->ops = &snd_pcm_share_ops; + handle->op_arg = handle; + handle->fast_ops = &snd_pcm_share_fast_ops; + handle->fast_op_arg = handle; + handle->private = share; + err = snd_pcm_init(handle); + if (err < 0) { + snd_pcm_close(handle); + return err; + } + *handlep = handle; + return 0; +} + +int _snd_pcm_share_open(snd_pcm_t **pcmp, char *name, snd_config_t *conf, + int stream, int mode) +{ + snd_config_iterator_t i; + char *sname = NULL; + snd_config_t *binding = NULL; + int err; + unsigned int idx; + int *channels_map; + size_t channels_count = 0; + long schannels_count = -1; + size_t schannel_max = 0; + snd_config_foreach(i, conf) { + snd_config_t *n = snd_config_entry(i); + if (strcmp(n->id, "comment") == 0) + continue; + if (strcmp(n->id, "type") == 0) + continue; + if (strcmp(n->id, "stream") == 0) + continue; + if (strcmp(n->id, "sname") == 0) { + err = snd_config_string_get(n, &sname); + if (err < 0) { + ERR("Invalid type for sname"); + return -EINVAL; + } + continue; + } + if (strcmp(n->id, "schannels") == 0) { + err = snd_config_integer_get(n, &schannels_count); + if (err < 0) { + ERR("Invalid type for schannels"); + return -EINVAL; + } + continue; + } + if (strcmp(n->id, "binding") == 0) { + if (snd_config_type(n) != SND_CONFIG_TYPE_COMPOUND) { + ERR("Invalid type for binding"); + return -EINVAL; + } + binding = n; + continue; + } + ERR("Unknown field: %s", n->id); + return -EINVAL; + } + if (!sname) { + ERR("Missing sname"); + return -EINVAL; + } + if (!binding) { + ERR("Missing binding"); + return -EINVAL; + } + snd_config_foreach(i, binding) { + int cchannel = -1; + char *p; + snd_config_t *n = snd_config_entry(i); + errno = 0; + cchannel = strtol(n->id, &p, 10); + if (errno || *p || cchannel < 0) { + ERR("Invalid client channel in binding: %s", n->id); + return -EINVAL; + } + if ((unsigned)cchannel > channels_count) + channels_count = cchannel + 1; + } + if (channels_count == 0) { + ERR("No bindings defined"); + return -EINVAL; + } + channels_map = calloc(channels_count, sizeof(*channels_map)); + for (idx = 0; idx < channels_count; ++idx) + channels_map[idx] = -1; + + snd_config_foreach(i, binding) { + snd_config_t *n = snd_config_entry(i); + long cchannel; + long schannel = -1; + cchannel = strtol(n->id, 0, 10); + err = snd_config_integer_get(n, &schannel); + if (err < 0) + goto _free; + assert(schannels_count <= 0 || schannel < schannels_count); + channels_map[cchannel] = schannel; + if ((unsigned)schannel > schannel_max) + schannel_max = schannel; + } + if (schannels_count <= 0) + schannels_count = schannel_max + 1; + err = snd_pcm_share_open(pcmp, name, sname, schannels_count, + channels_count, channels_map, stream, mode); +_free: + free(channels_map); + return err; +} -- 2.47.1