From 773bdfd137b4d4bb350903a1cb6b25ef304b3e0a Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 18 Apr 2006 20:42:15 +0200 Subject: [PATCH 01/16] add new writer to para_play: file It simply writes the input to a random filename under ~/.paraslash. Also, add some writer-related macros to config.h and two new options for para_play: --writer and --list_writers. --- configure.ac | 13 +++ error.h | 3 + play.c | 220 +++++++++++++++++++++++++++++++++++++++++++++------ play.ggo | 17 ++++ 4 files changed, 228 insertions(+), 25 deletions(-) diff --git a/configure.ac b/configure.ac index 61b1808a..bfd659f1 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,8 @@ server_ldflags="" play_cmdline_objs="play.cmdline" play_errlist_objs="play time fd string" play_ldflags="" +write_writers="file" + ########################################################################### ssl dnl @synopsis CHECK_SSL @@ -230,6 +232,7 @@ AC_CHECK_LIB([asound], [snd_pcm_open], [], [ if test "$have_alsa" = "yes"; then extras="$extras para_play" play_ldflags="$play_ldflags -lasound" + write_writers="$write_writers alsa" fi ########################################################################### ortp have_ortp="yes" @@ -320,6 +323,16 @@ AC_SUBST(play_ldflags, $play_ldflags) AC_DEFINE_UNQUOTED(INIT_PLAY_ERRLISTS, objlist_to_errlist($play_errlist_objs), errors used by para_play) +enum="$(for i in $write_writers; do printf "${i}_WRITE, " | tr '[a-z]' '[A-Z]'; done)" +AC_DEFINE_UNQUOTED(WRITER_ENUM, $enum NUM_SUPPORTED_WRITERS, + enum of supported writers) +names="$(for i in $write_writers; do printf '\"'$i'\", ' ; done)" +AC_DEFINE_UNQUOTED(WRITER_NAMES, $names, supported writer names) +inits="$(for i in $write_writers; do printf 'extern void '$i'_writer_init(struct writer *); '; done)" +AC_DEFINE_UNQUOTED(DECLARE_WRITER_INITS, $inits, init functions of the supported writers) +array="$(for i in $write_writers; do printf '{.init = '$i'_writer_init},'; done)" +AC_DEFINE_UNQUOTED(WRITER_ARRAY, $array, array of supported writers) + gui_cmdline_objs="gui.cmdline" gui_errlist_objs="exec close_on_fork signal string stat ringbuffer fd" gui_other_objs="gui gui_common gui_theme" diff --git a/error.h b/error.h index 880df55b..81691667 100644 --- a/error.h +++ b/error.h @@ -302,6 +302,7 @@ extern const char **para_errlist[]; PARA_ERROR(READ_STDIN, "failed to read from stdin"), \ PARA_ERROR(PLAY_SYNTAX, "syntax error"), \ PARA_ERROR(PLAY_OVERRUN, "buffer overrun"), \ + PARA_ERROR(LIST_WRITERS_GIVEN, ""), \ PARA_ERROR(PREMATURE_END, "premature end of audio file"), \ PARA_ERROR(BROKEN_CONF, "Broken alsa configuration"), \ PARA_ERROR(ACCESS_TYPE, "alsa access type not available"), \ @@ -321,6 +322,8 @@ extern const char **para_errlist[]; PARA_ERROR(START_THRESHOLD, "snd_pcm_sw_params_set_start_threshold() failed"), \ PARA_ERROR(STOP_THRESHOLD, "snd_pcm_sw_params_set_stop_threshold() failed"), \ PARA_ERROR(ALSA_LOG, "snd_output_stdio_attach() failed"), \ + PARA_ERROR(FW_WRITE, "file writer write error"), \ + PARA_ERROR(FW_OPEN, "file writer: can not open output file"), \ diff --git a/play.c b/play.c index f312626c..e1fdb2ef 100644 --- a/play.c +++ b/play.c @@ -31,26 +31,36 @@ #include "string.h" #include "error.h" -#define FORMAT SND_PCM_FORMAT_S16_LE - -struct private_alsa_data { - snd_pcm_t *handle; - size_t bytes_per_frame; -}; - +/* +files: +~~~~~~ +write.c +write.h wr +write_common.c +write_common.h: decratation of the wng funcs +alsa_writer.c +*/ + +/* write.h */ +enum writer_enum {WRITER_ENUM}; + +/* write.h */ struct writer_node { struct writer *writer; void *private_data; int chunk_bytes; }; +/* write.h */ struct writer { + void (*init)(struct writer *w); int (*open)(struct writer_node *); int (*write)(char *data, size_t nbytes, struct writer_node *); void (*close)(struct writer_node *); void (*shutdown)(struct writer_node *); }; +/* write.h */ struct writer_node_group { unsigned num_writers; struct writer_node *writer_nodes; @@ -59,20 +69,22 @@ struct writer_node_group { int eof; }; - +/* write.h */ #define FOR_EACH_WRITER_NODE(i, wng) for (i = 0; i < (wng)->num_writers; i++) +#define FOR_EACH_WRITER(i) for (i = 0; i < NUM_SUPPORTED_WRITERS; i++) -#define NUM_WRITERS 1 -static struct writer writers[NUM_WRITERS]; -#define FOR_EACH_WRITER(i) for (i = 0; i < NUM_WRITERS, i++) +DECLARE_WRITER_INITS; +/* write.c */ static unsigned char *audiobuf; static struct timeval *start_time; static struct gengetopt_args_info conf; +/* write.c */ INIT_PLAY_ERRLISTS; +/* write.c */ void para_log(int ll, const char* fmt,...) { va_list argp; @@ -84,6 +96,7 @@ void para_log(int ll, const char* fmt,...) va_end(argp); } +/* write.c */ /** * read WAV_HEADER_LEN bytes from stdin to audio buffer * @@ -103,6 +116,13 @@ static int read_wav_header(void) return 1; } +/* alsa_writer.c */ +#define FORMAT SND_PCM_FORMAT_S16_LE +struct private_alsa_data { + snd_pcm_t *handle; + size_t bytes_per_frame; +}; + /* * open and prepare the PCM handle for writing * @@ -188,6 +208,7 @@ static int alsa_open(struct writer_node *w) return period_size * pad->bytes_per_frame; } +/* alsa_writer.c */ /** * push out pcm frames * \param data pointer do data to be written @@ -222,6 +243,7 @@ static int alsa_write(char *data, size_t nbytes, struct writer_node *wn) return result * pad->bytes_per_frame; } +/* alsa_writer.c */ static void alsa_close(struct writer_node *wn) { struct private_alsa_data *pad = wn->private_data; @@ -231,6 +253,7 @@ static void alsa_close(struct writer_node *wn) free(pad); } +/* alsa_writer.c */ void alsa_writer_init(struct writer *w) { w->open = alsa_open; @@ -240,6 +263,57 @@ void alsa_writer_init(struct writer *w) } + + +/* file_writer.c */ + +struct private_file_writer_data { + int fd; +}; +static int file_writer_open(struct writer_node *w) +{ + struct private_file_writer_data *pfwd = para_calloc( + sizeof(struct private_file_writer_data)); + char *tmp = para_tmpname(), *home = para_homedir(), + *filename = make_message("%s/.paraslash/%s", home, tmp); + + free(home); + free(tmp); + w->private_data = pfwd; + pfwd->fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + free(filename); + if (pfwd->fd >= 0) + return 8192; + free(pfwd); + return -E_FW_OPEN; +} + +static int file_writer_write(char *data, size_t nbytes, struct writer_node *wn) +{ + struct private_file_writer_data *pfwd = wn->private_data; + int ret = write(pfwd->fd, data, nbytes); + if (ret < 0) + ret = -E_FW_WRITE; + return ret; +} + +static void file_writer_close(struct writer_node *wn) +{ + struct private_file_writer_data *pfwd = wn->private_data; + close(pfwd->fd); + free(pfwd); +} + +void file_writer_init(struct writer *w) +{ + w->open = file_writer_open; + w->write = file_writer_write; + w->close = file_writer_close; + w->shutdown = NULL; /* nothing to do */ +} + + +/* write.c */ /** * check if current time is later than start_time * \param diff pointer to write remaining time to @@ -260,6 +334,7 @@ static int start_time_in_future(struct timeval *diff) return tv_diff(start_time, &now, diff) > 0? 1 : 0; } +/* write.c */ /** * sleep until time given at command line * @@ -275,6 +350,7 @@ static void do_initial_delay(struct timeval *delay) while (start_time_in_future(delay)); } +/* write.c */ static int read_stdin(char *buf, size_t bytes_to_load, size_t *loaded) { ssize_t ret; @@ -291,6 +367,11 @@ static int read_stdin(char *buf, size_t bytes_to_load, size_t *loaded) return 1; } +/* write_common.c */ + +const char *writer_names[] ={WRITER_NAMES}; +static struct writer writers[NUM_SUPPORTED_WRITERS] = {WRITER_ARRAY}; + int wng_write(struct writer_node_group *g, char *buf, size_t *loaded) { int ret, i, need_more_writes = 1; @@ -336,6 +417,7 @@ out: return ret; } +/* write_common.c */ int wng_open(struct writer_node_group *g) { int i, ret = 1; @@ -352,6 +434,7 @@ out: return ret; } +/* write_common.c */ void wng_close(struct writer_node_group *g) { int i; @@ -362,6 +445,7 @@ void wng_close(struct writer_node_group *g) } } +/* write.c */ /** * play raw pcm data * \param loaded number of bytes already loaded @@ -415,6 +499,7 @@ out: return ret; } +/* writer_node.c */ struct writer_node_group *wng_new(unsigned num_writers) { struct writer_node_group *g = para_calloc(sizeof(struct writer_node_group)); @@ -425,6 +510,7 @@ struct writer_node_group *wng_new(unsigned num_writers) return g; } +/* writer_node.c */ void wng_destroy(struct writer_node_group *g) { if (!g) @@ -434,6 +520,100 @@ void wng_destroy(struct writer_node_group *g) free(g); } +void init_supported_writers(void) +{ + int i; + + FOR_EACH_WRITER(i) + writers[i].init(&writers[i]); +} + +int check_writer_arg(const char *arg) +{ + int i, ret = -E_PLAY_SYNTAX; + char *a = para_strdup(arg), *p = strchr(a, ':'); + if (p) + *p = '\0'; + p++; + FOR_EACH_WRITER(i) { + if (strcmp(writer_names[i], a)) + continue; + ret = i; + goto out; + } +out: + free(a); + return ret; +} + +struct writer_node_group *setup_default_wng(void) +{ + struct writer_node_group *wng = wng_new(1); + enum writer_enum default_writer; + + if (NUM_SUPPORTED_WRITERS == 1) + default_writer = FILE_WRITE; + else + default_writer = 1; + wng->writer_nodes[0].writer = &writers[default_writer]; + PARA_INFO_LOG("using default writer: %s\n", + writer_names[default_writer]); + return wng; +} + +/* write.c */ + +struct writer_node_group *check_args(void) +{ + int i, ret = -E_PLAY_SYNTAX; + static struct timeval tv; + struct writer_node_group *wng = NULL; + + if (conf.list_writers_given) { + char *msg = NULL; + FOR_EACH_WRITER(i) { + char *tmp = make_message("%s%s%s", + i? msg : "", + i? " " : "", + writer_names[i]); + free(msg); + msg = tmp; + } + fprintf(stderr, "%s\n", msg); + free(msg); + exit(EXIT_SUCCESS); + } + if (conf.prebuffer_arg < 0 || conf.prebuffer_arg > 100) + goto out; + if (conf.start_time_given) { + long unsigned sec, usec; + if (sscanf(conf.start_time_arg, "%lu:%lu", + &sec, &usec) != 2) + goto out; + tv.tv_sec = sec; + tv.tv_usec = usec; + start_time = &tv; + } + if (!conf.writer_given) { + wng = setup_default_wng(); + ret = 1; + goto out; + } + wng = wng_new(conf.writer_given); + for (i = 0; i < conf.writer_given; i++) { + ret = check_writer_arg(conf.writer_arg[i]); + if (ret < 0) + goto out; + wng->writer_nodes[i].writer = &writers[ret]; + } + ret = 1; +out: + if (ret > 0) + return wng; + free(wng); + return NULL; +} + /** * test if audio buffer contains a valid wave header * @@ -450,27 +630,17 @@ static size_t check_wave(void) return 0; } +/* write.c */ int main(int argc, char *argv[]) { - struct timeval tv; int ret = -E_PLAY_SYNTAX; struct writer_node_group *wng = NULL; cmdline_parser(argc, argv, &conf); - if (conf.prebuffer_arg < 0 || conf.prebuffer_arg > 100) + wng = check_args(); + if (!wng) goto out; - if (conf.start_time_given) { - if (sscanf(conf.start_time_arg, "%lu:%lu", - &tv.tv_sec, &tv.tv_usec) != 2) - goto out; - start_time = &tv; - } - /* call init for each supported writer */ - alsa_writer_init(&writers[0]); - - wng = wng_new(1); - wng->writer_nodes[0].writer = &writers[0]; /* alsa */ - + init_supported_writers(); audiobuf = para_malloc(WAV_HEADER_LEN); ret = read_wav_header(); if (ret < 0) diff --git a/play.ggo b/play.ggo index 23cd64ee..b1c80839 100644 --- a/play.ggo +++ b/play.ggo @@ -14,3 +14,20 @@ option "loglevel" l default="4" optional + +option "writer" w +#~~~~~~~~~~~~~~~ + +"select stream writer" + + string typestr="name" + default="alsa (file if alsa is unsupported)" + optional + multiple + +option "list_writers" L +#~~~~~~~~~~~~~~~~~~~~~~ +"print available writers and exit" + + flag off + optional -- 2.39.2 From 780f86d5f849308b5100b087c6b223a6deef1dd7 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 18 Apr 2006 21:53:48 +0200 Subject: [PATCH 02/16] split play.c and rename para_play to para_write This separates the code to write an audio stream into several independent pieces: write.h: definition of writer-related structures write.c: the para_write main program write_common.c: functions to be shared between para_write and para_audiod write_common.h: exported functions of write_common.c alsa_writer.c: writes the stream to an alsa sound device file_writer.c: writes the stream to a file Other writers (aka output plugins) can be added easily. --- Makefile.in | 6 +- alsa_writer.c | 177 ++++++++++++ configure.ac | 24 +- error.h | 27 +- file_writer.c | 67 +++++ play.c | 655 ------------------------------------------ write.c | 260 +++++++++++++++++ play.ggo => write.ggo | 0 write.h | 48 ++++ write_common.c | 156 ++++++++++ write_common.h | 26 ++ 11 files changed, 768 insertions(+), 678 deletions(-) create mode 100644 alsa_writer.c create mode 100644 file_writer.c delete mode 100644 play.c create mode 100644 write.c rename play.ggo => write.ggo (100%) create mode 100644 write.h create mode 100644 write_common.c create mode 100644 write_common.h diff --git a/Makefile.in b/Makefile.in index 50767941..68c60e19 100644 --- a/Makefile.in +++ b/Makefile.in @@ -56,7 +56,7 @@ CPPFLAGS += -DCODENAME='"$(codename)"' CPPFLAGS += -DCC_VERSION='"$(cc_version)"' CPPFLAGS += -Werror-implicit-function-declaration -BINARIES = para_server para_client para_gui para_audiod para_audioc para_recv para_filter $(extra_binaries) +BINARIES = para_server para_client para_gui para_audiod para_audioc para_recv para_filter para_write $(extra_binaries) FONTS := $(wildcard fonts/*.png) PICS := $(wildcard pics/paraslash/*.jpg) @@ -195,8 +195,8 @@ para_server: @server_objs@ para_sdl_gui: $(sdl_gui_objs) $(CC) -o $@ $(sdl_gui_objs) -lSDL_image -para_play: @play_objs@ - $(CC) -o $@ @play_objs@ @play_ldflags@ +para_write: @write_objs@ + $(CC) -o $@ @write_objs@ @write_ldflags@ para_compress: $(compress_objs) $(CC) -o $@ $(compress_objs) diff --git a/alsa_writer.c b/alsa_writer.c new file mode 100644 index 00000000..b0bc3b09 --- /dev/null +++ b/alsa_writer.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/* + * Based in parts on aplay.c from the alsa-utils-1.0.8 package, + * Copyright (c) by Jaroslav Kysela , which is + * based on the vplay program by Michael Beck. + */ + +#include "para.h" +#include "fd.h" +#include "string.h" +#include "write.h" + +#include + +#include "write.cmdline.h" +#include "error.h" + +extern struct gengetopt_args_info conf; + +#define FORMAT SND_PCM_FORMAT_S16_LE +struct private_alsa_data { + snd_pcm_t *handle; + size_t bytes_per_frame; +}; + +/* + * open and prepare the PCM handle for writing + * + * Install PCM software and hardware configuration. Exit on errors. + */ +static int alsa_open(struct writer_node *w) +{ + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + snd_pcm_uframes_t buffer_size, xfer_align, start_threshold, + stop_threshold; + unsigned buffer_time = 0; + int err; + snd_pcm_info_t *info; + snd_output_t *log; + snd_pcm_uframes_t period_size; + struct private_alsa_data *pad = para_malloc(sizeof(struct private_alsa_data)); + w->private_data = pad; + + snd_pcm_info_alloca(&info); + if (snd_output_stdio_attach(&log, stderr, 0) < 0) + return -E_ALSA_LOG; + err = snd_pcm_open(&pad->handle, conf.device_arg, + SND_PCM_STREAM_PLAYBACK, 0); + if (err < 0) + return -E_PCM_OPEN; + if ((err = snd_pcm_info(pad->handle, info)) < 0) + return -E_SND_PCM_INFO; + + snd_pcm_hw_params_alloca(&hwparams); + snd_pcm_sw_params_alloca(&swparams); + if (snd_pcm_hw_params_any(pad->handle, hwparams) < 0) + return -E_BROKEN_CONF; + if (snd_pcm_hw_params_set_access(pad->handle, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED) < 0) + return -E_ACCESS_TYPE; + if (snd_pcm_hw_params_set_format(pad->handle, hwparams, FORMAT) < 0) + return -E_SAMPLE_FORMAT; + if (snd_pcm_hw_params_set_channels(pad->handle, hwparams, + conf.channels_arg) < 0) + return -E_CHANNEL_COUNT; + if (snd_pcm_hw_params_set_rate_near(pad->handle, hwparams, + (unsigned int*) &conf.sample_rate_arg, 0) < 0) + return -E_SET_RATE; + err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0); + if (err < 0 || !buffer_time) + return -E_GET_BUFFER_TIME; + PARA_DEBUG_LOG("buffer time: %d\n", buffer_time); + if (snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams, + &buffer_time, 0) < 0) + return -E_SET_BUFFER_TIME; + if (snd_pcm_hw_params(pad->handle, hwparams) < 0) + return -E_HW_PARAMS; + snd_pcm_hw_params_get_period_size(hwparams, &period_size, 0); + snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); + PARA_DEBUG_LOG("buffer size: %lu, period_size: %lu\n", buffer_size, + period_size); + if (period_size == buffer_size) + return -E_BAD_PERIOD; + snd_pcm_sw_params_current(pad->handle, swparams); + err = snd_pcm_sw_params_get_xfer_align(swparams, &xfer_align); + if (err < 0 || !xfer_align) + return -E_GET_XFER; + snd_pcm_sw_params_set_avail_min(pad->handle, swparams, period_size); + /* round to closest transfer boundary */ + start_threshold = (buffer_size / xfer_align) * xfer_align; + if (start_threshold < 1) + start_threshold = 1; + if (snd_pcm_sw_params_set_start_threshold(pad->handle, swparams, + start_threshold) < 0) + return -E_START_THRESHOLD; + stop_threshold = buffer_size; + if (snd_pcm_sw_params_set_stop_threshold(pad->handle, swparams, + stop_threshold) < 0) + return -E_STOP_THRESHOLD; + if (snd_pcm_sw_params_set_xfer_align(pad->handle, swparams, + xfer_align) < 0) + return -E_SET_XFER; + if (snd_pcm_sw_params(pad->handle, swparams) < 0) + return -E_SW_PARAMS; + pad->bytes_per_frame = snd_pcm_format_physical_width(FORMAT) + * conf.channels_arg / 8; + return period_size * pad->bytes_per_frame; +} + +/** + * push out pcm frames + * \param data pointer do data to be written + * \param nbytes number of bytes (not frames) + * + * \return Number of bytes written, -E_ALSA_WRITE on errors. + */ +static int alsa_write(char *data, size_t nbytes, struct writer_node *wn) +{ + struct private_alsa_data *pad = wn->private_data; + size_t frames = nbytes / pad->bytes_per_frame; + unsigned char *d = data; + snd_pcm_sframes_t r, result = 0; + + while (frames > 0) { + /* write interleaved frames */ + r = snd_pcm_writei(pad->handle, d, frames); + if (r < 0) + PARA_ERROR_LOG("write error: %s\n", snd_strerror(r)); + if (r == -EAGAIN || (r >= 0 && r < frames)) + snd_pcm_wait(pad->handle, 1); + else if (r == -EPIPE) + snd_pcm_prepare(pad->handle); + else if (r < 0) + return -E_ALSA_WRITE; + if (r > 0) { + result += r; + frames -= r; + d += r * pad->bytes_per_frame; + } + } + return result * pad->bytes_per_frame; +} + +static void alsa_close(struct writer_node *wn) +{ + struct private_alsa_data *pad = wn->private_data; + snd_pcm_drain(pad->handle); + snd_pcm_close(pad->handle); + snd_config_update_free_global(); + free(pad); +} + +void alsa_writer_init(struct writer *w) +{ + w->open = alsa_open; + w->write = alsa_write; + w->close = alsa_close; + w->shutdown = NULL; /* nothing to do */ +} diff --git a/configure.ac b/configure.ac index bfd659f1..69846789 100644 --- a/configure.ac +++ b/configure.ac @@ -76,9 +76,9 @@ server_errlist_objs="server mp3 afs command net string signal random_selector ipc dccp dccp_send fd" server_ldflags="" -play_cmdline_objs="play.cmdline" -play_errlist_objs="play time fd string" -play_ldflags="" +write_cmdline_objs="write.cmdline" +write_errlist_objs="write write_common file_writer time fd string" +write_ldflags="" write_writers="file" @@ -220,7 +220,7 @@ else fi ########################################################################### alsa have_alsa="yes" -msg="=> no para_play" +msg="=> no alsa support for para_write" AC_CHECK_HEADERS([alsa/asoundlib.h], [], [ AC_MSG_WARN([no alsa/asoundlib $msg]) have_alsa="no" @@ -230,8 +230,8 @@ AC_CHECK_LIB([asound], [snd_pcm_open], [], [ have_alsa="no" ]) if test "$have_alsa" = "yes"; then - extras="$extras para_play" - play_ldflags="$play_ldflags -lasound" + write_errlist_objs="$write_errlist_objs alsa_writer" + write_ldflags="$write_ldflags -lasound" write_writers="$write_writers alsa" fi ########################################################################### ortp @@ -296,7 +296,7 @@ recv_objs="$recv_cmdline_objs $recv_errlist_objs" filter_objs="$filter_cmdline_objs $filter_errlist_objs" audiod_objs="$audiod_cmdline_objs $audiod_errlist_objs" server_objs="$server_cmdline_objs $server_errlist_objs" -play_objs="$play_cmdline_objs $play_errlist_objs" +write_objs="$write_cmdline_objs $write_errlist_objs" AC_SUBST(recv_objs, add_dot_o($recv_objs)) AC_SUBST(recv_ldflags, $recv_ldflags) @@ -318,10 +318,10 @@ AC_SUBST(server_ldflags, $server_ldflags) AC_DEFINE_UNQUOTED(INIT_SERVER_ERRLISTS, objlist_to_errlist($server_errlist_objs), errors used by para_server) -AC_SUBST(play_objs, add_dot_o($play_objs)) -AC_SUBST(play_ldflags, $play_ldflags) -AC_DEFINE_UNQUOTED(INIT_PLAY_ERRLISTS, - objlist_to_errlist($play_errlist_objs), errors used by para_play) +AC_SUBST(write_objs, add_dot_o($write_objs)) +AC_SUBST(write_ldflags, $write_ldflags) +AC_DEFINE_UNQUOTED(INIT_WRITE_ERRLISTS, + objlist_to_errlist($write_errlist_objs), errors used by para_write) enum="$(for i in $write_writers; do printf "${i}_WRITE, " | tr '[a-z]' '[A-Z]'; done)" AC_DEFINE_UNQUOTED(WRITER_ENUM, $enum NUM_SUPPORTED_WRITERS, @@ -352,5 +352,5 @@ ogg vorbis support: $have_ogg mp3dec support (libmad): $have_mad ortp support: $have_ortp unix socket credentials: $have_ucred -alsa support (para_play): $have_alsa +supported writers for para_write: $write_writers ]) diff --git a/error.h b/error.h index 81691667..237978eb 100644 --- a/error.h +++ b/error.h @@ -58,8 +58,10 @@ enum para_subsystem { SS_DCCP_SEND, SS_FD, SS_GUI, - SS_PLAY, - SS_ALSA, + SS_WRITE, + SS_WRITE_COMMON, + SS_ALSA_WRITER, + SS_FILE_WRITER, SS_RINGBUFFER}; #define NUM_SS (SS_RINGBUFFER + 1) @@ -297,13 +299,16 @@ extern const char **para_errlist[]; PARA_ERROR(F_SETFL, "failed to set fd flags"), \ -#define PLAY_ERRORS \ +#define WRITE_ERRORS \ PARA_ERROR(READ_HDR, "failed to read audio file header"), \ PARA_ERROR(READ_STDIN, "failed to read from stdin"), \ - PARA_ERROR(PLAY_SYNTAX, "syntax error"), \ - PARA_ERROR(PLAY_OVERRUN, "buffer overrun"), \ + PARA_ERROR(WRITE_SYNTAX, "para_write syntax error"), \ + PARA_ERROR(WRITE_OVERRUN, "buffer overrun"), \ PARA_ERROR(LIST_WRITERS_GIVEN, ""), \ PARA_ERROR(PREMATURE_END, "premature end of audio file"), \ + + +#define ALSA_WRITER_ERRORS \ PARA_ERROR(BROKEN_CONF, "Broken alsa configuration"), \ PARA_ERROR(ACCESS_TYPE, "alsa access type not available"), \ PARA_ERROR(SAMPLE_FORMAT, "sample format not available"), \ @@ -322,13 +327,17 @@ extern const char **para_errlist[]; PARA_ERROR(START_THRESHOLD, "snd_pcm_sw_params_set_start_threshold() failed"), \ PARA_ERROR(STOP_THRESHOLD, "snd_pcm_sw_params_set_stop_threshold() failed"), \ PARA_ERROR(ALSA_LOG, "snd_output_stdio_attach() failed"), \ + + +#define FILE_WRITER_ERRORS \ PARA_ERROR(FW_WRITE, "file writer write error"), \ PARA_ERROR(FW_OPEN, "file writer: can not open output file"), \ +#define WRITE_COMMON_ERRORS \ + PARA_ERROR(WRITE_COMMON_SYNTAX, "syntax error in write option"), \ /* these do not need error handling (yet) */ -#define ALSA_ERRORS #define SERVER_ERRORS #define WAV_ERRORS #define COMPRESS_ERRORS @@ -448,8 +457,10 @@ SS_ENUM(DCCP_RECV); SS_ENUM(DCCP_SEND); SS_ENUM(FD); SS_ENUM(GUI); -SS_ENUM(PLAY); -SS_ENUM(ALSA); +SS_ENUM(WRITE); +SS_ENUM(WRITE_COMMON); +SS_ENUM(ALSA_WRITER); +SS_ENUM(FILE_WRITER); SS_ENUM(RINGBUFFER); /** \endcond */ #undef PARA_ERROR diff --git a/file_writer.c b/file_writer.c new file mode 100644 index 00000000..7878cce1 --- /dev/null +++ b/file_writer.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +#include "para.h" +#include "write.h" +#include "string.h" +#include "error.h" + +struct private_file_writer_data { + int fd; +}; +static int file_writer_open(struct writer_node *w) +{ + struct private_file_writer_data *pfwd = para_calloc( + sizeof(struct private_file_writer_data)); + char *tmp = para_tmpname(), *home = para_homedir(), + *filename = make_message("%s/.paraslash/%s", home, tmp); + + free(home); + free(tmp); + w->private_data = pfwd; + pfwd->fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + free(filename); + if (pfwd->fd >= 0) + return 8192; + free(pfwd); + return -E_FW_OPEN; +} + +static int file_writer_write(char *data, size_t nbytes, struct writer_node *wn) +{ + struct private_file_writer_data *pfwd = wn->private_data; + int ret = write(pfwd->fd, data, nbytes); + if (ret < 0) + ret = -E_FW_WRITE; + return ret; +} + +static void file_writer_close(struct writer_node *wn) +{ + struct private_file_writer_data *pfwd = wn->private_data; + close(pfwd->fd); + free(pfwd); +} + +void file_writer_init(struct writer *w) +{ + w->open = file_writer_open; + w->write = file_writer_write; + w->close = file_writer_close; + w->shutdown = NULL; /* nothing to do */ +} diff --git a/play.c b/play.c deleted file mode 100644 index e1fdb2ef..00000000 --- a/play.c +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Copyright (C) 2005-2006 Andre Noll - * - * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. - */ - -/* - * Based in parts on aplay.c from the alsa-utils-1.0.8 package, - * Copyright (c) by Jaroslav Kysela , which is - * based on the vplay program by Michael Beck. - */ - -#define WAV_HEADER_LEN 44 -#include /* gettimeofday */ -#include "para.h" -#include "fd.h" -#include "play.cmdline.h" -#include -#include "string.h" -#include "error.h" - -/* -files: -~~~~~~ -write.c -write.h wr -write_common.c -write_common.h: decratation of the wng funcs -alsa_writer.c -*/ - -/* write.h */ -enum writer_enum {WRITER_ENUM}; - -/* write.h */ -struct writer_node { - struct writer *writer; - void *private_data; - int chunk_bytes; -}; - -/* write.h */ -struct writer { - void (*init)(struct writer *w); - int (*open)(struct writer_node *); - int (*write)(char *data, size_t nbytes, struct writer_node *); - void (*close)(struct writer_node *); - void (*shutdown)(struct writer_node *); -}; - -/* write.h */ -struct writer_node_group { - unsigned num_writers; - struct writer_node *writer_nodes; - int *written; - size_t max_chunk_bytes; - int eof; -}; - -/* write.h */ -#define FOR_EACH_WRITER_NODE(i, wng) for (i = 0; i < (wng)->num_writers; i++) -#define FOR_EACH_WRITER(i) for (i = 0; i < NUM_SUPPORTED_WRITERS; i++) - -DECLARE_WRITER_INITS; - - -/* write.c */ -static unsigned char *audiobuf; -static struct timeval *start_time; -static struct gengetopt_args_info conf; - -/* write.c */ -INIT_PLAY_ERRLISTS; - -/* write.c */ -void para_log(int ll, const char* fmt,...) -{ - va_list argp; - - if (ll < conf.loglevel_arg) - return; - va_start(argp, fmt); - vfprintf(stderr, fmt, argp); - va_end(argp); -} - -/* write.c */ -/** - * read WAV_HEADER_LEN bytes from stdin to audio buffer - * - * \return -E_READ_HDR on errors and on eof before WAV_HEADER_LEN could be - * read. A positive return value indicates success. - */ -static int read_wav_header(void) -{ - ssize_t ret, count = 0; - - while (count < WAV_HEADER_LEN) { - ret = read(STDIN_FILENO, audiobuf + count, WAV_HEADER_LEN - count); - if (ret <= 0) - return -E_READ_HDR; - count += ret; - } - return 1; -} - -/* alsa_writer.c */ -#define FORMAT SND_PCM_FORMAT_S16_LE -struct private_alsa_data { - snd_pcm_t *handle; - size_t bytes_per_frame; -}; - -/* - * open and prepare the PCM handle for writing - * - * Install PCM software and hardware configuration. Exit on errors. - */ -static int alsa_open(struct writer_node *w) -{ - snd_pcm_hw_params_t *hwparams; - snd_pcm_sw_params_t *swparams; - snd_pcm_uframes_t buffer_size, xfer_align, start_threshold, - stop_threshold; - unsigned buffer_time = 0; - int err; - snd_pcm_info_t *info; - snd_output_t *log; - snd_pcm_uframes_t period_size; - struct private_alsa_data *pad = para_malloc(sizeof(struct private_alsa_data)); - w->private_data = pad; - - snd_pcm_info_alloca(&info); - if (snd_output_stdio_attach(&log, stderr, 0) < 0) - return -E_ALSA_LOG; - err = snd_pcm_open(&pad->handle, conf.device_arg, - SND_PCM_STREAM_PLAYBACK, 0); - if (err < 0) - return -E_PCM_OPEN; - if ((err = snd_pcm_info(pad->handle, info)) < 0) - return -E_SND_PCM_INFO; - - snd_pcm_hw_params_alloca(&hwparams); - snd_pcm_sw_params_alloca(&swparams); - if (snd_pcm_hw_params_any(pad->handle, hwparams) < 0) - return -E_BROKEN_CONF; - if (snd_pcm_hw_params_set_access(pad->handle, hwparams, - SND_PCM_ACCESS_RW_INTERLEAVED) < 0) - return -E_ACCESS_TYPE; - if (snd_pcm_hw_params_set_format(pad->handle, hwparams, FORMAT) < 0) - return -E_SAMPLE_FORMAT; - if (snd_pcm_hw_params_set_channels(pad->handle, hwparams, - conf.channels_arg) < 0) - return -E_CHANNEL_COUNT; - if (snd_pcm_hw_params_set_rate_near(pad->handle, hwparams, - (unsigned int*) &conf.sample_rate_arg, 0) < 0) - return -E_SET_RATE; - err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0); - if (err < 0 || !buffer_time) - return -E_GET_BUFFER_TIME; - PARA_DEBUG_LOG("buffer time: %d\n", buffer_time); - if (snd_pcm_hw_params_set_buffer_time_near(pad->handle, hwparams, - &buffer_time, 0) < 0) - return -E_SET_BUFFER_TIME; - if (snd_pcm_hw_params(pad->handle, hwparams) < 0) - return -E_HW_PARAMS; - snd_pcm_hw_params_get_period_size(hwparams, &period_size, 0); - snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); - PARA_DEBUG_LOG("buffer size: %lu, period_size: %lu\n", buffer_size, - period_size); - if (period_size == buffer_size) - return -E_BAD_PERIOD; - snd_pcm_sw_params_current(pad->handle, swparams); - err = snd_pcm_sw_params_get_xfer_align(swparams, &xfer_align); - if (err < 0 || !xfer_align) - return -E_GET_XFER; - snd_pcm_sw_params_set_avail_min(pad->handle, swparams, period_size); - /* round to closest transfer boundary */ - start_threshold = (buffer_size / xfer_align) * xfer_align; - if (start_threshold < 1) - start_threshold = 1; - if (snd_pcm_sw_params_set_start_threshold(pad->handle, swparams, - start_threshold) < 0) - return -E_START_THRESHOLD; - stop_threshold = buffer_size; - if (snd_pcm_sw_params_set_stop_threshold(pad->handle, swparams, - stop_threshold) < 0) - return -E_STOP_THRESHOLD; - if (snd_pcm_sw_params_set_xfer_align(pad->handle, swparams, - xfer_align) < 0) - return -E_SET_XFER; - if (snd_pcm_sw_params(pad->handle, swparams) < 0) - return -E_SW_PARAMS; - pad->bytes_per_frame = snd_pcm_format_physical_width(FORMAT) - * conf.channels_arg / 8; - return period_size * pad->bytes_per_frame; -} - -/* alsa_writer.c */ -/** - * push out pcm frames - * \param data pointer do data to be written - * \param nbytes number of bytes (not frames) - * - * \return Number of bytes written, -E_ALSA_WRITE on errors. - */ -static int alsa_write(char *data, size_t nbytes, struct writer_node *wn) -{ - struct private_alsa_data *pad = wn->private_data; - size_t frames = nbytes / pad->bytes_per_frame; - unsigned char *d = data; - snd_pcm_sframes_t r, result = 0; - - while (frames > 0) { - /* write interleaved frames */ - r = snd_pcm_writei(pad->handle, d, frames); - if (r < 0) - PARA_ERROR_LOG("write error: %s\n", snd_strerror(r)); - if (r == -EAGAIN || (r >= 0 && r < frames)) - snd_pcm_wait(pad->handle, 1); - else if (r == -EPIPE) - snd_pcm_prepare(pad->handle); - else if (r < 0) - return -E_ALSA_WRITE; - if (r > 0) { - result += r; - frames -= r; - d += r * pad->bytes_per_frame; - } - } - return result * pad->bytes_per_frame; -} - -/* alsa_writer.c */ -static void alsa_close(struct writer_node *wn) -{ - struct private_alsa_data *pad = wn->private_data; - snd_pcm_drain(pad->handle); - snd_pcm_close(pad->handle); - snd_config_update_free_global(); - free(pad); -} - -/* alsa_writer.c */ -void alsa_writer_init(struct writer *w) -{ - w->open = alsa_open; - w->write = alsa_write; - w->close = alsa_close; - w->shutdown = NULL; /* nothing to do */ -} - - - - -/* file_writer.c */ - -struct private_file_writer_data { - int fd; -}; -static int file_writer_open(struct writer_node *w) -{ - struct private_file_writer_data *pfwd = para_calloc( - sizeof(struct private_file_writer_data)); - char *tmp = para_tmpname(), *home = para_homedir(), - *filename = make_message("%s/.paraslash/%s", home, tmp); - - free(home); - free(tmp); - w->private_data = pfwd; - pfwd->fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); - free(filename); - if (pfwd->fd >= 0) - return 8192; - free(pfwd); - return -E_FW_OPEN; -} - -static int file_writer_write(char *data, size_t nbytes, struct writer_node *wn) -{ - struct private_file_writer_data *pfwd = wn->private_data; - int ret = write(pfwd->fd, data, nbytes); - if (ret < 0) - ret = -E_FW_WRITE; - return ret; -} - -static void file_writer_close(struct writer_node *wn) -{ - struct private_file_writer_data *pfwd = wn->private_data; - close(pfwd->fd); - free(pfwd); -} - -void file_writer_init(struct writer *w) -{ - w->open = file_writer_open; - w->write = file_writer_write; - w->close = file_writer_close; - w->shutdown = NULL; /* nothing to do */ -} - - -/* write.c */ -/** - * check if current time is later than start_time - * \param diff pointer to write remaining time to - * - * If start_time was not given, or current time is later than given - * start_time, return 0. Otherwise, return 1 and write the time - * difference between current time and start_time to diff. diff may be - * NULL. - * - */ -static int start_time_in_future(struct timeval *diff) -{ - struct timeval now; - - if (!conf.start_time_given) - return 0; - gettimeofday(&now, NULL); - return tv_diff(start_time, &now, diff) > 0? 1 : 0; -} - -/* write.c */ -/** - * sleep until time given at command line - * - * This is called if the initial buffer is filled. It returns - * immediately if no start_time was given at the command line - * or if the given start time is in the past. - * - */ -static void do_initial_delay(struct timeval *delay) -{ - do - para_select(1, NULL, NULL, delay); - while (start_time_in_future(delay)); -} - -/* write.c */ -static int read_stdin(char *buf, size_t bytes_to_load, size_t *loaded) -{ - ssize_t ret; - - while (*loaded < bytes_to_load) { - ret = read(STDIN_FILENO, buf + *loaded, bytes_to_load - *loaded); - if (ret <= 0) { - if (ret < 0) - ret = -E_READ_STDIN; - return ret; - } - *loaded += ret; - } - return 1; -} - -/* write_common.c */ - -const char *writer_names[] ={WRITER_NAMES}; -static struct writer writers[NUM_SUPPORTED_WRITERS] = {WRITER_ARRAY}; - -int wng_write(struct writer_node_group *g, char *buf, size_t *loaded) -{ - int ret, i, need_more_writes = 1; - size_t min_written = 0; - - while (need_more_writes) { - need_more_writes = 0; - FOR_EACH_WRITER_NODE(i, g) { - size_t w = g->written[i]; - unsigned char *p = buf + w; - int bytes_to_write; - struct writer_node *wn = &g->writer_nodes[i]; - if (!i) - min_written = w; - else - min_written = PARA_MIN(min_written, w); - if (w == *loaded) - continue; - if (!g->eof && (*loaded < wn->chunk_bytes + w)) - continue; - bytes_to_write = PARA_MIN(wn->chunk_bytes, - *loaded - w); - ret = wn->writer->write(p, bytes_to_write, wn); - if (ret < 0) - goto out; - if (ret != bytes_to_write) - PARA_WARNING_LOG("short write: %d/%d\n", ret, - bytes_to_write); - g->written[i] += ret; - need_more_writes = 1; - } - } - *loaded -= min_written; - ret = 0; - if (g->eof) - goto out; - if (*loaded) - memmove(buf, buf + min_written, *loaded); - FOR_EACH_WRITER_NODE(i, g) - g->written[i] -= min_written; - ret = 1; -out: - return ret; -} - -/* write_common.c */ -int wng_open(struct writer_node_group *g) -{ - int i, ret = 1; - - FOR_EACH_WRITER_NODE(i, g) { - struct writer_node *wn = &g->writer_nodes[i]; - ret = wn->writer->open(wn); - if (ret < 0) - goto out; - wn->chunk_bytes = ret; - g->max_chunk_bytes = PARA_MAX(g->max_chunk_bytes, ret); - } -out: - return ret; -} - -/* write_common.c */ -void wng_close(struct writer_node_group *g) -{ - int i; - - FOR_EACH_WRITER_NODE(i, g) { - struct writer_node *wn = &g->writer_nodes[i]; - wn->writer->close(wn); - } -} - -/* write.c */ -/** - * play raw pcm data - * \param loaded number of bytes already loaded - * - * If start_time was given, prebuffer data until buffer is full or - * start_time is reached. In any case, do not start playing before - * start_time. - * - * \return positive on success, negative on errors. - */ -static int pcm_write(struct writer_node_group *wng, size_t loaded) -{ - size_t bufsize, prebuf_size, bytes_to_load; - struct timeval delay; - int ret, not_yet_started = 1; - - ret = wng_open(wng); - if (ret < 0) - goto out; - PARA_INFO_LOG("max chunk_bytes: %d\n", wng->max_chunk_bytes); - bufsize = (conf.bufsize_arg * 1024 / wng->max_chunk_bytes) - * wng->max_chunk_bytes; - audiobuf = para_realloc(audiobuf, bufsize); - prebuf_size = conf.prebuffer_arg * bufsize / 100; - bytes_to_load = PARA_MAX(prebuf_size, wng->max_chunk_bytes); - ret = read_stdin(audiobuf, bytes_to_load, &loaded); - if (ret <= 0 || loaded < bytes_to_load) { - if (ret >= 0) - ret = -E_PREMATURE_END; - goto out; - } - if (not_yet_started && start_time && start_time_in_future(&delay)) - do_initial_delay(&delay); - not_yet_started = 0; -again: - ret = wng_write(wng, audiobuf, &loaded); - if (ret <= 0) - goto out; - ret = -E_PLAY_OVERRUN; - if (loaded >= bufsize) - goto out; - bytes_to_load = PARA_MIN(wng->max_chunk_bytes, bufsize); - ret = read_stdin(audiobuf, bytes_to_load, &loaded); - if (ret < 0) - goto out; - if (!ret) - wng->eof = 1; - goto again; -out: - wng_close(wng); - return ret; -} - -/* writer_node.c */ -struct writer_node_group *wng_new(unsigned num_writers) -{ - struct writer_node_group *g = para_calloc(sizeof(struct writer_node_group)); - g->num_writers = num_writers; - g->writer_nodes = para_calloc(num_writers - * sizeof(struct writer_node)); - g->written = para_calloc(num_writers * sizeof(size_t)); - return g; -} - -/* writer_node.c */ -void wng_destroy(struct writer_node_group *g) -{ - if (!g) - return; - free(g->written); - free(g->writer_nodes); - free(g); -} - -void init_supported_writers(void) -{ - int i; - - FOR_EACH_WRITER(i) - writers[i].init(&writers[i]); -} - -int check_writer_arg(const char *arg) -{ - int i, ret = -E_PLAY_SYNTAX; - char *a = para_strdup(arg), *p = strchr(a, ':'); - if (p) - *p = '\0'; - p++; - FOR_EACH_WRITER(i) { - if (strcmp(writer_names[i], a)) - continue; - ret = i; - goto out; - } -out: - free(a); - return ret; -} - -struct writer_node_group *setup_default_wng(void) -{ - struct writer_node_group *wng = wng_new(1); - enum writer_enum default_writer; - - if (NUM_SUPPORTED_WRITERS == 1) - default_writer = FILE_WRITE; - else - default_writer = 1; - wng->writer_nodes[0].writer = &writers[default_writer]; - PARA_INFO_LOG("using default writer: %s\n", - writer_names[default_writer]); - return wng; -} - -/* write.c */ - -struct writer_node_group *check_args(void) -{ - int i, ret = -E_PLAY_SYNTAX; - static struct timeval tv; - struct writer_node_group *wng = NULL; - - if (conf.list_writers_given) { - char *msg = NULL; - FOR_EACH_WRITER(i) { - char *tmp = make_message("%s%s%s", - i? msg : "", - i? " " : "", - writer_names[i]); - free(msg); - msg = tmp; - } - fprintf(stderr, "%s\n", msg); - free(msg); - exit(EXIT_SUCCESS); - } - if (conf.prebuffer_arg < 0 || conf.prebuffer_arg > 100) - goto out; - if (conf.start_time_given) { - long unsigned sec, usec; - if (sscanf(conf.start_time_arg, "%lu:%lu", - &sec, &usec) != 2) - goto out; - tv.tv_sec = sec; - tv.tv_usec = usec; - start_time = &tv; - } - if (!conf.writer_given) { - wng = setup_default_wng(); - ret = 1; - goto out; - } - wng = wng_new(conf.writer_given); - for (i = 0; i < conf.writer_given; i++) { - ret = check_writer_arg(conf.writer_arg[i]); - if (ret < 0) - goto out; - wng->writer_nodes[i].writer = &writers[ret]; - } - ret = 1; -out: - if (ret > 0) - return wng; - free(wng); - return NULL; -} - -/** - * test if audio buffer contains a valid wave header - * - * \return If not, return 0, otherwise, store number of channels and sample rate - * in struct conf and return WAV_HEADER_LEN. - */ -static size_t check_wave(void) -{ - unsigned char *a = audiobuf; - if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F') - return WAV_HEADER_LEN; - conf.channels_arg = (unsigned) a[22]; - conf.sample_rate_arg = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24); - return 0; -} - -/* write.c */ -int main(int argc, char *argv[]) -{ - int ret = -E_PLAY_SYNTAX; - struct writer_node_group *wng = NULL; - - cmdline_parser(argc, argv, &conf); - wng = check_args(); - if (!wng) - goto out; - init_supported_writers(); - audiobuf = para_malloc(WAV_HEADER_LEN); - ret = read_wav_header(); - if (ret < 0) - goto out; - ret = pcm_write(wng, check_wave()); -out: - wng_destroy(wng); - free(audiobuf); - if (ret < 0) - PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); - return ret; -} diff --git a/write.c b/write.c new file mode 100644 index 00000000..5e8c3195 --- /dev/null +++ b/write.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2005-2006 Andre Noll + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +#include "para.h" +#include "string.h" +#include "write.cmdline.h" +#include "write.h" +#include "write_common.h" +#include "fd.h" + +#include /* gettimeofday */ + +#include "error.h" + +#define WAV_HEADER_LEN 44 + +static unsigned char *audiobuf; +static struct timeval *start_time; +struct gengetopt_args_info conf; + +INIT_WRITE_ERRLISTS; + +void para_log(int ll, const char* fmt,...) +{ + va_list argp; + + if (ll < conf.loglevel_arg) + return; + va_start(argp, fmt); + vfprintf(stderr, fmt, argp); + va_end(argp); +} + +/** + * read WAV_HEADER_LEN bytes from stdin to audio buffer + * + * \return -E_READ_HDR on errors and on eof before WAV_HEADER_LEN could be + * read. A positive return value indicates success. + */ +static int read_wav_header(void) +{ + ssize_t ret, count = 0; + + while (count < WAV_HEADER_LEN) { + ret = read(STDIN_FILENO, audiobuf + count, WAV_HEADER_LEN - count); + if (ret <= 0) + return -E_READ_HDR; + count += ret; + } + return 1; +} + +/** + * check if current time is later than start_time + * \param diff pointer to write remaining time to + * + * If start_time was not given, or current time is later than given + * start_time, return 0. Otherwise, return 1 and write the time + * difference between current time and start_time to diff. diff may be + * NULL. + * + */ +static int start_time_in_future(struct timeval *diff) +{ + struct timeval now; + + if (!conf.start_time_given) + return 0; + gettimeofday(&now, NULL); + return tv_diff(start_time, &now, diff) > 0? 1 : 0; +} + +/** + * sleep until time given at command line + * + * This is called if the initial buffer is filled. It returns + * immediately if no start_time was given at the command line + * or if the given start time is in the past. + * + */ +static void do_initial_delay(struct timeval *delay) +{ + do + para_select(1, NULL, NULL, delay); + while (start_time_in_future(delay)); +} + +static int read_stdin(char *buf, size_t bytes_to_load, size_t *loaded) +{ + ssize_t ret; + + while (*loaded < bytes_to_load) { + ret = read(STDIN_FILENO, buf + *loaded, bytes_to_load - *loaded); + if (ret <= 0) { + if (ret < 0) + ret = -E_READ_STDIN; + return ret; + } + *loaded += ret; + } + return 1; +} +/** + * play raw pcm data + * \param loaded number of bytes already loaded + * + * If start_time was given, prebuffer data until buffer is full or + * start_time is reached. In any case, do not start playing before + * start_time. + * + * \return positive on success, negative on errors. + */ +static int pcm_write(struct writer_node_group *wng, size_t loaded) +{ + size_t bufsize, prebuf_size, bytes_to_load; + struct timeval delay; + int ret, not_yet_started = 1; + + ret = wng_open(wng); + if (ret < 0) + goto out; + PARA_INFO_LOG("max chunk_bytes: %d\n", wng->max_chunk_bytes); + bufsize = (conf.bufsize_arg * 1024 / wng->max_chunk_bytes) + * wng->max_chunk_bytes; + audiobuf = para_realloc(audiobuf, bufsize); + prebuf_size = conf.prebuffer_arg * bufsize / 100; + bytes_to_load = PARA_MAX(prebuf_size, wng->max_chunk_bytes); + ret = read_stdin(audiobuf, bytes_to_load, &loaded); + if (ret <= 0 || loaded < bytes_to_load) { + if (ret >= 0) + ret = -E_PREMATURE_END; + goto out; + } + if (not_yet_started && start_time && start_time_in_future(&delay)) + do_initial_delay(&delay); + not_yet_started = 0; +again: + ret = wng_write(wng, audiobuf, &loaded); + if (ret <= 0) + goto out; + ret = -E_WRITE_OVERRUN; + if (loaded >= bufsize) + goto out; + bytes_to_load = PARA_MIN(wng->max_chunk_bytes, bufsize); + ret = read_stdin(audiobuf, bytes_to_load, &loaded); + if (ret < 0) + goto out; + if (!ret) + wng->eof = 1; + goto again; +out: + wng_close(wng); + return ret; +} +/* write.c */ + +struct writer_node_group *check_args(void) +{ + int i, ret = -E_WRITE_SYNTAX; + static struct timeval tv; + struct writer_node_group *wng = NULL; + + if (conf.list_writers_given) { + char *msg = NULL; + FOR_EACH_WRITER(i) { + char *tmp = make_message("%s%s%s", + i? msg : "", + i? " " : "", + writer_names[i]); + free(msg); + msg = tmp; + } + fprintf(stderr, "%s\n", msg); + free(msg); + exit(EXIT_SUCCESS); + } + if (conf.prebuffer_arg < 0 || conf.prebuffer_arg > 100) + goto out; + if (conf.start_time_given) { + long unsigned sec, usec; + if (sscanf(conf.start_time_arg, "%lu:%lu", + &sec, &usec) != 2) + goto out; + tv.tv_sec = sec; + tv.tv_usec = usec; + start_time = &tv; + } + if (!conf.writer_given) { + wng = setup_default_wng(); + ret = 1; + goto out; + } + wng = wng_new(conf.writer_given); + for (i = 0; i < conf.writer_given; i++) { + ret = check_writer_arg(conf.writer_arg[i]); + if (ret < 0) + goto out; + wng->writer_nodes[i].writer = &writers[ret]; + } + ret = 1; +out: + if (ret > 0) + return wng; + free(wng); + return NULL; +} + +/** + * test if audio buffer contains a valid wave header + * + * \return If not, return 0, otherwise, store number of channels and sample rate + * in struct conf and return WAV_HEADER_LEN. + */ +static size_t check_wave(void) +{ + unsigned char *a = audiobuf; + if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F') + return WAV_HEADER_LEN; + conf.channels_arg = (unsigned) a[22]; + conf.sample_rate_arg = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24); + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = -E_WRITE_SYNTAX; + struct writer_node_group *wng = NULL; + + cmdline_parser(argc, argv, &conf); + wng = check_args(); + if (!wng) + goto out; + init_supported_writers(); + audiobuf = para_malloc(WAV_HEADER_LEN); + ret = read_wav_header(); + if (ret < 0) + goto out; + ret = pcm_write(wng, check_wave()); +out: + wng_destroy(wng); + free(audiobuf); + if (ret < 0) + PARA_ERROR_LOG("%s\n", PARA_STRERROR(-ret)); + return ret; +} diff --git a/play.ggo b/write.ggo similarity index 100% rename from play.ggo rename to write.ggo diff --git a/write.h b/write.h new file mode 100644 index 00000000..68730bf1 --- /dev/null +++ b/write.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +enum writer_enum {WRITER_ENUM}; + +struct writer_node { + struct writer *writer; + void *private_data; + int chunk_bytes; +}; + +struct writer { + void (*init)(struct writer *w); + int (*open)(struct writer_node *); + int (*write)(char *data, size_t nbytes, struct writer_node *); + void (*close)(struct writer_node *); + void (*shutdown)(struct writer_node *); +}; + +struct writer_node_group { + unsigned num_writers; + struct writer_node *writer_nodes; + int *written; + size_t max_chunk_bytes; + int eof; +}; + +#define FOR_EACH_WRITER_NODE(i, wng) for (i = 0; i < (wng)->num_writers; i++) +#define FOR_EACH_WRITER(i) for (i = 0; i < NUM_SUPPORTED_WRITERS; i++) + +DECLARE_WRITER_INITS; +const char *writer_names[NUM_SUPPORTED_WRITERS]; +struct writer writers[NUM_SUPPORTED_WRITERS]; diff --git a/write_common.c b/write_common.c new file mode 100644 index 00000000..a7896fcb --- /dev/null +++ b/write_common.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +#include "para.h" +#include "string.h" +#include "write.h" +#include "error.h" + +const char *writer_names[] ={WRITER_NAMES}; +struct writer writers[NUM_SUPPORTED_WRITERS] = {WRITER_ARRAY}; + +int wng_write(struct writer_node_group *g, char *buf, size_t *loaded) +{ + int ret, i, need_more_writes = 1; + size_t min_written = 0; + + while (need_more_writes) { + need_more_writes = 0; + FOR_EACH_WRITER_NODE(i, g) { + size_t w = g->written[i]; + unsigned char *p = buf + w; + int bytes_to_write; + struct writer_node *wn = &g->writer_nodes[i]; + if (!i) + min_written = w; + else + min_written = PARA_MIN(min_written, w); + if (w == *loaded) + continue; + if (!g->eof && (*loaded < wn->chunk_bytes + w)) + continue; + bytes_to_write = PARA_MIN(wn->chunk_bytes, + *loaded - w); + ret = wn->writer->write(p, bytes_to_write, wn); + if (ret < 0) + goto out; + if (ret != bytes_to_write) + PARA_WARNING_LOG("short write: %d/%d\n", ret, + bytes_to_write); + g->written[i] += ret; + need_more_writes = 1; + } + } + *loaded -= min_written; + ret = 0; + if (g->eof) + goto out; + if (*loaded) + memmove(buf, buf + min_written, *loaded); + FOR_EACH_WRITER_NODE(i, g) + g->written[i] -= min_written; + ret = 1; +out: + return ret; +} + +int wng_open(struct writer_node_group *g) +{ + int i, ret = 1; + + FOR_EACH_WRITER_NODE(i, g) { + struct writer_node *wn = &g->writer_nodes[i]; + ret = wn->writer->open(wn); + if (ret < 0) + goto out; + wn->chunk_bytes = ret; + g->max_chunk_bytes = PARA_MAX(g->max_chunk_bytes, ret); + } +out: + return ret; +} + +void wng_close(struct writer_node_group *g) +{ + int i; + + FOR_EACH_WRITER_NODE(i, g) { + struct writer_node *wn = &g->writer_nodes[i]; + wn->writer->close(wn); + } +} + +struct writer_node_group *wng_new(unsigned num_writers) +{ + struct writer_node_group *g = para_calloc(sizeof(struct writer_node_group)); + g->num_writers = num_writers; + g->writer_nodes = para_calloc(num_writers + * sizeof(struct writer_node)); + g->written = para_calloc(num_writers * sizeof(size_t)); + return g; +} + +void wng_destroy(struct writer_node_group *g) +{ + if (!g) + return; + free(g->written); + free(g->writer_nodes); + free(g); +} + +void init_supported_writers(void) +{ + int i; + + FOR_EACH_WRITER(i) + writers[i].init(&writers[i]); +} + +int check_writer_arg(const char *arg) +{ + int i, ret = -E_WRITE_COMMON_SYNTAX; + char *a = para_strdup(arg), *p = strchr(a, ':'); + if (p) + *p = '\0'; + p++; + FOR_EACH_WRITER(i) { + if (strcmp(writer_names[i], a)) + continue; + ret = i; + goto out; + } +out: + free(a); + return ret; +} + +struct writer_node_group *setup_default_wng(void) +{ + struct writer_node_group *wng = wng_new(1); + enum writer_enum default_writer; + + if (NUM_SUPPORTED_WRITERS == 1) + default_writer = FILE_WRITE; + else + default_writer = 1; + wng->writer_nodes[0].writer = &writers[default_writer]; + PARA_INFO_LOG("using default writer: %s\n", + writer_names[default_writer]); + return wng; +} diff --git a/write_common.h b/write_common.h new file mode 100644 index 00000000..e7652e20 --- /dev/null +++ b/write_common.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +int wng_write(struct writer_node_group *g, char *buf, size_t *loaded); +int wng_open(struct writer_node_group *g); +void wng_close(struct writer_node_group *g); +struct writer_node_group *wng_new(unsigned num_writers); +void wng_destroy(struct writer_node_group *g); +void init_supported_writers(void); +int check_writer_arg(const char *arg); +struct writer_node_group *setup_default_wng(void); -- 2.39.2 From 04473f0dc30bcfa6cb0ffd3c388b64218d93f651 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 18 Apr 2006 22:16:20 +0200 Subject: [PATCH 03/16] para_play -> para_write documentation update --- FEATURES | 6 +++--- INSTALL | 22 +++++++++++----------- NEWS | 13 +++++++++++-- README | 9 +++++---- audiod.c | 2 +- audiod.ggo | 11 ++++++----- 6 files changed, 37 insertions(+), 26 deletions(-) diff --git a/FEATURES b/FEATURES index ef0b7018..7fdd2399 100644 --- a/FEATURES +++ b/FEATURES @@ -12,9 +12,9 @@ configurable audio streaming software: sent through any of paraslash's filters (mp3 decoder, ogg vorbis decoder, volume normalizer,...). The resulting stream is written to an external program's standard in, usually an - audio player like para_play, the alsa player of the paraslash - package. It is possible to capture the stream at any position - in the filter chain. + audio player like para_write, which comes with paraslash and + contains an alsa player. It is possible to capture the stream + at any position in the filter chain. The receiving/filtering software is also available as standalone command line tool: para_recv reads the stream and diff --git a/INSTALL b/INSTALL index d791cb78..f0d1ec8c 100644 --- a/INSTALL +++ b/INSTALL @@ -115,7 +115,7 @@ on client_host: mp3: - para_recv -r 'http -i server_host' | para_filter -f mp3dec -f wav | para_play + para_recv -r 'http -i server_host' | para_filter -f mp3dec -f wav | para_write -w alsa or mpg123 http://server_host:8000/ or @@ -123,7 +123,7 @@ on client_host: ogg: - para_recv -r 'http -i server_host' | para_filter -f oggdec -f wav | para_play + para_recv -r 'http -i server_host' | para_filter -f oggdec -f wav | para_write -w alsa If this works, proceede. Otherwise doublecheck what is logged by para_server and use the --loglevel option of para_recv, para_filter @@ -173,23 +173,23 @@ information in a curses window. It also allows you to bind keys to arbitrary commands. There are several flavours of key-bindings: o internal: These are the built-in commands that can not be - changed (help, quit, loglevel, version...). + changed (help, quit, loglevel, version...). - o external: Shutdown curses before launching the given command. - Useful for starting other ncurses programs from within - para_gui, e.g. aumix or para_dbadm. Or, use + o external: Shutdown curses before launching the given command. + Useful for starting other ncurses programs from within + para_gui, e.g. aumix or para_dbadm. Or, use para_client mbox - to write a mailbox containing one mail for each file - in the mysql database and start mutt from within para_gui - to browse your collection! + to write a mailbox containing one mail for each file + in the mysql database and start mutt from within para_gui + to browse your collection! o display: Launch the command and display its stdout in - para_gui's bottom window. + para_gui's bottom window. o para: Like display, but start "para_client " instead of "". + command>" instead of "". That's all, congratulations. Check out all the other optional gimmics! diff --git a/NEWS b/NEWS index 0a653313..b6d4faa5 100644 --- a/NEWS +++ b/NEWS @@ -3,11 +3,22 @@ NEWS 0.?.? (to be announced) "oriented abstraction" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Many user-visible changes in this release and lots of new +features: + o the new optional dccp sender/receiver. It uses the datagram congestion control protocol. You'll need a fairly new kernel for this. + o paraslash compiles under MacOs (thanks to Gerd Becker) + + o para_play renamed to para_write + + o modular output plugin design (writers) for para_write + + o new file_writer output plugin for para_write + o compress filter speed improvements o update to libortp-0.9.1 @@ -17,8 +28,6 @@ NEWS o para_client no longer depends on libreadline (as the code for the interactive mode was removed). - o paraslash compiles under Mac Os (thanks to Gerd Becker) - 0.2.11 (2006-03-11) "atomic duality" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/README b/README index f0b77799..9b512c60 100644 --- a/README +++ b/README @@ -27,7 +27,7 @@ It contains the following programs: All senders have the same set of commands that allow to control the access permissions of the stream. - para_server needs an "audio file selector" to work, mainly + para_server needs an "audio file selector" to work, mainly to determine which song to stream next. There are three selectors available: random, playlist and mysql. The former chooses audio files randomly and playlist can handle, well, @@ -57,9 +57,10 @@ It contains the following programs: the given filters are 'piped' together in-memory, i.e. without calling any of the read(2)/write(2)/select(2) etc. functions. -- para_play (optional) +- para_write (obligatory) - A small wav/raw player for alsa. + A modular audio stream writer. It supports a simple file + writer output plugin and an optional wav/raw player for alsa. Debian package: libasound2-dev - para_audiod (optional, but recommended): @@ -163,7 +164,7 @@ make sure to install the corresponding development package as well. If you want to stream ogg vorbis files you'll need: - libogg, libvorbis, libvorbisfile, and a command line ogg vorbis - player, e.g. para_filter or ogg123. + decoder, e.g. para_filter or ogg123. http://www.xiph.org/downloads/ diff --git a/audiod.c b/audiod.c index 462ecc8b..912b2dbf 100644 --- a/audiod.c +++ b/audiod.c @@ -696,7 +696,7 @@ static void start_stream_writer(int slot_num) if (a->write_cmd) glob = glob_cmd(a->write_cmd); if (!glob) - glob = para_strdup("para_play"); + glob = para_strdup("para_write -w alsa"); PARA_INFO_LOG("starting stream writer: %s\n", glob); open_filters(slot_num); ret = para_exec_cmdline_pid(&s->wpid, glob, fds); diff --git a/audiod.ggo b/audiod.ggo index 5e89e6a4..dd385a16 100644 --- a/audiod.ggo +++ b/audiod.ggo @@ -150,11 +150,12 @@ option "stream_write_cmd" w May be given multiple times, once for each supported audio format. Default value is -'para_play' for both mp3 and ogg. You can use -the START_TIME() macro for these commands. -Each occurence of START_TIME() gets replaced -at runtime by the stream start time announced -by para_server, plus any offsets." +'para_write -w alsa' for both mp3 and ogg. +You can use the START_TIME() macro for these +commands. Each occurence of START_TIME() +gets replaced at runtime by the stream start +time announced by para_server, plus any +offsets." string typestr="format:command" optional -- 2.39.2 From 97e74ab40f208840f2330d64f5f053aab9d5fa44 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 18 Apr 2006 22:36:37 +0200 Subject: [PATCH 04/16] reformat and improve write.ggo add more documentation for the --writer option, reorder options and move alsa-specific options to an own section. --- write.ggo | 78 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/write.ggo b/write.ggo index b1c80839..4afff0c0 100644 --- a/write.ggo +++ b/write.ggo @@ -1,33 +1,85 @@ section "general options" -option "start_time" t "start playback at given time which must be in a:b format where a denotes seconds and b denotes microseconds since the epoch" string typestr="timeval" optional -option "device" d "set PCM device" string typestr="device" default="plughw:0,0" optional -option "channels" c "number of channels (only neccessary for raw audio)" int typestr="num" default="2" optional -option "sample_rate" s "force given sample rate (only neccessary for raw audio)" int typestr="num" default="44100" optional -option "bufsize" b "input buffer size" int typestr="kilobytes" default="64" optional -option "prebuffer" p "delay playback until buffer is filled" int typestr="percent" default="100" optional +######################### + +option "list_writers" L +#~~~~~~~~~~~~~~~~~~~~~~ +"print available writers and exit" + + flag off + optional + option "loglevel" l #~~~~~~~~~~~~~~~~~~ - "set loglevel (0-6)" int typestr="level" default="4" optional +option "bufsize" b +#~~~~~~~~~~~~~~~~~ +"input buffer size" + + int typestr="kilobytes" + default="64" + optional + +option "prebuffer" p +#~~~~~~~~~~~~~~~~~~~ +"delay playback until buffer is filled" + + int typestr="percent" + default="100" + optional option "writer" w -#~~~~~~~~~~~~~~~ +#~~~~~~~~~~~~~~~~ -"select stream writer" +"select stream writer +may be give multiple times. The same writer +may be specified more than once" string typestr="name" default="alsa (file if alsa is unsupported)" optional multiple -option "list_writers" L -#~~~~~~~~~~~~~~~~~~~~~~ -"print available writers and exit" +option "start_time" t +#~~~~~~~~~~~~~~~~~~~~ +"start playback at given time which must be +in a:b format where a denotes seconds and b +denotes microseconds since the epoch" - flag off + string typestr="timeval" + optional + + +section "alsa options" +###################### + +option "device" d +#~~~~~~~~~~~~~~~~ +"set PCM device" + string typestr="device" + default="plughw:0,0" optional + +option "channels" c +#~~~~~~~~~~~~~~~~~~ +"number of channels (only neccessary for raw +audio)" + + int typestr="num" + default="2" + optional + +option "sample_rate" s +#~~~~~~~~~~~~~~~~~~~~~ + +"force given sample rate (only neccessary for +raw audio)" + + int typestr="num" + default="44100" + optional + -- 2.39.2 From 45c2535cddb21671f64dbc26a69fad824087a6ae Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 18 Apr 2006 23:43:21 +0200 Subject: [PATCH 05/16] Makefile.in: add tow missing headers for the tarball With this fix, the tarball compiles again. --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 68c60e19..7b283d94 100644 --- a/Makefile.in +++ b/Makefile.in @@ -89,7 +89,7 @@ misc := bash_completion headers := para.h server.h SFont.h crypt.h list.h http.h send.h ortp.h rc4.h \ close_on_fork.h afs.h db.h gcc-compat.h recv.h filter.h audiod.h \ grab_client.h error.h net.h ringbuffer.h daemon.h string.h ipc.h dccp.h \ - fd.h + fd.h write.h write_common.h scripts := install-sh configure autocrap := Makefile.in config.h.in configure.ac autogen.sh tarball := web/sync/doc pics fonts $(c_sources) $(sample_conf) $(headers) \ -- 2.39.2 From bebec43faf068f44fcd5eb2c67bbdb2c1db23534 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 18 Apr 2006 23:45:18 +0200 Subject: [PATCH 06/16] Quote the the strings in WRITER_NAME correctly. That fixes problems on debian. --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 69846789..5e2d2b9a 100644 --- a/configure.ac +++ b/configure.ac @@ -326,7 +326,7 @@ AC_DEFINE_UNQUOTED(INIT_WRITE_ERRLISTS, enum="$(for i in $write_writers; do printf "${i}_WRITE, " | tr '[a-z]' '[A-Z]'; done)" AC_DEFINE_UNQUOTED(WRITER_ENUM, $enum NUM_SUPPORTED_WRITERS, enum of supported writers) -names="$(for i in $write_writers; do printf '\"'$i'\", ' ; done)" +names="$(for i in $write_writers; do printf \"$i\",' ' ; done)" AC_DEFINE_UNQUOTED(WRITER_NAMES, $names, supported writer names) inits="$(for i in $write_writers; do printf 'extern void '$i'_writer_init(struct writer *); '; done)" AC_DEFINE_UNQUOTED(DECLARE_WRITER_INITS, $inits, init functions of the supported writers) -- 2.39.2 From 850791c25b352396d11a1ca658fb735d057c9e57 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 01:30:47 +0200 Subject: [PATCH 07/16] kill unused E_LIST_WRITERS_GIVEN --- error.h | 1 - 1 file changed, 1 deletion(-) diff --git a/error.h b/error.h index 237978eb..d0c3afc2 100644 --- a/error.h +++ b/error.h @@ -304,7 +304,6 @@ extern const char **para_errlist[]; PARA_ERROR(READ_STDIN, "failed to read from stdin"), \ PARA_ERROR(WRITE_SYNTAX, "para_write syntax error"), \ PARA_ERROR(WRITE_OVERRUN, "buffer overrun"), \ - PARA_ERROR(LIST_WRITERS_GIVEN, ""), \ PARA_ERROR(PREMATURE_END, "premature end of audio file"), \ -- 2.39.2 From a2471d723be9e23a50e937c3a6e71af16dcaaaa4 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 03:38:29 +0200 Subject: [PATCH 08/16] Makefile.in: kill unused target para_compress para_compress went away in paraslash-0.2.8.. --- Makefile.in | 3 --- 1 file changed, 3 deletions(-) diff --git a/Makefile.in b/Makefile.in index 7b283d94..a6d55d41 100644 --- a/Makefile.in +++ b/Makefile.in @@ -198,9 +198,6 @@ para_sdl_gui: $(sdl_gui_objs) para_write: @write_objs@ $(CC) -o $@ @write_objs@ @write_ldflags@ -para_compress: $(compress_objs) - $(CC) -o $@ $(compress_objs) - para_krell.so: $(krell_objs) $(CC) -Wall -fPIC @GTK_CFLAGS@ krell.o -o $@ @GTK_LIBS@ -shared -- 2.39.2 From 61e14be5d56d3076f727227cefd3dbf08d6e7644 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 03:57:59 +0200 Subject: [PATCH 09/16] use para_fd_set also in gui.c and in http_send.c --- gui.c | 19 ++++++------------- http_send.c | 14 +++++--------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/gui.c b/gui.c index 0babfa83..49f8f46f 100644 --- a/gui.c +++ b/gui.c @@ -886,22 +886,15 @@ repeat: /* audiod pipe */ if (audiod_pipe < 0) audiod_pipe = open_audiod_pipe(); - /* FIXME: para_fd_set */ - if (audiod_pipe >= 0) { - FD_SET(audiod_pipe, &rfds); - max_fileno = PARA_MAX(max_fileno, audiod_pipe); - } - + if (audiod_pipe >= 0) + para_fd_set(audiod_pipe, &rfds, &max_fileno); /* signal pipe */ - FD_SET(signal_pipe, &rfds); - max_fileno = PARA_MAX(max_fileno, signal_pipe); + para_fd_set(signal_pipe, &rfds, &max_fileno); /* command pipe only for COMMAND_MODE */ - if (command_pipe >= 0 && mode == COMMAND_MODE) { - FD_SET(command_pipe, &rfds); - max_fileno = PARA_MAX(max_fileno, command_pipe); - } + if (command_pipe >= 0 && mode == COMMAND_MODE) + para_fd_set(command_pipe, &rfds, &max_fileno); if (curses_active) - FD_SET(STDIN_FILENO, &rfds); + para_fd_set(STDIN_FILENO, &rfds, &max_fileno); ret = para_select(max_fileno + 1, &rfds, NULL, &tv); // PARA_DEBUG_LOG("select returned %d\n", ret); if (ret <= 0) diff --git a/http_send.c b/http_send.c index bb9534cb..285215f9 100644 --- a/http_send.c +++ b/http_send.c @@ -29,6 +29,7 @@ #include "error.h" #include "net.h" #include "string.h" +#include "fd.h" /** \cond convert sock_addr_in to ascii */ #define CLIENT_ADDR(hc) inet_ntoa((hc)->addr.sin_addr) @@ -346,7 +347,6 @@ err_out: free(hc); } -/* FIXME: use para_fdset */ static void http_pre_select(struct audio_format *af, int *max_fileno, fd_set *rfds, fd_set *wfds) { @@ -354,8 +354,7 @@ static void http_pre_select(struct audio_format *af, int *max_fileno, fd_set *rf if (server_fd < 0) return; - FD_SET(server_fd, rfds); - *max_fileno = PARA_MAX(*max_fileno, server_fd); + para_fd_set(server_fd, rfds, max_fileno); list_for_each_entry_safe(hc, tmp, &clients, node) { //PARA_DEBUG_LOG("hc %p on fd %d: status %d\n", hc, hc->fd, hc->status); hc->check_r = 0; @@ -365,21 +364,18 @@ static void http_pre_select(struct audio_format *af, int *max_fileno, fd_set *rf case HTTP_READY_TO_STREAM: break; case HTTP_CONNECTED: /* need to recv get request */ - FD_SET(hc->fd, rfds); - *max_fileno = PARA_MAX(*max_fileno, hc->fd); + para_fd_set(hc->fd, rfds, max_fileno); hc->check_r = 1; break; case HTTP_GOT_GET_REQUEST: /* need to send ok msg */ case HTTP_INVALID_GET_REQUEST: /* need to send err msg */ - FD_SET(hc->fd, wfds); - *max_fileno = PARA_MAX(*max_fileno, hc->fd); + para_fd_set(hc->fd, wfds, max_fileno); hc->check_w = 1; break; case HTTP_SENT_OK_MSG: if (!af || !afs_playing()) break; /* wait until server starts playing */ - FD_SET(hc->fd, wfds); - *max_fileno = PARA_MAX(*max_fileno, hc->fd); + para_fd_set(hc->fd, wfds, max_fileno); hc->check_w = 1; break; } -- 2.39.2 From b7cbaf0b4dc832e8277b5867505dac59bb803d44 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 04:54:20 +0200 Subject: [PATCH 10/16] NEWS: gcc-2.95 is no longer supported The admin of the single remaining system I have access to which was still running gcc-2.95 recently upgraded to gcc-3 (a version which is merely 3 years old rather than the 7 years old gcc-2.95). This means that upcoming paraslash releases will no longer be compile-tested with gcc-2.95. gcc-2.95 finally didn't manage to survive it served all well for all the time but no one really cares to whine as we all meet in some other life --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index b6d4faa5..16ad2ab4 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,9 @@ features: o para_client no longer depends on libreadline (as the code for the interactive mode was removed). + o gcc-2-95 is no longer a supported compiler. It may still + work, but it gets no more testing. + 0.2.11 (2006-03-11) "atomic duality" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- 2.39.2 From e13df9bc2a622a6df960b0f7e2648078aa6ae097 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 05:06:34 +0200 Subject: [PATCH 11/16] cosmetics --- audioc.c | 6 +++--- write.c | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/audioc.c b/audioc.c index 1fa98802..b9e7853f 100644 --- a/audioc.c +++ b/audioc.c @@ -51,7 +51,7 @@ static char *concat_args(const int argc, char * const *argv) static char *configfile_exists(void) { static char *config_file; - struct stat statbuf; + struct stat statbuf; if (!config_file) { @@ -59,7 +59,7 @@ static char *configfile_exists(void) config_file = make_message("%s/.paraslash/audioc.conf", home); free(home); } - if (!stat(config_file, &statbuf)) + if (!stat(config_file, &statbuf)) return config_file; return NULL; } @@ -137,7 +137,7 @@ int main(int argc, char *argv[]) goto out; } if (loaded < conf.bufsize_arg && FD_ISSET(fd, &rfd)) { - len = recv_bin_buffer(fd, buf + loaded, + len = recv_bin_buffer(fd, buf + loaded, conf.bufsize_arg - loaded); if (len <= 0) { ret = len < 0? -E_READ : 0; diff --git a/write.c b/write.c index 5e8c3195..3fb0ef0d 100644 --- a/write.c +++ b/write.c @@ -167,7 +167,6 @@ out: wng_close(wng); return ret; } -/* write.c */ struct writer_node_group *check_args(void) { -- 2.39.2 From 8f2cf66189cc863c3af702f78bb68596f2f3ef12 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 05:22:14 +0200 Subject: [PATCH 12/16] audioc.c, audiod.c, server.c: use para_fd_set() --- audioc.c | 9 +++------ audiod.c | 3 +-- server.c | 11 +++++------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/audioc.c b/audioc.c index b9e7853f..7d472a6d 100644 --- a/audioc.c +++ b/audioc.c @@ -119,13 +119,10 @@ int main(int argc, char *argv[]) FD_ZERO(&wfd); if (loaded && loaded > 10000) fprintf(stderr, "loaded: %d\n", loaded); - if (loaded < conf.bufsize_arg) { - FD_SET(fd, &rfd); - max_fileno = PARA_MAX(max_fileno, fd); - } + if (loaded < conf.bufsize_arg) + para_fd_set(fd, &rfd, &max_fileno); if (loaded > 0) { - FD_SET(STDOUT_FILENO, &wfd); - max_fileno = PARA_MAX(max_fileno, STDOUT_FILENO); + para_fd_set(STDOUT_FILENO, &wfd, &max_fileno); check_write = 1; } ret = -E_OVERRUN; diff --git a/audiod.c b/audiod.c index 912b2dbf..61711dc3 100644 --- a/audiod.c +++ b/audiod.c @@ -1562,8 +1562,7 @@ repeat: FD_ZERO(&wfds); FD_ZERO(&rfds); /* always check signal pipe and the local socket */ - FD_SET(signal_pipe, &rfds); - max_fileno = signal_pipe; + para_fd_set(signal_pipe, &rfds, &max_fileno); para_fd_set(audiod_socket, &rfds, &max_fileno); if (audiod_status != AUDIOD_ON) diff --git a/server.c b/server.c index e10c1e03..6f70d977 100644 --- a/server.c +++ b/server.c @@ -63,6 +63,7 @@ extern void http_send_init(struct sender *); extern void ortp_send_init(struct sender *); extern struct audio_format afl[]; +/* TODO: This is better handled by autoconf */ /** the list of supported audio file selectors */ struct audio_file_selector selectors[] = { { @@ -436,14 +437,12 @@ int main(int argc, char *argv[]) valid_fd_012(); sockfd = do_inits(argc, argv); repeat: - /* check socket and signal pipe in any case */ FD_ZERO(&rfds); FD_ZERO(&wfds); - FD_SET(sockfd, &rfds); - max_fileno = sockfd; - FD_SET(signal_pipe, &rfds); - max_fileno = PARA_MAX(max_fileno, signal_pipe); - + max_fileno = -1; + /* check socket and signal pipe in any case */ + para_fd_set(sockfd, &rfds, &max_fileno); + para_fd_set(signal_pipe, &rfds, &max_fileno); timeout = afs_preselect(); status_refresh(); for (i = 0; senders[i].name; i++) { -- 2.39.2 From 9091f57db37adb226c3798bf7d53b94b03c171d6 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 17:12:19 +0200 Subject: [PATCH 13/16] README updates Update info on required gcc version in README, small README.mysql update. --- README | 5 +---- README.mysql | 30 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/README b/README index 9b512c60..61ebeca3 100644 --- a/README +++ b/README @@ -132,10 +132,7 @@ REQUIREMENTS: In any case you need - gcc, the gnu compiler collection (shipped with distro): gcc-3 - or newer is prefered, but gcc-2.95 is still supported. Note - that gcc-2.95 may spit out many warnings like unused function - parameters and missing initializers. These are all harmless - and may be ignored. + or newer is prefered, but gcc-2.95 might still work. - openssl (needed by server, client): usually shipped with distro, but you might have to install the "development" diff --git a/README.mysql b/README.mysql index 92c8c6dc..f6f56842 100644 --- a/README.mysql +++ b/README.mysql @@ -10,15 +10,19 @@ in INSTALL, so read README and INSTALL before proceeding. First of all, make sure that - mysqld is running - - para_server is running and compiled with mysql support (type - "para_client si" to find out) - - the user who runs para_client has the paraslash DB_WRITE and DB_READ - permissions set in server.users - - the user who runs para_server has create privileges on the mysql - server. -Remember: If something doesn't work as expected, look at the server log file -and/or increase output verbosity by using the -l switch for server and client. + - para_server is running and compiled with mysql support + (type "para_client si" to find out) + + - the user who runs para_client has the paraslash DB_WRITE + and DB_READ permissions set in server.users + + - the user who runs para_server has create privileges on the + mysql server. + +Remember: If something doesn't work as expected, look at the server +log file and/or increase output verbosity by using the -l switch for +server and client. Specify mysql data (port, passwd,...) @@ -171,8 +175,8 @@ the client rather than using temporary files. Like this: Example: Assume you already have an attribute "test" and you'd like to - to restrict the set of songs being played to those having the - "test" attribute set. Define a new stream "only_test" by + to restrict audio streaming to those files having the "test" + attribute set. Define a new stream "only_test" by echo 'accept: IS_SET(test)' | para_client stradd only_test @@ -180,7 +184,7 @@ Example: para_client cs only_test - only the desired songs are going to be played. + only the desired files are going to be streamed. There is no need to keep the temporary files containing the stream definition since you can always use the strq command to get it back: @@ -260,8 +264,8 @@ You can then switch to the new stream with or you can let cron do this for you on a daily basis.. -Accept/deny lines affect only the set of admissible songs, but not -the order in which these songs are played. That's where the score +Accept/deny lines affect only the set of admissible audio files, +but not the order in which these are streamed. That's where the score expression comes into play. Scoring -- 2.39.2 From c9d743cd9a0ac9788884fafd70e6cf5e78941290 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 22:35:04 +0200 Subject: [PATCH 14/16] net.c: make it compile without ucred This patch is ugly as hell and only compile-tested. It obviously needs more work (or a totally different approch like using the usual send/recv functions in case ucred is not available). MacOs testers are welcome. --- net.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/net.c b/net.c index 4d2f520f..5af967b9 100644 --- a/net.c +++ b/net.c @@ -27,6 +27,13 @@ extern void (*crypt_function_recv)(unsigned long len, const unsigned char *indata, unsigned char *outdata); extern void (*crypt_function_send)(unsigned long len, const unsigned char *indata, unsigned char *outdata); + +#ifndef HAVE_UCRED + struct ucred { + uid_t uid, pid, gid; +}; +#endif /* HAVE_UCRED */ + /** * initialize a struct sockaddr_in * @param addr A pointer to the struct to be initialized @@ -363,6 +370,7 @@ ssize_t send_cred_buffer(int sock, char *buf) msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); +#ifdef HAVE_UCRED /* attach the ucred struct */ cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; @@ -370,6 +378,7 @@ ssize_t send_cred_buffer(int sock, char *buf) cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); *(struct ucred *)CMSG_DATA(cmsg) = c; msg.msg_controllen = cmsg->cmsg_len; +#endif ret = sendmsg(sock, &msg, 0); if (ret < 0) ret = -E_SENDMSG; @@ -390,7 +399,6 @@ static void dispose_fds(int *fds, int num) * \param fd the socket file descriptor * \param buf the buffer to store the message * \param size the size of \a buffer - * \param cred the credentials are returned here * * \return negative on errors, the user id on success. * @@ -403,11 +411,13 @@ int recv_cred_buffer(int fd, char *buf, size_t size) struct msghdr msg; struct cmsghdr *cmsg; struct iovec iov; - int result; + int result = 0; int yes = 1; struct ucred cred; +#ifdef HAVE_UCRED setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &yes, sizeof(int)); +#endif memset(&msg, 0, sizeof(msg)); memset(buf, 0, size); iov.iov_base = buf; @@ -418,6 +428,7 @@ int recv_cred_buffer(int fd, char *buf, size_t size) msg.msg_controllen = sizeof(control); if (recvmsg(fd, &msg, 0) < 0) return -E_RECVMSG; +#ifdef HAVE_UCRED result = -E_SCM_CREDENTIALS; cmsg = CMSG_FIRSTHDR(&msg); while (cmsg) { @@ -434,6 +445,7 @@ int recv_cred_buffer(int fd, char *buf, size_t size) } cmsg = CMSG_NXTHDR(&msg, cmsg); } +#endif return result; } @@ -447,7 +459,7 @@ int recv_cred_buffer(int fd, char *buf, size_t size) * \return The file descriptor of the created socket, negative * on errors. * - * \sa get_socket() + * \sa get_socket() * \sa setsockopt(2) * \sa bind(2) * \sa listen(2) -- 2.39.2 From b517a66f86d3d4d27bbe48d790e46126b08c1c82 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 23:20:20 +0200 Subject: [PATCH 15/16] Fix typo in gcc-compat.h It only affects gcc-2 and non-gcc (which is untested anyway). --- gcc-compat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcc-compat.h b/gcc-compat.h index f649c1d4..985ebd23 100644 --- a/gcc-compat.h +++ b/gcc-compat.h @@ -25,7 +25,7 @@ # define __noreturn /* no noreturn */ # define __malloc /* no malloc */ # define __used /* no used */ -# define __a__unused /* no unused */ +# define __a_unused /* no unused */ # define __packed /* no packed */ # define likely(x) (x) # define unlikely(x) (x) -- 2.39.2 From 89cde2c587229f04d779e3a387c4a801261d87f0 Mon Sep 17 00:00:00 2001 From: Andre Date: Wed, 19 Apr 2006 23:59:08 +0200 Subject: [PATCH 16/16] Makefile.in: Remove special treatment of ortp_send/ortp_recv they no longer depend on glib, so use the generic rule for building object files. This made gcc spit out some trivial warnings about unused variables which are also fixed in this patch. --- Makefile.in | 5 ----- ortp_send.c | 9 +++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Makefile.in b/Makefile.in index a6d55d41..efef94b0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -146,11 +146,6 @@ grab_client.cmdline.h grab_client.cmdline.c: grab_client.ggo esac; \ gengetopt $$O --conf-parser --file-name=$(*F).cmdline --set-package="para_$(subst .cmdline,,$(*F))" --set-version="$V" < $< -ortp_send.o: ortp_send.c - $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@ $< -ortp_recv.o: ortp_recv.c - $(CC) -c -Wall -o $@ -g @GLIB_CFLAGS@ $< - para_recv: @recv_objs@ $(CC) @recv_objs@ -o $@ @recv_ldflags@ diff --git a/ortp_send.c b/ortp_send.c index 19b54d39..07e3cccf 100644 --- a/ortp_send.c +++ b/ortp_send.c @@ -192,14 +192,14 @@ static void ortp_send(struct audio_format *af, long unsigned current_chunk, free(sendbuf); } -static int ortp_com_on(struct sender_command_data *scd) +static int ortp_com_on(__a_unused struct sender_command_data *scd) { self->status = SENDER_ON; return 1; } -static int ortp_com_off(struct sender_command_data *scd) +static int ortp_com_off(__a_unused struct sender_command_data *scd) { ortp_shutdown_targets(); self->status = SENDER_OFF; @@ -290,8 +290,9 @@ success: } } -static void ortp_pre_select(struct audio_format *af, int *max_fileno, - fd_set *rfds, fd_set *wfds) +static void ortp_pre_select(__a_unused struct audio_format *af, + __a_unused int *max_fileno, __a_unused fd_set *rfds, + __a_unused fd_set *wfds) { return; } -- 2.39.2