First draft of the oss writer.
authorAndre Noll <maan@systemlinux.org>
Sat, 9 May 2009 08:59:14 +0000 (10:59 +0200)
committerAndre Noll <maan@systemlinux.org>
Sat, 9 May 2009 08:59:14 +0000 (10:59 +0200)
This adds client-side support for FreeBSD and NetBSD and provides an
alternative writer for Linux.

Makefile.in
NEWS
README
configure.ac
error.h
fade.c
ggo/oss_write.ggo [new file with mode: 0644]
oss_write.c [new file with mode: 0644]

index f63c21f..d221892 100644 (file)
@@ -64,7 +64,7 @@ man_pages_in := $(patsubst %, web/%.man.in.html, $(man_binaries))
 ggo_dir := ggo
 
 m4_ggos := afh audioc audiod client filter fsck gui recv server write
-all_ggos := $(m4_ggos) dccp_recv oggdec_filter alsa_write fade http_recv \
+all_ggos := $(m4_ggos) dccp_recv oggdec_filter alsa_write oss_write fade http_recv \
        osx_write udp_recv amp_filter compress_filter file_write \
        grab_client mp3dec_filter
 ggo_generated := $(addsuffix .cmdline.c, $(all_ggos)) $(addsuffix .cmdline.h, $(all_ggos)) \
@@ -158,7 +158,7 @@ para_audioc: @audioc_objs@
        $(CC) $(LDFLAGS) -o $@ @audioc_objs@ @audioc_ldflags@
 
 para_fade: @fade_objs@
-       $(CC) $(LDFLAGS) -o $@ @fade_objs@
+       $(CC) $(LDFLAGS) -o $@ @fade_objs@ @fade_ldflags@
 
 para_server: @server_objs@
        $(CC) $(LDFLAGS) -o $@ @server_objs@  @server_ldflags@
diff --git a/NEWS b/NEWS
index cf8844b..c444464 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ NEWS
 0.3.5 (to be announced) "symplectic separability"
 -------------------------------------------------
 
+       - the new oss writer (supported on *BSD and Linux)
        - improved signal handling
        - variable fec output buffer size
 
diff --git a/README b/README
index dd0dc76..6cf34f2 100644 (file)
--- a/README
+++ b/README
@@ -158,7 +158,7 @@ be added easily.
 para_fade
 ---------
 
-A (Linux-only) alarm clock and volume-fader.
+A (oss-only) alarm clock and volume-fader.
 
 ---------------
 bash_completion
index da5ec77..d158fd3 100644 (file)
@@ -467,27 +467,52 @@ if test ${have_libid3tag} = yes; then
 else
        AC_MSG_WARN([no support for id3v2 tags])
 fi
+########################################################################### oss
+OLD_CPPFLAGS="$CPPFLAGS"
+OLD_LD_FLAGS="$LDFLAGS"
+OLD_LIBS="$LIBS"
+
+have_oss="yes"
+msg="=> will not build para_fade/oss writer"
+
+AC_CHECK_HEADER(sys/soundcard.h, [
+       extras="$extras para_fade"
+       all_executables="$all_executables fade"
+       all_errlist_objs="$all_errlist_objs oss_write"
+       audiod_errlist_objs="$audiod_errlist_objs oss_write"
+       audiod_cmdline_objs="$audiod_cmdline_objs oss_write.cmdline"
+
+       write_errlist_objs="$write_errlist_objs oss_write"
+       write_cmdline_objs="$write_cmdline_objs oss_write.cmdline"
+       writers="$writers oss"
+       default_writer="OSS_WRITE"
+       AC_CHECK_LIB(ossaudio, _oss_ioctl, [
+                       audiod_ldflags="$audiod_ldflags -lossaudio"
+                       write_ldflags="$write_ldflags -lossaudio"
+                       fade_ldflags="$write_ldflags -lossaudio"
+               ]
+       )
+       ],
+       [
+               have_oss="no"
+               AC_MSG_WARN([no linux/soundcard.h $msg])
+       ]
+)
+CPPFLAGS="$OLD_CPPFLAGS"
+LDFLAGS="$OLD_LDFLAGS"
+LIBS="$OLD_LIBS"
+
 ########################################################################### alsa
-have_alsa="yes"
 OLD_CPPFLAGS="$CPPFLAGS"
 OLD_LD_FLAGS="$LDFLAGS"
 OLD_LIBS="$LIBS"
+
+msg="=> no alsa support for para_audiod/para_write"
 if test "$OSTYPE" != "Linux"; then
        have_alsa="no"
+else
+       have_alsa="yes"
 fi
-msg="=> will not build para_fade"
-if test "$have_alsa" = "yes"; then
-       AC_CHECK_HEADER(linux/soundcard.h, [
-               extras="$extras para_fade"
-               all_executables="$all_executables fade"
-               ],
-               [
-                       have_alsa="no"
-                       AC_MSG_WARN([no linux/soundcard.h $msg])
-               ]
-       )
-fi
-msg="=> no alsa support for para_audiod/para_write"
 if test "$have_alsa" = "yes"; then
        AC_CHECK_HEADERS([alsa/asoundlib.h], [], [
                have_alsa="no"
@@ -514,6 +539,7 @@ if test "$have_alsa" = "yes"; then
        writers="$writers alsa"
        default_writer="ALSA_WRITE"
 fi
+
 CPPFLAGS="$OLD_CPPFLAGS"
 LDFLAGS="$OLD_LDFLAGS"
 LIBS="$OLD_LIBS"
@@ -675,6 +701,7 @@ AC_DEFINE_UNQUOTED(INIT_GUI_ERRLISTS,
        objlist_to_errlist($gui_errlist_objs), errors used by para_gui)
 
 AC_SUBST(fade_objs, add_dot_o($fade_objs))
+AC_SUBST(fade_ldflags, $fade_ldflags)
 AC_DEFINE_UNQUOTED(INIT_FADE_ERRLISTS,
        objlist_to_errlist($fade_errlist_objs), errors used by para_fade)
 
diff --git a/error.h b/error.h
index 555ca15..fbc6c4d 100644 (file)
--- a/error.h
+++ b/error.h
@@ -34,6 +34,12 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 
 extern const char **para_errlist[];
 
+#define OSS_WRITE_ERRORS \
+       PARA_ERROR(BAD_SAMPLE_FORMAT, "sample format not supported"), \
+       PARA_ERROR(BAD_CHANNEL_COUNT, "channel count not supported"), \
+       PARA_ERROR(BAD_SAMPLERATE, "sample rate not supported"), \
+
+
 #define COMPRESS_FILTER_ERRORS \
        PARA_ERROR(COMPRESS_SYNTAX, "syntax error in compress filter config"), \
 
diff --git a/fade.c b/fade.c
index 40b4623..7f88447 100644 (file)
--- a/fade.c
+++ b/fade.c
 #include "para.h"
 #include "fd.h"
 
-#include <stropts.h>
+#include <sys/ioctl.h>
 #include <ctype.h>
 #include <stdlib.h> /* EXIT_SUCCESS */
 #include <unistd.h>
 #include <signal.h>
 #include <string.h>
 #include <limits.h>
-#include <linux/soundcard.h>
+#include <sys/soundcard.h>
 #include "string.h"
 #include "error.h"
 
diff --git a/ggo/oss_write.ggo b/ggo/oss_write.ggo
new file mode 100644 (file)
index 0000000..6068218
--- /dev/null
@@ -0,0 +1,30 @@
+option "device" d
+#~~~~~~~~~~~~~~~~
+"set PCM device"
+string typestr="device"
+default="/dev/dsp"
+optional
+
+option "channels" c
+#~~~~~~~~~~~~~~~~~~
+"specify number of channels"
+int typestr="num"
+default="2"
+optional
+details="
+       This option is only necessary for playing raw audio with
+       para_write.  In all other cases (plaing wav files with
+       para_write or using this writer with para_audiod), the number
+       of channels will be obtained from other resources.
+"
+
+option "samplerate" s
+#~~~~~~~~~~~~~~~~~~~~~
+"force given sample rate"
+int typestr="num"
+default="44100"
+optional
+details="
+       Again, it is only necessary to specify this when playing raw
+       audio with para_write.
+"
diff --git a/oss_write.c b/oss_write.c
new file mode 100644 (file)
index 0000000..552d5b9
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2009 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file oss_write.c Paraslash's oss output plugin. */
+
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/soundcard.h>
+
+#include "para.h"
+#include "fd.h"
+#include "string.h"
+#include "list.h"
+#include "sched.h"
+#include "ggo.h"
+#include "write.h"
+#include "oss_write.cmdline.h"
+#include "error.h"
+
+/** Always use 16 bit little endian. */
+#define FORMAT AFMT_S16_LE
+
+/** Data specific to the oss writer. */
+struct private_oss_write_data {
+       /** The file handle of the device. */
+       int fd;
+       /**
+        * The samplerate given by command line option or the decoder
+        * of the writer node group.
+        */
+       int samplerate;
+       /**
+        * The number of channels, given by command line option or the
+        * decoder of the writer node group.
+        */
+       int channels;
+       /** Four bytes for stereo streams, two bytes for mono streams. */
+       int bytes_per_frame;
+};
+
+static int oss_pre_select(struct sched *s, struct writer_node *wn)
+{
+       struct private_oss_write_data *powd = wn->private_data;
+       struct writer_node_group *wng = wn->wng;
+
+       if (!*wng->loaded)
+               return 0;
+       para_fd_set(powd->fd, &s->wfds, &s->max_fileno);
+       return 1;
+}
+static int oss_post_select(__a_unused struct sched *s,
+               struct writer_node *wn)
+{
+       int ret;
+       struct private_oss_write_data *powd = wn->private_data;
+       struct writer_node_group *wng = wn->wng;
+       size_t frames, bytes = *wng->loaded - wn->written;
+       char *data = *wng->bufp + wn->written;
+
+       if (*wng->input_error < 0 && bytes < powd->bytes_per_frame) {
+               wn->written = *wng->loaded;
+               return *wng->input_error;
+       }
+       frames = bytes / powd->bytes_per_frame;
+       if (!frames) /* less than a single frame available */
+               goto out;
+       if (!FD_ISSET(powd->fd, &s->wfds))
+               goto out;
+       ret = write_nonblock(powd->fd, data, frames * powd->bytes_per_frame, 0);
+       if (ret < 0)
+               return ret;
+       wn->written += ret;
+out:
+       return 1;
+}
+
+static void oss_close(struct writer_node *wn)
+{
+       struct private_oss_write_data *powd = wn->private_data;
+
+       close(powd->fd);
+       free(powd);
+}
+
+/*
+ * The Open Sound System Programmer's Guide sayeth:
+ *
+ * Set sampling parameters always so that number of channels (mono/stereo) is
+ * set before selecting sampling rate (speed).  Failing to do this will make
+ * your program incompatible with cards such as the SoundBlaster Pro which
+ * supports 44.1 kHz in mono but just 22.05 kHz in stereo. A program which
+ * selects 44.1 kHz speed and then sets the device to stereo mode will
+ * incorrectly believe that the device is still in 44.1 kHz mode when actually
+ * the speed is decreased to 22.05 kHz.
+ */
+static int oss_open(struct writer_node *wn)
+{
+       int ret, format = FORMAT, channels, samplerate;
+       struct oss_write_args_info *conf = wn->conf;
+       struct writer_node_group *wng = wn->wng;
+       struct private_oss_write_data *powd;
+
+       PARA_INFO_LOG("opening %s\n", conf->device_arg);
+       ret = para_open(conf->device_arg, O_WRONLY, 0);
+       if (ret < 0)
+               return ret;
+       powd = para_calloc(sizeof(*powd));
+       wn->private_data = powd;
+       powd->fd = ret;
+       ret = mark_fd_nonblocking(powd->fd);
+       if (ret < 0)
+               goto err;
+       /* set PCM format */
+       ret = ioctl(powd->fd, SNDCTL_DSP_SETFMT, &format);
+       if (ret < 0) {
+               ret = -ERRNO_TO_PARA_ERROR(errno);
+               goto err;
+       }
+       ret = -E_BAD_SAMPLE_FORMAT;
+       if (format != FORMAT)
+               goto err;
+       /* set number of channels */
+       if (!conf->channels_given && wng->channels)
+               channels = *wng->channels;
+       else
+               channels = conf->channels_arg;
+       ret = -E_BAD_CHANNEL_COUNT;
+       if (channels == 0)
+               goto err;
+       powd->channels = channels;
+       ret = ioctl(powd->fd, SNDCTL_DSP_CHANNELS, &channels);
+       if (ret < 0) {
+               ret = -ERRNO_TO_PARA_ERROR(errno);
+               goto err;
+       }
+       if (powd->channels != channels)
+               goto err;
+       powd->bytes_per_frame = channels * 2;
+
+       /*
+        * Set sampling rate
+        *
+        * If we request a higher sampling rate than is supported by the
+        * device, the the highest possible speed is automatically used. The
+        * value actually used is returned as the new value of the argument.
+        */
+       if (!conf->samplerate_given && wng->samplerate)
+               samplerate = *wng->samplerate;
+       else
+               samplerate = conf->samplerate_arg;
+       powd->samplerate = samplerate;
+       ret = ioctl(powd->fd, SNDCTL_DSP_SPEED, &samplerate);
+       if (ret < 0) {
+               ret = -ERRNO_TO_PARA_ERROR(errno);
+               goto err;
+       }
+       if (samplerate != powd->samplerate) {
+               int min = PARA_MIN(samplerate, powd->samplerate),
+                       max = PARA_MAX(samplerate, powd->samplerate);
+               /*
+                * Check whether the returned sample rate differs significantly
+                * from the requested one.
+                */
+               ret = -E_BAD_SAMPLERATE;
+               if (100 * max > 110 * min) /* more than 10% deviation */
+                       goto err;
+               PARA_NOTICE_LOG("using %dHz rather than %dHz\n", samplerate,
+                       powd->samplerate);
+       }
+
+       return 1;
+err:
+       close(powd->fd);
+       free(powd);
+       return ret;
+}
+
+__malloc static void *oss_parse_config(const char *options)
+{
+       int ret;
+       struct oss_write_args_info *conf = para_calloc(sizeof(*conf));
+
+       PARA_INFO_LOG("options: %s, %zd\n", options, strcspn(options, " \t"));
+       ret = oss_cmdline_parser_string(options, conf, "oss_write");
+       if (ret)
+               goto err_out;
+       return conf;
+err_out:
+       free(conf);
+       return NULL;
+}
+
+/**
+ * The init function of the oss writer.
+ *
+ * \param w Pointer to the writer to initialize.
+ *
+ * \sa struct writer.
+ */
+void oss_write_init(struct writer *w)
+{
+       struct oss_write_args_info dummy;
+
+       oss_cmdline_parser_init(&dummy);
+       w->open = oss_open;
+       w->close = oss_close;
+       w->pre_select = oss_pre_select;
+       w->post_select = oss_post_select;
+       w->parse_config = oss_parse_config;
+       w->shutdown = NULL;
+       w->help = (struct ggo_help) {
+               .short_help = oss_write_args_info_help,
+               .detailed_help = oss_write_args_info_detailed_help
+       };
+}