split play.c and rename para_play to para_write
authorAndre <maan@p133.(none)>
Tue, 18 Apr 2006 19:53:48 +0000 (21:53 +0200)
committerAndre <maan@p133.(none)>
Tue, 18 Apr 2006 19:53:48 +0000 (21:53 +0200)
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.

12 files changed:
Makefile.in
alsa_writer.c [new file with mode: 0644]
configure.ac
error.h
file_writer.c [new file with mode: 0644]
play.c [deleted file]
play.ggo [deleted file]
write.c [new file with mode: 0644]
write.ggo [new file with mode: 0644]
write.h [new file with mode: 0644]
write_common.c [new file with mode: 0644]
write_common.h [new file with mode: 0644]

index 5076794..68c60e1 100644 (file)
@@ -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 (file)
index 0000000..b0bc3b0
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2005-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 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 <perex@suse.cz>, which is
+ * based on the vplay program by Michael Beck.
+ */
+
+#include "para.h"
+#include "fd.h"
+#include "string.h"
+#include "write.h"
+
+#include <alsa/asoundlib.h>
+
+#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 */
+}
index bfd659f..6984678 100644 (file)
@@ -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 8169166..237978e 100644 (file)
--- 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 (file)
index 0000000..7878cce
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 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 (file)
index e1fdb2e..0000000
--- a/play.c
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * Copyright (C) 2005-2006 Andre Noll <maan@systemlinux.org>
- *
- *     This program is free software; you can redistribute it and/or modify
- *     it under the terms of the GNU General Public License as published by
- *     the Free Software Foundation; either version 2 of the License, or
- *     (at your option) any later version.
- *
- *     This program is distributed in the hope that it will be useful,
- *     but WITHOUT ANY WARRANTY; without even the implied warranty of
- *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *     GNU General Public License for more details.
- *
- *     You should have received a copy of the GNU General Public License
- *     along with this program; if not, write to the Free Software
- *     Foundation, Inc., 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 <perex@suse.cz>, which is
- * based on the vplay program by Michael Beck.
- */
-
-#define WAV_HEADER_LEN 44
-#include <sys/time.h> /* gettimeofday */
-#include "para.h"
-#include "fd.h"
-#include "play.cmdline.h"
-#include <alsa/asoundlib.h>
-#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/play.ggo b/play.ggo
deleted file mode 100644 (file)
index b1c8083..0000000
--- a/play.ggo
+++ /dev/null
@@ -1,33 +0,0 @@
-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 "loglevel" l
-#~~~~~~~~~~~~~~~~~~
-
-"set loglevel (0-6)"
-
-       int typestr="level"
-       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
diff --git a/write.c b/write.c
new file mode 100644 (file)
index 0000000..5e8c319
--- /dev/null
+++ b/write.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2005-2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 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 <sys/time.h> /* 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/write.ggo b/write.ggo
new file mode 100644 (file)
index 0000000..b1c8083
--- /dev/null
+++ b/write.ggo
@@ -0,0 +1,33 @@
+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 "loglevel" l
+#~~~~~~~~~~~~~~~~~~
+
+"set loglevel (0-6)"
+
+       int typestr="level"
+       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
diff --git a/write.h b/write.h
new file mode 100644 (file)
index 0000000..68730bf
--- /dev/null
+++ b/write.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 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 (file)
index 0000000..a7896fc
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 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 (file)
index 0000000..e7652e2
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2006 Andre Noll <maan@systemlinux.org>
+ *
+ *     This program is free software; you can redistribute it and/or modify
+ *     it under the terms of the GNU General Public License as published by
+ *     the Free Software Foundation; either version 2 of the License, or
+ *     (at your option) any later version.
+ *
+ *     This program is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ *     You should have received a copy of the GNU General Public License
+ *     along with this program; if not, write to the Free Software
+ *     Foundation, Inc., 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);