From 4dbf56eff63e120c3888fa2badc9fbf930c6ba46 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Sat, 9 May 2009 10:59:14 +0200 Subject: [PATCH] First draft of the oss writer. This adds client-side support for FreeBSD and NetBSD and provides an alternative writer for Linux. --- Makefile.in | 4 +- NEWS | 1 + README | 2 +- configure.ac | 55 +++++++++--- error.h | 6 ++ fade.c | 4 +- ggo/oss_write.ggo | 30 +++++++ oss_write.c | 219 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 302 insertions(+), 19 deletions(-) create mode 100644 ggo/oss_write.ggo create mode 100644 oss_write.c diff --git a/Makefile.in b/Makefile.in index f63c21fa..d2218929 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 cf8844b8..c444464f 100644 --- 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 dd0dc76a..6cf34f27 100644 --- 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 diff --git a/configure.ac b/configure.ac index da5ec770..d158fd3d 100644 --- a/configure.ac +++ b/configure.ac @@ -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 555ca15f..fbc6c4de 100644 --- 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 40b4623c..7f884473 100644 --- a/fade.c +++ b/fade.c @@ -13,14 +13,14 @@ #include "para.h" #include "fd.h" -#include +#include #include #include /* EXIT_SUCCESS */ #include #include #include #include -#include +#include #include "string.h" #include "error.h" diff --git a/ggo/oss_write.ggo b/ggo/oss_write.ggo new file mode 100644 index 00000000..60682186 --- /dev/null +++ b/ggo/oss_write.ggo @@ -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 index 00000000..552d5b97 --- /dev/null +++ b/oss_write.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2009 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file oss_write.c Paraslash's oss output plugin. */ + +#include +#include +#include +#include + +#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 + }; +} -- 2.39.2