diff options
author | Abramo Bagnara <abramo@alsa-project.org> | 2000-08-31 11:21:05 +0000 |
---|---|---|
committer | Abramo Bagnara <abramo@alsa-project.org> | 2000-08-31 11:21:05 +0000 |
commit | 4637f62ff5134cf9df4640b0ccc931d164024989 (patch) | |
tree | d89eb184f51d4b6c31f2a81dd6cf15a41ed90231 | |
parent | e94033141d96d27c60450f006d627766c884de34 (diff) |
First version of ALSA client/server
-rw-r--r-- | configure.in | 2 | ||||
-rw-r--r-- | include/Makefile.am | 2 | ||||
-rw-r--r-- | include/aserver.h | 77 | ||||
-rw-r--r-- | include/header.h | 10 | ||||
-rw-r--r-- | include/pcm.h | 25 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/aserver/Makefile.am | 8 | ||||
-rw-r--r-- | src/aserver/aserver.c | 885 | ||||
-rw-r--r-- | src/pcm/Makefile.am | 2 | ||||
-rw-r--r-- | src/pcm/pcm.c | 58 | ||||
-rw-r--r-- | src/pcm/pcm_client.c | 878 | ||||
-rw-r--r-- | src/pcm/pcm_hw.c | 16 |
12 files changed, 1931 insertions, 34 deletions
diff --git a/configure.in b/configure.in index 26a143dd..d7fdedcb 100644 --- a/configure.in +++ b/configure.in @@ -53,6 +53,6 @@ AC_OUTPUT(Makefile doc/Makefile include/Makefile src/Makefile \ src/control/Makefile src/mixer/Makefile src/pcm/Makefile \ src/pcm/plugin/Makefile src/rawmidi/Makefile src/timer/Makefile \ src/hwdep/Makefile src/seq/Makefile src/instr/Makefile \ - src/compat/Makefile src/conf/Makefile \ + src/compat/Makefile src/conf/Makefile src/aserver/Makefile \ test/Makefile utils/Makefile \ utils/alsa-lib.spec) diff --git a/include/Makefile.am b/include/Makefile.am index 2761c59a..a930d93a 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -6,7 +6,7 @@ sysinclude_HEADERS = asoundlib.h header_files=header.h version.h error.h control.h mixer.h pcm.h rawmidi.h \ timer.h hwdep.h seq.h seqmid.h conv.h instr.h conf.h footer.h -noinst_HEADERS=$(header_files) search.h +noinst_HEADERS=$(header_files) search.h list.h aserver.h asoundlib.h: $(header_files) cat $^ > $@ diff --git a/include/aserver.h b/include/aserver.h new file mode 100644 index 00000000..428ec286 --- /dev/null +++ b/include/aserver.h @@ -0,0 +1,77 @@ +/* + * ALSA client/server header file + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + + +#define SND_PCM_IOCTL_MMAP_DATA _IO ('A', 0xf0) +#define SND_PCM_IOCTL_MMAP_CONTROL _IO ('A', 0xf1) +#define SND_PCM_IOCTL_MMAP_STATUS _IO ('A', 0xf2) +#define SND_PCM_IOCTL_MUNMAP_DATA _IO ('A', 0xf3) +#define SND_PCM_IOCTL_MUNMAP_CONTROL _IO ('A', 0xf4) +#define SND_PCM_IOCTL_MUNMAP_STATUS _IO ('A', 0xf5) +#define SND_PCM_IOCTL_CLOSE _IO ('A', 0xf6) + +typedef struct { + int result; + int cmd; + union { + snd_pcm_info_t info; + snd_pcm_params_t params; + snd_pcm_params_info_t params_info; + snd_pcm_setup_t setup; + snd_pcm_status_t status; + int pause; + snd_pcm_channel_info_t channel_info; + snd_pcm_channel_params_t channel_params; + snd_pcm_channel_setup_t channel_setup; + off_t frame_data; + int frame_io; + int link; + snd_xfer_t read; + snd_xfer_t write; + snd_xferv_t readv; + snd_xferv_t writev; + } u; + char data[0]; +} snd_pcm_client_shm_t; + +#define PCM_SHM_SIZE 65536 +#define PCM_SHM_DATA_MAXLEN (PCM_SHM_SIZE - offsetof(snd_pcm_client_shm_t, data)) + +typedef struct { + unsigned char dev_type; + unsigned char transport_type; + unsigned char stream; + unsigned char mode; + unsigned char namelen; + char name[0]; +} snd_client_open_request_t; + +typedef struct { + long result; + long cookie; +} snd_client_open_answer_t; + +struct cmsg_fd +{ + int len; /* sizeof structure */ + int level; /* SOL_SOCKET */ + int type; /* SCM_RIGHTS */ + int fd; /* fd to pass */ +}; diff --git a/include/header.h b/include/header.h index 93531352..6c667bd2 100644 --- a/include/header.h +++ b/include/header.h @@ -35,3 +35,13 @@ #define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) #endif +#define SND_DEV_TYPE_PCM 0 +#define SND_DEV_TYPE_CONTROL 1 +#define SND_DEV_TYPE_RAWMIDI 2 +#define SND_DEV_TYPE_TIMER 3 +#define SND_DEV_TYPE_HWDEP 4 +#define SND_DEV_TYPE_SEQ 5 + +#define SND_TRANSPORT_TYPE_SHM 0 +#define SND_TRANSPORT_TYPE_TCP 1 + diff --git a/include/pcm.h b/include/pcm.h index 7750d9cc..0e67cf71 100644 --- a/include/pcm.h +++ b/include/pcm.h @@ -98,28 +98,7 @@ static inline size_t bitset_count(bitset_t *bitset, size_t nbits) typedef struct snd_pcm snd_pcm_t; typedef struct snd_pcm_loopback snd_pcm_loopback_t; -typedef enum { SND_PCM_TYPE_HW, SND_PCM_TYPE_PLUG, SND_PCM_TYPE_MULTI } snd_pcm_type_t; - -#if 0 -typedef struct { - snd_pcm_t *handle; - snd_timestamp_t tstamp; - int result; - union { - char reserved[256]; - } arg; -} snd_pcm_synchro_request_t; - -typedef enum { SND_PCM_SYNCHRO_GO } snd_pcm_synchro_cmd_t; - -#define snd_pcm_synchro_mode_t snd_pcm_sync_mode_t -#define SND_PCM_SYNCHRO_MODE_NORMAL SND_PCM_SYNC_MODE_NORMAL -#define SND_PCM_SYNCHRO_MODE_HARDWARE SND_PCM_SYNC_MODE_HARDWARE -#define SND_PCM_SYNCHRO_MODE_RELAXED SND_PCM_SYNC_MODE_RELAXED -int snd_pcm_synchro(snd_pcm_synchro_cmd_t cmd, - unsigned int reqs_count, snd_pcm_synchro_request_t *reqs, - snd_pcm_synchro_mode_t mode); -#endif +typedef enum { SND_PCM_TYPE_HW, SND_PCM_TYPE_PLUG, SND_PCM_TYPE_MULTI, SND_PCM_TYPE_CLIENT } snd_pcm_type_t; int snd_pcm_open(snd_pcm_t **handle, char *name, @@ -384,6 +363,8 @@ int snd_pcm_multi_create(snd_pcm_t **handlep, size_t slaves_count, unsigned int *binds_slave, unsigned int *binds_slave_channel, int close_slaves); +int snd_pcm_client_create(snd_pcm_t **handlep, char *host, int port, int transport, char *name, int stream, int mode); + #ifdef __cplusplus } #endif diff --git a/src/Makefile.am b/src/Makefile.am index 4286ea43..60eb8612 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS=control mixer pcm rawmidi timer hwdep seq instr compat conf +SUBDIRS=control mixer pcm rawmidi timer hwdep seq instr compat conf aserver COMPATNUM=@LIBTOOL_VERSION_INFO@ lib_LTLIBRARIES = libasound.la diff --git a/src/aserver/Makefile.am b/src/aserver/Makefile.am new file mode 100644 index 00000000..dca5ca07 --- /dev/null +++ b/src/aserver/Makefile.am @@ -0,0 +1,8 @@ + +bin_PROGRAMS = aserver +aserver_SOURCES = aserver.c +aserver_LDADD = -lasound + +all: aserver + +INCLUDES=-I$(top_srcdir)/include -I$(top_srcdir)/src/pcm diff --git a/src/aserver/aserver.c b/src/aserver/aserver.c new file mode 100644 index 00000000..edc749af --- /dev/null +++ b/src/aserver/aserver.c @@ -0,0 +1,885 @@ +/* + * ALSA server + * Copyright (c) by Abramo Bagnara <abramo@alsa-project.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <sys/shm.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <sys/un.h> +#include <sys/uio.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <assert.h> +#include <stddef.h> +#include <getopt.h> +#include <netinet/in.h> + +#include "asoundlib.h" +#include "pcm_local.h" +#include "aserver.h" + +char *command; + +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define error(...) do {\ + fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + putc('\n', stderr); \ +} while (0) +#else +#define error(args...) do {\ + fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ + fprintf(stderr, ##args); \ + putc('\n', stderr); \ +} while (0) +#endif + +#define perrno(string) error("%s", strerror(errno)) + +int make_local_socket(const char *filename) +{ + 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; + perrno("socket"); + return result; + } + + unlink(filename); + + addr->sun_family = AF_LOCAL; + memcpy(addr->sun_path, filename, l); + + if (bind(sock, (struct sockaddr *) addr, size) < 0) { + int result = -errno; + perrno("bind"); + return result; + } + + return sock; +} + +int make_inet_socket(int port) +{ + struct sockaddr_in addr; + int sock; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + int result = -errno; + perrno("socket"); + return result; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + int result = -errno; + perrno("bind"); + return result; + } + + return sock; +} + +int send_fd(int socket, void *data, size_t len, int fd) +{ + int ret; + struct cmsg_fd cmsg; + struct msghdr msghdr; + struct iovec vec; + + vec.iov_base = (void *)&data; + vec.iov_len = len; + + cmsg.len = sizeof(cmsg); + cmsg.level = SOL_SOCKET; + cmsg.type = SCM_RIGHTS; + cmsg.fd = fd; + + msghdr.msg_name = NULL; + msghdr.msg_namelen = 0; + msghdr.msg_iov = &vec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &cmsg; + msghdr.msg_controllen = sizeof(cmsg); + msghdr.msg_flags = 0; + + ret = sendmsg(socket, &msghdr, 0 ); + if (ret < 0) + return -errno; + return ret; +} + +typedef struct client client_t; + +typedef struct { + int (*open)(client_t *client, long *cookie); + int (*cmd)(client_t *client); + int (*close)(client_t *client); + int (*poll_prepare)(client_t *client, struct pollfd *pfds, int pindex); + void (*poll_events)(client_t *client, struct pollfd *pfds); +} transport_ops_t; + +struct client { + struct socket { + int fd; + int pindex; + int local; + } data, ctrl; + int transport_type; + int dev_type; + char name[256]; + int stream; + int mode; + transport_ops_t *ops; + union { + struct { + snd_pcm_t *handle; + int fd; + int pindex; + } pcm; +#if 0 + struct { + snd_ctl_t *handle; + } control; + struct { + snd_rawmidi_t *handle; + } rawmidi; + struct { + snd_timer_open_t *handle; + } timer; + struct { + snd_hwdep_t *handle; + } hwdep; + struct { + snd_seq_t *handle; + } seq; +#endif + } device; + enum { CLOSED = 0, STOPPED, NORMAL, UNKNOWN } state; + int cookie; + union { + struct { + int ctrl_id; + void *ctrl; + } shm; + } transport; +}; + +#define PENDINGS_MAX 4 +#define CLIENTS_MAX 2 + +client_t clients[CLIENTS_MAX]; +int clients_count = 0; + +int pcm_shm_open(client_t *client, long *cookie) +{ + int shmid; + snd_pcm_t *pcm; + int err; + int result; + err = snd_pcm_open(&pcm, client->name, client->stream, client->mode); + if (err < 0) + return err; + client->device.pcm.handle = pcm; + client->device.pcm.fd = snd_pcm_file_descriptor(pcm); + + shmid = shmget(IPC_PRIVATE, PCM_SHM_SIZE, 0666); + if (shmid < 0) { + result = -errno; + perrno("shmget"); + goto _err; + } + client->transport.shm.ctrl_id = shmid; + client->transport.shm.ctrl = shmat(shmid, 0, 0); + if (!client->transport.shm.ctrl) { + result = -errno; + shmctl(shmid, IPC_RMID, 0); + perrno("shmat"); + goto _err; + } + *cookie = shmid; + return 0; + + _err: + snd_pcm_close(pcm); + return result; + +} + +int pcm_shm_close(client_t *client) +{ + int err; + snd_pcm_client_shm_t *ctrl = client->transport.shm.ctrl; + /* FIXME: blocking */ + err = snd_pcm_close(client->device.pcm.handle); + ctrl->result = err; + if (err < 0) + perrno("snd_pcm_close"); + if (client->transport.shm.ctrl) { + err = shmdt((void *)client->transport.shm.ctrl); + if (err < 0) + perrno("shmdt"); + err = shmctl(client->transport.shm.ctrl_id, IPC_RMID, 0); + if (err < 0) + perrno("shmctl"); + client->transport.shm.ctrl = 0; + } + client->state = CLOSED; + return 0; +} + +int pcm_poll_prepare(client_t *client, struct pollfd *pfds, int pindex) +{ + struct pollfd *pfd = &pfds[pindex]; + pfd->events = 0; + switch (client->state) { + case UNKNOWN: + pfd->events = client->stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; + case NORMAL: + pfd->fd = client->device.pcm.fd; + client->device.pcm.pindex = pindex; + return 1; + break; + default: + client->device.pcm.pindex = -1; + return 0; + } +} + +void pcm_poll_events(client_t *client, struct pollfd *pfds) +{ + int n; + char zero = 0; + struct pollfd *pfd; + if (client->device.pcm.pindex < 0) + return; + pfd = &pfds[client->device.pcm.pindex]; + if (pfd->revents & POLLIN) { + client->state = NORMAL; + n = write(client->data.fd, &zero, 1); + if (n != 1) { + perrno("write"); + exit(1); + } + } else if (pfd->revents & POLLOUT) { + client->state = NORMAL; + n = read(client->data.fd, &zero, 1); + if (n != 1) { + perrno("read"); + exit(1); + } + } +} + +int pcm_shm_cmd(client_t *client) +{ + snd_pcm_client_shm_t *ctrl = client->transport.shm.ctrl; + struct pollfd pfd; + char buf[1]; + int err; + int cmd; + snd_pcm_t *pcm; + err = read(client->ctrl.fd, buf, 1); + if (err != 1) + return -EBADFD; + cmd = ctrl->cmd; + ctrl->cmd = 0; + pcm = client->device.pcm.handle; + switch (cmd) { + case SND_PCM_IOCTL_INFO: + ctrl->result = snd_pcm_info(pcm, &ctrl->u.info); + break; + case SND_PCM_IOCTL_PARAMS: + ctrl->result = snd_pcm_params(pcm, &ctrl->u.params); + break; + case SND_PCM_IOCTL_PARAMS_INFO: + ctrl->result = snd_pcm_params_info(pcm, &ctrl->u.params_info); + break; + case SND_PCM_IOCTL_SETUP: + ctrl->result = snd_pcm_setup(pcm, &ctrl->u.setup); + break; + case SND_PCM_IOCTL_STATUS: + ctrl->result = snd_pcm_status(pcm, &ctrl->u.status); + break; + case SND_PCM_IOCTL_FRAME_IO: + ctrl->result = snd_pcm_frame_io(pcm, ctrl->u.frame_io); + break; + case SND_PCM_IOCTL_PREPARE: + ctrl->result = snd_pcm_prepare(pcm); + break; + case SND_PCM_IOCTL_GO: + ctrl->result = snd_pcm_go(pcm); + break; + case SND_PCM_IOCTL_FLUSH: + ctrl->result = snd_pcm_flush(pcm); + break; + case SND_PCM_IOCTL_DRAIN: + ctrl->result = snd_pcm_drain(pcm); + break; + case SND_PCM_IOCTL_PAUSE: + ctrl->result = snd_pcm_pause(pcm, ctrl->u.pause); + break; + case SND_PCM_IOCTL_CHANNEL_INFO: + ctrl->result = snd_pcm_channel_info(pcm, &ctrl->u.channel_info); + break; + case SND_PCM_IOCTL_CHANNEL_PARAMS: + ctrl->result = snd_pcm_channel_params(pcm, &ctrl->u.channel_params); + break; + case SND_PCM_IOCTL_CHANNEL_SETUP: + ctrl->result = snd_pcm_channel_setup(pcm, &ctrl->u.channel_setup); + break; + case SND_PCM_IOCTL_WRITE_FRAMES: + { + size_t maxsize = PCM_SHM_DATA_MAXLEN; + client->state = UNKNOWN; + maxsize = snd_pcm_bytes_to_frames(pcm, maxsize); + if (ctrl->u.write.count > maxsize) { + ctrl->result = -EFAULT; + break; + } + /* FIXME: blocking */ + ctrl->result = snd_pcm_write(pcm, ctrl->data, ctrl->u.write.count); + break; + } + case SND_PCM_IOCTL_READ_FRAMES: + { + size_t maxsize = PCM_SHM_DATA_MAXLEN; + client->state = UNKNOWN; + maxsize = snd_pcm_bytes_to_frames(pcm, maxsize); + if (ctrl->u.read.count > maxsize) { + ctrl->result = -EFAULT; + break; + } + /* FIXME: blocking */ + ctrl->result = snd_pcm_read(pcm, ctrl->data, ctrl->u.read.count); + break; + } + case SND_PCM_IOCTL_WRITEV_FRAMES: + { + /* FIXME: interleaved */ + size_t maxsize = PCM_SHM_DATA_MAXLEN; + unsigned long k; + struct iovec *vector; + size_t vecsize; + char *base; + int bits_per_sample = snd_pcm_samples_to_bytes(pcm, 8); + client->state = UNKNOWN; + vecsize = ctrl->u.writev.count * sizeof(struct iovec); + if (vecsize > maxsize) { + ctrl->result = -EFAULT; + break; + } + maxsize -= vecsize; + vector = (struct iovec *) ctrl->data; + base = ctrl->data + vecsize; + for (k = 0; k < ctrl->u.writev.count; ++k) { + unsigned long ofs = (unsigned long) vector[k].iov_base; + size_t len = vector[k].iov_len * bits_per_sample / 8; + if (ofs + len > maxsize) + return -EFAULT; + vector[k].iov_base = base + ofs; + } + /* FIXME: blocking */ + ctrl->result = snd_pcm_writev(pcm, vector, ctrl->u.writev.count); + break; + } + case SND_PCM_IOCTL_READV_FRAMES: + { + /* FIXME: interleaved */ + size_t maxsize = PCM_SHM_DATA_MAXLEN; + unsigned long k; + struct iovec *vector; + size_t vecsize; + char *base; + int bits_per_sample = snd_pcm_samples_to_bytes(pcm, 8); + client->state = UNKNOWN; + vecsize = ctrl->u.readv.count * sizeof(struct iovec); + if (vecsize > maxsize) { + ctrl->result = -EFAULT; + break; + } + maxsize -= vecsize; + vector = (struct iovec *) ctrl->data; + base = ctrl->data + vecsize; + for (k = 0; k < ctrl->u.readv.count; ++k) { + unsigned long ofs = (unsigned long) vector[k].iov_base; + size_t len = vector[k].iov_len * bits_per_sample / 8; + if (ofs + len > maxsize) + return -EFAULT; + vector[k].iov_base = base + ofs; + } + /* FIXME: blocking */ + ctrl->result = snd_pcm_readv(pcm, vector, ctrl->u.readv.count); + break; + } + case SND_PCM_IOCTL_FRAME_DATA: + client->state = UNKNOWN; + ctrl->result = snd_pcm_frame_data(pcm, ctrl->u.frame_data); + break; + case SND_PCM_IOCTL_LINK: + { + int k; + for (k = 0; k < clients_count; ++k) { + if (clients[k].state == CLOSED) + continue; + if (clients[k].data.fd == ctrl->u.link) { + ctrl->result = snd_pcm_link(pcm, clients[k].device.pcm.handle); + break; + } + } + ctrl->result = -EBADFD; + break; + } + case SND_PCM_IOCTL_UNLINK: + ctrl->result = snd_pcm_unlink(pcm); + break; + case SND_PCM_IOCTL_MMAP_DATA: + case SND_PCM_IOCTL_MMAP_CONTROL: + case SND_PCM_IOCTL_MMAP_STATUS: + { + pfd.fd = client->ctrl.fd; + pfd.events = POLLHUP; + if (poll(&pfd, 1, 0) == 1) + return -EBADFD; + err = send_fd(client->ctrl.fd, buf, 1, client->device.pcm.fd); + if (err != 1) + return -EBADFD; + ctrl->result = 0; + return 0; + } +#if 0 + case SND_PCM_IOCTL_MUNMAP_DATA: + case SND_PCM_IOCTL_MUNMAP_CONTROL: + case SND_PCM_IOCTL_MUNMAP_STATUS: + ctrl->result = 0; + break; + } +#endif + case SND_PCM_IOCTL_CLOSE: + client->ops->close(client); + break; + default: + fprintf(stderr, "Bogus cmd: %x\n", ctrl->cmd); + ctrl->result = -ENOSYS; + } + pfd.fd = client->ctrl.fd; + pfd.events = POLLHUP; + if (poll(&pfd, 1, 0) == 1) + return -EBADFD; + err = write(client->ctrl.fd, buf, 1); + if (err != 1) + return -EBADFD; + return 0; +} + +transport_ops_t pcm_shm_ops = { + open: pcm_shm_open, + cmd: pcm_shm_cmd, + close: pcm_shm_close, + poll_prepare: pcm_poll_prepare, + poll_events: pcm_poll_events, +}; + +void snd_client_open(client_t *client) +{ + int err; + snd_client_open_request_t req; + snd_client_open_answer_t ans; + char *name; + memset(&ans, 0, sizeof(ans)); + err = read(client->ctrl.fd, &req, sizeof(req)); + if (err < 0) { + perrno("read"); + exit(1); + } + if (err != sizeof(req)) { + ans.result = -EINVAL; + goto _answer; + } + name = alloca(req.namelen); + err = read(client->ctrl.fd, name, req.namelen); + if (err < 0) { + perrno("read"); + exit(1); + } + if (err != req.namelen) { + ans.result = -EINVAL; + goto _answer; + } + + switch (req.dev_type) { + case SND_DEV_TYPE_PCM: + switch (req.transport_type) { + case SND_TRANSPORT_TYPE_SHM: + client->ops = &pcm_shm_ops; + break; + default: + ans.result = -EINVAL; + goto _answer; + } + break; + default: + ans.result = -EINVAL; + goto _answer; + } + + name[req.namelen] = '\0'; + + client->transport_type = req.transport_type; + strcpy(client->name, name); + client->stream = req.stream; + client->mode = req.mode; + + err = client->ops->open(client, &ans.cookie); + if (err < 0) { + ans.result = err; + } else { + client->state = STOPPED; + ans.result = client->data.fd; + } + + _answer: + ans.result = htonl(ans.result); + ans.cookie = htonl(ans.cookie); + err = write(client->ctrl.fd, &ans, sizeof(ans)); + if (err != sizeof(ans)) { + perrno("write"); + exit(1); + } + return; +} + +int server(char *sockname, int port) +{ + typedef struct { + int fd; + int pindex; + int local; + } master_t; + master_t masters[2]; + int masters_count = 0; + typedef struct { + int fd; + int pindex; + uint32_t cookie; + int local; + } pending_t; + pending_t pendings[PENDINGS_MAX]; + int pendings_count = 0; + struct pollfd pfds[CLIENTS_MAX * 3 + 16]; + int pfds_count; + int err; + int k; + + if (sockname) { + int master = make_local_socket(sockname); + if (master < 0) + return master; + masters[masters_count].fd = master; + masters[masters_count].local = 1; + masters_count++; + } + if (port >= 0) { + int master = make_inet_socket(port); + if (master < 0) + return master; + masters[masters_count].fd = master; + masters[masters_count].local = 0; + masters_count++; + } + + if (masters_count == 0) + return -EINVAL; + + for (k = 0; k < masters_count; ++k) { + master_t *master = &masters[k]; + if (fcntl(master->fd, F_SETFL, O_NONBLOCK) < 0) { + int result = -errno; + perrno("fcntl"); + return result; + } + if (listen(master->fd, 4) < 0) { + int result = -errno; + perrno("listen"); + return result; + } + } + + for (k = 0; k < PENDINGS_MAX; ++k) { + pendings[k].fd = -1; + } + + while (1) { + pfds_count = 0; + + /* Prepare to poll masters */ + if (pendings_count < PENDINGS_MAX) { + for (k = 0; k < masters_count; ++k) { + master_t *master = &masters[k]; + master->pindex = pfds_count; + pfds[pfds_count].fd = master->fd; + pfds[pfds_count].events = POLLIN; + pfds_count++; + } + } else { + for (k = 0; k < masters_count; ++k) { + master_t *master = &masters[k]; + master->pindex = -1; + } + } + + /* Prepare to poll pendings */ + for (k = 0; k < PENDINGS_MAX; ++k) { + pending_t *pending = &pendings[k]; + if (pending->fd < 0 || + pending->cookie != 0) { + pending->pindex = -1; + continue; + } + pending->pindex = pfds_count; + pfds[pfds_count].fd = pending->fd; + pfds[pfds_count].events = POLLHUP; + if (pendings_count < PENDINGS_MAX && + clients_count < CLIENTS_MAX) + pfds[pfds_count].events |= POLLIN; + pfds_count++; + } + + /* Prepare to poll clients */ + for (k = 0; k < clients_count; ++k) { + client_t *client = &clients[k]; + client->data.pindex = pfds_count; + pfds[pfds_count].fd = client->data.fd; + pfds[pfds_count].events = POLLHUP; + pfds_count++; + + client->ctrl.pindex = pfds_count; + pfds[pfds_count].fd = client->ctrl.fd; + pfds[pfds_count].events = POLLIN | POLLHUP; + pfds_count++; + } + + /* Prepare to poll devices */ + for (k = 0; k < clients_count; ++k) { + client_t *client = &clients[k]; + int n; + if (client->state == CLOSED) + continue; + n = client->ops->poll_prepare(client, pfds, pfds_count); + pfds_count += n; + } + + + /* Poll */ + do { + err = poll(pfds, pfds_count, 1000); + } while (err == 0); + if (err < 0) { + int result = -errno; + perrno("poll"); + return result; + } + + /* Handle clients events */ + for (k = clients_count - 1; k >= 0; --k) { + client_t *client; + struct pollfd *data_pfd = &pfds[clients[k].data.pindex]; + struct pollfd *ctrl_pfd = &pfds[clients[k].ctrl.pindex]; + if (!data_pfd->revents && !ctrl_pfd->revents) + continue; + client = &clients[k]; + if ((data_pfd->revents & POLLHUP) || + (ctrl_pfd->revents & POLLHUP)) { + if (client->state != CLOSED) { + client->ops->close(client); + } + close(client->data.fd); + if (client->ctrl.fd >= 0) + close(client->ctrl.fd); + memmove(client, client + 1, + (clients_count - k) * sizeof(client_t)); + clients_count--; + continue; + } + if (ctrl_pfd->revents & POLLIN) { + if (client->state == CLOSED) + snd_client_open(client); + else + client->ops->cmd(client); + } + } + + /* Handle device events */ + for (k = 0; k < clients_count; ++k) { + client_t *client = &clients[k]; + client->ops->poll_events(client, pfds); + } + + /* Handle pending events */ + for (k = 0; k < PENDINGS_MAX; ++k) { + struct pollfd *pfd; + uint32_t cookie; + int j; + pending_t *pending = &pendings[k]; + client_t *client; + int remove = 0; + if (pending->pindex < 0) + continue; + pfd = &pfds[pending->pindex]; + if (!pfd->revents) + continue; + if (pfd->revents & POLLHUP) + remove = 1; + else { + if (clients_count >= CLIENTS_MAX) + continue; + err = read(pfd->fd, &cookie, sizeof(cookie)); + if (err != sizeof(cookie)) + remove = 1; + else { + err = write(pfd->fd, &cookie, sizeof(cookie)); + if (err != sizeof(cookie)) + remove = 1; + } + } + if (remove) { + close(pending->fd); + pending->fd = -1; + pendings_count--; + continue; + } + + for (j = 0; j < PENDINGS_MAX; ++j) { + if (pendings[j].cookie == cookie) + break; + } + if (j == PENDINGS_MAX) { + pendings[k].cookie = cookie; + continue; + } + + client = &clients[clients_count]; + memset(client, 0, sizeof(*client)); + client->data.fd = pendings[j].fd; + client->data.local = pendings[j].local; + client->ctrl.fd = pendings[k].fd; + client->ctrl.local = pendings[k].local; + client->state = CLOSED; + clients_count++; + pendings[j].fd = -1; + pendings[k].fd = -1; + pendings_count -= 2; + } + + /* Handle master events */ + for (k = 0; k < masters_count; ++k) { + struct pollfd *pfd; + master_t *master; + int sock; + if (pendings_count >= PENDINGS_MAX) + break; + master = &masters[k]; + if (master->pindex < 0) + continue; + pfd = &pfds[master->pindex]; + if (!pfd->revents) + continue; + + sock = accept(master->fd, 0, 0); + if (sock < 0) { + int result = -errno; + perrno("accept"); + return result; + } else { + int j; + for (j = 0; j < PENDINGS_MAX; ++j) { + if (pendings[j].fd < 0) + break; + } + assert(j < PENDINGS_MAX); + pendings[j].fd = sock; + pendings[j].local = master->local; + pendings[j].cookie = 0; + pendings_count++; + } + } + + } + return 0; +} + + +void usage() +{ + fprintf(stderr, "\ +Usage: %s [OPTIONS] + +--help help +--version print current version +-l,--local SOCKNAME local socket name +-p,--port PORT port number +", command); +} + +int main(int argc, char **argv) +{ + static struct option long_options[] = { + {"local", 1, 0, 'l'}, + {"port", 1, 0, 'p'}, + {"help", 0, 0, 'h'} + }; + int c; + char *local = NULL; + int port = -1; + command = argv[0]; + while ((c = getopt_long(argc, argv, "hl:p:", long_options, 0)) != -1) { + switch (c) { + case 'h': + usage(); + return 0; + case 'l': + local = optarg; + break; + case 'p': + port = atoi(optarg); + break; + default: + fprintf(stderr, "Try `%s --help' for more information\n", command); + return 1; + } + } + if (!local && port == -1) { + fprintf(stderr, "%s: you need to specify at least one master socket\n", command); + return 1; + } + + server(local, port); + return 0; +} diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am index 927560ab..f08c8369 100644 --- a/src/pcm/Makefile.am +++ b/src/pcm/Makefile.am @@ -3,7 +3,7 @@ SUBDIRS = plugin EXTRA_LTLIBRARIES = libpcm.la libpcm_la_SOURCES = pcm.c pcm_hw.c pcm_plug.c pcm_common.c \ - pcm_misc.c pcm_mmap.c pcm_multi.c + pcm_misc.c pcm_mmap.c pcm_multi.c pcm_client.c libpcm_la_LIBADD = plugin/libpcmplugin.la noinst_HEADERS = pcm_local.h diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c index d37a4254..fc2507a1 100644 --- a/src/pcm/pcm.c +++ b/src/pcm/pcm.c @@ -736,6 +736,62 @@ _free: return err; } +static int _snd_pcm_open_client(snd_pcm_t **handlep, snd_config_t *conf, + int stream, int mode) +{ + snd_config_iterator_t i; + char *socket = NULL; + char *name = NULL; + char *host = NULL; + long port = -1; + int err; + snd_config_foreach(i, conf) { + snd_config_t *n = snd_config_entry(i); + if (strcmp(n->id, "comment") == 0) + continue; + if (strcmp(n->id, "type") == 0) + continue; + if (strcmp(n->id, "stream") == 0) + continue; + if (strcmp(n->id, "socket") == 0) { + err = snd_config_string_get(n, &socket); + if (err < 0) + return -EINVAL; + continue; + } + if (strcmp(n->id, "host") == 0) { + err = snd_config_string_get(n, &host); + if (err < 0) + return -EINVAL; + continue; + } + if (strcmp(n->id, "port") == 0) { + err = snd_config_integer_get(n, &port); + if (err < 0) + return -EINVAL; + continue; + } + if (strcmp(n->id, "name") == 0) { + err = snd_config_string_get(n, &name); + if (err < 0) + return -EINVAL; + continue; + } + return -EINVAL; + } + if (!name) + return -EINVAL; + if (socket) { + if (port >= 0 || host) + return -EINVAL; + return snd_pcm_client_create(handlep, socket, -1, SND_TRANSPORT_TYPE_SHM, name, stream, mode); + } else { + if (port < 0 || !name) + return -EINVAL; + return snd_pcm_client_create(handlep, host, port, SND_TRANSPORT_TYPE_SHM, name, stream, mode); + } +} + int snd_pcm_open(snd_pcm_t **handlep, char *name, int stream, int mode) { @@ -777,6 +833,8 @@ int snd_pcm_open(snd_pcm_t **handlep, char *name, return _snd_pcm_open_plug(handlep, pcm_conf, stream, mode); else if (strcmp(str, "multi") == 0) return _snd_pcm_open_multi(handlep, pcm_conf, stream, mode); + else if (strcmp(str, "client") == 0) + return _snd_pcm_open_client(handlep, pcm_conf, stream, mode); else return -EINVAL; } diff --git a/src/pcm/pcm_client.c b/src/pcm/pcm_client.c new file mode 100644 index 00000000..31069058 --- /dev/null +++ b/src/pcm/pcm_client.c @@ -0,0 +1,878 @@ +/* + * PCM - Client + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/shm.h> +#include <sys/socket.h> +#include <sys/poll.h> +#include <sys/un.h> +#include <sys/uio.h> +#include <sys/mman.h> +#include <netinet/in.h> +#include <netdb.h> +#include "pcm_local.h" +#include "aserver.h" + +typedef struct { + snd_pcm_t *handle; + int data_fd; + int ctrl_fd; + union { + struct { + void *ctrl; + } shm; + } u; +} snd_pcm_client_t; + +int receive_fd(int socket, void *data, size_t len, int *fd) +{ + int ret; + struct cmsg_fd cmsg; + struct msghdr msghdr; + struct iovec vec; + + vec.iov_base = (void *)&data; + vec.iov_len = len; + + cmsg.len = sizeof(cmsg); + cmsg.level = SOL_SOCKET; + cmsg.type = SCM_RIGHTS; + cmsg.fd = -1; + + msghdr.msg_name = NULL; + msghdr.msg_namelen = 0; + msghdr.msg_iov = &vec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &cmsg; + msghdr.msg_controllen = sizeof(cmsg); + msghdr.msg_flags = 0; + + ret = recvmsg(socket, &msghdr, 0); + if (ret < 0) + return -errno; + *fd = cmsg.fd; + return ret; +} + +static void clean_state(snd_pcm_client_t *client) +{ + struct pollfd pfd; + int err; + char buf[1]; + pfd.fd = client->data_fd; + switch (client->handle->stream) { + case SND_PCM_STREAM_PLAYBACK: + pfd.events = POLLOUT; + while (1) { + err = poll(&pfd, 1, 0); + if (err == 0) + break; + assert(err > 0); + err = write(client->data_fd, buf, 1); + assert(err == 1); + } + break; + case SND_PCM_STREAM_CAPTURE: + pfd.events = POLLIN; + while (1) { + err = poll(&pfd, 1, 0); + if (err == 0) + break; + assert(err > 0); + err = read(client->data_fd, buf, 1); + assert(err == 1); + } + break; + } +} + +static int snd_pcm_client_shm_action(snd_pcm_client_t *client) +{ + int err; + char buf[1]; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + err = write(client->ctrl_fd, buf, 1); + if (err != 1) + return -EBADFD; + err = read(client->ctrl_fd, buf, 1); + if (err != 1) + return -EBADFD; + if (ctrl->cmd) { + fprintf(stderr, "Server has not done the cmd\n"); + return -EBADFD; + } + return 0; +} + +static int snd_pcm_client_shm_action_fd(snd_pcm_client_t *client) +{ + int err; + char buf[1]; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int fd; + err = write(client->ctrl_fd, buf, 1); + if (err != 1) + return -EBADFD; + err = receive_fd(client->ctrl_fd, buf, 1, &fd); + if (err != 1) + return -EBADFD; + if (ctrl->cmd) { + fprintf(stderr, "Server has not done the cmd\n"); + return -EBADFD; + } + if (ctrl->result < 0) + return ctrl->result; + return fd; +} + +static int snd_pcm_client_shm_close(void *private) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int result; + ctrl->cmd = SND_PCM_IOCTL_CLOSE; + result = snd_pcm_client_shm_action(client); + if (result >= 0) + result = ctrl->result; + shmdt((void *)ctrl); + close(client->data_fd); + close(client->ctrl_fd); + return result; +} + +static int snd_pcm_client_shm_nonblock(void *private, int nonblock) +{ + /* FIXME */ + return 0; +} + +static int snd_pcm_client_shm_info(void *private, snd_pcm_info_t * info) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; +// ctrl->u.info = *info; + ctrl->cmd = SND_PCM_IOCTL_INFO; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + memcpy(info, &ctrl->u.info, sizeof(*info)); + return ctrl->result; +} + +static int snd_pcm_client_shm_params_info(void *private, snd_pcm_params_info_t * info) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_PARAMS_INFO; + ctrl->u.params_info = *info; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + *info = ctrl->u.params_info; + return ctrl->result; +} + +static int snd_pcm_client_shm_params(void *private, snd_pcm_params_t * params) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_PARAMS; + ctrl->u.params = *params; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + *params = ctrl->u.params; + return ctrl->result; +} + +static int snd_pcm_client_shm_setup(void *private, snd_pcm_setup_t * setup) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_SETUP; + // ctrl->u.setup = *setup; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + *setup = ctrl->u.setup; + return ctrl->result; +} + +static int snd_pcm_client_shm_channel_info(void *private, snd_pcm_channel_info_t * info) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_CHANNEL_INFO; + ctrl->u.channel_info = *info; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + *info = ctrl->u.channel_info; + return ctrl->result; +} + +static int snd_pcm_client_shm_channel_params(void *private, snd_pcm_channel_params_t * params) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_CHANNEL_PARAMS; + ctrl->u.channel_params = *params; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + *params = ctrl->u.channel_params; + return ctrl->result; +} + +static int snd_pcm_client_shm_channel_setup(void *private, snd_pcm_channel_setup_t * setup) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_CHANNEL_SETUP; + ctrl->u.channel_setup = *setup; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + *setup = ctrl->u.channel_setup; + return ctrl->result; +} + +static int snd_pcm_client_shm_status(void *private, snd_pcm_status_t * status) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_STATUS; + // ctrl->u.status = *status; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + *status = ctrl->u.status; + return ctrl->result; +} + +static int snd_pcm_client_shm_state(void *private) +{ + snd_pcm_status_t status; + int err = snd_pcm_client_shm_status(private, &status); + if (err < 0) + return err; + return status.state; +} + +static ssize_t snd_pcm_client_shm_frame_io(void *private, int update) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_FRAME_IO; + ctrl->u.frame_io = update; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static int snd_pcm_client_shm_prepare(void *private) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_PREPARE; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static int snd_pcm_client_shm_go(void *private) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_GO; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static int snd_pcm_client_shm_drain(void *private) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_DRAIN; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static int snd_pcm_client_shm_flush(void *private) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_FLUSH; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static int snd_pcm_client_shm_pause(void *private, int enable) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_PAUSE; + ctrl->u.pause = enable; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static ssize_t snd_pcm_client_shm_frame_data(void *private, off_t offset) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_FRAME_DATA; + ctrl->u.frame_data = offset; + clean_state(client); + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static ssize_t snd_pcm_client_shm_write(void *private, snd_timestamp_t *tstamp, const void *buffer, size_t size) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + size_t maxsize = PCM_SHM_DATA_MAXLEN; + size_t bytes = snd_pcm_frames_to_bytes(client->handle, size); + int err; + if (bytes > maxsize) + return -EINVAL; + ctrl->cmd = SND_PCM_IOCTL_WRITE_FRAMES; +// ctrl->u.write.tstamp = *tstamp; + ctrl->u.write.count = size; + memcpy(ctrl->data, buffer, bytes); + clean_state(client); + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static ssize_t snd_pcm_client_shm_writev(void *private, snd_timestamp_t *tstamp, const struct iovec *vector, unsigned long count) +{ + /* FIXME: interleaved */ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + size_t vecsize = count * sizeof(struct iovec); + size_t maxsize = PCM_SHM_DATA_MAXLEN; + int bits_per_sample = client->handle->bits_per_sample; + char *base; + struct iovec *vec; + unsigned long k; + size_t ofs; + int err; + if (vecsize > maxsize) + return -EINVAL; + maxsize -= vecsize; + ctrl->cmd = SND_PCM_IOCTL_WRITEV_FRAMES; +// ctrl->u.writev.tstamp = *tstamp; + ctrl->u.writev.count = count; + memcpy(ctrl->data, vector, vecsize); + vec = (struct iovec *) ctrl->data; + base = ctrl->data + vecsize; + ofs = 0; + for (k = 0; k < count; ++k) { + size_t len = vector[k].iov_len * bits_per_sample / 8; + memcpy(base + ofs, vector[k].iov_base, len); + vec[k].iov_base = (void *) ofs; + ofs += len; + } + clean_state(client); + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +} + +static ssize_t snd_pcm_client_shm_read(void *private, snd_timestamp_t *tstamp, void *buffer, size_t size) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + size_t maxsize = PCM_SHM_DATA_MAXLEN; + size_t bytes = snd_pcm_frames_to_bytes(client->handle, size); + int err; + if (bytes > maxsize) + return -EINVAL; + ctrl->cmd = SND_PCM_IOCTL_READ_FRAMES; +// ctrl->u.read.tstamp = *tstamp; + ctrl->u.read.count = size; + clean_state(client); + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + if (ctrl->result <= 0) + return ctrl->result; + bytes = snd_pcm_frames_to_bytes(client->handle, ctrl->result); + memcpy(buffer, ctrl->data, bytes); + return ctrl->result; +} + +ssize_t snd_pcm_client_shm_readv(void *private, snd_timestamp_t *tstamp, const struct iovec *vector, unsigned long count) +{ + /* FIXME: interleaved */ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + size_t vecsize = count * sizeof(struct iovec); + size_t maxsize = PCM_SHM_DATA_MAXLEN; + int bits_per_sample = client->handle->bits_per_sample; + char *base; + struct iovec *vec; + unsigned long k; + size_t ofs, bytes; + int err; + if (vecsize > maxsize) + return -EINVAL; + maxsize -= vecsize; + ctrl->cmd = SND_PCM_IOCTL_WRITEV_FRAMES; +// ctrl->u.writev.tstamp = *tstamp; + ctrl->u.writev.count = count; + memcpy(ctrl->data, vector, vecsize); + vec = (struct iovec *) ctrl->data; + base = ctrl->data + vecsize; + ofs = 0; + for (k = 0; k < count; ++k) { + size_t len = vector[k].iov_len * bits_per_sample / 8; + vec[k].iov_base = (void *) ofs; + ofs += len; + } + clean_state(client); + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + if (ctrl->result <= 0) + return ctrl->result; + bytes = snd_pcm_frames_to_bytes(client->handle, ctrl->result); + ofs = 0; + for (k = 0; k < count; ++k) { + /* FIXME: optimize partial read */ + size_t len = vector[k].iov_len * bits_per_sample / 8; + memcpy(vector[k].iov_base, base + ofs, len); + ofs += len; + } + return ctrl->result; +} + +static int snd_pcm_client_shm_mmap_status(void *private, snd_pcm_mmap_status_t **status) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + void *ptr; + int fd; + ctrl->cmd = SND_PCM_IOCTL_MMAP_STATUS; + fd = snd_pcm_client_shm_action_fd(client); + if (fd < 0) + return fd; + ptr = mmap(NULL, sizeof(snd_pcm_mmap_status_t), PROT_READ, MAP_FILE|MAP_SHARED, + fd, SND_PCM_MMAP_OFFSET_STATUS); + close(fd); + if (ptr == MAP_FAILED || ptr == NULL) + return -errno; + *status = ptr; + return 0; +} + +static int snd_pcm_client_shm_mmap_control(void *private, snd_pcm_mmap_control_t **control) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + void *ptr; + int fd; + ctrl->cmd = SND_PCM_IOCTL_MMAP_CONTROL; + fd = snd_pcm_client_shm_action_fd(client); + if (fd < 0) + return fd; + ptr = mmap(NULL, sizeof(snd_pcm_mmap_control_t), PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, + fd, SND_PCM_MMAP_OFFSET_CONTROL); + close(fd); + if (ptr == MAP_FAILED || ptr == NULL) + return -errno; + *control = ptr; + return 0; +} + +static int snd_pcm_client_shm_mmap_data(void *private, void **buffer, size_t bsize ATTRIBUTE_UNUSED) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + void *ptr; + int prot; + int fd; + ctrl->cmd = SND_PCM_IOCTL_MMAP_DATA; + fd = snd_pcm_client_shm_action_fd(client); + if (fd < 0) + return fd; + prot = client->handle->stream == SND_PCM_STREAM_PLAYBACK ? PROT_WRITE : PROT_READ; + ptr = mmap(NULL, bsize, prot, MAP_FILE|MAP_SHARED, + fd, SND_PCM_MMAP_OFFSET_DATA); + close(fd); + if (ptr == MAP_FAILED || ptr == NULL) + return -errno; + *buffer = ptr; + return 0; +} + +static int snd_pcm_client_shm_munmap_status(void *private ATTRIBUTE_UNUSED, snd_pcm_mmap_status_t *status ATTRIBUTE_UNUSED) +{ +#if 0 + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_MUNMAP_STATUS; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +#else + if (munmap(status, sizeof(*status)) < 0) + return -errno; + return 0; +#endif +} + +static int snd_pcm_client_shm_munmap_control(void *private ATTRIBUTE_UNUSED, snd_pcm_mmap_control_t *control ATTRIBUTE_UNUSED) +{ +#if 0 + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_MUNMAP_CONTROL; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +#else + if (munmap(control, sizeof(*control)) < 0) + return -errno; + return 0; +#endif +} + +static int snd_pcm_client_shm_munmap_data(void *private ATTRIBUTE_UNUSED, void *buffer, size_t bsize) +{ +#if 0 + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_client_shm_t *ctrl = client->u.shm.ctrl; + int err; + ctrl->cmd = SND_PCM_IOCTL_MUNMAP_DATA; + err = snd_pcm_client_shm_action(client); + if (err < 0) + return err; + return ctrl->result; +#else + if (munmap(buffer, bsize) < 0) + return -errno; + return 0; +#endif +} + +static int snd_pcm_client_file_descriptor(void *private) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + return client->data_fd; +} + +static int snd_pcm_client_channels_mask(void *private ATTRIBUTE_UNUSED, + bitset_t *client_vmask ATTRIBUTE_UNUSED) +{ + return 0; +} + +static void snd_pcm_client_dump(void *private, FILE *fp) +{ + snd_pcm_client_t *client = (snd_pcm_client_t*) private; + snd_pcm_t *handle = client->handle; + fprintf(fp, "Client PCM\n"); + if (handle->valid_setup) { + fprintf(fp, "\nIts setup is:\n"); + snd_pcm_dump_setup(handle, fp); + } +} + +struct snd_pcm_ops snd_pcm_client_ops = { + close: snd_pcm_client_shm_close, + info: snd_pcm_client_shm_info, + params_info: snd_pcm_client_shm_params_info, + params: snd_pcm_client_shm_params, + setup: snd_pcm_client_shm_setup, + dump: snd_pcm_client_dump, +}; + +struct snd_pcm_fast_ops snd_pcm_client_fast_ops = { + nonblock: snd_pcm_client_shm_nonblock, + channel_info: snd_pcm_client_shm_channel_info, + channel_params: snd_pcm_client_shm_channel_params, + channel_setup: snd_pcm_client_shm_channel_setup, + status: snd_pcm_client_shm_status, + frame_io: snd_pcm_client_shm_frame_io, + state: snd_pcm_client_shm_state, + prepare: snd_pcm_client_shm_prepare, + go: snd_pcm_client_shm_go, + drain: snd_pcm_client_shm_drain, + flush: snd_pcm_client_shm_flush, + pause: snd_pcm_client_shm_pause, + frame_data: snd_pcm_client_shm_frame_data, + write: snd_pcm_client_shm_write, + writev: snd_pcm_client_shm_writev, + read: snd_pcm_client_shm_read, + readv: snd_pcm_client_shm_readv, + mmap_status: snd_pcm_client_shm_mmap_status, + mmap_control: snd_pcm_client_shm_mmap_control, + mmap_data: snd_pcm_client_shm_mmap_data, + munmap_status: snd_pcm_client_shm_munmap_status, + munmap_control: snd_pcm_client_shm_munmap_control, + munmap_data: snd_pcm_client_shm_munmap_data, + file_descriptor: snd_pcm_client_file_descriptor, + channels_mask: snd_pcm_client_channels_mask, +}; + +static int make_local_socket(const char *filename) +{ + 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) + return -errno; + + addr->sun_family = AF_LOCAL; + memcpy(addr->sun_path, filename, l); + + if (connect(sock, (struct sockaddr *) addr, size) < 0) + return -errno; + return sock; +} + +static int make_inet_socket(const char *host, int port) +{ + struct sockaddr_in addr; + int sock; + struct hostent *h = gethostbyname(host); + if (!h) + return -ENOENT; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) + return -errno; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + memcpy(&addr.sin_addr, h->h_addr_list[0], sizeof(struct in_addr)); + + if (connect(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) + return -errno; + return sock; +} + +/* port == -1 -> PF_LOCAL and host is the socket name */ +int snd_pcm_client_create(snd_pcm_t **handlep, char *host, int port, int transport, char *name, int stream, int mode) +{ + snd_pcm_t *handle; + snd_pcm_client_t *client; + snd_client_open_request_t *req; + snd_client_open_answer_t ans; + size_t namelen, reqlen; + int err; + int result; + int fds[2] = {-1, -1}; + int k; + snd_pcm_client_shm_t *ctrl = NULL; + uint32_t rcookie, scookie = getpid(); + namelen = strlen(name); + if (namelen > 255) + return -EINVAL; + + for (k = 0; k < 2; ++k) { + if (port == -1) + fds[k] = make_local_socket(host); + else + fds[k] = make_inet_socket(host, port); + if (fds[k] < 0) { + result = fds[k]; + goto _err; + } + err = write(fds[k], &scookie, sizeof(scookie)); + if (err != sizeof(scookie)) { + result = -EBADFD; + goto _err; + } + err = read(fds[k], &rcookie, sizeof(rcookie)); + if (err != sizeof(rcookie) || + rcookie != scookie) { + result = -EBADFD; + goto _err; + } + } + + reqlen = sizeof(*req) + namelen; + req = alloca(reqlen); + memcpy(req->name, name, namelen); + req->dev_type = SND_DEV_TYPE_PCM; + req->transport_type = transport; + req->stream = stream; + req->mode = mode; + req->namelen = namelen; + err = write(fds[1], req, reqlen); + if (err < 0) { + result = -errno; + goto _err; + } + if ((size_t) err != reqlen) { + result = -EINVAL; + goto _err; + } + err = read(fds[1], &ans, sizeof(ans)); + if (err < 0) { + result = -errno; + goto _err; + } + if (err != sizeof(ans)) { + result = -EINVAL; + goto _err; + } + result = ntohl(ans.result); + ans.cookie = ntohl(ans.cookie); + if (result < 0) + goto _err; + + switch (transport) { + case SND_TRANSPORT_TYPE_SHM: + ctrl = shmat(ans.cookie, 0, 0); + if (!ctrl) { + result = -errno; + goto _err; + } + break; + default: + result = -ENOSYS; + goto _err; + } + + if (stream == SND_PCM_STREAM_PLAYBACK) { + struct pollfd pfd; + char buf[1]; + int bufsize = 1; + pfd.fd = fds[0]; + pfd.events = POLLOUT; + err = setsockopt(fds[0], SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)); + if (err < 0) { + result = -errno; + goto _err; + } + if (poll(&pfd, 1, 0) != 1) { + result = -errno; + goto _err; + } + while (1) { + err = write(fds[0], buf, 1); + if (err != 1) { + result = -errno; + goto _err; + } + err = poll(&pfd, 1, 0); + if (err < 0) { + result = -errno; + goto _err; + } + if (err == 0) + break; + } + } + + handle = calloc(1, sizeof(snd_pcm_t)); + if (!handle) { + result = -ENOMEM; + goto _err; + } + client = calloc(1, sizeof(snd_pcm_client_t)); + if (!handle) { + free(handle); + result = -ENOMEM; + goto _err; + } + + client->handle = handle; + client->data_fd = fds[0]; + client->ctrl_fd = fds[1]; + switch (transport) { + case SND_TRANSPORT_TYPE_SHM: + client->u.shm.ctrl = ctrl; + break; + } + handle->type = SND_PCM_TYPE_CLIENT; + handle->stream = stream; + handle->ops = &snd_pcm_client_ops; + handle->op_arg = client; + handle->fast_ops = &snd_pcm_client_fast_ops; + handle->fast_op_arg = client; + handle->mode = mode; + handle->private = client; + *handlep = handle; + return 0; + + _err: + if (fds[0] >= 0) + close(fds[0]); + if (fds[1] >= 0) + close(fds[1]); + switch (transport) { + case SND_TRANSPORT_TYPE_SHM: + if (ctrl) + shmdt(ctrl); + break; + } + return result; +} + diff --git a/src/pcm/pcm_hw.c b/src/pcm/pcm_hw.c index f0b80bb1..81c1ec82 100644 --- a/src/pcm/pcm_hw.c +++ b/src/pcm/pcm_hw.c @@ -292,9 +292,9 @@ ssize_t snd_pcm_hw_readv(void *private, snd_timestamp_t *tstamp, const struct io static int snd_pcm_hw_mmap_status(void *private, snd_pcm_mmap_status_t **status) { - void *ptr; snd_pcm_hw_t *hw = (snd_pcm_hw_t*) private; - ptr = mmap(NULL, sizeof(snd_pcm_mmap_control_t), PROT_READ, MAP_FILE|MAP_SHARED, + void *ptr; + ptr = mmap(NULL, sizeof(snd_pcm_mmap_status_t), PROT_READ, MAP_FILE|MAP_SHARED, hw->fd, SND_PCM_MMAP_OFFSET_STATUS); if (ptr == MAP_FAILED || ptr == NULL) return -errno; @@ -304,8 +304,8 @@ static int snd_pcm_hw_mmap_status(void *private, snd_pcm_mmap_status_t **status) static int snd_pcm_hw_mmap_control(void *private, snd_pcm_mmap_control_t **control) { - void *ptr; snd_pcm_hw_t *hw = (snd_pcm_hw_t*) private; + void *ptr; ptr = mmap(NULL, sizeof(snd_pcm_mmap_control_t), PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, hw->fd, SND_PCM_MMAP_OFFSET_CONTROL); if (ptr == MAP_FAILED || ptr == NULL) @@ -316,15 +316,15 @@ static int snd_pcm_hw_mmap_control(void *private, snd_pcm_mmap_control_t **contr static int snd_pcm_hw_mmap_data(void *private, void **buffer, size_t bsize) { - int prot; - void *daddr; snd_pcm_hw_t *hw = (snd_pcm_hw_t*) private; + void *ptr; + int prot; prot = hw->handle->stream == SND_PCM_STREAM_PLAYBACK ? PROT_WRITE : PROT_READ; - daddr = mmap(NULL, bsize, prot, MAP_FILE|MAP_SHARED, + ptr = mmap(NULL, bsize, prot, MAP_FILE|MAP_SHARED, hw->fd, SND_PCM_MMAP_OFFSET_DATA); - if (daddr == MAP_FAILED || daddr == NULL) + if (ptr == MAP_FAILED || ptr == NULL) return -errno; - *buffer = daddr; + *buffer = ptr; return 0; } |