From ef422fa4ae626e9638ca70d1c56f27e701dd69c2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 29 Jun 2004 16:48:37 +0000 Subject: [PATCH] esound protocol git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@40 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/Makefile.am | 31 ++- src/client.c | 6 + src/client.h | 2 + src/esound-spec.h | 191 +++++++++++++++++++ src/main.c | 9 +- src/module-pipe-sink.c | 2 +- src/module-protocol-stub.c | 39 +++- src/oss-util.c | 6 +- src/pacat.c | 2 +- src/protocol-esound.c | 462 +++++++++++++++++++++++++++++++++++++++++++++ src/protocol-esound.h | 12 ++ src/protocol-native.c | 1 + src/protocol-simple.c | 3 +- src/sample-util.c | 18 +- src/sample.c | 26 ++- src/sample.h | 17 +- src/util.c | 23 +++ src/util.h | 2 + 18 files changed, 806 insertions(+), 46 deletions(-) create mode 100644 src/esound-spec.h create mode 100644 src/protocol-esound.c create mode 100644 src/protocol-esound.h diff --git a/src/Makefile.am b/src/Makefile.am index a67f395..af4478b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -29,7 +29,8 @@ pkglib_LTLIBRARIES=libiochannel.la libsocket-server.la libsocket-client.la \ libprotocol-cli.la module-cli-protocol-unix.la libtagstruct.la \ libpdispatch.la libprotocol-native.la libpstream-util.la \ module-native-protocol-tcp.la module-native-protocol-unix.la \ - libpolyp.la + libpolyp.la libprotocol-esound.la module-esound-protocol-unix.la \ + module-esound-protocol-tcp.la polypaudio_SOURCES = idxset.c idxset.h \ queue.c queue.h \ @@ -113,40 +114,54 @@ libprotocol_cli_la_LIBADD = libsocket-server.la libiochannel.la libcli.la libprotocol_native_la_SOURCES = protocol-native.c protocol-native.h libprotocol_native_la_LDFLAGS = -avoid-version -libprotocol_native_la_LIBADD = libsocket-server.la libiochannel.la libpacket.la libpstream.la libpstream-util.la libpdispatch.la +libprotocol_native_la_LIBADD = libsocket-server.la libiochannel.la libpacket.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libtagstruct_la_SOURCES = tagstruct.c tagstruct.h libtagstruct_la_LDFLAGS = -avoid-version +libprotocol_esound_la_SOURCES = protocol-esound.c protocol-esound.h protocol-esound-spec.h +libprotocol_esound_la_LDFLAGS = -avoid-version +libprotocol_esound_la_LIBADD = libsocket-server.la libiochannel.la + module_simple_protocol_tcp_la_SOURCES = module-protocol-stub.c module_simple_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS) module_simple_protocol_tcp_la_LDFLAGS = -module -avoid-version -module_simple_protocol_tcp_la_LIBADD = libprotocol-simple.la libiochannel.la +module_simple_protocol_tcp_la_LIBADD = libprotocol-simple.la libsocket-server.la module_simple_protocol_unix_la_SOURCES = module-protocol-stub.c module_simple_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS) module_simple_protocol_unix_la_LDFLAGS = -module -avoid-version -module_simple_protocol_unix_la_LIBADD = libprotocol-simple.la libiochannel.la +module_simple_protocol_unix_la_LIBADD = libprotocol-simple.la libsocket-server.la module_cli_protocol_tcp_la_SOURCES = module-protocol-stub.c module_cli_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS) module_cli_protocol_tcp_la_LDFLAGS = -module -avoid-version -module_cli_protocol_tcp_la_LIBADD = libprotocol-cli.la libiochannel.la +module_cli_protocol_tcp_la_LIBADD = libprotocol-cli.la libsocket-server.la module_cli_protocol_unix_la_SOURCES = module-protocol-stub.c module_cli_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS) module_cli_protocol_unix_la_LDFLAGS = -module -avoid-version -module_cli_protocol_unix_la_LIBADD = libprotocol-cli.la libiochannel.la +module_cli_protocol_unix_la_LIBADD = libprotocol-cli.la libsocket-server.la module_native_protocol_tcp_la_SOURCES = module-protocol-stub.c module_native_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS) module_native_protocol_tcp_la_LDFLAGS = -module -avoid-version -module_native_protocol_tcp_la_LIBADD = libprotocol-native.la libiochannel.la libtagstruct.la +module_native_protocol_tcp_la_LIBADD = libprotocol-native.la libsocket-server.la module_native_protocol_unix_la_SOURCES = module-protocol-stub.c module_native_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS) module_native_protocol_unix_la_LDFLAGS = -module -avoid-version -module_native_protocol_unix_la_LIBADD = libprotocol-native.la libiochannel.la libtagstruct.la +module_native_protocol_unix_la_LIBADD = libprotocol-native.la libsocket-server.la + +module_esound_protocol_tcp_la_SOURCES = module-protocol-stub.c +module_esound_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS) +module_esound_protocol_tcp_la_LDFLAGS = -module -avoid-version +module_esound_protocol_tcp_la_LIBADD = libprotocol-esound.la libsocket-server.la + +module_esound_protocol_unix_la_SOURCES = module-protocol-stub.c +module_esound_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS) +module_esound_protocol_unix_la_LDFLAGS = -module -avoid-version +module_esound_protocol_unix_la_LIBADD = libprotocol-esound.la libsocket-server.la module_pipe_sink_la_SOURCES = module-pipe-sink.c module_pipe_sink_la_LDFLAGS = -module -avoid-version diff --git a/src/client.c b/src/client.c index 4c8a049..3769128 100644 --- a/src/client.c +++ b/src/client.c @@ -60,3 +60,9 @@ char *client_list_to_string(struct core *c) { return strbuf_tostring_free(s); } + +void client_rename(struct client *c, const char *name) { + assert(c); + free(c->name); + c->name = name ? strdup(name) : NULL; +} diff --git a/src/client.h b/src/client.h index 4817205..39167ee 100644 --- a/src/client.h +++ b/src/client.h @@ -25,4 +25,6 @@ void client_kill(struct client *c); char *client_list_to_string(struct core *c); +void client_rename(struct client *c, const char *name); + #endif diff --git a/src/esound-spec.h b/src/esound-spec.h new file mode 100644 index 0000000..1c2dc02 --- /dev/null +++ b/src/esound-spec.h @@ -0,0 +1,191 @@ +#ifndef fooesoundhfoo +#define fooesoundhfoo + +/* Most of the following is blatantly stolen from esound. */ + + +/* path and name of the default EsounD domain socket */ +#define ESD_UNIX_SOCKET_DIR "/tmp/.esd" +#define ESD_UNIX_SOCKET_NAME "/tmp/.esd/socket" + +/* length of the audio buffer size */ +#define ESD_BUF_SIZE (4 * 1024) +/* maximum size we can write(). Otherwise we might overflow */ +#define ESD_MAX_WRITE_SIZE (21 * 4096) + +/* length of the authorization key, octets */ +#define ESD_KEY_LEN (16) + +/* default port for the EsounD server */ +#define ESD_DEFAULT_PORT (16001) + +/* default sample rate for the EsounD server */ +#define ESD_DEFAULT_RATE (44100) + +/* maximum length of a stream/sample name */ +#define ESD_NAME_MAX (128) + +/* a magic number to identify the relative endianness of a client */ +#define ESD_ENDIAN_KEY ((uint32_t) (('E' << 24) + ('N' << 16) + ('D' << 8) + ('N'))) + +#define ESD_VOLUME_BASE (256) + + +/*************************************/ +/* what can we do to/with the EsounD */ +enum esd_proto { + ESD_PROTO_CONNECT, /* implied on inital client connection */ + + /* pseudo "security" functionality */ + ESD_PROTO_LOCK, /* disable "foreign" client connections */ + ESD_PROTO_UNLOCK, /* enable "foreign" client connections */ + + /* stream functionality: play, record, monitor */ + ESD_PROTO_STREAM_PLAY, /* play all following data as a stream */ + ESD_PROTO_STREAM_REC, /* record data from card as a stream */ + ESD_PROTO_STREAM_MON, /* send mixed buffer output as a stream */ + + /* sample functionality: cache, free, play, loop, EOL, kill */ + ESD_PROTO_SAMPLE_CACHE, /* cache a sample in the server */ + ESD_PROTO_SAMPLE_FREE, /* release a sample in the server */ + ESD_PROTO_SAMPLE_PLAY, /* play a cached sample */ + ESD_PROTO_SAMPLE_LOOP, /* loop a cached sample, til eoloop */ + ESD_PROTO_SAMPLE_STOP, /* stop a looping sample when done */ + ESD_PROTO_SAMPLE_KILL, /* stop the looping sample immed. */ + + /* free and reclaim /dev/dsp functionality */ + ESD_PROTO_STANDBY, /* release /dev/dsp and ignore all data */ + ESD_PROTO_RESUME, /* reclaim /dev/dsp and play sounds again */ + + /* TODO: move these to a more logical place. NOTE: will break the protocol */ + ESD_PROTO_SAMPLE_GETID, /* get the ID for an already-cached sample */ + ESD_PROTO_STREAM_FILT, /* filter mixed buffer output as a stream */ + + /* esd remote management */ + ESD_PROTO_SERVER_INFO, /* get server info (ver, sample rate, format) */ + ESD_PROTO_ALL_INFO, /* get all info (server info, players, samples) */ + ESD_PROTO_SUBSCRIBE, /* track new and removed players and samples */ + ESD_PROTO_UNSUBSCRIBE, /* stop tracking updates */ + + /* esd remote control */ + ESD_PROTO_STREAM_PAN, /* set stream panning */ + ESD_PROTO_SAMPLE_PAN, /* set default sample panning */ + + /* esd status */ + ESD_PROTO_STANDBY_MODE, /* see if server is in standby, autostandby, etc */ + + /* esd latency */ + ESD_PROTO_LATENCY, /* retrieve latency between write()'s and output */ + + ESD_PROTO_MAX /* for bounds checking */ +}; + +/******************/ +/* The EsounD api */ + +/* the properties of a sound buffer are logically or'd */ + +/* bits of stream/sample data */ +#define ESD_MASK_BITS ( 0x000F ) +#define ESD_BITS8 ( 0x0000 ) +#define ESD_BITS16 ( 0x0001 ) + +/* how many interleaved channels of data */ +#define ESD_MASK_CHAN ( 0x00F0 ) +#define ESD_MONO ( 0x0010 ) +#define ESD_STEREO ( 0x0020 ) + +/* whether it's a stream or a sample */ +#define ESD_MASK_MODE ( 0x0F00 ) +#define ESD_STREAM ( 0x0000 ) +#define ESD_SAMPLE ( 0x0100 ) +#define ESD_ADPCM ( 0x0200 ) /* TODO: anyone up for this? =P */ + +/* the function of the stream/sample, and common functions */ +#define ESD_MASK_FUNC ( 0xF000 ) +#define ESD_PLAY ( 0x1000 ) +/* functions for streams only */ +#define ESD_MONITOR ( 0x0000 ) +#define ESD_RECORD ( 0x2000 ) +/* functions for samples only */ +#define ESD_STOP ( 0x0000 ) +#define ESD_LOOP ( 0x2000 ) + +typedef int esd_format_t; +typedef int esd_proto_t; + +/*******************************************************************/ +/* esdmgr.c - functions to implement a "sound manager" for esd */ + +/* structures to retrieve information about streams/samples from the server */ +typedef struct esd_server_info { + + int version; /* server version encoded as an int */ + esd_format_t format; /* magic int with the format info */ + int rate; /* sample rate */ + +} esd_server_info_t; + +typedef struct esd_player_info { + + struct esd_player_info *next; /* point to next entry in list */ + esd_server_info_t *server; /* the server that contains this stream */ + + int source_id; /* either a stream fd or sample id */ + char name[ ESD_NAME_MAX ]; /* name of stream for remote control */ + int rate; /* sample rate */ + int left_vol_scale; /* volume scaling */ + int right_vol_scale; + + esd_format_t format; /* magic int with the format info */ + +} esd_player_info_t; + +typedef struct esd_sample_info { + + struct esd_sample_info *next; /* point to next entry in list */ + esd_server_info_t *server; /* the server that contains this sample */ + + int sample_id; /* either a stream fd or sample id */ + char name[ ESD_NAME_MAX ]; /* name of stream for remote control */ + int rate; /* sample rate */ + int left_vol_scale; /* volume scaling */ + int right_vol_scale; + + esd_format_t format; /* magic int with the format info */ + int length; /* total buffer length */ + +} esd_sample_info_t; + +typedef struct esd_info { + + esd_server_info_t *server; + esd_player_info_t *player_list; + esd_sample_info_t *sample_list; + +} esd_info_t; + +enum esd_standby_mode { + ESM_ERROR, ESM_ON_STANDBY, ESM_ON_AUTOSTANDBY, ESM_RUNNING +}; +typedef int esd_standby_mode_t; + +enum esd_client_state { + ESD_STREAMING_DATA, /* data from here on is streamed data */ + ESD_CACHING_SAMPLE, /* midway through caching a sample */ + ESD_NEEDS_REQDATA, /* more data needed to complere request */ + ESD_NEXT_REQUEST, /* proceed to next request */ + ESD_CLIENT_STATE_MAX /* place holder */ +}; +typedef int esd_client_state_t; + +/* switch endian order for cross platform playing */ +#define swap_endian_32(x) ((x >> 24) | ((x >> 8) & 0xFF00) | (((x & 0xFF00) << 8)) | (x << 24)) + +/* the endian key is transferred in binary, if it's read into int, */ +/* and matches ESD_ENDIAN_KEY (ENDN), then the endianness of the */ +/* server and the client match; if it's SWAP_ENDIAN_KEY, swap data */ +#define ESD_SWAP_ENDIAN_KEY ((uint32_t) swap_endian_32(ESD_ENDIAN_KEY)) + + +#endif diff --git a/src/main.c b/src/main.c index 0fe333e..e50321f 100644 --- a/src/main.c +++ b/src/main.c @@ -41,11 +41,12 @@ int main(int argc, char *argv[]) { module_load(c, "module-oss-mmap", "/dev/dsp1"); /* module_load(c, "module-pipe-sink", NULL); module_load(c, "module-simple-protocol-tcp", NULL); - module_load(c, "module-simple-protocol-unix", NULL);*/ + module_load(c, "module-simple-protocol-unix", NULL); module_load(c, "module-cli-protocol-tcp", NULL); -/* module_load(c, "module-cli-protocol-unix", NULL); - module_load(c, "module-native-protocol-tcp", NULL);*/ - module_load(c, "module-native-protocol-unix", NULL); + module_load(c, "module-cli-protocol-unix", NULL); + module_load(c, "module-native-protocol-tcp", NULL); + module_load(c, "module-native-protocol-unix", NULL);*/ + module_load(c, "module-esound-protocol-tcp", NULL); module_load(c, "module-cli", NULL); fprintf(stderr, "main: mainloop entry.\n"); diff --git a/src/module-pipe-sink.c b/src/module-pipe-sink.c index 6cc4de8..9747c33 100644 --- a/src/module-pipe-sink.c +++ b/src/module-pipe-sink.c @@ -79,7 +79,7 @@ int module_init(struct core *c, struct module*m) { char *p; int fd = -1; static const struct pa_sample_spec ss = { - .format = SAMPLE_S16NE, + .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2, }; diff --git a/src/module-protocol-stub.c b/src/module-protocol-stub.c index 97bf5ef..29ce6b1 100644 --- a/src/module-protocol-stub.c +++ b/src/module-protocol-stub.c @@ -1,31 +1,47 @@ +#include #include #include +#include #include "module.h" #include "socket-server.h" +#include "util.h" #ifdef USE_PROTOCOL_SIMPLE #include "protocol-simple.h" #define protocol_free protocol_simple_free #define IPV4_PORT 4711 - #define UNIX_SOCKET "/tmp/polypaudio_simple" + #define UNIX_SOCKET_DIR "/tmp/polypaudio" + #define UNIX_SOCKET "/tmp/polypaudio/simple" #else #ifdef USE_PROTOCOL_CLI #include "protocol-cli.h" #define protocol_new protocol_cli_new #define protocol_free protocol_cli_free #define IPV4_PORT 4712 - #define UNIX_SOCKET "/tmp/polypaudio_cli" + #define UNIX_SOCKET_DIR "/tmp/polypaudio" + #define UNIX_SOCKET "/tmp/polypaudio/cli" #else #ifdef USE_PROTOCOL_NATIVE #include "protocol-native.h" #define protocol_new protocol_native_new #define protocol_free protocol_native_free #define IPV4_PORT 4713 - #define UNIX_SOCKET "/tmp/polypaudio_native" + #define UNIX_SOCKET_DIR "/tmp/polypaudio" + #define UNIX_SOCKET "/tmp/polypaudio/native" #else - #error "Broken build system" - #endif + #ifdef USE_PROTOCOL_ESOUND + #include "protocol-esound.h" + #include "esound-spec.h" + #define protocol_new protocol_esound_new + #define protocol_free protocol_esound_free + #define IPV4_PORT ESD_DEFAULT_PORT + #define UNIX_SOCKET_DIR ESD_UNIX_SOCKET_DIR + #define UNIX_SOCKET ESD_UNIX_SOCKET_NAME + #else + #error "Broken build system" + #endif + #endif #endif #endif @@ -37,8 +53,15 @@ int module_init(struct core *c, struct module*m) { if (!(s = socket_server_new_ipv4(c->mainloop, INADDR_LOOPBACK, IPV4_PORT))) return -1; #else - if (!(s = socket_server_new_unix(c->mainloop, UNIX_SOCKET))) + if (make_secure_dir(UNIX_SOCKET_DIR) < 0) { + fprintf(stderr, "Failed to create secure socket directory.\n"); return -1; + } + + if (!(s = socket_server_new_unix(c->mainloop, UNIX_SOCKET))) { + rmdir(UNIX_SOCKET_DIR); + return -1; + } #endif #ifdef USE_PROTOCOL_SIMPLE @@ -55,4 +78,8 @@ void module_done(struct core *c, struct module*m) { assert(c && m); protocol_free(m->userdata); + +#ifndef USE_TCP_SOCKETS + rmdir(UNIX_SOCKET_DIR); +#endif } diff --git a/src/oss-util.c b/src/oss-util.c index bf6b62e..d3a5fec 100644 --- a/src/oss-util.c +++ b/src/oss-util.c @@ -22,11 +22,11 @@ int oss_auto_format(int fd, struct pa_sample_spec *ss) { fprintf(stderr, "SNDCTL_DSP_SETFMT: %s\n", format != AFMT_U8 ? "No supported sample format" : strerror(errno)); return -1; } else - ss->format = SAMPLE_U8; + ss->format = PA_SAMPLE_U8; } else - ss->format = f == AFMT_S16_LE ? SAMPLE_S16LE : SAMPLE_S16BE; + ss->format = f == AFMT_S16_LE ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE; } else - ss->format = SAMPLE_S16NE; + ss->format = PA_SAMPLE_S16NE; channels = 2; if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) { diff --git a/src/pacat.c b/src/pacat.c index 5ee1b86..fbd1d08 100644 --- a/src/pacat.c +++ b/src/pacat.c @@ -75,7 +75,7 @@ static void stream_complete_callback(struct pa_context*c, struct pa_stream *s, v static void context_complete_callback(struct pa_context *c, int success, void *userdata) { static const struct pa_sample_spec ss = { - .format = SAMPLE_S16NE, + .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 }; diff --git a/src/protocol-esound.c b/src/protocol-esound.c new file mode 100644 index 0000000..1668ef5 --- /dev/null +++ b/src/protocol-esound.c @@ -0,0 +1,462 @@ +#include +#include +#include +#include +#include +#include + +#include "protocol-esound.h" +#include "esound-spec.h" +#include "memblock.h" +#include "client.h" +#include "sinkinput.h" +#include "sink.h" +#include "sample.h" + +/* This is heavily based on esound's code */ + +struct connection { + struct protocol_esound *protocol; + struct iochannel *io; + struct client *client; + int authorized, swap_byte_order; + void *read_data; + size_t read_data_alloc, read_data_length; + void *write_data; + size_t write_data_alloc, write_data_index, write_data_length; + esd_proto_t request; + esd_client_state_t state; + struct sink_input *sink_input; + struct memblockq *input_memblockq; +}; + +struct protocol_esound { + int public; + struct core *core; + struct socket_server *server; + struct idxset *connections; +}; + +typedef struct proto_handler { + size_t data_length; + int (*proc)(struct connection *c, const void *data, size_t length); + const char *description; +} esd_proto_handler_info_t; + +#define BUFSIZE PIPE_BUF + +static void sink_input_drop_cb(struct sink_input *i, size_t length); +static int sink_input_peek_cb(struct sink_input *i, struct memchunk *chunk); +static void sink_input_kill_cb(struct sink_input *i); +static uint32_t sink_input_get_latency_cb(struct sink_input *i); + +static int esd_proto_connect(struct connection *c, const void *data, size_t length); +static int esd_proto_stream_play(struct connection *c, const void *data, size_t length); +static int esd_proto_stream_record(struct connection *c, const void *data, size_t length); + +static int do_write(struct connection *c); + +/* the big map of protocol handler info */ +static struct proto_handler proto_map[ESD_PROTO_MAX] = { + { ESD_KEY_LEN + sizeof(int), &esd_proto_connect, "connect" }, + { ESD_KEY_LEN + sizeof(int), NULL, "lock" }, + { ESD_KEY_LEN + sizeof(int), NULL, "unlock" }, + + { ESD_NAME_MAX + 2 * sizeof(int), &esd_proto_stream_play, "stream play" }, + { ESD_NAME_MAX + 2 * sizeof(int), &esd_proto_stream_record, "stream rec" }, + { ESD_NAME_MAX + 2 * sizeof(int), NULL, "stream mon" }, + + { ESD_NAME_MAX + 3 * sizeof(int), NULL, "sample cache" }, + { sizeof(int), NULL, "sample free" }, + { sizeof(int), NULL, "sample play" }, + { sizeof(int), NULL, "sample loop" }, + { sizeof(int), NULL, "sample stop" }, + { -1, NULL, "TODO: sample kill" }, + + { ESD_KEY_LEN + sizeof(int), NULL, "standby" }, + { ESD_KEY_LEN + sizeof(int), NULL, "resume" }, + + { ESD_NAME_MAX, NULL, "sample getid" }, + { ESD_NAME_MAX + 2 * sizeof(int), NULL, "stream filter" }, + + { sizeof(int), NULL, "server info" }, + { sizeof(int), NULL, "all info" }, + { -1, NULL, "TODO: subscribe" }, + { -1, NULL, "TODO: unsubscribe" }, + + { 3 * sizeof(int), NULL, "stream pan"}, + { 3 * sizeof(int), NULL, "sample pan" }, + + { sizeof(int), NULL, "standby mode" }, + { 0, NULL, "get latency" } +}; + + +static void connection_free(struct connection *c) { + assert(c); + idxset_remove_by_data(c->protocol->connections, c, NULL); + + client_free(c->client); + + if (c->sink_input) + sink_input_free(c->sink_input); + if (c->input_memblockq) + memblockq_free(c->input_memblockq); + + free(c->read_data); + free(c->write_data); + + iochannel_free(c->io); + free(c); +} + +static struct sink* get_output_sink(struct protocol_esound *p) { + assert(p); + return sink_get_default(p->core); +} + +static void* connection_write(struct connection *c, size_t length) { + size_t t, i; + assert(c); + + t = c->write_data_length+length; + + if (c->write_data_alloc < t) + c->write_data = realloc(c->write_data, c->write_data_alloc = t); + + assert(c->write_data); + + i = c->write_data_length; + c->write_data_length += length; + + return c->write_data+i; +} + +/*** esound commands ***/ + +static int esd_proto_connect(struct connection *c, const void *data, size_t length) { + uint32_t ekey; + int *ok; + assert(length == (ESD_KEY_LEN + sizeof(uint32_t))); + + c->authorized = 1; + + ekey = *(uint32_t*)(data+ESD_KEY_LEN); + if (ekey == ESD_ENDIAN_KEY) + c->swap_byte_order = 0; + else if (ekey == ESD_SWAP_ENDIAN_KEY) + c->swap_byte_order = 1; + else { + fprintf(stderr, "protocol-esound.c: client sent invalid endian key\n"); + return -1; + } + + ok = connection_write(c, sizeof(int)); + assert(ok); + *ok = 1; + + do_write(c); + + return 0; +} + +static int esd_proto_stream_play(struct connection *c, const void *data, size_t length) { + char name[ESD_NAME_MAX]; + int format, rate; + struct sink *sink; + struct pa_sample_spec ss; + assert(length == (sizeof(int)*2+ESD_NAME_MAX)); + + format = *(int*)data; + rate = *((int*)data + 1); + + if (c->swap_byte_order) + format = swap_endian_32(format); + if (c->swap_byte_order) + rate = swap_endian_32(rate); + + ss.rate = rate; + ss.channels = ((format & ESD_MASK_CHAN) == ESD_STEREO) ? 2 : 1; + ss.format = ((format & ESD_MASK_BITS) == ESD_BITS16) ? PA_SAMPLE_S16NE : PA_SAMPLE_U8; + + if (!pa_sample_spec_valid(&ss)) + return -1; + + if (!(sink = get_output_sink(c->protocol))) + return -1; + + strncpy(name, data + sizeof(int)*2, sizeof(name)); + name[sizeof(name)-1] = 0; + + client_rename(c->client, name); + + assert(!c->input_memblockq); + c->input_memblockq = memblockq_new(1024*10, pa_sample_size(&ss), 1024*2); + assert(c->input_memblockq); + + assert(!c->sink_input); + c->sink_input = sink_input_new(sink, &ss, name); + assert(c->sink_input); + + c->sink_input->peek = sink_input_peek_cb; + c->sink_input->drop = sink_input_drop_cb; + c->sink_input->kill = sink_input_kill_cb; + c->sink_input->get_latency = sink_input_get_latency_cb; + c->sink_input->userdata = c; + + c->state = ESD_STREAMING_DATA; + + return 0; +} + +static int esd_proto_stream_record(struct connection *c, const void *data, size_t length) { + assert(0); +} + +/*** client callbacks ***/ + +static void client_kill_cb(struct client *c) { + assert(c && c->userdata); + connection_free(c->userdata); +} + +/*** iochannel callbacks ***/ + +static int do_read(struct connection *c) { + assert(c && c->io); + + if (!iochannel_is_readable(c->io)) + return 0; + + if (c->state == ESD_NEXT_REQUEST) { + ssize_t r; + assert(c->read_data_length < sizeof(c->request)); + + if ((r = iochannel_read(c->io, ((void*) &c->request) + c->read_data_length, sizeof(c->request) - c->read_data_length)) <= 0) { + fprintf(stderr, "protocol-esound.c: read() failed: %s\n", r == 0 ? "EOF" : strerror(errno)); + return -1; + } + + if ((c->read_data_length+= r) >= sizeof(c->request)) { + struct proto_handler *handler; + + if (c->swap_byte_order) + c->request = swap_endian_32(c->request); + + if (c->request < ESD_PROTO_CONNECT || c->request > ESD_PROTO_MAX) { + fprintf(stderr, "protocol-esound.c: recieved invalid request.\n"); + return -1; + } + + handler = proto_map+c->request; + + if (!handler->proc) { + fprintf(stderr, "protocol-sound.c: recieved unimplemented request.\n"); + return -1; + } + + if (handler->data_length == 0) { + c->read_data_length = 0; + + if (handler->proc(c, NULL, 0) < 0) + return -1; + + } else { + if (c->read_data_alloc < handler->data_length) + c->read_data = realloc(c->read_data, c->read_data_alloc = handler->data_length); + assert(c->read_data); + + c->state = ESD_NEEDS_REQDATA; + c->read_data_length = 0; + } + } + + } else if (c->state == ESD_NEEDS_REQDATA) { + ssize_t r; + struct proto_handler *handler = proto_map+c->request; + + assert(handler->proc); + + assert(c->read_data && c->read_data_length < handler->data_length); + + if ((r = iochannel_read(c->io, c->read_data + c->read_data_length, handler->data_length - c->read_data_length)) <= 0) { + fprintf(stderr, "protocol-esound.c: read() failed: %s\n", r == 0 ? "EOF" : strerror(errno)); + return -1; + } + + if ((c->read_data_length+= r) >= handler->data_length) { + size_t l = c->read_data_length; + assert(handler->proc); + + c->state = ESD_NEXT_REQUEST; + c->read_data_length = 0; + + if (handler->proc(c, c->read_data, l) < 0) + return -1; + } + } else if (c->state == ESD_STREAMING_DATA) { + struct memchunk chunk; + ssize_t r; + + assert(c->input_memblockq); + + if (!memblockq_is_writable(c->input_memblockq, BUFSIZE)) + return 0; + + chunk.memblock = memblock_new(BUFSIZE); + assert(chunk.memblock && chunk.memblock->data); + + if ((r = iochannel_read(c->io, chunk.memblock->data, BUFSIZE)) <= 0) { + fprintf(stderr, "protocol-esound.c: read() failed: %s\n", r == 0 ? "EOF" : strerror(errno)); + memblock_unref(chunk.memblock); + return -1; + } + + chunk.memblock->length = chunk.length = r; + chunk.index = 0; + + assert(c->input_memblockq); + memblockq_push(c->input_memblockq, &chunk, 0); + memblock_unref(chunk.memblock); + assert(c->sink_input); + sink_notify(c->sink_input->sink); + } else + assert(0); + + return 0; +} + +static int do_write(struct connection *c) { + ssize_t r; + assert(c && c->io); + + if (!iochannel_is_writable(c->io)) + return 0; + + if (!c->write_data_length) + return 0; + + assert(c->write_data_index < c->write_data_length); + if ((r = iochannel_write(c->io, c->write_data+c->write_data_index, c->write_data_length-c->write_data_index)) < 0) { + fprintf(stderr, "protocol-esound.c: write() failed: %s\n", strerror(errno)); + return -1; + } + + if ((c->write_data_index +=r) >= c->write_data_length) + c->write_data_length = c->write_data_index = 0; + + return 0; +} + +static void io_callback(struct iochannel*io, void *userdata) { + struct connection *c = userdata; + assert(io && c && c->io == io); + + if (do_read(c) < 0 || do_write(c) < 0) + connection_free(c); +} + +/*** sink_input callbacks ***/ + +static int sink_input_peek_cb(struct sink_input *i, struct memchunk *chunk) { + struct connection*c; + assert(i && i->userdata && chunk); + c = i->userdata; + + if (memblockq_peek(c->input_memblockq, chunk) < 0) + return -1; + + return 0; +} + +static void sink_input_drop_cb(struct sink_input *i, size_t length) { + struct connection*c = i->userdata; + assert(i && c && length); + + memblockq_drop(c->input_memblockq, length); + + if (do_read(c) < 0) + connection_free(c); +} + +static void sink_input_kill_cb(struct sink_input *i) { + assert(i && i->userdata); + connection_free((struct connection *) i->userdata); +} + + +static uint32_t sink_input_get_latency_cb(struct sink_input *i) { + struct connection*c = i->userdata; + assert(i && c); + return pa_samples_usec(memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); +} + +/*** socket server callback ***/ + +static void on_connection(struct socket_server*s, struct iochannel *io, void *userdata) { + struct connection *c; + char cname[256]; + assert(s && io && userdata); + + c = malloc(sizeof(struct connection)); + assert(c); + c->protocol = userdata; + c->io = io; + iochannel_set_callback(c->io, io_callback, c); + + iochannel_peer_to_string(io, cname, sizeof(cname)); + assert(c->protocol->core); + c->client = client_new(c->protocol->core, "ESOUND", cname); + assert(c->client); + c->client->kill = client_kill_cb; + c->client->userdata = c; + + c->authorized = c->protocol->public; + c->swap_byte_order = 0; + + c->read_data_length = 0; + c->read_data = malloc(c->read_data_alloc = proto_map[ESD_PROTO_CONNECT].data_length); + assert(c->read_data); + + c->write_data_length = c->write_data_index = c->write_data_alloc = 0; + c->write_data = NULL; + + c->state = ESD_NEEDS_REQDATA; + c->request = ESD_PROTO_CONNECT; + + c->sink_input = NULL; + c->input_memblockq = NULL; + + idxset_put(c->protocol->connections, c, NULL); +} + +/*** entry points ***/ + +struct protocol_esound* protocol_esound_new(struct core*core, struct socket_server *server) { + struct protocol_esound *p; + assert(core && server); + + p = malloc(sizeof(struct protocol_esound)); + assert(p); + p->public = 1; + p->server = server; + p->core = core; + p->connections = idxset_new(NULL, NULL); + assert(p->connections); + + socket_server_set_callback(p->server, on_connection, p); + + return p; +} + +void protocol_esound_free(struct protocol_esound *p) { + struct connection *c; + assert(p); + + while ((c = idxset_first(p->connections, NULL))) + connection_free(c); + + idxset_free(p->connections, NULL, NULL); + socket_server_free(p->server); + free(p); +} diff --git a/src/protocol-esound.h b/src/protocol-esound.h new file mode 100644 index 0000000..2600cfa --- /dev/null +++ b/src/protocol-esound.h @@ -0,0 +1,12 @@ +#ifndef fooprotocolesoundhfoo +#define fooprotocolesoundhfoo + +#include "core.h" +#include "socket-server.h" + +struct protocol_esound; + +struct protocol_esound* protocol_esound_new(struct core*core, struct socket_server *server); +void protocol_esound_free(struct protocol_esound *p); + +#endif diff --git a/src/protocol-native.c b/src/protocol-native.c index 27b547a..9af438a 100644 --- a/src/protocol-native.c +++ b/src/protocol-native.c @@ -384,6 +384,7 @@ struct protocol_native* protocol_native_new(struct core *core, struct socket_ser p->server = server; p->core = core; p->connections = idxset_new(NULL, NULL); + assert(p->connections); socket_server_set_callback(p->server, on_connection, p); diff --git a/src/protocol-simple.c b/src/protocol-simple.c index c8c4585..80249ee 100644 --- a/src/protocol-simple.c +++ b/src/protocol-simple.c @@ -73,8 +73,7 @@ static int do_read(struct connection *c) { return -1; } - chunk.memblock->length = r; - chunk.length = r; + chunk.memblock->length = chunk.length = r; chunk.index = 0; assert(c->input_memblockq); diff --git a/src/sample-util.c b/src/sample-util.c index 7a3c267..ff14548 100644 --- a/src/sample-util.c +++ b/src/sample-util.c @@ -4,7 +4,7 @@ #include "sample-util.h" struct pa_sample_spec default_sample_spec = { - .format = SAMPLE_S16NE, + .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 }; @@ -27,18 +27,20 @@ void silence_memory(void *p, size_t length, struct pa_sample_spec *spec) { assert(p && length && spec); switch (spec->format) { - case SAMPLE_U8: + case PA_SAMPLE_U8: c = 127; break; - case SAMPLE_S16LE: - case SAMPLE_S16BE: - case SAMPLE_FLOAT32: + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + case PA_SAMPLE_FLOAT32: c = 0; break; - case SAMPLE_ALAW: - case SAMPLE_ULAW: + case PA_SAMPLE_ALAW: + case PA_SAMPLE_ULAW: c = 80; break; + default: + assert(0); } memset(p, c, length); @@ -47,7 +49,7 @@ void silence_memory(void *p, size_t length, struct pa_sample_spec *spec) { size_t mix_chunks(struct mix_info channels[], unsigned nchannels, void *data, size_t length, struct pa_sample_spec *spec, uint8_t volume) { unsigned c, d; assert(channels && data && length && spec); - assert(spec->format == SAMPLE_S16NE); + assert(spec->format == PA_SAMPLE_S16NE); for (d = 0;; d += sizeof(int16_t)) { int32_t sum = 0; diff --git a/src/sample.c b/src/sample.c index 2454630..b0d0cdb 100644 --- a/src/sample.c +++ b/src/sample.c @@ -7,18 +7,20 @@ size_t pa_sample_size(struct pa_sample_spec *spec) { size_t b = 1; switch (spec->format) { - case SAMPLE_U8: - case SAMPLE_ULAW: - case SAMPLE_ALAW: + case PA_SAMPLE_U8: + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: b = 1; break; - case SAMPLE_S16LE: - case SAMPLE_S16BE: + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: b = 2; break; - case SAMPLE_FLOAT32: + case PA_SAMPLE_FLOAT32: b = 4; break; + default: + assert(0); } return b * spec->channels; @@ -35,3 +37,15 @@ uint32_t pa_samples_usec(size_t length, struct pa_sample_spec *spec) { return (uint32_t) (((double) length /pa_sample_size(spec))/spec->rate*1000000); } + +int pa_sample_spec_valid(struct pa_sample_spec *spec) { + assert(spec); + + if (!spec->rate || !spec->channels) + return 0; + + if (spec->format <= 0 || spec->format >= PA_SAMPLE_MAX) + return 0; + + return 1; +} diff --git a/src/sample.h b/src/sample.h index a4a973b..697937e 100644 --- a/src/sample.h +++ b/src/sample.h @@ -5,15 +5,16 @@ #include enum pa_sample_format { - SAMPLE_U8, - SAMPLE_ALAW, - SAMPLE_ULAW, - SAMPLE_S16LE, - SAMPLE_S16BE, - SAMPLE_FLOAT32 + PA_SAMPLE_U8, + PA_SAMPLE_ALAW, + PA_SAMPLE_ULAW, + PA_SAMPLE_S16LE, + PA_SAMPLE_S16BE, + PA_SAMPLE_FLOAT32, + PA_SAMPLE_MAX }; -#define SAMPLE_S16NE SAMPLE_S16LE +#define PA_SAMPLE_S16NE PA_SAMPLE_S16LE struct pa_sample_spec { enum pa_sample_format format; @@ -25,4 +26,6 @@ size_t pa_bytes_per_second(struct pa_sample_spec *spec); size_t pa_sample_size(struct pa_sample_spec *spec); uint32_t pa_samples_usec(size_t length, struct pa_sample_spec *spec); +int pa_sample_spec_valid(struct pa_sample_spec *spec); + #endif diff --git a/src/util.c b/src/util.c index 0383a0a..9535042 100644 --- a/src/util.c +++ b/src/util.c @@ -1,9 +1,12 @@ +#include #include #include #include #include #include #include +#include +#include #include "util.h" @@ -60,3 +63,23 @@ void peer_to_string(char *c, size_t l, int fd) { snprintf(c, l, "Unknown client"); } + +int make_secure_dir(const char* dir) { + struct stat st; + + if (mkdir(dir, 0700) < 0) + if (errno != EEXIST) + return -1; + + if (lstat(dir, &st) < 0) + goto fail; + + if (!S_ISDIR(st.st_mode) || (st.st_uid != getuid()) || ((st.st_mode & 0777) != 0700)) + goto fail; + + return 0; + +fail: + rmdir(dir); + return -1; +} diff --git a/src/util.h b/src/util.h index 830ee2e..2a50719 100644 --- a/src/util.h +++ b/src/util.h @@ -5,4 +5,6 @@ void make_nonblock_fd(int fd); void peer_to_string(char *c, size_t l, int fd); +int make_secure_dir(const char* dir); + #endif -- 2.7.4