From: Jaroslav Kysela Date: Tue, 18 Mar 2003 20:37:42 +0000 (+0000) Subject: Extracted common code from dmix plugin. X-Git-Tag: v1.0.3~197 X-Git-Url: https://git.alsa-project.org/?a=commitdiff_plain;h=bc28eccdcdfe60df35d37397bd80d295977f262d;p=alsa-lib.git Extracted common code from dmix plugin. Initial framework for dsnoop and dshare plugins. --- diff --git a/include/pcm.h b/include/pcm.h index cc71d00f..7463825c 100644 --- a/include/pcm.h +++ b/include/pcm.h @@ -348,7 +348,11 @@ enum _snd_pcm_type { SND_PCM_TYPE_DMIX, /** Jack Audio Connection Kit plugin */ SND_PCM_TYPE_JACK, - SND_PCM_TYPE_LAST = SND_PCM_TYPE_JACK + /** Direct Snooping plugin */ + SND_PCM_TYPE_DSNOOP, + /** Direct Sharing plugin */ + SND_PCM_TYPE_DSHARE, + SND_PCM_TYPE_LAST = SND_PCM_TYPE_DSNOOP }; /** PCM type */ diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am index 6d3c98ce..6d7281b8 100644 --- a/src/pcm/Makefile.am +++ b/src/pcm/Makefile.am @@ -10,10 +10,11 @@ libpcm_la_SOURCES = atomic.c mask.c interval.c \ pcm_rate.c pcm_plug.c pcm_misc.c pcm_mmap.c pcm_multi.c \ pcm_shm.c pcm_file.c pcm_null.c pcm_share.c \ pcm_meter.c pcm_hooks.c pcm_lfloat.c pcm_ladspa.c \ - pcm_dmix.c pcm_symbols.c + pcm_direct.c pcm_dmix.c pcm_dsnoop.c pcm_dshare.c \ + pcm_symbols.c noinst_HEADERS = pcm_local.h pcm_plugin.h mask.h mask_inline.h \ interval.h interval_inline.h plugin_ops.h ladspa.h \ - pcm_dmix_i386.h + pcm_direct.h pcm_dmix_i386.h alsadir = $(datadir)/alsa diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c index 6a514b74..b133d78b 100644 --- a/src/pcm/pcm.c +++ b/src/pcm/pcm.c @@ -1755,7 +1755,7 @@ snd_pcm_t *snd_async_handler_get_pcm(snd_async_handler_t *handler) static char *build_in_pcms[] = { "adpcm", "alaw", "copy", "dmix", "file", "hooks", "hw", "ladspa", "lfloat", "linear", "meter", "mulaw", "multi", "null", "plug", "rate", "route", "share", - "shm", NULL + "shm", "dsnoop", "dshare", NULL }; static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name, diff --git a/src/pcm/pcm_direct.c b/src/pcm/pcm_direct.c new file mode 100644 index 00000000..2d74d151 --- /dev/null +++ b/src/pcm/pcm_direct.c @@ -0,0 +1,652 @@ +/** + * \file pcm/pcm_direct.c + * \ingroup PCM_Plugins + * \brief PCM Direct Stream Mixing (dmix) Plugin Interface + * \author Jaroslav Kysela + * \date 2003 + */ +/* + * PCM - Direct Stream Mixing + * Copyright (c) 2003 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcm_direct.h" + +/* + * + */ + + +/* + * FIXME: + * add possibility to use futexes here + */ + +int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix) +{ + dmix->semid = semget(dmix->ipc_key, DIRECT_IPC_SEMS, IPC_CREAT | 0666); + if (dmix->semid < 0) + return -errno; + return 0; +} + +int snd_pcm_direct_semaphore_discard(snd_pcm_direct_t *dmix) +{ + if (dmix->semid < 0) + return -EINVAL; + if (semctl(dmix->semid, 0, IPC_RMID, NULL) < 0) + return -errno; + dmix->semid = -1; + return 0; +} + +int snd_pcm_direct_semaphore_down(snd_pcm_direct_t *dmix, int sem_num) +{ + struct sembuf op[2] = { { 0, 0, SEM_UNDO }, { 0, 1, SEM_UNDO | IPC_NOWAIT } }; + assert(dmix->semid >= 0); + op[0].sem_num = sem_num; + op[1].sem_num = sem_num; + if (semop(dmix->semid, op, 2) < 0) + return -errno; + return 0; +} + +int snd_pcm_direct_semaphore_up(snd_pcm_direct_t *dmix, int sem_num) +{ + struct sembuf op = { 0, -1, SEM_UNDO | IPC_NOWAIT }; + assert(dmix->semid >= 0); + op.sem_num = sem_num; + if (semop(dmix->semid, &op, 1) < 0) + return -errno; + return 0; +} + +/* + * global shared memory area + */ + +int snd_pcm_direct_shm_create_or_connect(snd_pcm_direct_t *dmix) +{ + static int snd_pcm_direct_shm_discard(snd_pcm_direct_t *dmix); + struct shmid_ds buf; + int ret = 0; + + dmix->shmid = shmget(dmix->ipc_key, sizeof(snd_pcm_direct_share_t), IPC_CREAT | 0666); + if (dmix->shmid < 0) + return -errno; + dmix->shmptr = shmat(dmix->shmid, 0, 0); + if (dmix->shmptr == (void *) -1) { + snd_pcm_direct_shm_discard(dmix); + return -errno; + } + mlock(dmix->shmptr, sizeof(snd_pcm_direct_share_t)); + if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) { + snd_pcm_direct_shm_discard(dmix); + return -errno; + } + if (buf.shm_nattch == 1) { /* we're the first user, clear the segment */ + memset(dmix->shmptr, 0, sizeof(snd_pcm_direct_share_t)); + ret = 1; + } + return ret; +} + +int snd_pcm_direct_shm_discard(snd_pcm_direct_t *dmix) +{ + struct shmid_ds buf; + int ret = 0; + + if (dmix->shmid < 0) + return -EINVAL; + if (dmix->shmptr != (void *) -1 && shmdt(dmix->shmptr) < 0) + return -errno; + dmix->shmptr = (void *) -1; + if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) + return -errno; + if (buf.shm_nattch == 0) { /* we're the last user, destroy the segment */ + if (shmctl(dmix->shmid, IPC_RMID, NULL) < 0) + return -errno; + ret = 1; + } + dmix->shmid = -1; + return ret; +} + +/* + * server side + */ + +static int get_tmp_name(char *filename, size_t size) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + snprintf(filename, size, "/tmp/alsa-dmix-%i-%li-%li", getpid(), tv.tv_sec, tv.tv_usec); + filename[size-1] = '\0'; + return 0; +} + +static int make_local_socket(const char *filename, int server) +{ + size_t l = strlen(filename); + size_t size = offsetof(struct sockaddr_un, sun_path) + l; + struct sockaddr_un *addr = alloca(size); + int sock; + + sock = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sock < 0) { + int result = -errno; + SYSERR("socket failed"); + return result; + } + + if (server) + unlink(filename); + addr->sun_family = AF_LOCAL; + memcpy(addr->sun_path, filename, l); + + if (server) { + if (bind(sock, (struct sockaddr *) addr, size) < 0) { + int result = -errno; + SYSERR("bind failed"); + return result; + } + } else { + if (connect(sock, (struct sockaddr *) addr, size) < 0) { + SYSERR("connect failed"); + return -errno; + } + } + return sock; +} + +#if 0 +#define server_printf(fmt, args...) printf(fmt, ##args) +#else +#define server_printf(fmt, args...) /* nothing */ +#endif + +static void server_job(snd_pcm_direct_t *dmix) +{ + int ret, sck, i; + int max = 128, current = 0; + struct pollfd pfds[max + 1]; + + /* close all files to free resources */ + i = sysconf(_SC_OPEN_MAX); + while (--i >= 3) { + if (i != dmix->server_fd && i != dmix->hw_fd) + close(i); + } + + /* detach from parent */ + setsid(); + + pfds[0].fd = dmix->server_fd; + pfds[0].events = POLLIN | POLLERR | POLLHUP; + + server_printf("DIRECT SERVER STARTED\n"); + while (1) { + ret = poll(pfds, current + 1, 500); + server_printf("DIRECT SERVER: poll ret = %i, revents[0] = 0x%x\n", ret, pfds[0].revents); + if (ret < 0) /* some error */ + break; + if (ret == 0 || pfds[0].revents & (POLLERR | POLLHUP)) { /* timeout or error? */ + struct shmid_ds buf; + snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT); + if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) { + snd_pcm_direct_shm_discard(dmix); + continue; + } + server_printf("DIRECT SERVER: nattch = %i\n", (int)buf.shm_nattch); + if (buf.shm_nattch == 1) /* server is the last user, exit */ + break; + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + continue; + } + if (pfds[0].revents & POLLIN) { + ret--; + sck = accept(dmix->server_fd, 0, 0); + if (sck >= 0) { + server_printf("DIRECT SERVER: new connection %i\n", sck); + if (current == max) { + close(sck); + } else { + unsigned char buf = 'A'; + pfds[current+1].fd = sck; + pfds[current+1].events = POLLIN | POLLERR | POLLHUP; + snd_send_fd(sck, &buf, 1, dmix->hw_fd); + server_printf("DIRECT SERVER: fd sent ok\n"); + current++; + } + } + } + for (i = 0; i < current && ret > 0; i++) { + struct pollfd *pfd = &pfds[i+1]; + unsigned char cmd; + server_printf("client %i revents = 0x%x\n", pfd->fd, pfd->revents); + if (pfd->revents & (POLLERR | POLLHUP)) { + ret--; + close(pfd->fd); + pfd->fd = -1; + continue; + } + if (!(pfd->revents & POLLIN)) + continue; + ret--; + if (read(pfd->fd, &cmd, 1) == 1) + cmd = 0 /*process command */; + } + for (i = 0; i < current; i++) { + if (pfds[i+1].fd < 0) { + if (i + 1 != max) + memcpy(&pfds[i+1], &pfds[i+2], sizeof(struct pollfd) * (max - i - 1)); + current--; + } + } + } + close(dmix->server_fd); + close(dmix->hw_fd); + snd_pcm_direct_shm_discard(dmix); + snd_pcm_direct_semaphore_discard(dmix); + server_printf("DIRECT SERVER EXIT\n"); + exit(EXIT_SUCCESS); +} + +int snd_pcm_direct_server_create(snd_pcm_direct_t *dmix) +{ + int ret; + + dmix->server_fd = -1; + + ret = get_tmp_name(dmix->shmptr->socket_name, sizeof(dmix->shmptr->socket_name)); + if (ret < 0) + return ret; + + ret = make_local_socket(dmix->shmptr->socket_name, 1); + if (ret < 0) + return ret; + dmix->server_fd = ret; + + ret = listen(dmix->server_fd, 4); + if (ret < 0) { + close(dmix->server_fd); + return ret; + } + + ret = fork(); + if (ret < 0) { + close(dmix->server_fd); + return ret; + } else if (ret == 0) { + server_job(dmix); + } + dmix->server_pid = ret; + dmix->server = 1; + return 0; +} + +int snd_pcm_direct_server_discard(snd_pcm_direct_t *dmix) +{ + if (dmix->server) { + //kill(dmix->server_pid, SIGTERM); + //waitpid(dmix->server_pid, NULL, 0); + dmix->server_pid = (pid_t)-1; + } + if (dmix->server_fd > 0) { + close(dmix->server_fd); + dmix->server_fd = -1; + } + dmix->server = 0; + return 0; +} + +/* + * client side + */ + +int snd_pcm_direct_client_connect(snd_pcm_direct_t *dmix) +{ + int ret; + unsigned char buf; + + ret = make_local_socket(dmix->shmptr->socket_name, 0); + if (ret < 0) + return ret; + dmix->comm_fd = ret; + + ret = snd_receive_fd(dmix->comm_fd, &buf, 1, &dmix->hw_fd); + if (ret < 1 || buf != 'A') { + close(dmix->comm_fd); + dmix->comm_fd = -1; + return ret; + } + + dmix->client = 1; + return 0; +} + +int snd_pcm_direct_client_discard(snd_pcm_direct_t *dmix) +{ + if (dmix->client) { + close(dmix->comm_fd); + dmix->comm_fd = -1; + } + return 0; +} + +/* + * this function initializes hardware and starts playback operation with + * no stop threshold (it operates all time without xrun checking) + * also, the driver silences the unused ring buffer areas for us + */ +int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, struct slave_params *params) +{ + snd_pcm_hw_params_t *hw_params; + snd_pcm_sw_params_t *sw_params; + int ret, buffer_is_not_initialized; + snd_pcm_uframes_t boundary; + struct pollfd fd; + int loops = 10; + + hw_params = &dmix->shmptr->hw_params; + sw_params = &dmix->shmptr->sw_params; + + __again: + if (loops-- <= 0) { + SNDERR("unable to find a valid configuration for slave"); + return -EINVAL; + } + ret = snd_pcm_hw_params_any(spcm, hw_params); + if (ret < 0) { + SNDERR("snd_pcm_hw_params_any failed"); + return ret; + } + ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED); + if (ret < 0) { + ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + if (ret < 0) { + SNDERR("slave plugin does not support mmap interleaved or mmap noninterleaved access"); + return ret; + } + } + ret = snd_pcm_hw_params_set_format(spcm, hw_params, params->format); + if (ret < 0) { + snd_pcm_format_t format; + switch (params->format) { + case SND_PCM_FORMAT_S32: format = SND_PCM_FORMAT_S16; break; + case SND_PCM_FORMAT_S16: format = SND_PCM_FORMAT_S32; break; + default: + SNDERR("invalid format"); + return -EINVAL; + } + ret = snd_pcm_hw_params_set_format(spcm, hw_params, format); + if (ret < 0) { + SNDERR("requested or auto-format is not available"); + return ret; + } + params->format = format; + } + ret = snd_pcm_hw_params_set_channels(spcm, hw_params, params->channels); + if (ret < 0) { + unsigned int min, max; + ret = INTERNAL(snd_pcm_hw_params_get_channels_min)(hw_params, &min); + if (ret < 0) { + SNDERR("cannot obtain minimal count of channels"); + return ret; + } + ret = INTERNAL(snd_pcm_hw_params_get_channels_min)(hw_params, &max); + if (ret < 0) { + SNDERR("cannot obtain maximal count of channels"); + return ret; + } + if (min == max) { + ret = snd_pcm_hw_params_set_channels(spcm, hw_params, min); + if (ret >= 0) + params->channels = min; + } + if (ret < 0) { + SNDERR("requested count of channels is not available"); + return ret; + } + } + ret = INTERNAL(snd_pcm_hw_params_set_rate_near)(spcm, hw_params, ¶ms->rate, 0); + if (ret < 0) { + SNDERR("requested rate is not available"); + return ret; + } + + buffer_is_not_initialized = 0; + if (params->buffer_time > 0) { + ret = INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(spcm, hw_params, ¶ms->buffer_time, 0); + if (ret < 0) { + SNDERR("unable to set buffer time"); + return ret; + } + } else if (params->buffer_size > 0) { + ret = INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(spcm, hw_params, ¶ms->buffer_size); + if (ret < 0) { + SNDERR("unable to set buffer size"); + return ret; + } + } else { + buffer_is_not_initialized = 1; + } + + if (params->period_time > 0) { + ret = INTERNAL(snd_pcm_hw_params_set_period_time_near)(spcm, hw_params, ¶ms->period_time, 0); + if (ret < 0) { + SNDERR("unable to set period_time"); + return ret; + } + } else if (params->period_size > 0) { + ret = INTERNAL(snd_pcm_hw_params_set_period_size_near)(spcm, hw_params, ¶ms->period_size, 0); + if (ret < 0) { + SNDERR("unable to set period_size"); + return ret; + } + } + + if (buffer_is_not_initialized && params->periods > 0) { + int periods = params->periods; + ret = INTERNAL(snd_pcm_hw_params_set_periods_near)(spcm, hw_params, ¶ms->periods, 0); + if (ret < 0) { + SNDERR("unable to set requested periods"); + return ret; + } + if (params->periods == 1) { + params->periods = periods; + if (params->period_time > 0) { + params->period_time /= 2; + goto __again; + } else if (params->period_size > 0) { + params->period_size /= 2; + goto __again; + } + SNDERR("unable to use stream with periods == 1"); + return ret; + } + } + + ret = snd_pcm_hw_params(spcm, hw_params); + if (ret < 0) { + SNDERR("unable to install hw params"); + return ret; + } + + ret = snd_pcm_sw_params_current(spcm, sw_params); + if (ret < 0) { + SNDERR("unable to get current sw_params"); + return ret; + } + + ret = snd_pcm_sw_params_get_boundary(sw_params, &boundary); + if (ret < 0) { + SNDERR("unable to get boundary"); + return ret; + } + ret = snd_pcm_sw_params_set_stop_threshold(spcm, sw_params, boundary); + if (ret < 0) { + SNDERR("unable to set stop threshold"); + return ret; + } + ret = snd_pcm_sw_params_set_silence_threshold(spcm, sw_params, 0); + if (ret < 0) { + SNDERR("unable to set silence threshold"); + return ret; + } + ret = snd_pcm_sw_params_set_silence_size(spcm, sw_params, boundary); + if (ret < 0) { + SNDERR("unable to set silence threshold (please upgrade to 0.9.0rc8+ driver)"); + return ret; + } + + ret = snd_pcm_sw_params(spcm, sw_params); + if (ret < 0) { + SNDERR("unable to install sw params (please upgrade to 0.9.0rc8+ driver)"); + return ret; + } + + ret = snd_pcm_start(spcm); + if (ret < 0) { + SNDERR("unable to start PCM stream"); + return ret; + } + + if (snd_pcm_poll_descriptors_count(spcm) != 1) { + SNDERR("unable to use hardware pcm with fd more than one!!!"); + return ret; + } + snd_pcm_poll_descriptors(spcm, &fd, 1); + dmix->hw_fd = fd.fd; + + dmix->shmptr->s.boundary = spcm->boundary; + dmix->shmptr->s.buffer_size = spcm->buffer_size; + dmix->shmptr->s.sample_bits = spcm->sample_bits; + dmix->shmptr->s.channels = spcm->channels; + dmix->shmptr->s.rate = spcm->rate; + dmix->shmptr->s.format = spcm->format; + dmix->shmptr->s.boundary = spcm->boundary; + + spcm->donot_close = 1; + return 0; +} + +/* + * the trick is used here; we cannot use effectively the hardware handle because + * we cannot drive multiple accesses to appl_ptr; so we use slave timer of given + * PCM hardware handle; it's not this easy and cheap? + */ +int snd_pcm_direct_initialize_poll_fd(snd_pcm_direct_t *dmix) +{ + int ret; + snd_pcm_info_t *info; + snd_timer_params_t *params; + char name[128]; + struct pollfd fd; + + snd_pcm_info_alloca(&info); + snd_timer_params_alloca(¶ms); + ret = snd_pcm_info(dmix->spcm, info); + if (ret < 0) { + SNDERR("unable to info for slave pcm"); + return ret; + } + sprintf(name, "hw:CLASS=%i,SCLASS=0,CARD=%i,DEV=%i,SUBDEV=%i", + (int)SND_TIMER_CLASS_PCM, + snd_pcm_info_get_card(info), + snd_pcm_info_get_device(info), + snd_pcm_info_get_subdevice(info) * 2); /* it's a bit trick to distict playback and capture */ + ret = snd_timer_open(&dmix->timer, name, SND_TIMER_OPEN_NONBLOCK); + if (ret < 0) { + SNDERR("unable to open timer '%s'", name); + return ret; + } + snd_timer_params_set_auto_start(params, 1); + snd_timer_params_set_ticks(params, 1); + ret = snd_timer_params(dmix->timer, params); + if (ret < 0) { + SNDERR("unable to set timer parameters", name); + return ret; + } + if (snd_timer_poll_descriptors_count(dmix->timer) != 1) { + SNDERR("unable to use timer with fd more than one!!!", name); + return ret; + } + snd_timer_poll_descriptors(dmix->timer, &fd, 1); + dmix->poll_fd = fd.fd; + return 0; +} + +/* + * ring buffer operation + */ +int snd_pcm_direct_check_interleave(snd_pcm_direct_t *dmix, snd_pcm_t *pcm) +{ + unsigned int chn, channels; + int interleaved = 1; + const snd_pcm_channel_area_t *dst_areas; + const snd_pcm_channel_area_t *src_areas; + + channels = dmix->u.dmix.channels; + dst_areas = snd_pcm_mmap_areas(dmix->spcm); + src_areas = snd_pcm_mmap_areas(pcm); + for (chn = 1; chn < channels; chn++) { + if (dst_areas[chn-1].addr != dst_areas[chn].addr) { + interleaved = 0; + break; + } + if (src_areas[chn-1].addr != src_areas[chn].addr) { + interleaved = 0; + break; + } + } + for (chn = 0; chn < channels; chn++) { + if (dmix->u.dmix.bindings && dmix->u.dmix.bindings[chn] != chn) { + interleaved = 0; + break; + } + if (dst_areas[chn].first != sizeof(signed short) * chn * 8 || + dst_areas[chn].step != channels * sizeof(signed short) * 8) { + interleaved = 0; + break; + } + if (src_areas[chn].first != sizeof(signed short) * chn * 8 || + src_areas[chn].step != channels * sizeof(signed short) * 8) { + interleaved = 0; + break; + } + } + return dmix->interleaved = interleaved; +} diff --git a/src/pcm/pcm_direct.h b/src/pcm/pcm_direct.h new file mode 100644 index 00000000..4f339d0b --- /dev/null +++ b/src/pcm/pcm_direct.h @@ -0,0 +1,122 @@ +/** + * \file pcm/pcm_dmix.c + * \ingroup PCM_Plugins + * \brief PCM Direct Stream Mixing (dmix) Plugin Interface + * \author Jaroslav Kysela + * \date 2003 + */ +/* + * PCM - Direct Stream Mixing + * Copyright (c) 2003 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pcm_local.h" + +#define DIRECT_IPC_SEMS 1 +#define DIRECT_IPC_SEM_CLIENT 0 + +typedef void (mix_areas1_t)(unsigned int size, + volatile signed short *dst, signed short *src, + volatile signed int *sum, unsigned int dst_step, + unsigned int src_step, unsigned int sum_step); + +typedef void (mix_areas2_t)(unsigned int size, + volatile signed int *dst, signed int *src, + volatile signed int *sum, unsigned int dst_step, + unsigned int src_step, unsigned int sum_step); + +struct slave_params { + snd_pcm_format_t format; + int rate; + int channels; + int period_time; + int buffer_time; + snd_pcm_sframes_t period_size; + snd_pcm_sframes_t buffer_size; + int periods; +}; + +typedef struct { + char socket_name[256]; /* name of communication socket */ + snd_pcm_type_t type; /* PCM type (currently only hw) */ + snd_pcm_hw_params_t hw_params; + snd_pcm_sw_params_t sw_params; + struct { + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t boundary; + snd_pcm_uframes_t channels; + unsigned int sample_bits; + unsigned int rate; + snd_pcm_format_t format; + } s; +} snd_pcm_direct_share_t; + +typedef struct { + key_t ipc_key; /* IPC key for semaphore and memory */ + int semid; /* IPC global semaphore identification */ + int shmid; /* IPC global shared memory identification */ + snd_pcm_direct_share_t *shmptr; /* pointer to shared memory area */ + snd_pcm_t *spcm; /* slave PCM handle */ + snd_pcm_uframes_t appl_ptr; + snd_pcm_uframes_t hw_ptr; + snd_pcm_uframes_t avail_max; + snd_pcm_uframes_t slave_appl_ptr; + snd_pcm_uframes_t slave_hw_ptr; + snd_pcm_state_t state; + snd_htimestamp_t trigger_tstamp; + int server, client; + int comm_fd; /* communication file descriptor (socket) */ + int hw_fd; /* hardware file descriptor */ + int poll_fd; + int server_fd; + pid_t server_pid; + snd_timer_t *timer; /* timer used as poll_fd */ + int interleaved; /* we have interleaved buffer */ + union { + struct { + int shmid_sum; /* IPC global sum ring buffer memory identification */ + signed int *sum_buffer; /* shared sum buffer */ + mix_areas1_t *mix_areas1; + mix_areas2_t *mix_areas2; + unsigned int channels; /* client's channels */ + unsigned int *bindings; + } dmix; + struct { + } dsnoop; + struct { + } dshare; + } u; +} snd_pcm_direct_t; + +int snd_pcm_direct_semaphore_create_or_connect(snd_pcm_direct_t *dmix); +int snd_pcm_direct_semaphore_discard(snd_pcm_direct_t *dmix); +int snd_pcm_direct_semaphore_down(snd_pcm_direct_t *dmix, int sem_num); +int snd_pcm_direct_semaphore_up(snd_pcm_direct_t *dmix, int sem_num); +int snd_pcm_direct_shm_create_or_connect(snd_pcm_direct_t *dmix); +int snd_pcm_direct_shm_discard(snd_pcm_direct_t *dmix); +int snd_pcm_direct_server_create(snd_pcm_direct_t *dmix); +int snd_pcm_direct_server_discard(snd_pcm_direct_t *dmix); +int snd_pcm_direct_client_connect(snd_pcm_direct_t *dmix); +int snd_pcm_direct_client_discard(snd_pcm_direct_t *dmix); +int snd_pcm_direct_initialize_slave(snd_pcm_direct_t *dmix, snd_pcm_t *spcm, struct slave_params *params); +int snd_pcm_direct_initialize_poll_fd(snd_pcm_direct_t *dmix); +int snd_pcm_direct_check_interleave(snd_pcm_direct_t *dmix, snd_pcm_t *pcm); + +int snd_timer_async(snd_timer_t *timer, int sig, pid_t pid); +struct timespec snd_pcm_hw_fast_tstamp(snd_pcm_t *pcm); diff --git a/src/pcm/pcm_dmix.c b/src/pcm/pcm_dmix.c index 935f099c..3c5593ce 100644 --- a/src/pcm/pcm_dmix.c +++ b/src/pcm/pcm_dmix.c @@ -41,8 +41,7 @@ #include #include #include -#include "pcm_local.h" -#include "../control/control_local.h" +#include "pcm_direct.h" #ifndef PIC /* entry for static linking */ @@ -52,504 +51,51 @@ const char *_snd_module_pcm_dmix = ""; /* * */ - - -/* - * FIXME: - * add possibility to use futexes here - */ -#define DMIX_IPC_SEMS 1 -#define DMIX_IPC_SEM_CLIENT 0 - -/* - * - */ - -int snd_timer_async(snd_timer_t *timer, int sig, pid_t pid); -struct timespec snd_pcm_hw_fast_tstamp(snd_pcm_t *pcm); - -typedef void (mix_areas1_t)(unsigned int size, - volatile signed short *dst, signed short *src, - volatile signed int *sum, unsigned int dst_step, - unsigned int src_step, unsigned int sum_step); - -typedef void (mix_areas2_t)(unsigned int size, - volatile signed int *dst, signed int *src, - volatile signed int *sum, unsigned int dst_step, - unsigned int src_step, unsigned int sum_step); - -/* - * - */ - -struct slave_params { - snd_pcm_format_t format; - int rate; - int channels; - int period_time; - int buffer_time; - snd_pcm_sframes_t period_size; - snd_pcm_sframes_t buffer_size; - int periods; -}; - -typedef struct { - char socket_name[256]; /* name of communication socket */ - snd_pcm_type_t type; /* PCM type (currently only hw) */ - snd_pcm_hw_params_t hw_params; - snd_pcm_sw_params_t sw_params; - struct { - snd_pcm_uframes_t buffer_size; - snd_pcm_uframes_t boundary; - snd_pcm_uframes_t channels; - unsigned int sample_bits; - unsigned int rate; - snd_pcm_format_t format; - } s; -} snd_pcm_dmix_share_t; - -typedef struct { - key_t ipc_key; /* IPC key for semaphore and memory */ - int semid; /* IPC global semaphore identification */ - int shmid; /* IPC global shared memory identification */ - int shmid_sum; /* IPC global sum ring buffer memory identification */ - snd_pcm_dmix_share_t *shmptr; /* pointer to shared memory area */ - signed int *sum_buffer; /* shared sum buffer */ - snd_pcm_t *spcm; /* slave PCM handle */ - snd_pcm_uframes_t appl_ptr; - snd_pcm_uframes_t hw_ptr; - snd_pcm_uframes_t avail_max; - snd_pcm_uframes_t slave_appl_ptr; - snd_pcm_uframes_t slave_hw_ptr; - snd_pcm_state_t state; - snd_htimestamp_t trigger_tstamp; - int server, client; - int comm_fd; /* communication file descriptor (socket) */ - int hw_fd; /* hardware file descriptor */ - int poll_fd; - int server_fd; - pid_t server_pid; - snd_timer_t *timer; /* timer used as poll_fd */ - int interleaved; /* we have interleaved buffer */ - mix_areas1_t *mix_areas1; - mix_areas2_t *mix_areas2; - unsigned int channels; /* client's channels */ - unsigned int *bindings; -} snd_pcm_dmix_t; - -/* - * global semaphore and shared memory area - */ - -#if 0 -struct sembuf { - unsigned short sem_num; - short sem_op; - short sem_flg; -}; -#endif - -static int semaphore_create_or_connect(snd_pcm_dmix_t *dmix) -{ - dmix->semid = semget(dmix->ipc_key, DMIX_IPC_SEMS, IPC_CREAT | 0666); - if (dmix->semid < 0) - return -errno; - return 0; -} - -static int semaphore_discard(snd_pcm_dmix_t *dmix) -{ - if (dmix->semid < 0) - return -EINVAL; - if (semctl(dmix->semid, 0, IPC_RMID, NULL) < 0) - return -errno; - dmix->semid = -1; - return 0; -} - -static int semaphore_down(snd_pcm_dmix_t *dmix, int sem_num) -{ - struct sembuf op[2] = { { 0, 0, SEM_UNDO }, { 0, 1, SEM_UNDO | IPC_NOWAIT } }; - assert(dmix->semid >= 0); - op[0].sem_num = sem_num; - op[1].sem_num = sem_num; - if (semop(dmix->semid, op, 2) < 0) - return -errno; - return 0; -} - -static int semaphore_up(snd_pcm_dmix_t *dmix, int sem_num) -{ - struct sembuf op = { 0, -1, SEM_UNDO | IPC_NOWAIT }; - assert(dmix->semid >= 0); - op.sem_num = sem_num; - if (semop(dmix->semid, &op, 1) < 0) - return -errno; - return 0; -} - -/* - * global shared memory area - */ - -static int shm_create_or_connect(snd_pcm_dmix_t *dmix) -{ - static int shm_discard(snd_pcm_dmix_t *dmix); - struct shmid_ds buf; - int ret = 0; - - dmix->shmid = shmget(dmix->ipc_key, sizeof(snd_pcm_dmix_share_t), IPC_CREAT | 0666); - if (dmix->shmid < 0) - return -errno; - dmix->shmptr = shmat(dmix->shmid, 0, 0); - if (dmix->shmptr == (void *) -1) { - shm_discard(dmix); - return -errno; - } - mlock(dmix->shmptr, sizeof(snd_pcm_dmix_share_t)); - if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) { - shm_discard(dmix); - return -errno; - } - if (buf.shm_nattch == 1) { /* we're the first user, clear the segment */ - memset(dmix->shmptr, 0, sizeof(snd_pcm_dmix_share_t)); - ret = 1; - } - return ret; -} - -static int shm_discard(snd_pcm_dmix_t *dmix) -{ - struct shmid_ds buf; - int ret = 0; - - if (dmix->shmid < 0) - return -EINVAL; - if (dmix->shmptr != (void *) -1 && shmdt(dmix->shmptr) < 0) - return -errno; - dmix->shmptr = (void *) -1; - if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) - return -errno; - if (buf.shm_nattch == 0) { /* we're the last user, destroy the segment */ - if (shmctl(dmix->shmid, IPC_RMID, NULL) < 0) - return -errno; - ret = 1; - } - dmix->shmid = -1; - return ret; -} /* * sum ring buffer shared memory area */ - -static int shm_sum_create_or_connect(snd_pcm_dmix_t *dmix) +static int shm_sum_create_or_connect(snd_pcm_direct_t *dmix) { - static int shm_sum_discard(snd_pcm_dmix_t *dmix); + static int shm_sum_discard(snd_pcm_direct_t *dmix); size_t size; size = dmix->shmptr->s.channels * dmix->shmptr->s.buffer_size * sizeof(signed int); - dmix->shmid_sum = shmget(dmix->ipc_key + 1, size, IPC_CREAT | 0666); - if (dmix->shmid_sum < 0) + dmix->u.dmix.shmid_sum = shmget(dmix->ipc_key + 1, size, IPC_CREAT | 0666); + if (dmix->u.dmix.shmid_sum < 0) return -errno; - dmix->sum_buffer = shmat(dmix->shmid_sum, 0, 0); - if (dmix->sum_buffer == (void *) -1) { + dmix->u.dmix.sum_buffer = shmat(dmix->u.dmix.shmid_sum, 0, 0); + if (dmix->u.dmix.sum_buffer == (void *) -1) { shm_sum_discard(dmix); return -errno; } - mlock(dmix->sum_buffer, size); + mlock(dmix->u.dmix.sum_buffer, size); return 0; } -static int shm_sum_discard(snd_pcm_dmix_t *dmix) +static int shm_sum_discard(snd_pcm_direct_t *dmix) { struct shmid_ds buf; int ret = 0; - if (dmix->shmid_sum < 0) + if (dmix->u.dmix.shmid_sum < 0) return -EINVAL; - if (dmix->sum_buffer != (void *) -1 && shmdt(dmix->sum_buffer) < 0) + if (dmix->u.dmix.sum_buffer != (void *) -1 && shmdt(dmix->u.dmix.sum_buffer) < 0) return -errno; - dmix->sum_buffer = (void *) -1; - if (shmctl(dmix->shmid_sum, IPC_STAT, &buf) < 0) + dmix->u.dmix.sum_buffer = (void *) -1; + if (shmctl(dmix->u.dmix.shmid_sum, IPC_STAT, &buf) < 0) return -errno; if (buf.shm_nattch == 0) { /* we're the last user, destroy the segment */ - if (shmctl(dmix->shmid_sum, IPC_RMID, NULL) < 0) + if (shmctl(dmix->u.dmix.shmid_sum, IPC_RMID, NULL) < 0) return -errno; ret = 1; } - dmix->shmid_sum = -1; + dmix->u.dmix.shmid_sum = -1; return ret; } -/* - * server side - */ - -static int get_tmp_name(char *filename, size_t size) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - snprintf(filename, size, "/tmp/alsa-dmix-%i-%li-%li", getpid(), tv.tv_sec, tv.tv_usec); - filename[size-1] = '\0'; - return 0; -} - -static int make_local_socket(const char *filename, int server) -{ - size_t l = strlen(filename); - size_t size = offsetof(struct sockaddr_un, sun_path) + l; - struct sockaddr_un *addr = alloca(size); - int sock; - - sock = socket(PF_LOCAL, SOCK_STREAM, 0); - if (sock < 0) { - int result = -errno; - SYSERR("socket failed"); - return result; - } - - if (server) - unlink(filename); - addr->sun_family = AF_LOCAL; - memcpy(addr->sun_path, filename, l); - - if (server) { - if (bind(sock, (struct sockaddr *) addr, size) < 0) { - int result = -errno; - SYSERR("bind failed"); - return result; - } - } else { - if (connect(sock, (struct sockaddr *) addr, size) < 0) { - SYSERR("connect failed"); - return -errno; - } - } - return sock; -} - -#if 0 -#define server_printf(fmt, args...) printf(fmt, ##args) -#else -#define server_printf(fmt, args...) /* nothing */ -#endif - -static void server_job(snd_pcm_dmix_t *dmix) -{ - int ret, sck, i; - int max = 128, current = 0; - struct pollfd pfds[max + 1]; - - /* close all files to free resources */ - i = sysconf(_SC_OPEN_MAX); - while (--i >= 3) { - if (i != dmix->server_fd && i != dmix->hw_fd) - close(i); - } - - /* detach from parent */ - setsid(); - - pfds[0].fd = dmix->server_fd; - pfds[0].events = POLLIN | POLLERR | POLLHUP; - - server_printf("DMIX SERVER STARTED\n"); - while (1) { - ret = poll(pfds, current + 1, 500); - server_printf("DMIX SERVER: poll ret = %i, revents[0] = 0x%x\n", ret, pfds[0].revents); - if (ret < 0) /* some error */ - break; - if (ret == 0 || pfds[0].revents & (POLLERR | POLLHUP)) { /* timeout or error? */ - struct shmid_ds buf; - semaphore_down(dmix, DMIX_IPC_SEM_CLIENT); - if (shmctl(dmix->shmid, IPC_STAT, &buf) < 0) { - shm_discard(dmix); - continue; - } - server_printf("DMIX SERVER: nattch = %i\n", (int)buf.shm_nattch); - if (buf.shm_nattch == 1) /* server is the last user, exit */ - break; - semaphore_up(dmix, DMIX_IPC_SEM_CLIENT); - continue; - } - if (pfds[0].revents & POLLIN) { - ret--; - sck = accept(dmix->server_fd, 0, 0); - if (sck >= 0) { - server_printf("DMIX SERVER: new connection %i\n", sck); - if (current == max) { - close(sck); - } else { - unsigned char buf = 'A'; - pfds[current+1].fd = sck; - pfds[current+1].events = POLLIN | POLLERR | POLLHUP; - snd_send_fd(sck, &buf, 1, dmix->hw_fd); - server_printf("DMIX SERVER: fd sent ok\n"); - current++; - } - } - } - for (i = 0; i < current && ret > 0; i++) { - struct pollfd *pfd = &pfds[i+1]; - unsigned char cmd; - server_printf("client %i revents = 0x%x\n", pfd->fd, pfd->revents); - if (pfd->revents & (POLLERR | POLLHUP)) { - ret--; - close(pfd->fd); - pfd->fd = -1; - continue; - } - if (!(pfd->revents & POLLIN)) - continue; - ret--; - if (read(pfd->fd, &cmd, 1) == 1) - cmd = 0 /*process command */; - } - for (i = 0; i < current; i++) { - if (pfds[i+1].fd < 0) { - if (i + 1 != max) - memcpy(&pfds[i+1], &pfds[i+2], sizeof(struct pollfd) * (max - i - 1)); - current--; - } - } - } - close(dmix->server_fd); - close(dmix->hw_fd); - shm_discard(dmix); - semaphore_discard(dmix); - server_printf("DMIX SERVER EXIT\n"); - exit(EXIT_SUCCESS); -} - -static int server_create(snd_pcm_dmix_t *dmix) -{ - int ret; - - dmix->server_fd = -1; - - ret = get_tmp_name(dmix->shmptr->socket_name, sizeof(dmix->shmptr->socket_name)); - if (ret < 0) - return ret; - - ret = make_local_socket(dmix->shmptr->socket_name, 1); - if (ret < 0) - return ret; - dmix->server_fd = ret; - - ret = listen(dmix->server_fd, 4); - if (ret < 0) { - close(dmix->server_fd); - return ret; - } - - ret = fork(); - if (ret < 0) { - close(dmix->server_fd); - return ret; - } else if (ret == 0) { - server_job(dmix); - } - dmix->server_pid = ret; - dmix->server = 1; - return 0; -} - -static int server_discard(snd_pcm_dmix_t *dmix) -{ - if (dmix->server) { - //kill(dmix->server_pid, SIGTERM); - //waitpid(dmix->server_pid, NULL, 0); - dmix->server_pid = (pid_t)-1; - } - if (dmix->server_fd > 0) { - close(dmix->server_fd); - dmix->server_fd = -1; - } - dmix->server = 0; - return 0; -} - -/* - * client side - */ - -static int client_connect(snd_pcm_dmix_t *dmix) -{ - int ret; - unsigned char buf; - - ret = make_local_socket(dmix->shmptr->socket_name, 0); - if (ret < 0) - return ret; - dmix->comm_fd = ret; - - ret = snd_receive_fd(dmix->comm_fd, &buf, 1, &dmix->hw_fd); - if (ret < 0 || buf != 'A') { - close(dmix->comm_fd); - dmix->comm_fd = -1; - return ret; - } - - dmix->client = 1; - return 0; -} - -static int client_discard(snd_pcm_dmix_t *dmix) -{ - if (dmix->client) { - close(dmix->comm_fd); - dmix->comm_fd = -1; - } - return 0; -} - -/* - * ring buffer operation - */ - -static int check_interleave(snd_pcm_dmix_t *dmix, snd_pcm_t *pcm) -{ - unsigned int chn, channels; - int interleaved = 1; - const snd_pcm_channel_area_t *dst_areas; - const snd_pcm_channel_area_t *src_areas; - - channels = dmix->channels; - dst_areas = snd_pcm_mmap_areas(dmix->spcm); - src_areas = snd_pcm_mmap_areas(pcm); - for (chn = 1; chn < channels; chn++) { - if (dst_areas[chn-1].addr != dst_areas[chn].addr) { - interleaved = 0; - break; - } - if (src_areas[chn-1].addr != src_areas[chn].addr) { - interleaved = 0; - break; - } - } - for (chn = 0; chn < channels; chn++) { - if (dmix->bindings && dmix->bindings[chn] != chn) { - interleaved = 0; - break; - } - if (dst_areas[chn].first != sizeof(signed short) * chn * 8 || - dst_areas[chn].step != channels * sizeof(signed short) * 8) { - interleaved = 0; - break; - } - if (src_areas[chn].first != sizeof(signed short) * chn * 8 || - src_areas[chn].step != channels * sizeof(signed short) * 8) { - interleaved = 0; - break; - } - } - return dmix->interleaved = interleaved; -} - /* * the main function of this plugin: mixing * FIXME: optimize it for different architectures @@ -579,14 +125,14 @@ static int check_interleave(snd_pcm_dmix_t *dmix, snd_pcm_t *pcm) #undef MIX_AREAS2 #undef LOCK_PREFIX -static void mix_select_callbacks(snd_pcm_dmix_t *dmix) +static void mix_select_callbacks(snd_pcm_direct_t *dmix) { FILE *in; char line[255]; int smp = 0, mmx = 0; /* safe settings for all i386 CPUs */ - dmix->mix_areas1 = mix_areas1_smp; + dmix->u.dmix.mix_areas1 = mix_areas1_smp; /* try to determine, if we have a MMX capable CPU */ in = fopen("/proc/cpuinfo", "r"); if (in == NULL) @@ -603,11 +149,11 @@ static void mix_select_callbacks(snd_pcm_dmix_t *dmix) fclose(in); // printf("MMX: %i, SMP: %i\n", mmx, smp); if (mmx) { - dmix->mix_areas1 = smp > 1 ? mix_areas1_smp_mmx : mix_areas1_mmx; + dmix->u.dmix.mix_areas1 = smp > 1 ? mix_areas1_smp_mmx : mix_areas1_mmx; } else { - dmix->mix_areas1 = smp > 1 ? mix_areas1_smp : mix_areas1; + dmix->u.dmix.mix_areas1 = smp > 1 ? mix_areas1_smp : mix_areas1; } - dmix->mix_areas2 = smp > 1 ? mix_areas2_smp : mix_areas2; + dmix->u.dmix.mix_areas2 = smp > 1 ? mix_areas2_smp : mix_areas2; } #endif @@ -672,14 +218,14 @@ static void mix_areas2(unsigned int size, } } -static void mix_select_callbacks(snd_pcm_dmix_t *dmix) +static void mix_select_callbacks(snd_pcm_direct_t *dmix) { - dmix->mix_areas1 = mix_areas1; - dmix->mix_areas2 = mix_areas2; + dmix->u.dmix.mix_areas1 = mix_areas1; + dmix->u.dmix.mix_areas2 = mix_areas2; } #endif -static void mix_areas(snd_pcm_dmix_t *dmix, +static void mix_areas(snd_pcm_direct_t *dmix, const snd_pcm_channel_area_t *src_areas, const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t src_ofs, @@ -690,7 +236,7 @@ static void mix_areas(snd_pcm_dmix_t *dmix, unsigned int src_step, dst_step; unsigned int chn, dchn, channels; - channels = dmix->channels; + channels = dmix->u.dmix.channels; if (dmix->shmptr->s.format == SND_PCM_FORMAT_S16) { signed short *src; volatile signed short *dst; @@ -699,25 +245,25 @@ static void mix_areas(snd_pcm_dmix_t *dmix, * process all areas in one loop * it optimizes the memory accesses for this case */ - dmix->mix_areas1(size * channels, + dmix->u.dmix.mix_areas1(size * channels, ((signed short *)dst_areas[0].addr) + (dst_ofs * channels), ((signed short *)src_areas[0].addr) + (src_ofs * channels), - dmix->sum_buffer + (dst_ofs * channels), + dmix->u.dmix.sum_buffer + (dst_ofs * channels), sizeof(signed short), sizeof(signed short), sizeof(signed int)); return; } for (chn = 0; chn < channels; chn++) { - dchn = dmix->bindings ? dmix->bindings[chn] : chn; + dchn = dmix->u.dmix.bindings ? dmix->u.dmix.bindings[chn] : chn; if (dchn >= dmix->shmptr->s.channels) continue; src_step = src_areas[chn].step / 8; dst_step = dst_areas[dchn].step / 8; src = (signed short *)(((char *)src_areas[chn].addr + src_areas[chn].first / 8) + (src_ofs * src_step)); dst = (signed short *)(((char *)dst_areas[dchn].addr + dst_areas[dchn].first / 8) + (dst_ofs * dst_step)); - sum = dmix->sum_buffer + channels * dst_ofs + chn; - dmix->mix_areas1(size, dst, src, sum, dst_step, src_step, channels * sizeof(signed int)); + sum = dmix->u.dmix.sum_buffer + channels * dst_ofs + chn; + dmix->u.dmix.mix_areas1(size, dst, src, sum, dst_step, src_step, channels * sizeof(signed int)); } } else { signed int *src; @@ -727,25 +273,25 @@ static void mix_areas(snd_pcm_dmix_t *dmix, * process all areas in one loop * it optimizes the memory accesses for this case */ - dmix->mix_areas2(size * channels, + dmix->u.dmix.mix_areas2(size * channels, ((signed int *)dst_areas[0].addr) + (dst_ofs * channels), ((signed int *)src_areas[0].addr) + (src_ofs * channels), - dmix->sum_buffer + (dst_ofs * channels), + dmix->u.dmix.sum_buffer + (dst_ofs * channels), sizeof(signed int), sizeof(signed int), sizeof(signed int)); return; } for (chn = 0; chn < channels; chn++) { - dchn = dmix->bindings ? dmix->bindings[chn] : chn; + dchn = dmix->u.dmix.bindings ? dmix->u.dmix.bindings[chn] : chn; if (dchn >= dmix->shmptr->s.channels) continue; src_step = src_areas[chn].step / 8; dst_step = dst_areas[dchn].step / 8; src = (signed int *)(((char *)src_areas[chn].addr + src_areas[chn].first / 8) + (src_ofs * src_step)); dst = (signed int *)(((char *)dst_areas[dchn].addr + dst_areas[dchn].first / 8) + (dst_ofs * dst_step)); - sum = dmix->sum_buffer + channels * dst_ofs + chn; - dmix->mix_areas2(size, dst, src, sum, dst_step, src_step, channels * sizeof(signed int)); + sum = dmix->u.dmix.sum_buffer + channels * dst_ofs + chn; + dmix->u.dmix.mix_areas2(size, dst, src, sum, dst_step, src_step, channels * sizeof(signed int)); } } } @@ -755,7 +301,7 @@ static void mix_areas(snd_pcm_dmix_t *dmix, */ static void snd_pcm_dmix_sync_area(snd_pcm_t *pcm, snd_pcm_uframes_t size) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; snd_pcm_uframes_t appl_ptr, slave_appl_ptr, transfer; const snd_pcm_channel_area_t *src_areas, *dst_areas; @@ -787,7 +333,7 @@ static void snd_pcm_dmix_sync_area(snd_pcm_t *pcm, snd_pcm_uframes_t size) */ static int snd_pcm_dmix_sync_ptr(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; snd_pcm_uframes_t slave_hw_ptr, old_slave_hw_ptr, avail; snd_pcm_sframes_t diff; @@ -831,13 +377,13 @@ static int snd_pcm_dmix_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock A static int snd_pcm_dmix_async(snd_pcm_t *pcm, int sig, pid_t pid) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; return snd_timer_async(dmix->timer, sig, pid); } static int snd_pcm_dmix_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; unsigned short events; static snd_timer_read_t rbuf[5]; /* can be overwriten by multiple plugins, we don't need the value */ @@ -855,7 +401,7 @@ static int snd_pcm_dmix_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsign static int snd_pcm_dmix_info(snd_pcm_t *pcm, snd_pcm_info_t * info) { - // snd_pcm_dmix_t *dmix = pcm->private_data; + // snd_pcm_direct_t *dmix = pcm->private_data; memset(info, 0, sizeof(*info)); info->stream = pcm->stream; @@ -902,7 +448,7 @@ static int hw_param_interval_refine_one(snd_pcm_hw_params_t *params, static int snd_pcm_dmix_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; snd_pcm_hw_params_t *hw_params = &dmix->shmptr->hw_params; static snd_mask_t access = { .bits = { (1<rmask & (1<channels); + err = snd_interval_refine_set(hw_param_interval(params, SND_PCM_HW_PARAM_CHANNELS), dmix->u.dmix.channels); if (err < 0) return err; } @@ -998,7 +543,7 @@ static int snd_pcm_dmix_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * in static int snd_pcm_dmix_status(snd_pcm_t *pcm, snd_pcm_status_t * status) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; memset(status, 0, sizeof(*status)); status->state = dmix->state; @@ -1012,13 +557,13 @@ static int snd_pcm_dmix_status(snd_pcm_t *pcm, snd_pcm_status_t * status) static snd_pcm_state_t snd_pcm_dmix_state(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; return dmix->state; } static int snd_pcm_dmix_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; int err; switch(dmix->state) { @@ -1040,7 +585,7 @@ static int snd_pcm_dmix_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) static int snd_pcm_dmix_hwsync(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; switch(dmix->state) { case SNDRV_PCM_STATE_DRAINING: @@ -1058,9 +603,9 @@ static int snd_pcm_dmix_hwsync(snd_pcm_t *pcm) static int snd_pcm_dmix_prepare(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; - check_interleave(dmix, pcm); + snd_pcm_direct_check_interleave(dmix, pcm); // assert(pcm->boundary == dmix->shmptr->s.boundary); /* for sure */ dmix->state = SND_PCM_STATE_PREPARED; dmix->appl_ptr = 0; @@ -1070,7 +615,7 @@ static int snd_pcm_dmix_prepare(snd_pcm_t *pcm) static int snd_pcm_dmix_reset(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; dmix->hw_ptr %= pcm->period_size; dmix->appl_ptr = dmix->hw_ptr; dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr; @@ -1079,7 +624,7 @@ static int snd_pcm_dmix_reset(snd_pcm_t *pcm) static int snd_pcm_dmix_start(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; snd_pcm_sframes_t avail; struct timeval tv; int err; @@ -1105,7 +650,7 @@ static int snd_pcm_dmix_start(snd_pcm_t *pcm) static int snd_pcm_dmix_drop(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; if (dmix->state == SND_PCM_STATE_OPEN) return -EBADFD; snd_timer_stop(dmix->timer); @@ -1115,7 +660,7 @@ static int snd_pcm_dmix_drop(snd_pcm_t *pcm) static int snd_pcm_dmix_drain(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; snd_pcm_uframes_t stop_threshold; int err; @@ -1138,7 +683,7 @@ static int snd_pcm_dmix_drain(snd_pcm_t *pcm) static int snd_pcm_dmix_pause(snd_pcm_t *pcm, int enable) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; if (enable) { if (dmix->state != SND_PCM_STATE_RUNNING) return -EBADFD; @@ -1175,7 +720,7 @@ static snd_pcm_sframes_t snd_pcm_dmix_forward(snd_pcm_t *pcm, snd_pcm_uframes_t static int snd_pcm_dmix_resume(snd_pcm_t *pcm ATTRIBUTE_UNUSED) { - // snd_pcm_dmix_t *dmix = pcm->private_data; + // snd_pcm_direct_t *dmix = pcm->private_data; // FIXME return 0; } @@ -1202,25 +747,25 @@ static int snd_pcm_dmix_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) static int snd_pcm_dmix_close(snd_pcm_t *pcm) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; if (dmix->timer) snd_timer_close(dmix->timer); - semaphore_down(dmix, DMIX_IPC_SEM_CLIENT); + snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT); snd_pcm_close(dmix->spcm); if (dmix->server) - server_discard(dmix); + snd_pcm_direct_server_discard(dmix); if (dmix->client) - client_discard(dmix); + snd_pcm_direct_client_discard(dmix); shm_sum_discard(dmix); - if (shm_discard(dmix) > 0) { - if (semaphore_discard(dmix) < 0) - semaphore_up(dmix, DMIX_IPC_SEM_CLIENT); + if (snd_pcm_direct_shm_discard(dmix) > 0) { + if (snd_pcm_direct_semaphore_discard(dmix) < 0) + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); } else { - semaphore_up(dmix, DMIX_IPC_SEM_CLIENT); + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); } - if (dmix->bindings) - free(dmix->bindings); + if (dmix->u.dmix.bindings) + free(dmix->u.dmix.bindings); pcm->private_data = NULL; free(dmix); return 0; @@ -1230,7 +775,7 @@ static snd_pcm_sframes_t snd_pcm_dmix_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, snd_pcm_uframes_t size) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; int err; snd_pcm_mmap_appl_forward(pcm, size); @@ -1247,7 +792,7 @@ static snd_pcm_sframes_t snd_pcm_dmix_mmap_commit(snd_pcm_t *pcm, static snd_pcm_sframes_t snd_pcm_dmix_avail_update(snd_pcm_t *pcm ATTRIBUTE_UNUSED) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; int err; if (dmix->state == SND_PCM_STATE_RUNNING) { @@ -1260,7 +805,7 @@ static snd_pcm_sframes_t snd_pcm_dmix_avail_update(snd_pcm_t *pcm ATTRIBUTE_UNUS static void snd_pcm_dmix_dump(snd_pcm_t *pcm, snd_output_t *out) { - snd_pcm_dmix_t *dmix = pcm->private_data; + snd_pcm_direct_t *dmix = pcm->private_data; snd_output_printf(out, "Direct Stream Mixing PCM\n"); if (pcm->setup) { @@ -1309,261 +854,18 @@ static snd_pcm_fast_ops_t snd_pcm_dmix_fast_ops = { mmap_commit: snd_pcm_dmix_mmap_commit, }; -/* - * this function initializes hardware and starts playback operation with - * no stop threshold (it operates all time without xrun checking) - * also, the driver silences the unused ring buffer areas for us - */ -static int snd_pcm_dmix_initialize_slave(snd_pcm_dmix_t *dmix, snd_pcm_t *spcm, struct slave_params *params) -{ - snd_pcm_hw_params_t *hw_params; - snd_pcm_sw_params_t *sw_params; - int ret, buffer_is_not_initialized; - snd_pcm_uframes_t boundary; - struct pollfd fd; - int loops = 10; - - hw_params = &dmix->shmptr->hw_params; - sw_params = &dmix->shmptr->sw_params; - - __again: - if (loops-- <= 0) { - SNDERR("unable to find a valid configuration for slave"); - return -EINVAL; - } - ret = snd_pcm_hw_params_any(spcm, hw_params); - if (ret < 0) { - SNDERR("snd_pcm_hw_params_any failed"); - return ret; - } - ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_INTERLEAVED); - if (ret < 0) { - ret = snd_pcm_hw_params_set_access(spcm, hw_params, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); - if (ret < 0) { - SNDERR("slave plugin does not support mmap interleaved or mmap noninterleaved access"); - return ret; - } - } - ret = snd_pcm_hw_params_set_format(spcm, hw_params, params->format); - if (ret < 0) { - snd_pcm_format_t format; - switch (params->format) { - case SND_PCM_FORMAT_S32: format = SND_PCM_FORMAT_S16; break; - case SND_PCM_FORMAT_S16: format = SND_PCM_FORMAT_S32; break; - default: - SNDERR("invalid format"); - return -EINVAL; - } - ret = snd_pcm_hw_params_set_format(spcm, hw_params, format); - if (ret < 0) { - SNDERR("requested or auto-format is not available"); - return ret; - } - params->format = format; - } - ret = snd_pcm_hw_params_set_channels(spcm, hw_params, params->channels); - if (ret < 0) { - unsigned int min, max; - ret = INTERNAL(snd_pcm_hw_params_get_channels_min)(hw_params, &min); - if (ret < 0) { - SNDERR("cannot obtain minimal count of channels"); - return ret; - } - ret = INTERNAL(snd_pcm_hw_params_get_channels_min)(hw_params, &max); - if (ret < 0) { - SNDERR("cannot obtain maximal count of channels"); - return ret; - } - if (min == max) { - ret = snd_pcm_hw_params_set_channels(spcm, hw_params, min); - if (ret >= 0) - params->channels = min; - } - if (ret < 0) { - SNDERR("requested count of channels is not available"); - return ret; - } - } - ret = INTERNAL(snd_pcm_hw_params_set_rate_near)(spcm, hw_params, ¶ms->rate, 0); - if (ret < 0) { - SNDERR("requested rate is not available"); - return ret; - } - - buffer_is_not_initialized = 0; - if (params->buffer_time > 0) { - ret = INTERNAL(snd_pcm_hw_params_set_buffer_time_near)(spcm, hw_params, ¶ms->buffer_time, 0); - if (ret < 0) { - SNDERR("unable to set buffer time"); - return ret; - } - } else if (params->buffer_size > 0) { - ret = INTERNAL(snd_pcm_hw_params_set_buffer_size_near)(spcm, hw_params, ¶ms->buffer_size); - if (ret < 0) { - SNDERR("unable to set buffer size"); - return ret; - } - } else { - buffer_is_not_initialized = 1; - } - - if (params->period_time > 0) { - ret = INTERNAL(snd_pcm_hw_params_set_period_time_near)(spcm, hw_params, ¶ms->period_time, 0); - if (ret < 0) { - SNDERR("unable to set period_time"); - return ret; - } - } else if (params->period_size > 0) { - ret = INTERNAL(snd_pcm_hw_params_set_period_size_near)(spcm, hw_params, ¶ms->period_size, 0); - if (ret < 0) { - SNDERR("unable to set period_size"); - return ret; - } - } - - if (buffer_is_not_initialized && params->periods > 0) { - int periods = params->periods; - ret = INTERNAL(snd_pcm_hw_params_set_periods_near)(spcm, hw_params, ¶ms->periods, 0); - if (ret < 0) { - SNDERR("unable to set requested periods"); - return ret; - } - if (params->periods == 1) { - params->periods = periods; - if (params->period_time > 0) { - params->period_time /= 2; - goto __again; - } else if (params->period_size > 0) { - params->period_size /= 2; - goto __again; - } - SNDERR("unable to use stream with periods == 1"); - return ret; - } - } - - ret = snd_pcm_hw_params(spcm, hw_params); - if (ret < 0) { - SNDERR("unable to install hw params"); - return ret; - } - - ret = snd_pcm_sw_params_current(spcm, sw_params); - if (ret < 0) { - SNDERR("unable to get current sw_params"); - return ret; - } - - ret = snd_pcm_sw_params_get_boundary(sw_params, &boundary); - if (ret < 0) { - SNDERR("unable to get boundary"); - return ret; - } - ret = snd_pcm_sw_params_set_stop_threshold(spcm, sw_params, boundary); - if (ret < 0) { - SNDERR("unable to set stop threshold"); - return ret; - } - ret = snd_pcm_sw_params_set_silence_threshold(spcm, sw_params, 0); - if (ret < 0) { - SNDERR("unable to set silence threshold"); - return ret; - } - ret = snd_pcm_sw_params_set_silence_size(spcm, sw_params, boundary); - if (ret < 0) { - SNDERR("unable to set silence threshold (please upgrade to 0.9.0rc8+ driver)"); - return ret; - } - - ret = snd_pcm_sw_params(spcm, sw_params); - if (ret < 0) { - SNDERR("unable to install sw params (please upgrade to 0.9.0rc8+ driver)"); - return ret; - } - - ret = snd_pcm_start(spcm); - if (ret < 0) { - SNDERR("unable to start PCM stream"); - return ret; - } - - if (snd_pcm_poll_descriptors_count(spcm) != 1) { - SNDERR("unable to use hardware pcm with fd more than one!!!"); - return ret; - } - snd_pcm_poll_descriptors(spcm, &fd, 1); - dmix->hw_fd = fd.fd; - - dmix->shmptr->s.boundary = spcm->boundary; - dmix->shmptr->s.buffer_size = spcm->buffer_size; - dmix->shmptr->s.sample_bits = spcm->sample_bits; - dmix->shmptr->s.channels = spcm->channels; - dmix->shmptr->s.rate = spcm->rate; - dmix->shmptr->s.format = spcm->format; - dmix->shmptr->s.boundary = spcm->boundary; - - spcm->donot_close = 1; - return 0; -} - -/* - * the trick is used here; we cannot use effectively the hardware handle because - * we cannot drive multiple accesses to appl_ptr; so we use slave timer of given - * PCM hardware handle; it's not this easy and cheap? - */ -static int snd_pcm_dmix_initialize_poll_fd(snd_pcm_dmix_t *dmix) -{ - int ret; - snd_pcm_info_t *info; - snd_timer_params_t *params; - char name[128]; - struct pollfd fd; - - snd_pcm_info_alloca(&info); - snd_timer_params_alloca(¶ms); - ret = snd_pcm_info(dmix->spcm, info); - if (ret < 0) { - SNDERR("unable to info for slave pcm"); - return ret; - } - sprintf(name, "hw:CLASS=%i,SCLASS=0,CARD=%i,DEV=%i,SUBDEV=%i", - (int)SND_TIMER_CLASS_PCM, - snd_pcm_info_get_card(info), - snd_pcm_info_get_device(info), - snd_pcm_info_get_subdevice(info) * 2); /* it's a bit trick to distict playback and capture */ - ret = snd_timer_open(&dmix->timer, name, SND_TIMER_OPEN_NONBLOCK); - if (ret < 0) { - SNDERR("unable to open timer '%s'", name); - return ret; - } - snd_timer_params_set_auto_start(params, 1); - snd_timer_params_set_ticks(params, 1); - ret = snd_timer_params(dmix->timer, params); - if (ret < 0) { - SNDERR("unable to set timer parameters", name); - return ret; - } - if (snd_timer_poll_descriptors_count(dmix->timer) != 1) { - SNDERR("unable to use timer with fd more than one!!!", name); - return ret; - } - snd_timer_poll_descriptors(dmix->timer, &fd, 1); - dmix->poll_fd = fd.fd; - return 0; -} - /* * parse the channel map * id == client channel * value == slave's channel */ -static int parse_bindings(snd_pcm_dmix_t *dmix, snd_config_t *cfg) +static int parse_bindings(snd_pcm_direct_t *dmix, snd_config_t *cfg) { snd_config_iterator_t i, next; unsigned int chn, chn1, count = 0; int err; - dmix->channels = UINT_MAX; + dmix->u.dmix.channels = UINT_MAX; if (cfg == NULL) return 0; if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { @@ -1590,9 +892,9 @@ static int parse_bindings(snd_pcm_dmix_t *dmix, snd_config_t *cfg) SNDERR("client channel out of range"); return -EINVAL; } - dmix->bindings = malloc(count * sizeof(unsigned int)); + dmix->u.dmix.bindings = malloc(count * sizeof(unsigned int)); for (chn = 0; chn < count; chn++) - dmix->bindings[chn] = UINT_MAX; /* don't route */ + dmix->u.dmix.bindings[chn] = UINT_MAX; /* don't route */ snd_config_for_each(i, next, cfg) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; @@ -1604,19 +906,19 @@ static int parse_bindings(snd_pcm_dmix_t *dmix, snd_config_t *cfg) SNDERR("unable to get slave channel (should be integer type) in binding: %s\n", id); return -EINVAL; } - dmix->bindings[cchannel] = schannel; + dmix->u.dmix.bindings[cchannel] = schannel; } for (chn = 0; chn < count; chn++) { for (chn1 = 0; chn1 < count; chn1++) { if (chn == chn1) continue; - if (dmix->bindings[chn] == dmix->bindings[chn1]) { - SNDERR("unable to route channels %d,%d to same destination %d", chn, chn1, dmix->bindings[chn]); + if (dmix->u.dmix.bindings[chn] == dmix->u.dmix.bindings[chn1]) { + SNDERR("unable to route channels %d,%d to same destination %d", chn, chn1, dmix->u.dmix.bindings[chn]); return -EINVAL; } } } - dmix->channels = count; + dmix->u.dmix.channels = count; return 0; } @@ -1642,7 +944,7 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t stream, int mode) { snd_pcm_t *pcm = NULL, *spcm = NULL; - snd_pcm_dmix_t *dmix = NULL; + snd_pcm_direct_t *dmix = NULL; int ret, first_instance; assert(pcmp); @@ -1652,7 +954,7 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, return -EINVAL; } - dmix = calloc(1, sizeof(snd_pcm_dmix_t)); + dmix = calloc(1, sizeof(snd_pcm_direct_t)); if (!dmix) { ret = -ENOMEM; goto _err; @@ -1670,19 +972,19 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, if (ret < 0) goto _err; - ret = semaphore_create_or_connect(dmix); + ret = snd_pcm_direct_semaphore_create_or_connect(dmix); if (ret < 0) { SNDERR("unable to create IPC semaphore"); goto _err; } - ret = semaphore_down(dmix, DMIX_IPC_SEM_CLIENT); + ret = snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT); if (ret < 0) { - semaphore_discard(dmix); + snd_pcm_direct_semaphore_discard(dmix); goto _err; } - first_instance = ret = shm_create_or_connect(dmix); + first_instance = ret = snd_pcm_direct_shm_create_or_connect(dmix); if (ret < 0) { SNDERR("unable to create IPC shm instance"); goto _err; @@ -1705,7 +1007,7 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, goto _err; } - ret = snd_pcm_dmix_initialize_slave(dmix, spcm, params); + ret = snd_pcm_direct_initialize_slave(dmix, spcm, params); if (ret < 0) { SNDERR("unable to initialize slave"); goto _err; @@ -1713,7 +1015,7 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, dmix->spcm = spcm; - ret = server_create(dmix); + ret = snd_pcm_direct_server_create(dmix); if (ret < 0) { SNDERR("unable to create server"); goto _err; @@ -1721,7 +1023,7 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, dmix->shmptr->type = spcm->type; } else { - ret = client_connect(dmix); + ret = snd_pcm_direct_client_connect(dmix); if (ret < 0) { SNDERR("unable to connect client"); return ret; @@ -1754,7 +1056,7 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, goto _err; } - ret = snd_pcm_dmix_initialize_poll_fd(dmix); + ret = snd_pcm_direct_initialize_poll_fd(dmix); if (ret < 0) { SNDERR("unable to initialize poll_fd"); goto _err; @@ -1769,10 +1071,10 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, snd_pcm_set_hw_ptr(pcm, &dmix->hw_ptr, -1, 0); snd_pcm_set_appl_ptr(pcm, &dmix->appl_ptr, -1, 0); - if (dmix->channels == UINT_MAX) - dmix->channels = dmix->shmptr->s.channels; + if (dmix->u.dmix.channels == UINT_MAX) + dmix->u.dmix.channels = dmix->shmptr->s.channels; - semaphore_up(dmix, DMIX_IPC_SEM_CLIENT); + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); *pcmp = pcm; return 0; @@ -1782,23 +1084,23 @@ int snd_pcm_dmix_open(snd_pcm_t **pcmp, const char *name, if (dmix->timer) snd_timer_close(dmix->timer); if (dmix->server) - server_discard(dmix); + snd_pcm_direct_server_discard(dmix); if (dmix->client) - client_discard(dmix); + snd_pcm_direct_client_discard(dmix); if (spcm) snd_pcm_close(spcm); - if (dmix->shmid_sum >= 0) + if (dmix->u.dmix.shmid_sum >= 0) shm_sum_discard(dmix); if (dmix->shmid >= 0) { - if (shm_discard(dmix) > 0) { + if (snd_pcm_direct_shm_discard(dmix) > 0) { if (dmix->semid >= 0) { - if (semaphore_discard(dmix) < 0) - semaphore_up(dmix, DMIX_IPC_SEM_CLIENT); + if (snd_pcm_direct_semaphore_discard(dmix) < 0) + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); } } } - if (dmix->bindings) - free(dmix->bindings); + if (dmix->u.dmix.bindings) + free(dmix->u.dmix.bindings); free(dmix); } if (pcm) diff --git a/src/pcm/pcm_dshare.c b/src/pcm/pcm_dshare.c new file mode 100644 index 00000000..d94d98f9 --- /dev/null +++ b/src/pcm/pcm_dshare.c @@ -0,0 +1,1021 @@ +/** + * \file pcm/pcm_dshare.c + * \ingroup PCM_Plugins + * \brief PCM Direct Sharing of Channels Plugin Interface + * \author Jaroslav Kysela + * \date 2003 + */ +/* + * PCM - Direct Sharing of Channels + * Copyright (c) 2003 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcm_direct.h" + +#ifndef PIC +/* entry for static linking */ +const char *_snd_module_pcm_dshare = ""; +#endif + +static void share_areas(snd_pcm_direct_t *dmix, + const snd_pcm_channel_area_t *src_areas, + const snd_pcm_channel_area_t *dst_areas, + snd_pcm_uframes_t src_ofs, + snd_pcm_uframes_t dst_ofs, + snd_pcm_uframes_t size) +{ + if (dmix->interleaved) { + } else { + } +} + +/* + * synchronize shm ring buffer with hardware + */ +static void snd_pcm_dshare_sync_area(snd_pcm_t *pcm, snd_pcm_uframes_t size) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_uframes_t appl_ptr, slave_appl_ptr, transfer; + const snd_pcm_channel_area_t *src_areas, *dst_areas; + + /* get the start of update area */ + appl_ptr = dmix->appl_ptr - size; + if (appl_ptr > pcm->boundary) + appl_ptr += pcm->boundary; + appl_ptr %= pcm->buffer_size; + /* add sample areas here */ + src_areas = snd_pcm_mmap_areas(pcm); + dst_areas = snd_pcm_mmap_areas(dmix->spcm); + slave_appl_ptr = dmix->slave_appl_ptr % dmix->shmptr->s.buffer_size; + dmix->slave_appl_ptr += size; + dmix->slave_appl_ptr %= dmix->shmptr->s.boundary; + while (size > 0) { + transfer = appl_ptr + size > pcm->buffer_size ? pcm->buffer_size - appl_ptr : size; + transfer = slave_appl_ptr + transfer > dmix->shmptr->s.buffer_size ? dmix->shmptr->s.buffer_size - slave_appl_ptr : transfer; + size -= transfer; + share_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer); + slave_appl_ptr += transfer; + slave_appl_ptr %= dmix->shmptr->s.buffer_size; + appl_ptr += transfer; + appl_ptr %= pcm->buffer_size; + } +} + +/* + * synchronize hardware pointer (hw_ptr) with ours + */ +static int snd_pcm_dshare_sync_ptr(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_uframes_t slave_hw_ptr, old_slave_hw_ptr, avail; + snd_pcm_sframes_t diff; + + old_slave_hw_ptr = dmix->slave_hw_ptr; + slave_hw_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr; + diff = slave_hw_ptr - old_slave_hw_ptr; + if (diff == 0) /* fast path */ + return 0; + if (diff < 0) { + slave_hw_ptr += dmix->shmptr->s.boundary; + diff = slave_hw_ptr - old_slave_hw_ptr; + } + dmix->hw_ptr += diff; + dmix->hw_ptr %= pcm->boundary; + // printf("sync ptr diff = %li\n", diff); + if (pcm->stop_threshold >= pcm->boundary) /* don't care */ + return 0; + if ((avail = snd_pcm_mmap_playback_avail(pcm)) >= pcm->stop_threshold) { + struct timeval tv; + gettimeofday(&tv, 0); + dmix->trigger_tstamp.tv_sec = tv.tv_sec; + dmix->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L; + dmix->state = SND_PCM_STATE_XRUN; + dmix->avail_max = avail; + return -EPIPE; + } + if (avail > dmix->avail_max) + dmix->avail_max = avail; + return 0; +} + +/* + * plugin implementation + */ + +static int snd_pcm_dshare_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED) +{ + /* value is cached for us in pcm->mode (SND_PCM_NONBLOCK flag) */ + return 0; +} + +static int snd_pcm_dshare_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + return snd_timer_async(dmix->timer, sig, pid); +} + +static int snd_pcm_dshare_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + unsigned short events; + static snd_timer_read_t rbuf[5]; /* can be overwriten by multiple plugins, we don't need the value */ + + assert(pfds && nfds == 1 && revents); + events = pfds[0].revents; + if (events & POLLIN) { + events |= POLLOUT; + events &= ~POLLIN; + /* empty the timer read queue */ + while (snd_timer_read(dmix->timer, &rbuf, sizeof(rbuf)) == sizeof(rbuf)) ; + } + *revents = events; + return 0; +} + +static int snd_pcm_dshare_info(snd_pcm_t *pcm, snd_pcm_info_t * info) +{ + // snd_pcm_direct_t *dmix = pcm->private_data; + + memset(info, 0, sizeof(*info)); + info->stream = pcm->stream; + info->card = -1; + /* FIXME: fill this with something more useful: we know the hardware name */ + strncpy(info->id, pcm->name, sizeof(info->id)); + strncpy(info->name, pcm->name, sizeof(info->name)); + strncpy(info->subname, pcm->name, sizeof(info->subname)); + info->subdevices_count = 1; + return 0; +} + +static inline snd_mask_t *hw_param_mask(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + return ¶ms->masks[var - SND_PCM_HW_PARAM_FIRST_MASK]; +} + +static inline snd_interval_t *hw_param_interval(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + return ¶ms->intervals[var - SND_PCM_HW_PARAM_FIRST_INTERVAL]; +} + +static int hw_param_interval_refine_one(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, + snd_pcm_hw_params_t *src) +{ + snd_interval_t *i; + + if (!(params->rmask & (1<cmask |= 1<private_data; + snd_pcm_hw_params_t *hw_params = &dmix->shmptr->hw_params; + static snd_mask_t access = { .bits = { + (1<rmask & (1<cmask |= 1<rmask & (1<cmask |= 1<rmask & (1<u.dmix.channels); + if (err < 0) + return err; + } + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_RATE, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_BUFFER_SIZE, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_BUFFER_TIME, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_SIZE, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_TIME, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIODS, hw_params); + if (err < 0) + return err; +#ifdef REFINE_DEBUG + snd_output_puts(log, "DMIX REFINE (end):\n"); + snd_pcm_hw_params_dump(params, log); + snd_output_close(log); +#endif + return 0; +} + +static int snd_pcm_dshare_hw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t * params ATTRIBUTE_UNUSED) +{ + /* values are cached in the pcm structure */ + + return 0; +} + +static int snd_pcm_dshare_hw_free(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + /* values are cached in the pcm structure */ + return 0; +} + +static int snd_pcm_dshare_sw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t * params ATTRIBUTE_UNUSED) +{ + /* values are cached in the pcm structure */ + return 0; +} + +static int snd_pcm_dshare_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info) +{ + return snd_pcm_channel_info_shm(pcm, info, -1); +} + +static int snd_pcm_dshare_status(snd_pcm_t *pcm, snd_pcm_status_t * status) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + memset(status, 0, sizeof(*status)); + status->state = dmix->state; + status->trigger_tstamp = dmix->trigger_tstamp; + status->tstamp = snd_pcm_hw_fast_tstamp(dmix->spcm); + status->avail = snd_pcm_mmap_playback_avail(pcm); + status->avail_max = status->avail > dmix->avail_max ? status->avail : dmix->avail_max; + dmix->avail_max = 0; + return 0; +} + +static snd_pcm_state_t snd_pcm_dshare_state(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + return dmix->state; +} + +static int snd_pcm_dshare_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + int err; + + switch(dmix->state) { + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_RUNNING: + err = snd_pcm_dshare_sync_ptr(pcm); + if (err < 0) + return err; + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + *delayp = snd_pcm_mmap_playback_hw_avail(pcm); + return 0; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + return -EBADFD; + } +} + +static int snd_pcm_dshare_hwsync(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + switch(dmix->state) { + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_RUNNING: + return snd_pcm_dshare_sync_ptr(pcm); + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + return 0; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + return -EBADFD; + } +} + +static int snd_pcm_dshare_prepare(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + snd_pcm_direct_check_interleave(dmix, pcm); + // assert(pcm->boundary == dmix->shmptr->s.boundary); /* for sure */ + dmix->state = SND_PCM_STATE_PREPARED; + dmix->appl_ptr = 0; + dmix->hw_ptr = 0; + return 0; +} + +static int snd_pcm_dshare_reset(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + dmix->hw_ptr %= pcm->period_size; + dmix->appl_ptr = dmix->hw_ptr; + dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr; + return 0; +} + +static int snd_pcm_dshare_start(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_sframes_t avail; + struct timeval tv; + int err; + + if (dmix->state != SND_PCM_STATE_PREPARED) + return -EBADFD; + err = snd_timer_start(dmix->timer); + if (err < 0) + return err; + dmix->state = SND_PCM_STATE_RUNNING; + dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr; + avail = snd_pcm_mmap_playback_hw_avail(pcm); + if (avail < 0) + return 0; + if (avail > (snd_pcm_sframes_t)pcm->buffer_size) + avail = pcm->buffer_size; + snd_pcm_dshare_sync_area(pcm, avail); + gettimeofday(&tv, 0); + dmix->trigger_tstamp.tv_sec = tv.tv_sec; + dmix->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L; + return 0; +} + +static int snd_pcm_dshare_drop(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + if (dmix->state == SND_PCM_STATE_OPEN) + return -EBADFD; + snd_timer_stop(dmix->timer); + dmix->state = SND_PCM_STATE_SETUP; + return 0; +} + +static int snd_pcm_dshare_drain(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_uframes_t stop_threshold; + int err; + + if (dmix->state == SND_PCM_STATE_OPEN) + return -EBADFD; + stop_threshold = pcm->stop_threshold; + if (pcm->stop_threshold > pcm->buffer_size) + pcm->stop_threshold = pcm->buffer_size; + while (dmix->state == SND_PCM_STATE_RUNNING) { + err = snd_pcm_dshare_sync_ptr(pcm); + if (err < 0) + break; + if (pcm->mode & SND_PCM_NONBLOCK) + return -EAGAIN; + snd_pcm_wait(pcm, -1); + } + pcm->stop_threshold = stop_threshold; + return snd_pcm_dshare_drop(pcm); +} + +static int snd_pcm_dshare_pause(snd_pcm_t *pcm, int enable) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + if (enable) { + if (dmix->state != SND_PCM_STATE_RUNNING) + return -EBADFD; + dmix->state = SND_PCM_STATE_PAUSED; + snd_timer_stop(dmix->timer); + } else { + if (dmix->state != SND_PCM_STATE_PAUSED) + return -EBADFD; + dmix->state = SND_PCM_STATE_RUNNING; + snd_timer_start(dmix->timer); + } + return 0; +} + +static snd_pcm_sframes_t snd_pcm_dshare_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + /* FIXME: substract samples from the mix ring buffer, too? */ + snd_pcm_mmap_appl_backward(pcm, frames); + return frames; +} + +static snd_pcm_sframes_t snd_pcm_dshare_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + snd_pcm_sframes_t avail; + + avail = snd_pcm_mmap_avail(pcm); + if (avail < 0) + return 0; + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + snd_pcm_mmap_appl_forward(pcm, frames); + return frames; +} + +static int snd_pcm_dshare_resume(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + // snd_pcm_direct_t *dmix = pcm->private_data; + // FIXME + return 0; +} + +static snd_pcm_sframes_t snd_pcm_dshare_readi(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED) +{ + return -ENODEV; +} + +static snd_pcm_sframes_t snd_pcm_dshare_readn(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED) +{ + return -ENODEV; +} + +static int snd_pcm_dshare_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_dshare_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_dshare_close(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + if (dmix->timer) + snd_timer_close(dmix->timer); + snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT); + snd_pcm_close(dmix->spcm); + if (dmix->server) + snd_pcm_direct_server_discard(dmix); + if (dmix->client) + snd_pcm_direct_client_discard(dmix); + if (snd_pcm_direct_shm_discard(dmix) > 0) { + if (snd_pcm_direct_semaphore_discard(dmix) < 0) + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + } else { + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + } + if (dmix->u.dmix.bindings) + free(dmix->u.dmix.bindings); + pcm->private_data = NULL; + free(dmix); + return 0; +} + +static snd_pcm_sframes_t snd_pcm_dshare_mmap_commit(snd_pcm_t *pcm, + snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, + snd_pcm_uframes_t size) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + int err; + + snd_pcm_mmap_appl_forward(pcm, size); + if (dmix->state == SND_PCM_STATE_RUNNING) { + err = snd_pcm_dshare_sync_ptr(pcm); + if (err < 0) + return err; + /* ok, we commit the changes after the validation of area */ + /* it's intended, although the result might be crappy */ + snd_pcm_dshare_sync_area(pcm, size); + } + return size; +} + +static snd_pcm_sframes_t snd_pcm_dshare_avail_update(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + int err; + + if (dmix->state == SND_PCM_STATE_RUNNING) { + err = snd_pcm_dshare_sync_ptr(pcm); + if (err < 0) + return err; + } + return snd_pcm_mmap_playback_avail(pcm); +} + +static void snd_pcm_dshare_dump(snd_pcm_t *pcm, snd_output_t *out) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + snd_output_printf(out, "Direct Stream Mixing PCM\n"); + if (pcm->setup) { + snd_output_printf(out, "\nIts setup is:\n"); + snd_pcm_dump_setup(pcm, out); + } + if (dmix->spcm) + snd_pcm_dump(dmix->spcm, out); +} + +static snd_pcm_ops_t snd_pcm_dshare_ops = { + close: snd_pcm_dshare_close, + info: snd_pcm_dshare_info, + hw_refine: snd_pcm_dshare_hw_refine, + hw_params: snd_pcm_dshare_hw_params, + hw_free: snd_pcm_dshare_hw_free, + sw_params: snd_pcm_dshare_sw_params, + channel_info: snd_pcm_dshare_channel_info, + dump: snd_pcm_dshare_dump, + nonblock: snd_pcm_dshare_nonblock, + async: snd_pcm_dshare_async, + poll_revents: snd_pcm_dshare_poll_revents, + mmap: snd_pcm_dshare_mmap, + munmap: snd_pcm_dshare_munmap, +}; + +static snd_pcm_fast_ops_t snd_pcm_dshare_fast_ops = { + status: snd_pcm_dshare_status, + state: snd_pcm_dshare_state, + hwsync: snd_pcm_dshare_hwsync, + delay: snd_pcm_dshare_delay, + prepare: snd_pcm_dshare_prepare, + reset: snd_pcm_dshare_reset, + start: snd_pcm_dshare_start, + drop: snd_pcm_dshare_drop, + drain: snd_pcm_dshare_drain, + pause: snd_pcm_dshare_pause, + rewind: snd_pcm_dshare_rewind, + forward: snd_pcm_dshare_forward, + resume: snd_pcm_dshare_resume, + writei: snd_pcm_mmap_writei, + writen: snd_pcm_mmap_writen, + readi: snd_pcm_dshare_readi, + readn: snd_pcm_dshare_readn, + avail_update: snd_pcm_dshare_avail_update, + mmap_commit: snd_pcm_dshare_mmap_commit, +}; + +/* + * parse the channel map + * id == client channel + * value == slave's channel + */ +static int parse_bindings(snd_pcm_direct_t *dmix, snd_config_t *cfg) +{ + snd_config_iterator_t i, next; + unsigned int chn, chn1, count = 0; + int err; + + dmix->u.dmix.channels = UINT_MAX; + if (cfg == NULL) + return 0; + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("invalid type for bindings"); + return -EINVAL; + } + snd_config_for_each(i, next, cfg) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + long cchannel; + if (snd_config_get_id(n, &id) < 0) + continue; + err = safe_strtol(id, &cchannel); + if (err < 0 || cchannel < 0) { + SNDERR("invalid client channel in binding: %s\n", id); + return -EINVAL; + } + if ((unsigned)cchannel > count) + count = cchannel + 1; + } + if (count == 0) + return 0; + if (count > 1024) { + SNDERR("client channel out of range"); + return -EINVAL; + } + dmix->u.dmix.bindings = malloc(count * sizeof(unsigned int)); + for (chn = 0; chn < count; chn++) + dmix->u.dmix.bindings[chn] = UINT_MAX; /* don't route */ + snd_config_for_each(i, next, cfg) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + long cchannel, schannel; + if (snd_config_get_id(n, &id) < 0) + continue; + safe_strtol(id, &cchannel); + if (snd_config_get_integer(n, &schannel) < 0) { + SNDERR("unable to get slave channel (should be integer type) in binding: %s\n", id); + return -EINVAL; + } + dmix->u.dmix.bindings[cchannel] = schannel; + } + for (chn = 0; chn < count; chn++) { + for (chn1 = 0; chn1 < count; chn1++) { + if (chn == chn1) + continue; + if (dmix->u.dmix.bindings[chn] == dmix->u.dmix.bindings[chn1]) { + SNDERR("unable to route channels %d,%d to same destination %d", chn, chn1, dmix->u.dmix.bindings[chn]); + return -EINVAL; + } + } + } + dmix->u.dmix.channels = count; + return 0; +} + +/** + * \brief Creates a new dmix PCM + * \param pcmp Returns created PCM handle + * \param name Name of PCM + * \param ipc_key IPC key for semaphore and shared memory + * \param params Parameters for slave + * \param root Configuration root + * \param sconf Slave configuration + * \param stream PCM Direction (stream) + * \param mode PCM Mode + * \retval zero on success otherwise a negative error code + * \warning Using of this function might be dangerous in the sense + * of compatibility reasons. The prototype might be freely + * changed in future. + */ +int snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name, + key_t ipc_key, struct slave_params *params, + snd_config_t *bindings, + snd_config_t *root, snd_config_t *sconf, + snd_pcm_stream_t stream, int mode) +{ + snd_pcm_t *pcm = NULL, *spcm = NULL; + snd_pcm_direct_t *dmix = NULL; + int ret, first_instance; + + assert(pcmp); + + if (stream != SND_PCM_STREAM_PLAYBACK) { + SNDERR("The dmix plugin supports only playback stream"); + return -EINVAL; + } + + dmix = calloc(1, sizeof(snd_pcm_direct_t)); + if (!dmix) { + ret = -ENOMEM; + goto _err; + } + + ret = parse_bindings(dmix, bindings); + if (ret < 0) + goto _err; + + dmix->ipc_key = ipc_key; + dmix->semid = -1; + dmix->shmid = -1; + + ret = snd_pcm_new(&pcm, SND_PCM_TYPE_DSHARE, name, stream, mode); + if (ret < 0) + goto _err; + + ret = snd_pcm_direct_semaphore_create_or_connect(dmix); + if (ret < 0) { + SNDERR("unable to create IPC semaphore"); + goto _err; + } + + ret = snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT); + if (ret < 0) { + snd_pcm_direct_semaphore_discard(dmix); + goto _err; + } + + first_instance = ret = snd_pcm_direct_shm_create_or_connect(dmix); + if (ret < 0) { + SNDERR("unable to create IPC shm instance"); + goto _err; + } + + pcm->ops = &snd_pcm_dshare_ops; + pcm->fast_ops = &snd_pcm_dshare_fast_ops; + pcm->private_data = dmix; + dmix->state = SND_PCM_STATE_OPEN; + + if (first_instance) { + ret = snd_pcm_open_slave(&spcm, root, sconf, stream, mode); + if (ret < 0) { + SNDERR("unable to open slave"); + goto _err; + } + + if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) { + SNDERR("dmix plugin can be only connected to hw plugin"); + goto _err; + } + + ret = snd_pcm_direct_initialize_slave(dmix, spcm, params); + if (ret < 0) { + SNDERR("unable to initialize slave"); + goto _err; + } + + dmix->spcm = spcm; + + ret = snd_pcm_direct_server_create(dmix); + if (ret < 0) { + SNDERR("unable to create server"); + goto _err; + } + + dmix->shmptr->type = spcm->type; + } else { + ret = snd_pcm_direct_client_connect(dmix); + if (ret < 0) { + SNDERR("unable to connect client"); + return ret; + } + + ret = snd_pcm_hw_open_fd(&spcm, "dmix_client", dmix->hw_fd, 0); + if (ret < 0) { + SNDERR("unable to open hardware"); + goto _err; + } + + spcm->donot_close = 1; + spcm->setup = 1; + spcm->buffer_size = dmix->shmptr->s.buffer_size; + spcm->sample_bits = dmix->shmptr->s.sample_bits; + spcm->channels = dmix->shmptr->s.channels; + spcm->format = dmix->shmptr->s.format; + spcm->boundary = dmix->shmptr->s.boundary; + ret = snd_pcm_mmap(spcm); + if (ret < 0) { + SNDERR("unable to mmap channels"); + goto _err; + } + dmix->spcm = spcm; + } + + ret = snd_pcm_direct_initialize_poll_fd(dmix); + if (ret < 0) { + SNDERR("unable to initialize poll_fd"); + goto _err; + } + + pcm->poll_fd = dmix->poll_fd; + pcm->poll_events = POLLIN; /* it's different than other plugins */ + + pcm->mmap_rw = 1; + snd_pcm_set_hw_ptr(pcm, &dmix->hw_ptr, -1, 0); + snd_pcm_set_appl_ptr(pcm, &dmix->appl_ptr, -1, 0); + + if (dmix->u.dmix.channels == UINT_MAX) + dmix->u.dmix.channels = dmix->shmptr->s.channels; + + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + + *pcmp = pcm; + return 0; + + _err: + if (dmix) { + if (dmix->timer) + snd_timer_close(dmix->timer); + if (dmix->server) + snd_pcm_direct_server_discard(dmix); + if (dmix->client) + snd_pcm_direct_client_discard(dmix); + if (spcm) + snd_pcm_close(spcm); + if (dmix->shmid >= 0) { + if (snd_pcm_direct_shm_discard(dmix) > 0) { + if (dmix->semid >= 0) { + if (snd_pcm_direct_semaphore_discard(dmix) < 0) + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + } + } + } + if (dmix->u.dmix.bindings) + free(dmix->u.dmix.bindings); + free(dmix); + } + if (pcm) + snd_pcm_free(pcm); + return ret; +} + +/*! \page pcm_plugins + +\section pcm_plugins_dshare Plugin: dshare + +This plugin provides sharing channels. + +\code +pcm.name { + type dshare # Direct sharing + ipc_key INT # unique IPC key + ipc_key_add_uid BOOL # add current uid to unique IPC key + slave STR + # or + slave { # Slave definition + pcm STR # slave PCM name + # or + pcm { } # slave PCM definition + format STR # format definition + rate INT # rate definition + channels INT + period_time INT # in usec + # or + period_size INT # in bytes + buffer_time INT # in usec + # or + buffer_size INT # in bytes + periods INT # when buffer_size or buffer_time is not specified + } + bindings { # note: this is client independent!!! + N INT # maps slave channel to client channel N + } +} +\endcode + +\subsection pcm_plugins_hw_funcref Function reference + +
    +
  • snd_pcm_dshare_open() +
  • _snd_pcm_dshare_open() +
+ +*/ + +/** + * \brief Creates a new dmix PCM + * \param pcmp Returns created PCM handle + * \param name Name of PCM + * \param root Root configuration node + * \param conf Configuration node with dmix PCM description + * \param stream PCM Stream + * \param mode PCM Mode + * \warning Using of this function might be dangerous in the sense + * of compatibility reasons. The prototype might be freely + * changed in future. + */ +int _snd_pcm_dshare_open(snd_pcm_t **pcmp, const char *name, + snd_config_t *root, snd_config_t *conf, + snd_pcm_stream_t stream, int mode) +{ + snd_config_iterator_t i, next; + snd_config_t *slave = NULL, *bindings = NULL, *sconf; + struct slave_params params; + int bsize, psize, ipc_key_add_uid = 0; + key_t ipc_key = 0; + int err; + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (snd_pcm_conf_generic_id(id)) + continue; + if (strcmp(id, "ipc_key") == 0) { + long key; + err = snd_config_get_integer(n, &key); + if (err < 0) { + SNDERR("The field ipc_key must be an integer type"); + return err; + } + ipc_key = key; + continue; + } + if (strcmp(id, "ipc_key_add_uid") == 0) { + char *tmp; + err = snd_config_get_ascii(n, &tmp); + if (err < 0) { + SNDERR("The field ipc_key_add_uid must be a boolean type"); + return err; + } + err = snd_config_get_bool_ascii(tmp); + free(tmp); + if (err < 0) { + SNDERR("The field ipc_key_add_uid must be a boolean type"); + return err; + } + ipc_key_add_uid = err; + continue; + } + if (strcmp(id, "slave") == 0) { + slave = n; + continue; + } + if (strcmp(id, "bindings") == 0) { + bindings = n; + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + if (!slave) { + SNDERR("slave is not defined"); + return -EINVAL; + } + if (ipc_key_add_uid) + ipc_key += getuid(); + if (!ipc_key) { + SNDERR("Unique IPC key is not defined"); + return -EINVAL; + } + /* the default settings, it might be invalid for some hardware */ + params.format = SND_PCM_FORMAT_S16; + params.rate = 48000; + params.channels = 2; + params.period_time = 125000; /* 0.125 seconds */ + params.buffer_time = -1; + bsize = psize = -1; + params.periods = 3; + err = snd_pcm_slave_conf(root, slave, &sconf, 8, + SND_PCM_HW_PARAM_FORMAT, 0, ¶ms.format, + SND_PCM_HW_PARAM_RATE, 0, ¶ms.rate, + SND_PCM_HW_PARAM_CHANNELS, 0, ¶ms.channels, + SND_PCM_HW_PARAM_PERIOD_TIME, 0, ¶ms.period_time, + SND_PCM_HW_PARAM_BUFFER_TIME, 0, ¶ms.buffer_time, + SND_PCM_HW_PARAM_PERIOD_SIZE, 0, &bsize, + SND_PCM_HW_PARAM_BUFFER_SIZE, 0, &psize, + SND_PCM_HW_PARAM_PERIODS, 0, ¶ms.periods); + if (err < 0) + return err; + + /* sorry, limited features */ + if (params.format != SND_PCM_FORMAT_S16 && + params.format != SND_PCM_FORMAT_S32) { + SNDERR("invalid format, specify s16 or s32"); + snd_config_delete(sconf); + return -EINVAL; + } + + params.period_size = psize; + params.buffer_size = bsize; + err = snd_pcm_dshare_open(pcmp, name, ipc_key, ¶ms, bindings, root, sconf, stream, mode); + if (err < 0) + snd_config_delete(sconf); + return err; +} +#ifndef DOC_HIDDEN +SND_DLSYM_BUILD_VERSION(_snd_pcm_dshare_open, SND_PCM_DLSYM_VERSION); +#endif diff --git a/src/pcm/pcm_dsnoop.c b/src/pcm/pcm_dsnoop.c new file mode 100644 index 00000000..b3d711d6 --- /dev/null +++ b/src/pcm/pcm_dsnoop.c @@ -0,0 +1,1024 @@ +/** + * \file pcm/pcm_snoop.c + * \ingroup PCM_Plugins + * \brief PCM Capture Stream Snooping (dsnoop) Plugin Interface + * \author Jaroslav Kysela + * \date 2003 + */ +/* + * PCM - Capture Stream Snooping + * Copyright (c) 2003 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcm_direct.h" + +#ifndef PIC +/* entry for static linking */ +const char *_snd_module_pcm_dsnoop = ""; +#endif + +/* + * + */ + +static void snoop_areas(snd_pcm_direct_t *dmix, + const snd_pcm_channel_area_t *src_areas, + const snd_pcm_channel_area_t *dst_areas, + snd_pcm_uframes_t src_ofs, + snd_pcm_uframes_t dst_ofs, + snd_pcm_uframes_t size) +{ + if (dmix->interleaved) { + } else { + } +} + +/* + * synchronize shm ring buffer with hardware + */ +static void snd_pcm_dsnoop_sync_area(snd_pcm_t *pcm, snd_pcm_uframes_t size) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_uframes_t appl_ptr, slave_appl_ptr, transfer; + const snd_pcm_channel_area_t *src_areas, *dst_areas; + + /* get the start of update area */ + appl_ptr = dmix->appl_ptr - size; + if (appl_ptr > pcm->boundary) + appl_ptr += pcm->boundary; + appl_ptr %= pcm->buffer_size; + /* add sample areas here */ + src_areas = snd_pcm_mmap_areas(pcm); + dst_areas = snd_pcm_mmap_areas(dmix->spcm); + slave_appl_ptr = dmix->slave_appl_ptr % dmix->shmptr->s.buffer_size; + dmix->slave_appl_ptr += size; + dmix->slave_appl_ptr %= dmix->shmptr->s.boundary; + while (size > 0) { + transfer = appl_ptr + size > pcm->buffer_size ? pcm->buffer_size - appl_ptr : size; + transfer = slave_appl_ptr + transfer > dmix->shmptr->s.buffer_size ? dmix->shmptr->s.buffer_size - slave_appl_ptr : transfer; + size -= transfer; + snoop_areas(dmix, src_areas, dst_areas, appl_ptr, slave_appl_ptr, transfer); + slave_appl_ptr += transfer; + slave_appl_ptr %= dmix->shmptr->s.buffer_size; + appl_ptr += transfer; + appl_ptr %= pcm->buffer_size; + } +} + +/* + * synchronize hardware pointer (hw_ptr) with ours + */ +static int snd_pcm_dsnoop_sync_ptr(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_uframes_t slave_hw_ptr, old_slave_hw_ptr, avail; + snd_pcm_sframes_t diff; + + old_slave_hw_ptr = dmix->slave_hw_ptr; + slave_hw_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr; + diff = slave_hw_ptr - old_slave_hw_ptr; + if (diff == 0) /* fast path */ + return 0; + if (diff < 0) { + slave_hw_ptr += dmix->shmptr->s.boundary; + diff = slave_hw_ptr - old_slave_hw_ptr; + } + dmix->hw_ptr += diff; + dmix->hw_ptr %= pcm->boundary; + // printf("sync ptr diff = %li\n", diff); + if (pcm->stop_threshold >= pcm->boundary) /* don't care */ + return 0; + if ((avail = snd_pcm_mmap_playback_avail(pcm)) >= pcm->stop_threshold) { + struct timeval tv; + gettimeofday(&tv, 0); + dmix->trigger_tstamp.tv_sec = tv.tv_sec; + dmix->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L; + dmix->state = SND_PCM_STATE_XRUN; + dmix->avail_max = avail; + return -EPIPE; + } + if (avail > dmix->avail_max) + dmix->avail_max = avail; + return 0; +} + +/* + * plugin implementation + */ + +static int snd_pcm_dsnoop_nonblock(snd_pcm_t *pcm ATTRIBUTE_UNUSED, int nonblock ATTRIBUTE_UNUSED) +{ + /* value is cached for us in pcm->mode (SND_PCM_NONBLOCK flag) */ + return 0; +} + +static int snd_pcm_dsnoop_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + return snd_timer_async(dmix->timer, sig, pid); +} + +static int snd_pcm_dsnoop_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + unsigned short events; + static snd_timer_read_t rbuf[5]; /* can be overwriten by multiple plugins, we don't need the value */ + + assert(pfds && nfds == 1 && revents); + events = pfds[0].revents; + if (events & POLLIN) { + events |= POLLOUT; + events &= ~POLLIN; + /* empty the timer read queue */ + while (snd_timer_read(dmix->timer, &rbuf, sizeof(rbuf)) == sizeof(rbuf)) ; + } + *revents = events; + return 0; +} + +static int snd_pcm_dsnoop_info(snd_pcm_t *pcm, snd_pcm_info_t * info) +{ + // snd_pcm_direct_t *dmix = pcm->private_data; + + memset(info, 0, sizeof(*info)); + info->stream = pcm->stream; + info->card = -1; + /* FIXME: fill this with something more useful: we know the hardware name */ + strncpy(info->id, pcm->name, sizeof(info->id)); + strncpy(info->name, pcm->name, sizeof(info->name)); + strncpy(info->subname, pcm->name, sizeof(info->subname)); + info->subdevices_count = 1; + return 0; +} + +static inline snd_mask_t *hw_param_mask(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + return ¶ms->masks[var - SND_PCM_HW_PARAM_FIRST_MASK]; +} + +static inline snd_interval_t *hw_param_interval(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var) +{ + return ¶ms->intervals[var - SND_PCM_HW_PARAM_FIRST_INTERVAL]; +} + +static int hw_param_interval_refine_one(snd_pcm_hw_params_t *params, + snd_pcm_hw_param_t var, + snd_pcm_hw_params_t *src) +{ + snd_interval_t *i; + + if (!(params->rmask & (1<cmask |= 1<private_data; + snd_pcm_hw_params_t *hw_params = &dmix->shmptr->hw_params; + static snd_mask_t access = { .bits = { + (1<rmask & (1<cmask |= 1<rmask & (1<cmask |= 1<rmask & (1<u.dmix.channels); + if (err < 0) + return err; + } + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_RATE, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_BUFFER_SIZE, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_BUFFER_TIME, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_SIZE, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIOD_TIME, hw_params); + if (err < 0) + return err; + err = hw_param_interval_refine_one(params, SND_PCM_HW_PARAM_PERIODS, hw_params); + if (err < 0) + return err; +#ifdef REFINE_DEBUG + snd_output_puts(log, "DMIX REFINE (end):\n"); + snd_pcm_hw_params_dump(params, log); + snd_output_close(log); +#endif + return 0; +} + +static int snd_pcm_dsnoop_hw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t * params ATTRIBUTE_UNUSED) +{ + /* values are cached in the pcm structure */ + + return 0; +} + +static int snd_pcm_dsnoop_hw_free(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + /* values are cached in the pcm structure */ + return 0; +} + +static int snd_pcm_dsnoop_sw_params(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_sw_params_t * params ATTRIBUTE_UNUSED) +{ + /* values are cached in the pcm structure */ + return 0; +} + +static int snd_pcm_dsnoop_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t * info) +{ + return snd_pcm_channel_info_shm(pcm, info, -1); +} + +static int snd_pcm_dsnoop_status(snd_pcm_t *pcm, snd_pcm_status_t * status) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + memset(status, 0, sizeof(*status)); + status->state = dmix->state; + status->trigger_tstamp = dmix->trigger_tstamp; + status->tstamp = snd_pcm_hw_fast_tstamp(dmix->spcm); + status->avail = snd_pcm_mmap_playback_avail(pcm); + status->avail_max = status->avail > dmix->avail_max ? status->avail : dmix->avail_max; + dmix->avail_max = 0; + return 0; +} + +static snd_pcm_state_t snd_pcm_dsnoop_state(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + return dmix->state; +} + +static int snd_pcm_dsnoop_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + int err; + + switch(dmix->state) { + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_RUNNING: + err = snd_pcm_dsnoop_sync_ptr(pcm); + if (err < 0) + return err; + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + *delayp = snd_pcm_mmap_playback_hw_avail(pcm); + return 0; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + return -EBADFD; + } +} + +static int snd_pcm_dsnoop_hwsync(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + switch(dmix->state) { + case SNDRV_PCM_STATE_DRAINING: + case SNDRV_PCM_STATE_RUNNING: + return snd_pcm_dsnoop_sync_ptr(pcm); + case SNDRV_PCM_STATE_PREPARED: + case SNDRV_PCM_STATE_SUSPENDED: + return 0; + case SNDRV_PCM_STATE_XRUN: + return -EPIPE; + default: + return -EBADFD; + } +} + +static int snd_pcm_dsnoop_prepare(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + snd_pcm_direct_check_interleave(dmix, pcm); + // assert(pcm->boundary == dmix->shmptr->s.boundary); /* for sure */ + dmix->state = SND_PCM_STATE_PREPARED; + dmix->appl_ptr = 0; + dmix->hw_ptr = 0; + return 0; +} + +static int snd_pcm_dsnoop_reset(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + dmix->hw_ptr %= pcm->period_size; + dmix->appl_ptr = dmix->hw_ptr; + dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr; + return 0; +} + +static int snd_pcm_dsnoop_start(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_sframes_t avail; + struct timeval tv; + int err; + + if (dmix->state != SND_PCM_STATE_PREPARED) + return -EBADFD; + err = snd_timer_start(dmix->timer); + if (err < 0) + return err; + dmix->state = SND_PCM_STATE_RUNNING; + dmix->slave_appl_ptr = dmix->slave_hw_ptr = *dmix->spcm->hw.ptr; + avail = snd_pcm_mmap_playback_hw_avail(pcm); + if (avail < 0) + return 0; + if (avail > (snd_pcm_sframes_t)pcm->buffer_size) + avail = pcm->buffer_size; + snd_pcm_dsnoop_sync_area(pcm, avail); + gettimeofday(&tv, 0); + dmix->trigger_tstamp.tv_sec = tv.tv_sec; + dmix->trigger_tstamp.tv_nsec = tv.tv_usec * 1000L; + return 0; +} + +static int snd_pcm_dsnoop_drop(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + if (dmix->state == SND_PCM_STATE_OPEN) + return -EBADFD; + snd_timer_stop(dmix->timer); + dmix->state = SND_PCM_STATE_SETUP; + return 0; +} + +static int snd_pcm_dsnoop_drain(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + snd_pcm_uframes_t stop_threshold; + int err; + + if (dmix->state == SND_PCM_STATE_OPEN) + return -EBADFD; + stop_threshold = pcm->stop_threshold; + if (pcm->stop_threshold > pcm->buffer_size) + pcm->stop_threshold = pcm->buffer_size; + while (dmix->state == SND_PCM_STATE_RUNNING) { + err = snd_pcm_dsnoop_sync_ptr(pcm); + if (err < 0) + break; + if (pcm->mode & SND_PCM_NONBLOCK) + return -EAGAIN; + snd_pcm_wait(pcm, -1); + } + pcm->stop_threshold = stop_threshold; + return snd_pcm_dsnoop_drop(pcm); +} + +static int snd_pcm_dsnoop_pause(snd_pcm_t *pcm, int enable) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + if (enable) { + if (dmix->state != SND_PCM_STATE_RUNNING) + return -EBADFD; + dmix->state = SND_PCM_STATE_PAUSED; + snd_timer_stop(dmix->timer); + } else { + if (dmix->state != SND_PCM_STATE_PAUSED) + return -EBADFD; + dmix->state = SND_PCM_STATE_RUNNING; + snd_timer_start(dmix->timer); + } + return 0; +} + +static snd_pcm_sframes_t snd_pcm_dsnoop_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + /* FIXME: substract samples from the mix ring buffer, too? */ + snd_pcm_mmap_appl_backward(pcm, frames); + return frames; +} + +static snd_pcm_sframes_t snd_pcm_dsnoop_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + snd_pcm_sframes_t avail; + + avail = snd_pcm_mmap_avail(pcm); + if (avail < 0) + return 0; + if (frames > (snd_pcm_uframes_t)avail) + frames = avail; + snd_pcm_mmap_appl_forward(pcm, frames); + return frames; +} + +static int snd_pcm_dsnoop_resume(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + // snd_pcm_direct_t *dmix = pcm->private_data; + // FIXME + return 0; +} + +static snd_pcm_sframes_t snd_pcm_dsnoop_readi(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void *buffer ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED) +{ + return -ENODEV; +} + +static snd_pcm_sframes_t snd_pcm_dsnoop_readn(snd_pcm_t *pcm ATTRIBUTE_UNUSED, void **bufs ATTRIBUTE_UNUSED, snd_pcm_uframes_t size ATTRIBUTE_UNUSED) +{ + return -ENODEV; +} + +static int snd_pcm_dsnoop_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_dsnoop_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_dsnoop_close(snd_pcm_t *pcm) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + if (dmix->timer) + snd_timer_close(dmix->timer); + snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT); + snd_pcm_close(dmix->spcm); + if (dmix->server) + snd_pcm_direct_server_discard(dmix); + if (dmix->client) + snd_pcm_direct_client_discard(dmix); + if (snd_pcm_direct_shm_discard(dmix) > 0) { + if (snd_pcm_direct_semaphore_discard(dmix) < 0) + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + } else { + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + } + if (dmix->u.dmix.bindings) + free(dmix->u.dmix.bindings); + pcm->private_data = NULL; + free(dmix); + return 0; +} + +static snd_pcm_sframes_t snd_pcm_dsnoop_mmap_commit(snd_pcm_t *pcm, + snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, + snd_pcm_uframes_t size) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + int err; + + snd_pcm_mmap_appl_forward(pcm, size); + if (dmix->state == SND_PCM_STATE_RUNNING) { + err = snd_pcm_dsnoop_sync_ptr(pcm); + if (err < 0) + return err; + /* ok, we commit the changes after the validation of area */ + /* it's intended, although the result might be crappy */ + snd_pcm_dsnoop_sync_area(pcm, size); + } + return size; +} + +static snd_pcm_sframes_t snd_pcm_dsnoop_avail_update(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + int err; + + if (dmix->state == SND_PCM_STATE_RUNNING) { + err = snd_pcm_dsnoop_sync_ptr(pcm); + if (err < 0) + return err; + } + return snd_pcm_mmap_playback_avail(pcm); +} + +static void snd_pcm_dsnoop_dump(snd_pcm_t *pcm, snd_output_t *out) +{ + snd_pcm_direct_t *dmix = pcm->private_data; + + snd_output_printf(out, "Direct Stream Mixing PCM\n"); + if (pcm->setup) { + snd_output_printf(out, "\nIts setup is:\n"); + snd_pcm_dump_setup(pcm, out); + } + if (dmix->spcm) + snd_pcm_dump(dmix->spcm, out); +} + +static snd_pcm_ops_t snd_pcm_dsnoop_ops = { + close: snd_pcm_dsnoop_close, + info: snd_pcm_dsnoop_info, + hw_refine: snd_pcm_dsnoop_hw_refine, + hw_params: snd_pcm_dsnoop_hw_params, + hw_free: snd_pcm_dsnoop_hw_free, + sw_params: snd_pcm_dsnoop_sw_params, + channel_info: snd_pcm_dsnoop_channel_info, + dump: snd_pcm_dsnoop_dump, + nonblock: snd_pcm_dsnoop_nonblock, + async: snd_pcm_dsnoop_async, + poll_revents: snd_pcm_dsnoop_poll_revents, + mmap: snd_pcm_dsnoop_mmap, + munmap: snd_pcm_dsnoop_munmap, +}; + +static snd_pcm_fast_ops_t snd_pcm_dsnoop_fast_ops = { + status: snd_pcm_dsnoop_status, + state: snd_pcm_dsnoop_state, + hwsync: snd_pcm_dsnoop_hwsync, + delay: snd_pcm_dsnoop_delay, + prepare: snd_pcm_dsnoop_prepare, + reset: snd_pcm_dsnoop_reset, + start: snd_pcm_dsnoop_start, + drop: snd_pcm_dsnoop_drop, + drain: snd_pcm_dsnoop_drain, + pause: snd_pcm_dsnoop_pause, + rewind: snd_pcm_dsnoop_rewind, + forward: snd_pcm_dsnoop_forward, + resume: snd_pcm_dsnoop_resume, + writei: snd_pcm_mmap_writei, + writen: snd_pcm_mmap_writen, + readi: snd_pcm_dsnoop_readi, + readn: snd_pcm_dsnoop_readn, + avail_update: snd_pcm_dsnoop_avail_update, + mmap_commit: snd_pcm_dsnoop_mmap_commit, +}; + +/* + * parse the channel map + * id == client channel + * value == slave's channel + */ +static int parse_bindings(snd_pcm_direct_t *dmix, snd_config_t *cfg) +{ + snd_config_iterator_t i, next; + unsigned int chn, chn1, count = 0; + int err; + + dmix->u.dmix.channels = UINT_MAX; + if (cfg == NULL) + return 0; + if (snd_config_get_type(cfg) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("invalid type for bindings"); + return -EINVAL; + } + snd_config_for_each(i, next, cfg) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + long cchannel; + if (snd_config_get_id(n, &id) < 0) + continue; + err = safe_strtol(id, &cchannel); + if (err < 0 || cchannel < 0) { + SNDERR("invalid client channel in binding: %s\n", id); + return -EINVAL; + } + if ((unsigned)cchannel > count) + count = cchannel + 1; + } + if (count == 0) + return 0; + if (count > 1024) { + SNDERR("client channel out of range"); + return -EINVAL; + } + dmix->u.dmix.bindings = malloc(count * sizeof(unsigned int)); + for (chn = 0; chn < count; chn++) + dmix->u.dmix.bindings[chn] = UINT_MAX; /* don't route */ + snd_config_for_each(i, next, cfg) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + long cchannel, schannel; + if (snd_config_get_id(n, &id) < 0) + continue; + safe_strtol(id, &cchannel); + if (snd_config_get_integer(n, &schannel) < 0) { + SNDERR("unable to get slave channel (should be integer type) in binding: %s\n", id); + return -EINVAL; + } + dmix->u.dmix.bindings[cchannel] = schannel; + } + for (chn = 0; chn < count; chn++) { + for (chn1 = 0; chn1 < count; chn1++) { + if (chn == chn1) + continue; + if (dmix->u.dmix.bindings[chn] == dmix->u.dmix.bindings[chn1]) { + SNDERR("unable to route channels %d,%d to same destination %d", chn, chn1, dmix->u.dmix.bindings[chn]); + return -EINVAL; + } + } + } + dmix->u.dmix.channels = count; + return 0; +} + +/** + * \brief Creates a new dmix PCM + * \param pcmp Returns created PCM handle + * \param name Name of PCM + * \param ipc_key IPC key for semaphore and shared memory + * \param params Parameters for slave + * \param root Configuration root + * \param sconf Slave configuration + * \param stream PCM Direction (stream) + * \param mode PCM Mode + * \retval zero on success otherwise a negative error code + * \warning Using of this function might be dangerous in the sense + * of compatibility reasons. The prototype might be freely + * changed in future. + */ +int snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name, + key_t ipc_key, struct slave_params *params, + snd_config_t *bindings, + snd_config_t *root, snd_config_t *sconf, + snd_pcm_stream_t stream, int mode) +{ + snd_pcm_t *pcm = NULL, *spcm = NULL; + snd_pcm_direct_t *dmix = NULL; + int ret, first_instance; + + assert(pcmp); + + if (stream != SND_PCM_STREAM_PLAYBACK) { + SNDERR("The dmix plugin supports only playback stream"); + return -EINVAL; + } + + dmix = calloc(1, sizeof(snd_pcm_direct_t)); + if (!dmix) { + ret = -ENOMEM; + goto _err; + } + + ret = parse_bindings(dmix, bindings); + if (ret < 0) + goto _err; + + dmix->ipc_key = ipc_key; + dmix->semid = -1; + dmix->shmid = -1; + + ret = snd_pcm_new(&pcm, SND_PCM_TYPE_DSNOOP, name, stream, mode); + if (ret < 0) + goto _err; + + ret = snd_pcm_direct_semaphore_create_or_connect(dmix); + if (ret < 0) { + SNDERR("unable to create IPC semaphore"); + goto _err; + } + + ret = snd_pcm_direct_semaphore_down(dmix, DIRECT_IPC_SEM_CLIENT); + if (ret < 0) { + snd_pcm_direct_semaphore_discard(dmix); + goto _err; + } + + first_instance = ret = snd_pcm_direct_shm_create_or_connect(dmix); + if (ret < 0) { + SNDERR("unable to create IPC shm instance"); + goto _err; + } + + pcm->ops = &snd_pcm_dsnoop_ops; + pcm->fast_ops = &snd_pcm_dsnoop_fast_ops; + pcm->private_data = dmix; + dmix->state = SND_PCM_STATE_OPEN; + + if (first_instance) { + ret = snd_pcm_open_slave(&spcm, root, sconf, stream, mode); + if (ret < 0) { + SNDERR("unable to open slave"); + goto _err; + } + + if (snd_pcm_type(spcm) != SND_PCM_TYPE_HW) { + SNDERR("dmix plugin can be only connected to hw plugin"); + goto _err; + } + + ret = snd_pcm_direct_initialize_slave(dmix, spcm, params); + if (ret < 0) { + SNDERR("unable to initialize slave"); + goto _err; + } + + dmix->spcm = spcm; + + ret = snd_pcm_direct_server_create(dmix); + if (ret < 0) { + SNDERR("unable to create server"); + goto _err; + } + + dmix->shmptr->type = spcm->type; + } else { + ret = snd_pcm_direct_client_connect(dmix); + if (ret < 0) { + SNDERR("unable to connect client"); + return ret; + } + + ret = snd_pcm_hw_open_fd(&spcm, "dmix_client", dmix->hw_fd, 0); + if (ret < 0) { + SNDERR("unable to open hardware"); + goto _err; + } + + spcm->donot_close = 1; + spcm->setup = 1; + spcm->buffer_size = dmix->shmptr->s.buffer_size; + spcm->sample_bits = dmix->shmptr->s.sample_bits; + spcm->channels = dmix->shmptr->s.channels; + spcm->format = dmix->shmptr->s.format; + spcm->boundary = dmix->shmptr->s.boundary; + ret = snd_pcm_mmap(spcm); + if (ret < 0) { + SNDERR("unable to mmap channels"); + goto _err; + } + dmix->spcm = spcm; + } + + ret = snd_pcm_direct_initialize_poll_fd(dmix); + if (ret < 0) { + SNDERR("unable to initialize poll_fd"); + goto _err; + } + + pcm->poll_fd = dmix->poll_fd; + pcm->poll_events = POLLIN; /* it's different than other plugins */ + + pcm->mmap_rw = 1; + snd_pcm_set_hw_ptr(pcm, &dmix->hw_ptr, -1, 0); + snd_pcm_set_appl_ptr(pcm, &dmix->appl_ptr, -1, 0); + + if (dmix->u.dmix.channels == UINT_MAX) + dmix->u.dmix.channels = dmix->shmptr->s.channels; + + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + + *pcmp = pcm; + return 0; + + _err: + if (dmix) { + if (dmix->timer) + snd_timer_close(dmix->timer); + if (dmix->server) + snd_pcm_direct_server_discard(dmix); + if (dmix->client) + snd_pcm_direct_client_discard(dmix); + if (spcm) + snd_pcm_close(spcm); + if (dmix->shmid >= 0) { + if (snd_pcm_direct_shm_discard(dmix) > 0) { + if (dmix->semid >= 0) { + if (snd_pcm_direct_semaphore_discard(dmix) < 0) + snd_pcm_direct_semaphore_up(dmix, DIRECT_IPC_SEM_CLIENT); + } + } + } + if (dmix->u.dmix.bindings) + free(dmix->u.dmix.bindings); + free(dmix); + } + if (pcm) + snd_pcm_free(pcm); + return ret; +} + +/*! \page pcm_plugins + +\section pcm_plugins_snoop Plugin: dsnoop + +This plugin splits one capture stream to more. + +\code +pcm.name { + type dsnoop # Direct snoop + ipc_key INT # unique IPC key + ipc_key_add_uid BOOL # add current uid to unique IPC key + slave STR + # or + slave { # Slave definition + pcm STR # slave PCM name + # or + pcm { } # slave PCM definition + format STR # format definition + rate INT # rate definition + channels INT + period_time INT # in usec + # or + period_size INT # in bytes + buffer_time INT # in usec + # or + buffer_size INT # in bytes + periods INT # when buffer_size or buffer_time is not specified + } + bindings { # note: this is client independent!!! + N INT # maps slave channel to client channel N + } +} +\endcode + +\subsection pcm_plugins_hw_funcref Function reference + +
    +
  • snd_pcm_dsnoop_open() +
  • _snd_pcm_dsnoop_open() +
+ +*/ + +/** + * \brief Creates a new dmix PCM + * \param pcmp Returns created PCM handle + * \param name Name of PCM + * \param root Root configuration node + * \param conf Configuration node with dmix PCM description + * \param stream PCM Stream + * \param mode PCM Mode + * \warning Using of this function might be dangerous in the sense + * of compatibility reasons. The prototype might be freely + * changed in future. + */ +int _snd_pcm_dsnoop_open(snd_pcm_t **pcmp, const char *name, + snd_config_t *root, snd_config_t *conf, + snd_pcm_stream_t stream, int mode) +{ + snd_config_iterator_t i, next; + snd_config_t *slave = NULL, *bindings = NULL, *sconf; + struct slave_params params; + int bsize, psize, ipc_key_add_uid = 0; + key_t ipc_key = 0; + int err; + snd_config_for_each(i, next, conf) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + if (snd_config_get_id(n, &id) < 0) + continue; + if (snd_pcm_conf_generic_id(id)) + continue; + if (strcmp(id, "ipc_key") == 0) { + long key; + err = snd_config_get_integer(n, &key); + if (err < 0) { + SNDERR("The field ipc_key must be an integer type"); + return err; + } + ipc_key = key; + continue; + } + if (strcmp(id, "ipc_key_add_uid") == 0) { + char *tmp; + err = snd_config_get_ascii(n, &tmp); + if (err < 0) { + SNDERR("The field ipc_key_add_uid must be a boolean type"); + return err; + } + err = snd_config_get_bool_ascii(tmp); + free(tmp); + if (err < 0) { + SNDERR("The field ipc_key_add_uid must be a boolean type"); + return err; + } + ipc_key_add_uid = err; + continue; + } + if (strcmp(id, "slave") == 0) { + slave = n; + continue; + } + if (strcmp(id, "bindings") == 0) { + bindings = n; + continue; + } + SNDERR("Unknown field %s", id); + return -EINVAL; + } + if (!slave) { + SNDERR("slave is not defined"); + return -EINVAL; + } + if (ipc_key_add_uid) + ipc_key += getuid(); + if (!ipc_key) { + SNDERR("Unique IPC key is not defined"); + return -EINVAL; + } + /* the default settings, it might be invalid for some hardware */ + params.format = SND_PCM_FORMAT_S16; + params.rate = 48000; + params.channels = 2; + params.period_time = 125000; /* 0.125 seconds */ + params.buffer_time = -1; + bsize = psize = -1; + params.periods = 3; + err = snd_pcm_slave_conf(root, slave, &sconf, 8, + SND_PCM_HW_PARAM_FORMAT, 0, ¶ms.format, + SND_PCM_HW_PARAM_RATE, 0, ¶ms.rate, + SND_PCM_HW_PARAM_CHANNELS, 0, ¶ms.channels, + SND_PCM_HW_PARAM_PERIOD_TIME, 0, ¶ms.period_time, + SND_PCM_HW_PARAM_BUFFER_TIME, 0, ¶ms.buffer_time, + SND_PCM_HW_PARAM_PERIOD_SIZE, 0, &bsize, + SND_PCM_HW_PARAM_BUFFER_SIZE, 0, &psize, + SND_PCM_HW_PARAM_PERIODS, 0, ¶ms.periods); + if (err < 0) + return err; + + /* sorry, limited features */ + if (params.format != SND_PCM_FORMAT_S16 && + params.format != SND_PCM_FORMAT_S32) { + SNDERR("invalid format, specify s16 or s32"); + snd_config_delete(sconf); + return -EINVAL; + } + + params.period_size = psize; + params.buffer_size = bsize; + err = snd_pcm_dsnoop_open(pcmp, name, ipc_key, ¶ms, bindings, root, sconf, stream, mode); + if (err < 0) + snd_config_delete(sconf); + return err; +} +#ifndef DOC_HIDDEN +SND_DLSYM_BUILD_VERSION(_snd_pcm_dsnoop_open, SND_PCM_DLSYM_VERSION); +#endif diff --git a/src/pcm/pcm_symbols.c b/src/pcm/pcm_symbols.c index 51f49e81..0a08744a 100644 --- a/src/pcm/pcm_symbols.c +++ b/src/pcm/pcm_symbols.c @@ -41,6 +41,8 @@ extern const char *_snd_module_pcm_shm; extern const char *_snd_module_pcm_lfloat; extern const char *_snd_module_pcm_ladspa; extern const char *_snd_module_pcm_dmix; +extern const char *_snd_module_pcm_dsnoop; +extern const char *_snd_module_pcm_dshare; static const char **snd_pcm_open_objects[] = { &_snd_module_pcm_adpcm, @@ -61,7 +63,9 @@ static const char **snd_pcm_open_objects[] = { &_snd_module_pcm_shm, &_snd_module_pcm_lfloat, &_snd_module_pcm_ladspa, - &_snd_module_pcm_dmix + &_snd_module_pcm_dmix, + &_snd_module_pcm_dsnoop, + &_snd_module_pcm_dshare }; void *snd_pcm_open_symbols(void)