*/
#include "disobedience.h"
-#include "mixer.h"
/* Forward declarations ---------------------------------------------------- */
++suppress_actions;
/* Only display volume/balance controls if they will work */
if(!rtp_supported
- || (rtp_supported && mixer_supported(DEFAULT_BACKEND)))
+ || (rtp_supported && backend && backend->set_volume))
volume_supported = TRUE;
else
volume_supported = FALSE;
* from the log. */
if(rtp_supported) {
int l = nearbyint(left(v, b) * 100), r = nearbyint(right(v, b) * 100);
- mixer_control(DEFAULT_BACKEND, &l, &r, 1);
+ if(backend && backend->set_volume)
+ backend->set_volume(&l, &r);
} else
disorder_eclient_volume(client, volume_completed,
nearbyint(left(v, b) * 100),
*/
#include "disobedience.h"
-#include "mixer.h"
#include "version.h"
#include <getopt.h>
/** @brief Right channel volume */
int volume_r;
+/** @brief Audio backend */
+const struct uaudio *backend;
+
double goesupto = 10; /* volume upper bound */
/** @brief True if a NOP is in flight */
}
last = now;
#endif
- if(rtp_supported && mixer_supported(DEFAULT_BACKEND)) {
+ if(rtp_supported && backend && backend->get_volume) {
int nl, nr;
- if(!mixer_control(DEFAULT_BACKEND, &nl, &nr, 0)
- && (nl != volume_l || nr != volume_r)) {
+ backend->get_volume(&nl, &nr);
+ if(nl != volume_l || nr != volume_r) {
volume_l = nl;
volume_r = nr;
event_raise("volume-changed", 0);
D(("create main loop"));
mainloop = g_main_loop_new(0, 0);
if(config_read(0)) fatal(0, "cannot read configuration");
+ /* we'll need mixer support */
+ backend = uaudio_apis[0];
+ if(backend->open_mixer)
+ backend->open_mixer();
/* create the clients */
if(!(client = gtkclient())
|| !(logclient = gtkclient()))
#include "eventdist.h"
#include "split.h"
#include "timeval.h"
+#include "uaudio.h"
#include <glib.h>
#include <gtk/gtk.h>
extern int rtp_supported;
extern int rtp_is_running;
extern GtkItemFactory *mainmenufactory;
+extern const struct uaudio *backend;
extern const disorder_eclient_log_callbacks log_callbacks;
.\"
-.\" Copyright (C) 2004-2008 Richard Kettlewell
+.\" Copyright (C) 2004-2009 Richard Kettlewell
.\"
.\" 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
.B speaker_command
is specified, or if no native is available.
.TP
-.B network
+.B rtp
Transmit audio over the network.
This is the default if \fBbroadcast\fR is specified.
You can use
.BR disorder-playrtp (1)
to receive and play the resulting stream on Linux and OS X.
+.B network
+is a deprecated synonym for this API.
.RE
.TP
.B authorization_algorithm \fIALGORITHM\fR
.TP
.B broadcast \fIADDRESS\fR \fIPORT\fR
Transmit sound data to \fIADDRESS\fR using UDP port \fIPORT\fR.
-This implies \fBapi network\fR.
+This implies \fBapi rtp\fR.
.IP
See also \fBmulticast_loop\fR and \fBmulticast_ttl\fR.
.TP
.B multicast_loop yes\fR|\fBno
Determines whether multicast packets are loop backed to the sending host.
The default is \fByes\fR.
-This only applies if \fBapi\fR is set to \fBnetwork\fR and \fBbroadcast\fR
+This only applies if \fBapi\fR is set to \fBrtp\fR and \fBbroadcast\fR
is actually a multicast address.
.TP
.B multicast_ttl \fIHOPS\fR
Set the maximum number of hops to send multicast packets.
-This only applies if \fBapi\fR is set to \fBnetwork\fR and
+This only applies if \fBapi\fR is set to \fBrtp\fR and
\fBbroadcast\fR is actually a multicast address.
The default is 1.
.TP
.BR 16/44100/2 .
.PP
With the
-.B network
+.B rtp
backend the sample format is forced to
.B 16b/44100/2
and with the
macros.c macros-builtin.c macros.h \
mem.c mem.h \
mime.h mime.c \
- mixer.c mixer.h mixer-oss.c mixer-alsa.c \
printf.c printf.h \
asprintf.c fprintf.c snprintf.c \
queue.c queue.h \
/*
* This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
* Portions copyright (C) 2007 Mark Wooding
*
* This program is free software: you can redistribute it and/or modify
#include "inputline.h"
#include "charset.h"
#include "defs.h"
-#include "mixer.h"
#include "printf.h"
#include "regsub.h"
#include "signame.h"
#include "authhash.h"
#include "vector.h"
+#include "uaudio.h"
/** @brief Path to config file
*
*/
int config_per_user = 1;
+/** @brief Table of audio APIs
+ *
+ * Only set in server processes.
+ */
+const struct uaudio *const *config_uaudio_apis;
+
/** @brief Config file parser state */
struct config_state {
/** @brief Filename */
return 0;
}
-static int set_backend(const struct config_state *cs,
- const struct conf *whoami,
- int nvec, char **vec) {
- int *const valuep = ADDRESS(cs->config, int);
-
- if(nvec != 1) {
- error(0, "%s:%d: '%s' requires one argument",
- cs->path, cs->line, whoami->name);
- return -1;
- }
- if(!strcmp(vec[0], "alsa")) {
-#if HAVE_ALSA_ASOUNDLIB_H
- *valuep = BACKEND_ALSA;
-#else
- error(0, "%s:%d: ALSA is not available on this platform",
- cs->path, cs->line);
- return -1;
-#endif
- } else if(!strcmp(vec[0], "command"))
- *valuep = BACKEND_COMMAND;
- else if(!strcmp(vec[0], "network"))
- *valuep = BACKEND_NETWORK;
- else if(!strcmp(vec[0], "coreaudio")) {
-#if HAVE_COREAUDIO_AUDIOHARDWARE_H
- *valuep = BACKEND_COREAUDIO;
-#else
- error(0, "%s:%d: Core Audio is not available on this platform",
- cs->path, cs->line);
- return -1;
-#endif
- } else if(!strcmp(vec[0], "oss")) {
-#if HAVE_SYS_SOUNDCARD_H
- *valuep = BACKEND_OSS;
-#else
- error(0, "%s:%d: OSS is not available on this platform",
- cs->path, cs->line);
- return -1;
-#endif
- } else {
- error(0, "%s:%d: invalid '%s' value '%s'",
- cs->path, cs->line, whoami->name, vec[0]);
- return -1;
- }
- return 0;
-}
-
static int set_rights(const struct config_state *cs,
const struct conf *whoami,
int nvec, char **vec) {
type_restrict = { set_restrict, free_none },
type_namepart = { set_namepart, free_namepartlist },
type_transform = { set_transform, free_transformlist },
- type_rights = { set_rights, free_none },
- type_backend = { set_backend, free_none };
+ type_rights = { set_rights, free_none };
/* specific validation routine */
return 0;
}
+static int validate_backend(const struct config_state attribute((unused)) *cs,
+ int nvec,
+ char **vec) {
+ int n;
+ if(nvec != 1) {
+ error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
+ return -1;
+ }
+ if(!strcmp(vec[0], "network")) {
+ error(0, "'api network' is deprecated; use 'api rtp'");
+ return 0;
+ }
+ if(config_uaudio_apis) {
+ for(n = 0; config_uaudio_apis[n]; ++n)
+ if(!strcmp(vec[0], config_uaudio_apis[n]->name))
+ return 0;
+ error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
+ return -1;
+ }
+ /* In non-server processes we have no idea what's valid */
+ return 0;
+}
+
/** @brief Item name and and offset */
#define C(x) #x, offsetof(struct config, x)
/** @brief Item name and and offset */
static const struct conf conf[] = {
{ C(alias), &type_string, validate_alias },
{ C(allow), &type_stringlist_accum, validate_allow },
- { C(api), &type_backend, validate_any },
+ { C(api), &type_string, validate_backend },
{ C(authorization_algorithm), &type_string, validate_algo },
{ C(broadcast), &type_stringlist, validate_addrport },
{ C(broadcast_from), &type_stringlist, validate_addrport },
{ C(signal), &type_signal, validate_any },
{ C(smtp_server), &type_string, validate_any },
{ C(sox_generation), &type_integer, validate_non_negative },
- { C2(speaker_backend, api), &type_backend, validate_any },
+ { C2(speaker_backend, api), &type_string, validate_backend },
{ C(speaker_command), &type_string, validate_any },
{ C(stopword), &type_string_accum, validate_any },
{ C(templates), &type_string_accum, validate_isdir },
c->sample_format.endian = ENDIAN_NATIVE;
c->queue_pad = 10;
c->replay_min = 8 * 3600;
- c->api = -1;
+ c->api = NULL;
c->multicast_ttl = 1;
c->multicast_loop = 1;
c->authorization_algorithm = xstrdup("sha1");
for(n = 0; n < NTRANSFORM; ++n)
set_transform(&cs, whoami, 5, (char **)transform[n]);
}
- if(c->api == -1) {
+ if(!c->api) {
if(c->speaker_command)
- c->api = BACKEND_COMMAND;
+ c->api = xstrdup("command");
else if(c->broadcast.n)
- c->api = BACKEND_NETWORK;
+ c->api = xstrdup("rtp");
+ else if(config_uaudio_apis)
+ c->api = xstrdup(config_uaudio_apis[0]->name);
else
- c->api = DEFAULT_BACKEND;
+ c->api = xstrdup("<none>");
}
+ if(!strcmp(c->api, "network"))
+ c->api = xstrdup("rtp");
if(server) {
- if(c->api == BACKEND_COMMAND && !c->speaker_command)
+ if(!strcmp(c->api, "command") && !c->speaker_command)
fatal(0, "'api command' but speaker_command is not set");
- if(c->api == BACKEND_NETWORK && !c->broadcast.n)
- fatal(0, "'api network' but broadcast is not set");
+ if((!strcmp(c->api, "rtp")) && !c->broadcast.n)
+ fatal(0, "'api rtp' but broadcast is not set");
}
/* Override sample format */
- switch(c->api) {
- case BACKEND_NETWORK:
+ if(!strcmp(c->api, "rtp")) {
c->sample_format.rate = 44100;
c->sample_format.channels = 2;
c->sample_format.bits = 16;
- c->sample_format.endian = ENDIAN_BIG;
- break;
- case BACKEND_COREAUDIO:
+ c->sample_format.endian = ENDIAN_NATIVE;
+ }
+ if(!strcmp(c->api, "coreaudio")) {
c->sample_format.rate = 44100;
c->sample_format.channels = 2;
c->sample_format.bits = 16;
c->sample_format.endian = ENDIAN_NATIVE;
- break;
}
if(!c->default_rights) {
rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
-
/*
* This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
*
* 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
#include "speaker-protocol.h"
#include "rights.h"
+struct uaudio;
+
/* Configuration is kept in a @struct config@; the live configuration
* is always pointed to by @config@. Values in @config@ are UTF-8 encoded.
*/
/** @brief Sox syntax generation */
long sox_generation;
- /** @brief API used to play sound
- *
- * Choices are @ref BACKEND_ALSA, @ref BACKEND_COMMAND or @ref
- * BACKEND_NETWORK.
- */
- int api;
-
-/* These values had better be non-negative */
-#define BACKEND_ALSA 0 /**< Use ALSA (Linux only) */
-#define BACKEND_COMMAND 1 /**< Execute a command */
-#define BACKEND_NETWORK 2 /**< Transmit RTP */
-#define BACKEND_COREAUDIO 3 /**< Use Core Audio (Mac only) */
-#define BACKEND_OSS 4 /**< Use OSS */
-
-#if HAVE_ALSA_ASOUNDLIB_H
-# define DEFAULT_BACKEND BACKEND_ALSA
-#elif HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
-# define DEFAULT_BACKEND BACKEND_OSS
-#elif HAVE_COREAUDIO_AUDIOHARDWARE_H
-# define DEFAULT_BACKEND BACKEND_COREAUDIO
-#else
-# error Cannot choose a default backend
-#endif
+ /** @brief API used to play sound */
+ const char *api;
/** @brief Home directory for state files */
const char *home;
extern char *configfile;
extern int config_per_user;
+extern const struct uaudio *const *config_uaudio_apis;
+
#endif /* CONFIGURATION_H */
/*
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007, 2008 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer-alsa.c
- * @brief ALSA mixer support
- *
- * The documentation for ALSA's mixer support is completely hopeless,
- * which is a particular nuisnace given it's got an incredibly verbose
- * API. Much of this code is cribbed from
- * alsa-utils-1.0.13/amixer/amixer.c.
- *
- * Mono output devices are supported, but the support is not tested
- * (as I don't one).
- */
-
-#include "common.h"
-
-#if HAVE_ALSA_ASOUNDLIB_H
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stddef.h>
-#include <ctype.h>
-#include <sys/ioctl.h>
-#include <alsa/asoundlib.h>
-
-#include "configuration.h"
-#include "mixer.h"
-#include "log.h"
-#include "syscalls.h"
-
-/** @brief Shared state for ALSA mixer support */
-struct alsa_mixer_state {
- /** @brief Mixer handle */
- snd_mixer_t *handle;
-
- /** @brief Mixer control */
- snd_mixer_elem_t *elem;
-
- /** @brief Left channel */
- snd_mixer_selem_channel_id_t left;
-
- /** @brief Right channel */
- snd_mixer_selem_channel_id_t right;
-
- /** @brief Minimum level */
- long min;
-
- /** @brief Maximum level */
- long max;
-};
-
-/** @brief Destroy a @ref alsa_mixer_state */
-static void alsa_close(struct alsa_mixer_state *h) {
- /* TODO h->elem */
- if(h->handle)
- snd_mixer_close(h->handle);
-}
-
-/** @brief Initialize a @ref alsa_mixer_state */
-static int alsa_open(struct alsa_mixer_state *h) {
- int err;
- snd_mixer_selem_id_t *id;
-
- snd_mixer_selem_id_alloca(&id);
- memset(h, 0, sizeof h);
- if((err = snd_mixer_open(&h->handle, 0))) {
- error(0, "snd_mixer_open: %s", snd_strerror(err));
- return -1;
- }
- if((err = snd_mixer_attach(h->handle, config->device))) {
- error(0, "snd_mixer_attach %s: %s",
- config->device, snd_strerror(err));
- goto error;
- }
- if((err = snd_mixer_selem_register(h->handle, 0/*options*/, 0/*classp*/))) {
- error(0, "snd_mixer_selem_register %s: %s",
- config->device, snd_strerror(err));
- goto error;
- }
- if((err = snd_mixer_load(h->handle))) {
- error(0, "snd_mixer_load %s: %s",
- config->device, snd_strerror(err));
- goto error;
- }
- snd_mixer_selem_id_set_name(id, config->channel);
- snd_mixer_selem_id_set_index(id, atoi(config->mixer));
- if(!(h->elem = snd_mixer_find_selem(h->handle, id))) {
- error(0, "device '%s' mixer control '%s,%s' does not exist",
- config->device, config->channel, config->mixer);
- goto error;
- }
- if(!snd_mixer_selem_has_playback_volume(h->elem)) {
- error(0, "device '%s' mixer control '%s,%s' has no playback volume",
- config->device, config->channel, config->mixer);
- goto error;
- }
- if(snd_mixer_selem_is_playback_mono(h->elem)) {
- h->left = h->right = SND_MIXER_SCHN_MONO;
- } else {
- h->left = SND_MIXER_SCHN_FRONT_LEFT;
- h->right = SND_MIXER_SCHN_FRONT_RIGHT;
- }
- if(!snd_mixer_selem_has_playback_channel(h->elem, h->left)
- || !snd_mixer_selem_has_playback_channel(h->elem, h->right)) {
- error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
- config->device, config->channel, config->mixer);
- goto error;
- }
- snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
- return 0;
-error:
- alsa_close(h);
- return -1;
-}
-
-/** @brief Convert a level to a percentage */
-static int to_percent(const struct alsa_mixer_state *h, long n) {
- return (n - h->min) * 100 / (h->max - h->min);
-}
-
-/** @brief Get ALSA volume */
-static int alsa_get(int *left, int *right) {
- struct alsa_mixer_state h[1];
- long l, r;
- int err;
-
- if(alsa_open(h))
- return -1;
- if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
- || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
- error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
- goto error;
- }
- *left = to_percent(h, l);
- *right = to_percent(h, r);
- alsa_close(h);
- return 0;
-error:
- alsa_close(h);
- return -1;
-}
-
-/** @brief Convert a percentage to a level */
-static int from_percent(const struct alsa_mixer_state *h, int n) {
- return h->min + n * (h->max - h->min) / 100;
-}
-
-/** @brief Set ALSA volume */
-static int alsa_set(int *left, int *right) {
- struct alsa_mixer_state h[1];
- long l, r;
- int err;
-
- if(alsa_open(h))
- return -1;
- /* Set the volume */
- if(h->left == h->right) {
- /* Mono output - just use the loudest */
- if((err = snd_mixer_selem_set_playback_volume
- (h->elem, h->left,
- from_percent(h, *left > *right ? *left : *right)))) {
- error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
- goto error;
- }
- } else {
- /* Stereo output */
- if((err = snd_mixer_selem_set_playback_volume
- (h->elem, h->left, from_percent(h, *left)))
- || (err = snd_mixer_selem_set_playback_volume
- (h->elem, h->right, from_percent(h, *right)))) {
- error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
- goto error;
- }
- }
- /* Read it back to see what we ended up at */
- if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
- || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
- error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
- goto error;
- }
- *left = to_percent(h, l);
- *right = to_percent(h, r);
- alsa_close(h);
- return 0;
-error:
- alsa_close(h);
- return -1;
-}
-
-/** @brief ALSA mixer vtable */
-const struct mixer mixer_alsa = {
- BACKEND_ALSA,
- alsa_get,
- alsa_set,
- "0",
- "PCM"
-};
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2004, 2007, 2008 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer-oss.c
- * @brief OSS mixer support
- *
- * Mono output devices aren't explicitly supported (but may work
- * nonetheless).
- */
-
-#include "common.h"
-
-#if HAVE_SYS_SOUNDCARD_H
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stddef.h>
-#include <sys/ioctl.h>
-#include <sys/soundcard.h>
-
-#include "configuration.h"
-#include "mixer.h"
-#include "log.h"
-#include "syscalls.h"
-#include "mem.h"
-
-/* documentation does not match implementation! */
-#ifndef SOUND_MIXER_READ
-# define SOUND_MIXER_READ(x) MIXER_READ(x)
-#endif
-#ifndef SOUND_MIXER_WRITE
-# define SOUND_MIXER_WRITE(x) MIXER_WRITE(x)
-#endif
-
-/** @brief Channel names */
-static const char *channels[] = SOUND_DEVICE_NAMES;
-
-/** @brief Convert channel name to number */
-static int mixer_channel(const char *c) {
- unsigned n;
-
- if(!c[strspn(c, "0123456789")])
- return atoi(c);
- else {
- for(n = 0; n < sizeof channels / sizeof *channels; ++n)
- if(!strcmp(channels[n], c))
- return n;
- return -1;
- }
-}
-
-/** @brief Open the OSS mixer device and return its fd */
-static int oss_do_open(void) {
- int fd;
-
- if((fd = open(config->mixer, O_RDWR, 0)) < 0) {
- static char *reported;
-
- if(!reported || strcmp(reported, config->mixer)) {
- if(reported)
- xfree(reported);
- reported = xstrdup(config->mixer);
- error(errno, "error opening %s", config->mixer);
- }
- }
- return fd;
-}
-
-/** @brief Get the OSS mixer setting */
-static int oss_do_get(int *left, int *right, int fd, int ch) {
- int r;
-
- if(ioctl(fd, SOUND_MIXER_READ(ch), &r) == -1) {
- error(errno, "error reading %s channel %s",
- config->mixer, config->channel);
- return -1;
- }
- *left = r & 0xff;
- *right = (r >> 8) & 0xff;
- return 0;
-}
-
-/** @brief Get OSS volume */
-static int oss_get(int *left, int *right) {
- int ch, fd;
-
- if(config->mixer
- && config->channel
- && (ch = mixer_channel(config->channel)) != -1) {
- if((fd = oss_do_open()) < 0)
- return -1;
- if(oss_do_get(left, right, fd, ch) < 0) {
- xclose(fd);
- return -1;
- }
- xclose(fd);
- return 0;
- } else
- return -1;
-}
-
-/** @brief Set OSS volume */
-static int oss_set(int *left, int *right) {
- int ch, fd, r;
-
- if(config->mixer
- && config->channel
- && (ch = mixer_channel(config->channel)) != -1) {
- if((fd = oss_do_open()) < 0)
- return -1;
- r = (*left & 0xff) + (*right & 0xff) * 256;
- if(ioctl(fd, SOUND_MIXER_WRITE(ch), &r) == -1) {
- error(errno, "error changing %s channel %s",
- config->mixer, config->channel);
- xclose(fd);
- return -1;
- }
- if(oss_do_get(left, right, fd, ch) < 0) {
- xclose(fd);
- return -1;
- }
- xclose(fd);
- return 0;
- } else
- return -1;
-}
-
-/** @brief OSS mixer vtable */
-const struct mixer mixer_oss = {
- BACKEND_OSS,
- oss_get,
- oss_set,
- "/dev/mixer",
- "pcm"
-};
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer.c
- * @brief Mixer support
- */
-
-#include "common.h"
-
-#include "configuration.h"
-#include "mixer.h"
-#include "log.h"
-#include "mem.h"
-
-/** @brief Whether lack of volume support has been reported yet */
-static int none_reported;
-
-/** @brief Get/set volume stub if volume control is not supported */
-static int none_get_set(int attribute((unused)) *left,
- int attribute((unused)) *right) {
- if(!none_reported) {
- error(0, "don't know how to get/set volume with this api");
- none_reported = 1;
- }
- return -1;
-}
-
-/** @brief Stub mixer control */
-static const struct mixer mixer_none = {
- -1,
- none_get_set,
- none_get_set,
- "",
- ""
-};
-
-/** @brief Table of mixer definitions */
-static const struct mixer *mixers[] = {
-#if HAVE_SYS_SOUNDCARD_H
- &mixer_oss,
-#endif
-#if HAVE_ALSA_ASOUNDLIB_H
- &mixer_alsa,
-#endif
- &mixer_none /* make sure array is never empty */
-};
-
-/** @brief Number of mixer definitions */
-#define NMIXERS (sizeof mixers / sizeof *mixers)
-
-/** @brief Find the mixer definition */
-static const struct mixer *find_mixer(int api) {
- size_t n;
-
- if(api == -1)
- api = config->api;
- for(n = 0; n < NMIXERS; ++n)
- if(mixers[n]->api == api)
- return mixers[n];
- return &mixer_none;
-}
-
-/** @brief Return true if we know how to drive the mixer
- * @param api Sound api or -1 for default
- * @return true if suppored, false otherwise
- */
-int mixer_supported(int api) {
- const struct mixer *const m = find_mixer(api);
- return m != &mixer_none;
-}
-
-/** @brief Get/set volume
- * @param api Sound api or -1 for default
- * @param left Left channel level, 0-100
- * @param right Right channel level, 0-100
- * @param set Set volume if non-0
- * @return 0 on success, non-0 on error
- *
- * If getting the volume then @p left and @p right are filled in.
- *
- * If setting the volume then the target levels are read from @p left and
- * @p right, and the actual level set is stored in them.
- */
-int mixer_control(int api, int *left, int *right, int set) {
- const struct mixer *const m = find_mixer(api);
-
- /* We impose defaults bizarrely late, but this has the advantage of
- * not making everything depend on sound libraries */
- if(!config->mixer)
- config->mixer = xstrdup(m->device);
- if(!config->channel)
- config->channel = xstrdup(m->channel);
- if(set)
- return m->set(left, right);
- else
- return m->get(left, right);
-}
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2004, 2007 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer.h
- * @brief Mixer support
- */
-
-#ifndef MIXER_H
-#define MIXER_H
-
-/** @brief Definition of a mixer */
-struct mixer {
- /** @brief API used by this mixer */
- int api;
-
- /** @brief Get the volume
- * @param left Where to store left-channel volume
- * @param right Where to store right-channel volume
- * @return 0 on success, non-0 on error
- */
- int (*get)(int *left, int *right);
-
- /** @brief Set the volume
- * @param left Pointer to target left-channel volume
- * @param right Pointer to target right-channel volume
- * @return 0 on success, non-0 on error
- *
- * @p left and @p right are updated with the actual volume set.
- */
- int (*set)(int *left, int *right);
-
- /** @brief Default device */
- const char *device;
-
- /** @brief Default channel */
- const char *channel;
-};
-
-int mixer_control(int api, int *left, int *right, int set);
-const char *mixer_default_device(int api);
-const char *mixer_default_channel(int api);
-int mixer_supported(int api);
-
-extern const struct mixer mixer_oss;
-extern const struct mixer mixer_alsa;
-
-#endif /* MIXER_H */
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
static const char *const alsa_options[] = {
"device",
+ "mixer-control",
+ "mixer-channel",
NULL
};
+/** @brief Mixer handle */
+snd_mixer_t *alsa_mixer_handle;
+
+/** @brief Mixer control */
+static snd_mixer_elem_t *alsa_mixer_elem;
+
+/** @brief Left channel */
+static snd_mixer_selem_channel_id_t alsa_mixer_left;
+
+/** @brief Right channel */
+static snd_mixer_selem_channel_id_t alsa_mixer_right;
+
+/** @brief Minimum level */
+static long alsa_mixer_min;
+
+/** @brief Maximum level */
+static long alsa_mixer_max;
+
/** @brief Actually play sound via ALSA */
static size_t alsa_play(void *buffer, size_t samples) {
int err;
/** @brief Open the ALSA sound device */
static void alsa_open(void) {
- const char *device = uaudio_get("device");
+ const char *device = uaudio_get("device", "default");
int err;
- if(!device || !*device)
- device = "default";
if((err = snd_pcm_open(&alsa_pcm,
device,
SND_PCM_STREAM_PLAYBACK,
alsa_pcm = 0;
}
+/** @brief Convert a level to a percentage */
+static int to_percent(long n) {
+ return (n - alsa_mixer_min) * 100 / (alsa_mixer_max - alsa_mixer_min);
+}
+
+static void alsa_open_mixer(void) {
+ int err;
+ snd_mixer_selem_id_t *id;
+ const char *device = uaudio_get("device", "default");
+ const char *mixer = uaudio_get("mixer-control", "0");
+ const char *channel = uaudio_get("mixer-channel", "PCM");
+
+ snd_mixer_selem_id_alloca(&id);
+ if((err = snd_mixer_open(&alsa_mixer_handle, 0)))
+ fatal(0, "snd_mixer_open: %s", snd_strerror(err));
+ if((err = snd_mixer_attach(alsa_mixer_handle, config->device)))
+ fatal(0, "snd_mixer_attach %s: %s", config->device, snd_strerror(err));
+ if((err = snd_mixer_selem_register(alsa_mixer_handle,
+ 0/*options*/, 0/*classp*/)))
+ fatal(0, "snd_mixer_selem_register %s: %s",
+ device, snd_strerror(err));
+ if((err = snd_mixer_load(alsa_mixer_handle)))
+ fatal(0, "snd_mixer_load %s: %s", device, snd_strerror(err));
+ snd_mixer_selem_id_set_name(id, channel);
+ snd_mixer_selem_id_set_index(id, atoi(mixer));
+ if(!(alsa_mixer_elem = snd_mixer_find_selem(alsa_mixer_handle, id)))
+ fatal(0, "device '%s' mixer control '%s,%s' does not exist",
+ device, channel, mixer);
+ if(!snd_mixer_selem_has_playback_volume(alsa_mixer_elem))
+ fatal(0, "device '%s' mixer control '%s,%s' has no playback volume",
+ device, channel, mixer);
+ if(snd_mixer_selem_is_playback_mono(alsa_mixer_elem)) {
+ alsa_mixer_left = alsa_mixer_right = SND_MIXER_SCHN_MONO;
+ } else {
+ alsa_mixer_left = SND_MIXER_SCHN_FRONT_LEFT;
+ alsa_mixer_right = SND_MIXER_SCHN_FRONT_RIGHT;
+ }
+ if(!snd_mixer_selem_has_playback_channel(alsa_mixer_elem,
+ alsa_mixer_left)
+ || !snd_mixer_selem_has_playback_channel(alsa_mixer_elem,
+ alsa_mixer_right))
+ fatal(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
+ device, channel, mixer);
+ snd_mixer_selem_get_playback_volume_range(alsa_mixer_elem,
+ &alsa_mixer_min, &alsa_mixer_max);
+
+}
+
+static void alsa_close_mixer(void) {
+ /* TODO alsa_mixer_elem */
+ if(alsa_mixer_handle)
+ snd_mixer_close(alsa_mixer_handle);
+}
+
+static void alsa_get_volume(int *left, int *right) {
+ long l, r;
+ int err;
+
+ if((err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+ alsa_mixer_left, &l))
+ || (err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+ alsa_mixer_right, &r)))
+ fatal(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
+ *left = to_percent(l);
+ *right = to_percent(r);
+}
+
+static void alsa_set_volume(int *left, int *right) {
+ long l, r;
+ int err;
+
+ /* Set the volume */
+ if(alsa_mixer_left == alsa_mixer_right) {
+ /* Mono output - just use the loudest */
+ if((err = snd_mixer_selem_set_playback_volume
+ (alsa_mixer_elem, alsa_mixer_left,
+ from_percent(*left > *right ? *left : *right))))
+ fatal(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
+ } else {
+ /* Stereo output */
+ if((err = snd_mixer_selem_set_playback_volume
+ (alsa_mixer_elem, alsa_mixer_left, from_percent(*left)))
+ || (err = snd_mixer_selem_set_playback_volume
+ (alsa_mixer_elem, alsa_mixer_right, from_percent(*right))))
+ fatal(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
+ }
+ /* Read it back to see what we ended up at */
+ if((err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+ alsa_mixer_left, &l))
+ || (err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+ alsa_mixer_right, &r)))
+ fatal(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
+ *left = to_percent(l);
+ *right = to_percent(r);
+}
+
const struct uaudio uaudio_alsa = {
.name = "alsa",
.options = alsa_options,
.start = alsa_start,
.stop = alsa_stop,
.activate = alsa_activate,
- .deactivate = alsa_deactivate
+ .deactivate = alsa_deactivate,
+ .open_mixer = alsa_open_mixer,
+ .close_mixer = alsa_close_mixer,
+ .get_volume = alsa_get_volume,
+ .set_volume = alsa_set_volume,
};
#endif
#include "common.h"
#include "uaudio.h"
+#include "log.h"
/** @brief List of known APIs
*
* The first one will be used as a default, so putting ALSA before OSS
* constitutes a policy decision.
*/
-const struct uaudio *const uaudio_apis[] = {
+const struct uaudio *const uaudio_apis[] = {
#if HAVE_COREAUDIO_AUDIOHARDWARE_H
&uaudio_coreaudio,
#endif
NULL,
};
+/** @brief Look up an audio API by name */
+const struct uaudio *uaudio_find(const char *name) {
+ int n;
+
+ for(n = 0; uaudio_apis[n]; ++n)
+ if(!strcmp(uaudio_apis[n]->name, name))
+ return uaudio_apis[n];
+ if(!strcmp(name, "network"))
+ return &uaudio_rtp;
+ fatal(0, "cannot find audio API '%s'", name);
+}
+
/*
Local Variables:
c-basic-offset:2
int pfd[2];
const char *command;
- if(!(command = uaudio_get("command")))
+ if(!(command = uaudio_get("command", NULL)))
fatal(0, "'command' not set");
xpipe(pfd);
command_pid = xfork();
uaudio_bits);
coreaudio_callback = callback;
coreaudio_userdata = userdata;
- device = uaudio_get("device");
+ device = uaudio_get("device", "default");
coreaudio_adid = coreaudio_getdevice(device);
/* Get the device properties */
propertySize = sizeof asbd;
# endif
#endif
+/* documentation does not match implementation! */
+#ifndef SOUND_MIXER_READ
+# define SOUND_MIXER_READ(x) MIXER_READ(x)
+#endif
+#ifndef SOUND_MIXER_WRITE
+# define SOUND_MIXER_WRITE(x) MIXER_WRITE(x)
+#endif
+
static int oss_fd = -1;
+static int oss_mixer_fd = -1;
+static int oss_mixer_channel;
static const char *const oss_options[] = {
"device",
+ "mixer-device",
+ "mixer-channel",
NULL
};
/** @brief Open the OSS sound device */
static void oss_open(void) {
- const char *device = uaudio_get("device");
+ const char *device = uaudio_get("device", NULL);
#if EMPEG_HOST
if(!device || !*device || !strcmp(device, "default"))
uaudio_thread_stop();
}
+/** @brief Channel names */
+static const char *oss_channels[] = SOUND_DEVICE_NAMES;
+
+static int oss_mixer_find_channel(const char *channel) {
+ if(!channel[strspn(c, "0123456789")])
+ return atoi(channel);
+ else {
+ for(int n = 0; n < sizeof oss_channels / sizeof *oss_channels; ++n)
+ if(!strcmp(oss_channels[n], channels))
+ return n;
+ return -1;
+ }
+}
+
+static void oss_open_mixer(void) {
+ const char *mixer = uaudio_get("mixer-device", "/dev/mixer");
+ /* TODO infer mixer-device from device */
+ if((oss_mixer_fd = open(mixer, O_RDWR, 0)) < 0)
+ fatal(errno, "error opening %s", mixer);
+ const char *channel = uaudio_get("mixer-channel", "pcm");
+ oss_mixer_channel = oss_mixer_find_channel(channel);
+ if(oss_mixer_channel < 0)
+ fatal(0, "no such channel as '%s'", channel);
+}
+
+static void oss_close_mixer(void) {
+ close(oss_mixer_fd);
+ oss_mixer_fd = -1;
+}
+
+static void oss_get_volume(int *left, int *right) {
+ int r;
+
+ *left = *right = 0;
+ if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(ch), &r) < 0)
+ error(errno, "error getting volume");
+ else {
+ *left = r & 0xff;
+ *right = (r >> 8) & 0xff;
+ }
+}
+
+static void oss_set_volume(int *left, int *right) {
+ int r = (*left & 0xff) + (*right & 0xff) * 256;
+ if(ioctl(fd, SOUND_MIXER_WRITE(ch), &r) == -1)
+ error(errno, "error setting volume");
+ else if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(ch), &r) < 0)
+ error(errno, "error getting volume");
+ else {
+ *left = r & 0xff;
+ *right = (r >> 8) & 0xff;
+ }
+}
+
const struct uaudio uaudio_oss = {
.name = "oss",
.options = oss_options,
.start = oss_start,
.stop = oss_stop,
.activate = oss_activate,
- .deactivate = oss_deactivate
+ .deactivate = oss_deactivate,
+ .open_mixer = oss_open_mixer,
+ .close_mixer = oss_close_mixer,
+ .get_volume = oss_get_volume,
+ .set_volume = oss_set_volume,
};
#endif
socklen_t len;
char *sockname, *ssockname;
struct stringlist dst, src;
- const char *delay;
/* Get configuration */
dst.n = 2;
dst.s = xcalloc(2, sizeof *dst.s);
- dst.s[0] = uaudio_get("rtp-destination");
- dst.s[1] = uaudio_get("rtp-destination-port");
+ dst.s[0] = uaudio_get("rtp-destination", NULL);
+ dst.s[1] = uaudio_get("rtp-destination-port", NULL);
src.n = 2;
src.s = xcalloc(2, sizeof *dst.s);
- src.s[0] = uaudio_get("rtp-source");
- src.s[1] = uaudio_get("rtp-source-port");
+ src.s[0] = uaudio_get("rtp-source", NULL);
+ src.s[1] = uaudio_get("rtp-source-port", NULL);
if(!dst.s[0])
fatal(0, "'rtp-destination' not set");
if(!dst.s[1])
src.n = 2;
} else
src.n = 0;
- if((delay = uaudio_get("rtp-delay-threshold")))
- rtp_delay_threshold = atoi(delay);
- else
- rtp_delay_threshold = 1000; /* microseconds */
+ rtp_delay_threshold = atoi(uaudio_get("rtp-delay-threshold", "1000"));
+ /* ...microseconds */
/* Resolve addresses */
res = get_address(&dst, &pref, &sockname);
fatal(errno, "error creating broadcast socket");
if(multicast(res->ai_addr)) {
/* Enable multicast options */
- const char *ttls = uaudio_get("multicast-ttl");
- const int ttl = ttls ? atoi(ttls) : 1;
- const char *loops = uaudio_get("multicast-loop");
- const int loop = loops ? !strcmp(loops, "yes") : 1;
+ const int ttl = atoi(uaudio_get("multicast-ttl", "1"));
+ const int loop = !strcmp(uaudio_get("multicast-loop", "yes"), "yes");
switch(res->ai_family) {
case PF_INET: {
if(setsockopt(rtp_fd, IPPROTO_IP, IP_MULTICAST_TTL,
hash_add(uaudio_options, name, &value, HASH_INSERT_OR_REPLACE);
}
-/** @brief Set a uaudio option */
-char *uaudio_get(const char *name) {
+/** @brief Get a uaudio option */
+char *uaudio_get(const char *name, const char *default_value) {
const char *value = (uaudio_options ?
*(char **)hash_find(uaudio_options, name)
- : NULL);
+ : default_value);
return value ? xstrdup(value) : NULL;
}
*/
void (*deactivate)(void);
+ /** @brief Open mixer device */
+ void (*open_mixer)(void);
+
+ /** @brief Closer mixer device */
+ void (*close_mixer)(void);
+
+ /** @brief Get volume
+ * @param left Where to put the left-channel value
+ * @param right Where to put the right-channel value
+ *
+ * 0 is silent and 100 is maximum volume.
+ */
+ void (*get_volume)(int *left, int *right);
+
+ /** @brief Set volume
+ * @param left Pointer to left-channel value (updated)
+ * @param right Pointer to right-channel value (updated)
+ *
+ * The values are updated with those actually set by the underlying system
+ * call.
+ *
+ * 0 is silent and 100 is maximum volume.
+ */
+ void (*set_volume)(int *left, int *right);
+
};
void uaudio_set_format(int rate, int channels, int samplesize, int signed_);
void uaudio_set(const char *name, const char *value);
-char *uaudio_get(const char *name);
+char *uaudio_get(const char *name, const char *default_value);
void uaudio_thread_start(uaudio_callback *callback,
void *userdata,
uaudio_playcallback *playcallback,
void uaudio_schedule_synchronize(void);
void uaudio_schedule_update(size_t written_samples);
void uaudio_schedule_init(void);
+const struct uaudio *uaudio_find(const char *name);
extern uint64_t uaudio_schedule_timestamp;
extern int uaudio_schedule_reactivated;
#
# This file is part of DisOrder.
-# Copyright (C) 2004-2008 Richard Kettlewell
+# Copyright (C) 2004-2009 Richard Kettlewell
#
# 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
schedule.c exports.c ../lib/memgc.c disorder-server.h
disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
- $(LIBASOUND)
+ $(LIBASOUND) $(COREAUDIO)
disorderd_LDFLAGS=-export-dynamic
disorderd_DEPENDENCIES=../lib/libdisorder.a
$(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
disorder_deadlock_DEPENDENCIES=../lib/libdisorder.a
-disorder_speaker_SOURCES=speaker.c speaker.h \
- speaker-command.c \
- speaker-network.c \
- speaker-coreaudio.c \
- speaker-oss.c \
- speaker-alsa.c
+disorder_speaker_SOURCES=speaker.c
disorder_speaker_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
$(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO)
disorder_speaker_DEPENDENCIES=../lib/libdisorder.a
/*
* This file is part of DisOrder
- * Copyright (C) 2008 Richard Kettlewell
+ * Copyright (C) 2008, 2009 Richard Kettlewell
*
* 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
#include "logfd.h"
#include "mem.h"
#include "mime.h"
-#include "mixer.h"
#include "printf.h"
#include "queue.h"
#include "random.h"
#include "trackdb-int.h"
#include "trackdb.h"
#include "trackname.h"
+#include "uaudio.h"
#include "unicode.h"
#include "user.h"
#include "vector.h"
#include "version.h"
#include "wstat.h"
+extern const struct uaudio *api;
+
void daemonize(const char *tag, int fac, const char *pidfile);
/* Go into background. Send stdout/stderr to syslog.
* If @pri@ is non-null, it should be "facility.level"
/*
* This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
*
* 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
int l, r;
char lb[32], rb[32];
- if(!mixer_control(-1/*as configured*/, &l, &r, 0)) {
+ if(api && api->get_volume) {
+ api->get_volume(&l, &r);
if(l != volume_left || r != volume_right) {
volume_left = l;
volume_right = r;
ev = ev_new();
if(ev_child_setup(ev)) fatal(0, "ev_child_setup failed");
/* read config */
+ config_uaudio_apis = uaudio_apis;
if(config_read(1))
fatal(0, "cannot read configuration");
/* make sure the home directory exists and has suitable permissions */
/*
* This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
*
* 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
sink_writes(ev_writer_sink(c->w), "510 Prohibited\n");
return 1;
}
- if(mixer_control(-1/*as configured*/, &l, &r, set))
+ if(!api || !api->set_volume) {
sink_writes(ev_writer_sink(c->w), "550 error accessing mixer\n");
- else {
- sink_printf(ev_writer_sink(c->w), "252 %d %d\n", l, r);
- if(l != volume_left || r != volume_right) {
- volume_left = l;
- volume_right = r;
- snprintf(lb, sizeof lb, "%d", l);
- snprintf(rb, sizeof rb, "%d", r);
- eventlog("volume", lb, rb, (char *)0);
- }
+ return 1;
+ }
+ api->set_volume(&l, &r);
+ sink_printf(ev_writer_sink(c->w), "252 %d %d\n", l, r);
+ if(l != volume_left || r != volume_right) {
+ volume_left = l;
+ volume_right = r;
+ snprintf(lb, sizeof lb, "%d", l);
+ snprintf(rb, sizeof rb, "%d", r);
+ eventlog("volume", lb, rb, (char *)0);
}
return 1;
}
static int c_rtp_address(struct conn *c,
char attribute((unused)) **vec,
int attribute((unused)) nvec) {
- if(config->api == BACKEND_NETWORK) {
+ if(api == &uaudio_rtp) {
sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
quoteutf8(config->broadcast.s[0]),
quoteutf8(config->broadcast.s[1]));
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005, 2006, 2007 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-alsa.c
- * @brief Support for @ref BACKEND_ALSA */
-
-#include "common.h"
-
-#if HAVE_ALSA_ASOUNDLIB_H
-
-#include <unistd.h>
-#include <poll.h>
-#include <alsa/asoundlib.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief The current PCM handle */
-static snd_pcm_t *pcm;
-
-/** @brief Last seen buffer size */
-static snd_pcm_uframes_t last_pcm_bufsize;
-
-/** @brief ALSA backend initialization */
-static void alsa_init(void) {
- info("selected ALSA backend");
-}
-
-/** @brief Log ALSA parameters */
-static void log_params(snd_pcm_hw_params_t *hwparams,
- snd_pcm_sw_params_t *swparams) {
- snd_pcm_uframes_t f;
-
- return; /* too verbose */
- if(hwparams) {
- /* TODO */
- }
- if(swparams) {
- snd_pcm_sw_params_get_silence_size(swparams, &f);
- info("sw silence_size=%lu", (unsigned long)f);
- snd_pcm_sw_params_get_silence_threshold(swparams, &f);
- info("sw silence_threshold=%lu", (unsigned long)f);
-#if HAVE_SND_PCM_SW_PARAMS_GET_SLEEP_MIN
- {
- unsigned u;
-
- snd_pcm_sw_params_get_sleep_min(swparams, &u);
- info("sw sleep_min=%lu", (unsigned long)u);
- }
-#endif
- snd_pcm_sw_params_get_start_threshold(swparams, &f);
- info("sw start_threshold=%lu", (unsigned long)f);
- snd_pcm_sw_params_get_stop_threshold(swparams, &f);
- info("sw stop_threshold=%lu", (unsigned long)f);
-#if HAVE_SND_PCM_SW_PARAMS_GET_XFER_ALIGN
- snd_pcm_sw_params_get_xfer_align(swparams, &f);
- info("sw xfer_align=%lu", (unsigned long)f);
-#endif
- }
-}
-
-/** @brief ALSA deactivation */
-static void alsa_deactivate(void) {
- if(pcm) {
- int err;
-
- if((err = snd_pcm_nonblock(pcm, 0)) < 0)
- fatal(0, "error calling snd_pcm_nonblock: %d", err);
- D(("draining pcm"));
- snd_pcm_drain(pcm);
- D(("closing pcm"));
- snd_pcm_close(pcm);
- pcm = 0;
- device_state = device_closed;
- D(("released audio device"));
- }
-}
-
-/** @brief ALSA backend activation */
-static void alsa_activate(void) {
- if(!pcm) {
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_sw_params_t *swparams;
- snd_pcm_uframes_t pcm_bufsize;
- int err;
- int sample_format = 0;
- unsigned rate;
-
- D(("snd_pcm_open"));
- if((err = snd_pcm_open(&pcm,
- config->device,
- SND_PCM_STREAM_PLAYBACK,
- SND_PCM_NONBLOCK))) {
- error(0, "error from snd_pcm_open: %d", err);
- goto error;
- }
- snd_pcm_hw_params_alloca(&hwparams);
- D(("set up hw params"));
- if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
- fatal(0, "error from snd_pcm_hw_params_any: %d", err);
- if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
- SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
- fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
- switch(config->sample_format.bits) {
- case 8:
- sample_format = SND_PCM_FORMAT_S8;
- break;
- case 16:
- switch(config->sample_format.endian) {
- case ENDIAN_LITTLE: sample_format = SND_PCM_FORMAT_S16_LE; break;
- case ENDIAN_BIG: sample_format = SND_PCM_FORMAT_S16_BE; break;
- default:
- error(0, "unrecognized byte format %d", config->sample_format.endian);
- goto fatal;
- }
- break;
- default:
- error(0, "unsupported sample size %d", config->sample_format.bits);
- goto fatal;
- }
- if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
- sample_format)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_format (%d): %d",
- sample_format, err);
- goto fatal;
- }
- rate = config->sample_format.rate;
- if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
- config->sample_format.rate, err);
- goto fatal;
- }
- if(rate != (unsigned)config->sample_format.rate)
- info("want rate %d, got %u", config->sample_format.rate, rate);
- if((err = snd_pcm_hw_params_set_channels
- (pcm, hwparams, config->sample_format.channels)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
- config->sample_format.channels, err);
- goto fatal;
- }
- pcm_bufsize = 3 * FRAMES;
- if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams,
- &pcm_bufsize)) < 0)
- fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%d): %d",
- 3 * FRAMES, err);
- if(pcm_bufsize != 3 * FRAMES && pcm_bufsize != last_pcm_bufsize)
- info("asked for PCM buffer of %d frames, got %d",
- 3 * FRAMES, (int)pcm_bufsize);
- last_pcm_bufsize = pcm_bufsize;
- if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
- fatal(0, "error calling snd_pcm_hw_params: %d", err);
- D(("set up sw params"));
- snd_pcm_sw_params_alloca(&swparams);
- if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0)
- fatal(0, "error calling snd_pcm_sw_params_current: %d", err);
- if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, FRAMES)) < 0)
- fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d",
- FRAMES, err);
- if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
- fatal(0, "error calling snd_pcm_sw_params: %d", err);
- D(("acquired audio device"));
- log_params(hwparams, swparams);
- device_state = device_open;
- }
- return;
-fatal:
- abandon();
-error:
- /* We assume the error is temporary and that we'll retry in a bit. */
- if(pcm) {
- snd_pcm_close(pcm);
- pcm = 0;
- device_state = device_error;
- }
- return;
-}
-
-/** @brief Play via ALSA */
-static size_t alsa_play(size_t frames) {
- snd_pcm_sframes_t pcm_written_frames;
- int err;
-
- pcm_written_frames = snd_pcm_writei(pcm,
- playing->buffer + playing->start,
- frames);
- D(("actually play %zu frames, wrote %d",
- frames, (int)pcm_written_frames));
- if(pcm_written_frames < 0) {
- switch(pcm_written_frames) {
- case -EPIPE: /* underrun */
- error(0, "snd_pcm_writei reports underrun");
- if((err = snd_pcm_prepare(pcm)) < 0)
- fatal(0, "error calling snd_pcm_prepare: %d", err);
- return 0;
- case -EAGAIN:
- return 0;
- default:
- fatal(0, "error calling snd_pcm_writei: %d",
- (int)pcm_written_frames);
- }
- } else
- return pcm_written_frames;
-}
-
-static int alsa_slots, alsa_nslots = -1;
-
-/** @brief Fill in poll fd array for ALSA */
-static void alsa_beforepoll(int attribute((unused)) *timeoutp) {
- /* We send sample data to ALSA as fast as it can accept it, relying on
- * the fact that it has a relatively small buffer to minimize pause
- * latency. */
- int retry = 3, err;
-
- alsa_slots = fdno;
- do {
- retry = 0;
- alsa_nslots = snd_pcm_poll_descriptors(pcm, &fds[fdno], NFDS - fdno);
- if((alsa_nslots <= 0
- || !(fds[alsa_slots].events & POLLOUT))
- && snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
- error(0, "underrun detected after call to snd_pcm_poll_descriptors()");
- if((err = snd_pcm_prepare(pcm)))
- fatal(0, "error calling snd_pcm_prepare: %d", err);
- } else
- break;
- } while(retry-- > 0);
- if(alsa_nslots >= 0)
- fdno += alsa_nslots;
-}
-
-/** @brief Process poll() results for ALSA */
-static int alsa_ready(void) {
- int err;
-
- unsigned short alsa_revents;
-
- if((err = snd_pcm_poll_descriptors_revents(pcm,
- &fds[alsa_slots],
- alsa_nslots,
- &alsa_revents)) < 0)
- fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err);
- if(alsa_revents & (POLLOUT | POLLERR))
- return 1;
- else
- return 0;
-}
-
-const struct speaker_backend alsa_backend = {
- BACKEND_ALSA,
- 0,
- alsa_init,
- alsa_activate,
- alsa_play,
- alsa_deactivate,
- alsa_beforepoll,
- alsa_ready
-};
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005, 2006, 2007 Richard Kettlewell
- * Portions (C) 2007 Mark Wooding
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-command.c
- * @brief Support for @ref BACKEND_COMMAND */
-
-#include "common.h"
-
-#include <unistd.h>
-#include <poll.h>
-#include <errno.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief Pipe to subprocess
- *
- * This is the file descriptor to write to for @ref BACKEND_COMMAND.
- */
-static int cmdfd = -1;
-
-/** @brief poll array slot for @ref cmdfd
- *
- * Set by command_beforepoll().
- */
-static int cmdfd_slot;
-
-/** @brief Start the subprocess for @ref BACKEND_COMMAND */
-static void fork_cmd(void) {
- pid_t cmdpid;
- int pfd[2];
- if(cmdfd != -1) close(cmdfd);
- xpipe(pfd);
- cmdpid = xfork();
- if(!cmdpid) {
- exitfn = _exit;
- signal(SIGPIPE, SIG_DFL);
- xdup2(pfd[0], 0);
- close(pfd[0]);
- close(pfd[1]);
- execl("/bin/sh", "sh", "-c", config->speaker_command, (char *)0);
- fatal(errno, "error execing /bin/sh");
- }
- close(pfd[0]);
- cmdfd = pfd[1];
- D(("forked cmd %d, fd = %d", cmdpid, cmdfd));
-}
-
-/** @brief Command backend initialization */
-static void command_init(void) {
- info("selected command backend");
- fork_cmd();
-}
-
-/** @brief Play to a subprocess */
-static size_t command_play(size_t frames) {
- size_t bytes = frames * bpf;
- int written_bytes;
-
- written_bytes = write(cmdfd, playing->buffer + playing->start, bytes);
- D(("actually play %zu bytes, wrote %d",
- bytes, written_bytes));
- if(written_bytes < 0) {
- switch(errno) {
- case EPIPE:
- error(0, "hmm, command died; trying another");
- fork_cmd();
- return 0;
- case EAGAIN:
- return 0;
- default:
- fatal(errno, "error writing to subprocess");
- }
- } else
- return written_bytes / bpf;
-}
-
-/** @brief Update poll array for writing to subprocess */
-static void command_beforepoll(int attribute((unused)) *timeoutp) {
- /* We send sample data to the subprocess as fast as it can accept it.
- * This isn't ideal as pause latency can be very high as a result. */
- if(cmdfd >= 0)
- cmdfd_slot = addfd(cmdfd, POLLOUT);
-}
-
-/** @brief Process poll() results for subprocess play */
-static int command_ready(void) {
- if(fds[cmdfd_slot].revents & (POLLOUT | POLLERR))
- return 1;
- else
- return 0;
-}
-
-const struct speaker_backend command_backend = {
- BACKEND_COMMAND,
- 0,
- command_init,
- 0, /* activate */
- command_play,
- 0, /* deactivate */
- command_beforepoll,
- command_ready
-};
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-coreaudio.c
- * @brief Support for @ref BACKEND_COREAUDIO
- *
- * Core Audio likes to make callbacks from a separate player thread
- * which then fill in the required number of bytes of audio. We fit
- * this into the existing architecture by means of a pipe between the
- * threads.
- *
- * We currently only support 16-bit 44100Hz stereo (and enforce this
- * in @ref lib/configuration.c.) There are some nasty bodges in this
- * code which depend on this and on further assumptions still...
- *
- * @todo support @ref config::device
- */
-
-#include "common.h"
-
-#if HAVE_COREAUDIO_AUDIOHARDWARE_H
-
-#include <poll.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-#include "coreaudio.h"
-
-/** @brief Core Audio Device ID */
-static AudioDeviceID adid;
-
-/** @brief Pipe between main and player threads
- *
- * We'll write samples to pfd[1] and read them from pfd[0].
- */
-static int pfd[2];
-
-/** @brief Slot number in poll array */
-static int pfd_slot;
-
-/** @brief Callback from Core Audio */
-static OSStatus adioproc
- (AudioDeviceID attribute((unused)) inDevice,
- const AudioTimeStamp attribute((unused)) *inNow,
- const AudioBufferList attribute((unused)) *inInputData,
- const AudioTimeStamp attribute((unused)) *inInputTime,
- AudioBufferList *outOutputData,
- const AudioTimeStamp attribute((unused)) *inOutputTime,
- void attribute((unused)) *inClientData) {
- UInt32 nbuffers = outOutputData->mNumberBuffers;
- AudioBuffer *ab = outOutputData->mBuffers;
-
- while(nbuffers > 0) {
- float *samplesOut = ab->mData;
- size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
- int16_t input[1024], *ptr;
- size_t bytes;
- ssize_t bytes_read;
- size_t samples;
-
- while(samplesOutLeft > 0) {
- /* Read some more data */
- bytes = samplesOutLeft * sizeof (int16_t);
- if(bytes > sizeof input)
- bytes = sizeof input;
-
- bytes_read = read(pfd[0], input, bytes);
- if(bytes_read < 0)
- switch(errno) {
- case EINTR:
- continue; /* just try again */
- case EAGAIN:
- return 0; /* underrun - just play 0s */
- default:
- fatal(errno, "read error in core audio thread");
- }
- assert(bytes_read % 4 == 0); /* TODO horrible bodge! */
- samples = bytes_read / sizeof (int16_t);
- assert(samples <= samplesOutLeft);
- ptr = input;
- samplesOutLeft -= samples;
- while(samples-- > 0)
- *samplesOut++ = *ptr++ * (0.5 / 32767);
- }
- ++ab;
- --nbuffers;
- }
- return 0;
-}
-
-/** @brief Core Audio backend initialization */
-static void coreaudio_init(void) {
- OSStatus status;
- UInt32 propertySize;
- AudioStreamBasicDescription asbd;
-
- adid = coreaudio_getdevice(config->device);
- propertySize = sizeof asbd;
- status = AudioDeviceGetProperty(adid, 0, false,
- kAudioDevicePropertyStreamFormat,
- &propertySize, &asbd);
- if(status)
- fatal(0, "AudioHardwareGetProperty: %d", (int)status);
- D(("mSampleRate %f", asbd.mSampleRate));
- D(("mFormatID %08lx", asbd.mFormatID));
- D(("mFormatFlags %08lx", asbd.mFormatFlags));
- D(("mBytesPerPacket %08lx", asbd.mBytesPerPacket));
- D(("mFramesPerPacket %08lx", asbd.mFramesPerPacket));
- D(("mBytesPerFrame %08lx", asbd.mBytesPerFrame));
- D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
- D(("mBitsPerChannel %08lx", asbd.mBitsPerChannel));
- D(("mReserved %08lx", asbd.mReserved));
- if(asbd.mFormatID != kAudioFormatLinearPCM)
- fatal(0, "audio device does not support kAudioFormatLinearPCM");
- status = AudioDeviceAddIOProc(adid, adioproc, 0);
- if(status)
- fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
- if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfd) < 0)
- fatal(errno, "error calling socketpair");
- nonblock(pfd[0]);
- nonblock(pfd[1]);
- info("selected Core Audio backend");
-}
-
-/** @brief Core Audio deactivation */
-static void coreaudio_deactivate(void) {
- const OSStatus status = AudioDeviceStop(adid, adioproc);
- if(status) {
- error(0, "AudioDeviceStop: %d", (int)status);
- device_state = device_error;
- } else
- device_state = device_closed;
-}
-
-/** @brief Core Audio backend activation */
-static void coreaudio_activate(void) {
- const OSStatus status = AudioDeviceStart(adid, adioproc);
-
- if(status) {
- error(0, "AudioDeviceStart: %d", (int)status);
- device_state = device_error;
- }
- device_state = device_open;
-}
-
-/** @brief Play via Core Audio */
-static size_t coreaudio_play(size_t frames) {
- static size_t leftover;
-
- size_t bytes = frames * bpf + leftover;
- ssize_t bytes_written;
-
- if(leftover)
- /* There is a partial frame left over from an earlier write. Try
- * and finish that off before doing anything else. */
- bytes = leftover;
- bytes_written = write(pfd[1], playing->buffer + playing->start, bytes);
- if(bytes_written < 0)
- switch(errno) {
- case EINTR: /* interrupted */
- case EAGAIN: /* buffer full */
- return 0; /* try later */
- default:
- fatal(errno, "error writing to core audio player thread");
- }
- if(leftover) {
- /* We were dealing the leftover bytes of a partial frame */
- leftover -= bytes_written;
- return !leftover;
- }
- leftover = bytes_written % bpf;
- return bytes_written / bpf;
-}
-
-/** @brief Fill in poll fd array for Core Audio */
-static void coreaudio_beforepoll(int attribute((unused)) *timeoutp) {
- pfd_slot = addfd(pfd[1], POLLOUT);
-}
-
-/** @brief Process poll() results for Core Audio */
-static int coreaudio_ready(void) {
- return !!(fds[pfd_slot].revents & (POLLOUT|POLLERR));
-}
-
-/** @brief Backend definition for Core Audio */
-const struct speaker_backend coreaudio_backend = {
- BACKEND_COREAUDIO,
- 0,
- coreaudio_init,
- coreaudio_activate,
- coreaudio_play,
- coreaudio_deactivate,
- coreaudio_beforepoll,
- coreaudio_ready
-};
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005-2008 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-network.c
- * @brief Support for @ref BACKEND_NETWORK */
-
-#include "common.h"
-
-#include <unistd.h>
-#include <poll.h>
-#include <netdb.h>
-#include <gcrypt.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-#include <net/if.h>
-#include <ifaddrs.h>
-#include <errno.h>
-#include <netinet/in.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "addr.h"
-#include "timeval.h"
-#include "rtp.h"
-#include "ifreq.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief Network socket
- *
- * This is the file descriptor to write to for @ref BACKEND_NETWORK.
- */
-static int bfd = -1;
-
-/** @brief RTP timestamp
- *
- * This counts the number of samples played (NB not the number of frames
- * played).
- *
- * The timestamp in the packet header is only 32 bits wide. With 44100Hz
- * stereo, that only gives about half a day before wrapping, which is not
- * particularly convenient for certain debugging purposes. Therefore the
- * timestamp is maintained as a 64-bit integer, giving around six million years
- * before wrapping, and truncated to 32 bits when transmitting.
- */
-static uint64_t rtp_time;
-
-/** @brief RTP base timestamp
- *
- * This is the real time correspoding to an @ref rtp_time of 0. It is used
- * to recalculate the timestamp after idle periods.
- */
-static struct timeval rtp_time_0;
-
-/** @brief RTP packet sequence number */
-static uint16_t rtp_seq;
-
-/** @brief RTP SSRC */
-static uint32_t rtp_id;
-
-/** @brief Error counter */
-static int audio_errors;
-
-/** @brief Network backend initialization */
-static void network_init(void) {
- struct addrinfo *res, *sres;
- static const struct addrinfo pref = {
- .ai_flags = 0,
- .ai_family = PF_INET,
- .ai_socktype = SOCK_DGRAM,
- .ai_protocol = IPPROTO_UDP,
- };
- static const struct addrinfo prefbind = {
- .ai_flags = AI_PASSIVE,
- .ai_family = PF_INET,
- .ai_socktype = SOCK_DGRAM,
- .ai_protocol = IPPROTO_UDP,
- };
- static const int one = 1;
- int sndbuf, target_sndbuf = 131072;
- socklen_t len;
- char *sockname, *ssockname;
-
- res = get_address(&config->broadcast, &pref, &sockname);
- if(!res) exit(-1);
- if(config->broadcast_from.n) {
- sres = get_address(&config->broadcast_from, &prefbind, &ssockname);
- if(!sres) exit(-1);
- } else
- sres = 0;
- if((bfd = socket(res->ai_family,
- res->ai_socktype,
- res->ai_protocol)) < 0)
- fatal(errno, "error creating broadcast socket");
- if(multicast(res->ai_addr)) {
- /* Multicasting */
- switch(res->ai_family) {
- case PF_INET: {
- const int mttl = config->multicast_ttl;
- if(setsockopt(bfd, IPPROTO_IP, IP_MULTICAST_TTL, &mttl, sizeof mttl) < 0)
- fatal(errno, "error setting IP_MULTICAST_TTL on multicast socket");
- if(setsockopt(bfd, IPPROTO_IP, IP_MULTICAST_LOOP,
- &config->multicast_loop, sizeof one) < 0)
- fatal(errno, "error setting IP_MULTICAST_LOOP on multicast socket");
- break;
- }
- case PF_INET6: {
- const int mttl = config->multicast_ttl;
- if(setsockopt(bfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
- &mttl, sizeof mttl) < 0)
- fatal(errno, "error setting IPV6_MULTICAST_HOPS on multicast socket");
- if(setsockopt(bfd, IPPROTO_IP, IPV6_MULTICAST_LOOP,
- &config->multicast_loop, sizeof (int)) < 0)
- fatal(errno, "error setting IPV6_MULTICAST_LOOP on multicast socket");
- break;
- }
- default:
- fatal(0, "unsupported address family %d", res->ai_family);
- }
- info("multicasting on %s", sockname);
- } else {
- struct ifaddrs *ifs;
-
- if(getifaddrs(&ifs) < 0)
- fatal(errno, "error calling getifaddrs");
- while(ifs) {
- /* (At least on Darwin) IFF_BROADCAST might be set but ifa_broadaddr
- * still a null pointer. It turns out that there's a subsequent entry
- * for he same interface which _does_ have ifa_broadaddr though... */
- if((ifs->ifa_flags & IFF_BROADCAST)
- && ifs->ifa_broadaddr
- && sockaddr_equal(ifs->ifa_broadaddr, res->ai_addr))
- break;
- ifs = ifs->ifa_next;
- }
- if(ifs) {
- if(setsockopt(bfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof one) < 0)
- fatal(errno, "error setting SO_BROADCAST on broadcast socket");
- info("broadcasting on %s (%s)", sockname, ifs->ifa_name);
- } else
- info("unicasting on %s", sockname);
- }
- len = sizeof sndbuf;
- if(getsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
- &sndbuf, &len) < 0)
- fatal(errno, "error getting SO_SNDBUF");
- if(target_sndbuf > sndbuf) {
- if(setsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
- &target_sndbuf, sizeof target_sndbuf) < 0)
- error(errno, "error setting SO_SNDBUF to %d", target_sndbuf);
- else
- info("changed socket send buffer size from %d to %d",
- sndbuf, target_sndbuf);
- } else
- info("default socket send buffer is %d",
- sndbuf);
- /* We might well want to set additional broadcast- or multicast-related
- * options here */
- if(sres && bind(bfd, sres->ai_addr, sres->ai_addrlen) < 0)
- fatal(errno, "error binding broadcast socket to %s", ssockname);
- if(connect(bfd, res->ai_addr, res->ai_addrlen) < 0)
- fatal(errno, "error connecting broadcast socket to %s", sockname);
- /* Select an SSRC */
- gcry_randomize(&rtp_id, sizeof rtp_id, GCRY_STRONG_RANDOM);
-}
-
-/** @brief Play over the network */
-static size_t network_play(size_t frames) {
- struct rtp_header header;
- struct iovec vec[2];
- size_t bytes = frames * bpf, written_frames;
- int written_bytes;
- /* We transmit using RTP (RFC3550) and attempt to conform to the internet
- * AVT profile (RFC3551). */
-
- /* If we're starting then initialize the base time */
- if(!rtp_time)
- xgettimeofday(&rtp_time_0, 0);
- if(idled) {
- /* There may have been a gap. Fix up the RTP time accordingly. */
- struct timeval now;
- uint64_t delta;
- uint64_t target_rtp_time;
-
- /* Find the current time */
- xgettimeofday(&now, 0);
- /* Find the number of microseconds elapsed since rtp_time=0 */
- delta = tvsub_us(now, rtp_time_0);
- if(delta > UINT64_MAX / 88200)
- fatal(0, "rtp_time=%"PRIu64" now=%ld.%06ld rtp_time_0=%ld.%06ld delta=%"PRIu64" (%"PRId64")",
- rtp_time,
- (long)now.tv_sec, (long)now.tv_usec,
- (long)rtp_time_0.tv_sec, (long)rtp_time_0.tv_usec,
- delta, delta);
- target_rtp_time = (delta * config->sample_format.rate
- * config->sample_format.channels) / 1000000;
- /* Overflows at ~6 years uptime with 44100Hz stereo */
-
- /* rtp_time is the number of samples we've played. NB that we play
- * RTP_AHEAD_MS ahead of ourselves, so it may legitimately be ahead of
- * the value we deduce from time comparison.
- *
- * Suppose we have 1s track started at t=0, and another track begins to
- * play at t=2s. Suppose 44100Hz stereo. We send 1s of audio over the
- * next (about) one second, giving rtp_time=88200. rtp_time stops at this
- * point.
- *
- * At t=2s we'll have calculated target_rtp_time=176400. In this case we
- * set rtp_time=176400 and the player can correctly conclude that it
- * should leave 1s between the tracks.
- *
- * It's never right to reduce rtp_time, for that would imply packets with
- * overlapping timestamp ranges, which does not make sense.
- */
- target_rtp_time &= ~(uint64_t)1; /* stereo! */
- if(target_rtp_time > rtp_time) {
- /* More time has elapsed than we've transmitted samples. That implies
- * we've been 'sending' silence. */
- info("advancing rtp_time by %"PRIu64" samples",
- target_rtp_time - rtp_time);
- rtp_time = target_rtp_time;
- } else if(target_rtp_time < rtp_time) {
- info("would reverse rtp_time by %"PRIu64" samples",
- rtp_time - target_rtp_time);
- }
- }
- header.vpxcc = 2 << 6; /* V=2, P=0, X=0, CC=0 */
- header.seq = htons(rtp_seq++);
- header.timestamp = htonl((uint32_t)rtp_time);
- header.ssrc = rtp_id;
- header.mpt = (idled ? 0x80 : 0x00) | 10;
- /* 10 = L16 = 16-bit x 2 x 44100KHz. We ought to deduce this value from
- * the sample rate (in a library somewhere so that configuration.c can rule
- * out invalid rates).
- */
- idled = 0;
- if(bytes > NETWORK_BYTES - sizeof header) {
- bytes = NETWORK_BYTES - sizeof header;
- /* Always send a whole number of frames */
- bytes -= bytes % bpf;
- }
- /* "The RTP clock rate used for generating the RTP timestamp is independent
- * of the number of channels and the encoding; it equals the number of
- * sampling periods per second. For N-channel encodings, each sampling
- * period (say, 1/8000 of a second) generates N samples. (This terminology
- * is standard, but somewhat confusing, as the total number of samples
- * generated per second is then the sampling rate times the channel
- * count.)"
- */
- vec[0].iov_base = (void *)&header;
- vec[0].iov_len = sizeof header;
- vec[1].iov_base = playing->buffer + playing->start;
- vec[1].iov_len = bytes;
- do {
- written_bytes = writev(bfd, vec, 2);
- } while(written_bytes < 0 && errno == EINTR);
- if(written_bytes < 0) {
- error(errno, "error transmitting audio data");
- ++audio_errors;
- if(audio_errors == 10)
- fatal(0, "too many audio errors");
- return 0;
- } else
- audio_errors /= 2;
- written_bytes -= sizeof (struct rtp_header);
- written_frames = written_bytes / bpf;
- /* Advance RTP's notion of the time */
- rtp_time += written_frames * config->sample_format.channels;
- return written_frames;
-}
-
-static int bfd_slot;
-
-/** @brief Set up poll array for network play */
-static void network_beforepoll(int *timeoutp) {
- struct timeval now;
- uint64_t target_us;
- uint64_t target_rtp_time;
- const int64_t samples_per_second = config->sample_format.rate
- * config->sample_format.channels;
- int64_t lead, ahead_ms;
-
- /* If we're starting then initialize the base time */
- if(!rtp_time)
- xgettimeofday(&rtp_time_0, 0);
- /* We send audio data whenever we would otherwise get behind */
- xgettimeofday(&now, 0);
- target_us = tvsub_us(now, rtp_time_0);
- if(target_us > UINT64_MAX / 88200)
- fatal(0, "rtp_time=%"PRIu64" rtp_time_0=%ld.%06ld now=%ld.%06ld target_us=%"PRIu64" (%"PRId64")\n",
- rtp_time,
- (long)rtp_time_0.tv_sec, (long)rtp_time_0.tv_usec,
- (long)now.tv_sec, (long)now.tv_usec,
- target_us, target_us);
- target_rtp_time = (target_us * config->sample_format.rate
- * config->sample_format.channels)
- / 1000000;
- /* Lead is how far ahead we are */
- lead = rtp_time - target_rtp_time;
- if(lead <= 0)
- /* We're behind or even, so we'll need to write as soon as we can */
- bfd_slot = addfd(bfd, POLLOUT);
- else {
- /* We've ahead, we can afford to wait a bit even if the IP stack thinks it
- * can accept more. */
- ahead_ms = 1000 * lead / samples_per_second;
- if(ahead_ms < *timeoutp)
- *timeoutp = ahead_ms;
- }
-}
-
-/** @brief Process poll() results for network play */
-static int network_ready(void) {
- if(fds[bfd_slot].revents & (POLLOUT | POLLERR))
- return 1;
- else
- return 0;
-}
-
-const struct speaker_backend network_backend = {
- BACKEND_NETWORK,
- 0,
- network_init,
- 0, /* activate */
- network_play,
- 0, /* deactivate */
- network_beforepoll,
- network_ready
-};
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007, 2008 Richard Kettlewell
- * Portions copyright (C) 2007 Ross Younger
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-oss.c
- * @brief Support for @ref BACKEND_OSS */
-
-#include "common.h"
-
-#if HAVE_SYS_SOUNDCARD_H
-
-#include <unistd.h>
-#include <poll.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <sys/ioctl.h>
-#include <sys/soundcard.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief Current device */
-static int ossfd = -1;
-
-/** @brief OSS backend initialization */
-static void oss_init(void) {
- info("selected OSS backend");
-}
-
-/** @brief OSS deactivation */
-static void oss_deactivate(void) {
- if(ossfd != -1) {
- xclose(ossfd);
- ossfd = -1;
- device_state = device_closed;
- D(("released audio device"));
- }
-}
-
-/** @brief OSS backend activation */
-static void oss_activate(void) {
- int stereo, format, rate;
- const char *device;
-
- if(ossfd == -1) {
- /* Try to pick a device */
- if(!strcmp(config->device, "default")) {
- if(access("/dev/dsp", W_OK) == 0)
- device = "/dev/dsp";
- else if(access("/dev/audio", W_OK) == 0)
- device = "/dev/audio";
- else {
- static int reported;
-
- if(!reported) {
- error(0, "cannot determine default OSS device");
- reported = 1;
- }
- goto failed;
- }
- } else
- device = config->device; /* just believe the user */
- /* Open the device */
- if((ossfd = open(device, O_WRONLY, 0)) < 0) {
- error(errno, "error opening %s", device);
- goto failed;
- }
- /* Set the audio format */
- stereo = (config->sample_format.channels == 2);
- if(ioctl(ossfd, SNDCTL_DSP_STEREO, &stereo) < 0) {
- error(errno, "error calling ioctl SNDCTL_DSP_STEREO %d", stereo);
- goto failed;
- }
- if(config->sample_format.bits == 8)
- format = AFMT_U8;
- else if(config->sample_format.bits == 16)
- format = (config->sample_format.endian == ENDIAN_LITTLE
- ? AFMT_S16_LE : AFMT_S16_BE);
- else {
- error(0, "unsupported sample_format for oss backend");
- goto failed;
- }
- if(ioctl(ossfd, SNDCTL_DSP_SETFMT, &format) < 0) {
- error(errno, "error calling ioctl SNDCTL_DSP_SETFMT %#x", format);
- goto failed;
- }
- rate = config->sample_format.rate;
- if(ioctl(ossfd, SNDCTL_DSP_SPEED, &rate) < 0) {
- error(errno, "error calling ioctl SNDCTL_DSP_SPEED %d", rate);
- goto failed;
- }
- if((unsigned)rate != config->sample_format.rate)
- error(0, "asked for %luHz, got %dHz",
- (unsigned long)config->sample_format.rate, rate);
- nonblock(ossfd);
- device_state = device_open;
- }
- return;
-failed:
- device_state = device_error;
- if(ossfd >= 0) {
- xclose(ossfd);
- ossfd = -1;
- }
-}
-
-/** @brief Play via OSS */
-static size_t oss_play(size_t frames) {
- const size_t bytes_to_play = frames * bpf;
- ssize_t bytes_written;
-
- bytes_written = write(ossfd, playing->buffer + playing->start,
- bytes_to_play);
- if(bytes_written < 0)
- switch(errno) {
- case EINTR: /* interruped */
- case EAGAIN: /* overrun */
- return 0; /* try again later */
- default:
- fatal(errno, "error writing to audio device");
- }
- return bytes_written / bpf;
-}
-
-static int oss_slot;
-
-/** @brief Fill in poll fd array for OSS */
-static void oss_beforepoll(int attribute((unused)) *timeoutp) {
- oss_slot = addfd(ossfd, POLLOUT|POLLERR);
-}
-
-/** @brief Process poll() results for OSS */
-static int oss_ready(void) {
- return !!(fds[oss_slot].revents & (POLLOUT|POLLERR));
-}
-
-const struct speaker_backend oss_backend = {
- BACKEND_OSS,
- 0,
- oss_init,
- oss_activate,
- oss_play,
- oss_deactivate,
- oss_beforepoll,
- oss_ready
-};
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
/*
* This file is part of DisOrder
- * Copyright (C) 2005-2008 Richard Kettlewell
+ * Copyright (C) 2005-2009 Richard Kettlewell
* Portions (C) 2007 Mark Wooding
*
* This program is free software: you can redistribute it and/or modify
* process that is about to become disorder-normalize) and plays them in the
* right order.
*
- * @b Encodings. For the <a href="http://www.alsa-project.org/">ALSA</a> API,
- * 8- and 16- bit stereo and mono are supported, with any sample rate (within
- * the limits that ALSA can deal with.)
+ * @b Model. mainloop() implements a select loop awaiting commands from the
+ * main server, new connections to the speaker socket, and audio data on those
+ * connections. Each connection starts with a queue ID (with a 32-bit
+ * native-endian length word), allowing it to be referred to in commands from
+ * the server.
+ *
+ * Data read on connections is buffered, up to a limit (currently 1Mbyte per
+ * track). No attempt is made here to limit the number of tracks, it is
+ * assumed that the main server won't start outrageously many decoders.
+ *
+ * Audio is supplied from this buffer to the uaudio play callback. Playback is
+ * enabled when a track is to be played and disabled when the its last bytes
+ * have been return by the callback; pause and resume is implemneted the
+ * obvious way. If the callback finds itself required to play when there is no
+ * playing track it returns dead air.
+ *
+ * @b Encodings. The encodings supported depend entirely on the uaudio backend
+ * chosen. See @ref uaudio.h, etc.
*
* Inbound data is expected to match @c config->sample_format. In normal use
* this is arranged by the @c disorder-normalize program (see @ref
#include <poll.h>
#include <sys/un.h>
#include <sys/stat.h>
+#include <pthread.h>
#include "configuration.h"
#include "syscalls.h"
#include "mem.h"
#include "speaker-protocol.h"
#include "user.h"
-#include "speaker.h"
#include "printf.h"
#include "version.h"
+#include "uaudio.h"
-/** @brief Linked list of all prepared tracks */
-struct track *tracks;
+/** @brief Maximum number of FDs to poll for */
+#define NFDS 1024
+
+/** @brief Track structure
+ *
+ * Known tracks are kept in a linked list. Usually there will be at most two
+ * of these but rearranging the queue can cause there to be more.
+ */
+struct track {
+ /** @brief Next track */
+ struct track *next;
+
+ /** @brief Input file descriptor */
+ int fd; /* input FD */
+
+ /** @brief Track ID */
+ char id[24];
+
+ /** @brief Start position of data in buffer */
+ size_t start;
+
+ /** @brief Number of bytes of data in buffer */
+ size_t used;
+
+ /** @brief Set @c fd is at EOF */
+ int eof;
+
+ /** @brief Total number of samples played */
+ unsigned long long played;
+
+ /** @brief Slot in @ref fds */
+ int slot;
+
+ /** @brief Set when playable
+ *
+ * A track becomes playable whenever it fills its buffer or reaches EOF; it
+ * stops being playable when it entirely empties its buffer. Tracks start
+ * out life not playable.
+ */
+ int playable;
+
+ /** @brief Input buffer
+ *
+ * 1Mbyte is enough for nearly 6s of 44100Hz 16-bit stereo
+ */
+ char buffer[1048576];
+};
+
+/** @brief Lock protecting data structures
+ *
+ * This lock protects values shared between the main thread and the callback.
+ * It is needed e.g. if changing @ref playing or if modifying buffer pointers.
+ * It is not needed to add a new track, to read values only modified in the
+ * same thread, etc.
+ */
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
-/** @brief Playing track, or NULL */
-struct track *playing;
+/** @brief Linked list of all prepared tracks */
+static struct track *tracks;
-/** @brief Number of bytes pre frame */
-size_t bpf;
+/** @brief Playing track, or NULL
+ *
+ * This means the DESIRED playing track. It does not reflect any other state
+ * (e.g. activation of uaudio backend).
+ */
+static struct track *playing;
/** @brief Array of file descriptors for poll() */
-struct pollfd fds[NFDS];
+static struct pollfd fds[NFDS];
/** @brief Next free slot in @ref fds */
-int fdno;
+static int fdno;
/** @brief Listen socket */
static int listenfd;
-static time_t last_report; /* when we last reported */
-static int paused; /* pause status */
+/** @brief Timestamp of last potential report to server */
+static time_t last_report;
-/** @brief The current device state */
-enum device_states device_state;
+/** @brief Set when paused */
+static int paused;
-/** @brief Set when idled
- *
- * This is set when the sound device is deliberately closed by idle().
- */
-int idled;
+/** @brief Set when back end activated */
+static int activated;
+
+/** @brief Signal pipe back into the poll() loop */
+static int sigpipe[2];
/** @brief Selected backend */
-static const struct speaker_backend *backend;
+static const struct uaudio *backend;
static const struct option options[] = {
{ "help", no_argument, 0, 'h' },
exit(0);
}
-/** @brief Return the number of bytes per frame in @p format */
-static size_t bytes_per_frame(const struct stream_header *format) {
- return format->channels * format->bits / 8;
-}
-
-/** @brief Find track @p id, maybe creating it if not found */
+/** @brief Find track @p id, maybe creating it if not found
+ * @param id Track ID to find
+ * @param create If nonzero, create track structure of @p id not found
+ * @return Pointer to track structure or NULL
+ */
static struct track *findtrack(const char *id, int create) {
struct track *t;
return t;
}
-/** @brief Remove track @p id (but do not destroy it) */
+/** @brief Remove track @p id (but do not destroy it)
+ * @param id Track ID to remove
+ * @return Track structure or NULL if not found
+ */
static struct track *removetrack(const char *id) {
struct track *t, **tt;
return t;
}
-/** @brief Destroy a track */
+/** @brief Destroy a track
+ * @param t Track structure
+ */
static void destroy(struct track *t) {
D(("destroy %s", t->id));
- if(t->fd != -1) xclose(t->fd);
+ if(t->fd != -1)
+ xclose(t->fd);
free(t);
}
*/
static int speaker_fill(struct track *t) {
size_t where, left;
- int n;
+ int n, rc;
D(("fill %s: eof=%d used=%zu",
t->id, t->eof, t->used));
- if(t->eof) return -1;
+ if(t->eof)
+ return -1;
+ pthread_mutex_lock(&lock);
if(t->used < sizeof t->buffer) {
/* there is room left in the buffer */
where = (t->start + t->used) % sizeof t->buffer;
/* Get as much data as we can */
- if(where >= t->start) left = (sizeof t->buffer) - where;
- else left = t->start - where;
+ if(where >= t->start)
+ left = (sizeof t->buffer) - where;
+ else
+ left = t->start - where;
+ pthread_mutex_unlock(&lock);
do {
n = read(t->fd, t->buffer + where, left);
} while(n < 0 && errno == EINTR);
+ pthread_mutex_lock(&lock);
if(n < 0) {
- if(errno != EAGAIN) fatal(errno, "error reading sample stream");
- return 0;
- }
- if(n == 0) {
+ if(errno != EAGAIN)
+ fatal(errno, "error reading sample stream");
+ rc = 0;
+ } else if(n == 0) {
D(("fill %s: eof detected", t->id));
t->eof = 1;
+ /* A track always becomes playable at EOF; we're not going to see any
+ * more data. */
t->playable = 1;
- return -1;
+ rc = -1;
+ } else {
+ t->used += n;
+ /* A track becomes playable when it (first) fills its buffer. For
+ * 44.1KHz 16-bit stereo this is ~6s of audio data. The latency will
+ * depend how long that takes to decode (hopefuly not very!) */
+ if(t->used == sizeof t->buffer)
+ t->playable = 1;
+ rc = 0;
}
- t->used += n;
- if(t->used == sizeof t->buffer)
- t->playable = 1;
}
- return 0;
-}
-
-/** @brief Close the sound device
- *
- * This is called to deactivate the output device when pausing, and also by the
- * ALSA backend when changing encoding (in which case the sound device will be
- * immediately reactivated).
- */
-static void idle(void) {
- D(("idle"));
- if(backend->deactivate)
- backend->deactivate();
- else
- device_state = device_closed;
- idled = 1;
-}
-
-/** @brief Abandon the current track */
-void abandon(void) {
- struct speaker_message sm;
-
- D(("abandon"));
- memset(&sm, 0, sizeof sm);
- sm.type = SM_FINISHED;
- strcpy(sm.id, playing->id);
- speaker_send(1, &sm);
- removetrack(playing->id);
- destroy(playing);
- playing = 0;
-}
-
-/** @brief Enable sound output
- *
- * Makes sure the sound device is open and has the right sample format. Return
- * 0 on success and -1 on error.
- */
-static void activate(void) {
- if(backend->activate)
- backend->activate();
- else
- device_state = device_open;
-}
-
-/** @brief Check whether the current track has finished
- *
- * The current track is determined to have finished either if the input stream
- * eded before the format could be determined (i.e. it is malformed) or the
- * input is at end of file and there is less than a frame left unplayed. (So
- * it copes with decoders that crash mid-frame.)
- */
-static void maybe_finished(void) {
- if(playing
- && playing->eof
- && playing->used < bytes_per_frame(&config->sample_format))
- abandon();
+ pthread_mutex_unlock(&lock);
+ return rc;
}
/** @brief Return nonzero if we want to play some audio
&& playing->playable;
}
-/** @brief Play up to @p frames frames of audio
- *
- * It is always safe to call this function.
- * - If @ref playing is 0 then it will just return
- * - If @ref paused is non-0 then it will just return
- * - If @ref device_state != @ref device_open then it will call activate() and
- * return if it it fails.
- * - If there is not enough audio to play then it play what is available.
- *
- * If there are not enough frames to play then whatever is available is played
- * instead. It is up to mainloop() to ensure that speaker_play() is not called
- * when unreasonably only an small amounts of data is available to play.
- */
-static void speaker_play(size_t frames) {
- size_t avail_frames, avail_bytes, written_frames;
- ssize_t written_bytes;
-
- /* Make sure there's a track to play and it is not paused */
- if(!playable())
- return;
- /* Make sure the output device is open */
- if(device_state != device_open) {
- activate();
- if(device_state != device_open)
- return;
- }
- D(("play: play %zu/%zu%s %dHz %db %dc", frames, playing->used / bpf,
- playing->eof ? " EOF" : "",
- config->sample_format.rate,
- config->sample_format.bits,
- config->sample_format.channels));
- /* Figure out how many frames there are available to write */
- if(playing->start + playing->used > sizeof playing->buffer)
- /* The ring buffer is currently wrapped, only play up to the wrap point */
- avail_bytes = (sizeof playing->buffer) - playing->start;
- else
- /* The ring buffer is not wrapped, can play the lot */
- avail_bytes = playing->used;
- avail_frames = avail_bytes / bpf;
- /* Only play up to the requested amount */
- if(avail_frames > frames)
- avail_frames = frames;
- if(!avail_frames)
- return;
- /* Play it, Sam */
- written_frames = backend->play(avail_frames);
- written_bytes = written_frames * bpf;
- /* written_bytes and written_frames had better both be set and correct by
- * this point */
- playing->start += written_bytes;
- playing->used -= written_bytes;
- playing->played += written_frames;
- /* If the pointer is at the end of the buffer (or the buffer is completely
- * empty) wrap it back to the start. */
- if(!playing->used || playing->start == (sizeof playing->buffer))
- playing->start = 0;
- /* If the buffer emptied out mark the track as unplayably */
- if(!playing->used && !playing->eof) {
- error(0, "track buffer emptied");
- playing->playable = 0;
- }
- frames -= written_frames;
- return;
-}
-
-/* Notify the server what we're up to. */
+/** @brief Notify the server what we're up to */
static void report(void) {
struct speaker_message sm;
memset(&sm, 0, sizeof sm);
sm.type = paused ? SM_PAUSED : SM_PLAYING;
strcpy(sm.id, playing->id);
- sm.data = playing->played / config->sample_format.rate;
+ pthread_mutex_lock(&lock);
+ sm.data = playing->played / (uaudio_rate * uaudio_channels);
+ pthread_mutex_unlock(&lock);
speaker_send(1, &sm);
}
time(&last_report);
}
-static void reap(int __attribute__((unused)) sig) {
- pid_t cmdpid;
- int st;
-
- do
- cmdpid = waitpid(-1, &st, WNOHANG);
- while(cmdpid > 0);
- signal(SIGCHLD, reap);
-}
-
-int addfd(int fd, int events) {
+/** @brief Add a file descriptor to the set to poll() for
+ * @param fd File descriptor
+ * @param events Events to wait for e.g. @c POLLIN
+ * @return Slot number
+ */
+static int addfd(int fd, int events) {
if(fdno < NFDS) {
fds[fdno].fd = fd;
fds[fdno].events = events;
return -1;
}
-/** @brief Table of speaker backends */
-static const struct speaker_backend *backends[] = {
-#if HAVE_ALSA_ASOUNDLIB_H
- &alsa_backend,
-#endif
- &command_backend,
- &network_backend,
-#if HAVE_COREAUDIO_AUDIOHARDWARE_H
- &coreaudio_backend,
-#endif
-#if HAVE_SYS_SOUNDCARD_H
- &oss_backend,
-#endif
- 0
-};
+/** @brief Callback to return some sampled data
+ * @param buffer Where to put sample data
+ * @param max_samples How many samples to return
+ * @param userdata User data
+ * @return Number of samples written
+ *
+ * See uaudio_callback().
+ */
+static size_t speaker_callback(void *buffer,
+ size_t max_samples,
+ void attribute((unused)) *userdata) {
+ const size_t max_bytes = max_samples * uaudio_sample_size;
+ size_t provided_samples = 0;
+
+ pthread_mutex_lock(&lock);
+ /* TODO perhaps we should immediately go silent if we've been asked to pause
+ * or cancel the playing track (maybe block in the cancel case and see what
+ * else turns up?) */
+ if(playing) {
+ if(playing->used > 0) {
+ size_t bytes;
+ /* Compute size of largest contiguous chunk. We get called as often as
+ * necessary so there's no need for cleverness here. */
+ if(playing->start + playing->used > sizeof playing->buffer)
+ bytes = sizeof playing->buffer - playing->start;
+ else
+ bytes = playing->used;
+ /* Limit to what we were asked for */
+ if(bytes > max_bytes)
+ bytes = max_bytes;
+ /* Provide it */
+ memcpy(buffer, playing->buffer + playing->start, bytes);
+ playing->start += bytes;
+ playing->used -= bytes;
+ /* Wrap around to start of buffer */
+ if(playing->start == sizeof playing->buffer)
+ playing->start = 0;
+ /* See if we've reached the end of the track */
+ if(playing->used == 0 && playing->eof)
+ write(sigpipe[1], "", 1);
+ provided_samples = bytes / uaudio_sample_size;
+ playing->played += provided_samples;
+ }
+ }
+ /* If we couldn't provide anything at all, play dead air */
+ /* TODO maybe it would be better to block, in some cases? */
+ if(!provided_samples) {
+ memset(buffer, 0, max_bytes);
+ provided_samples = max_samples;
+ }
+ pthread_mutex_unlock(&lock);
+ return provided_samples;
+}
/** @brief Main event loop */
static void mainloop(void) {
struct track *t;
struct speaker_message sm;
- int n, fd, stdin_slot, timeout, listen_slot;
+ int n, fd, stdin_slot, timeout, listen_slot, sigpipe_slot;
+ /* Keep going while our parent process is alive */
while(getppid() != 1) {
+ int force_report = 0;
+
fdno = 0;
/* By default we will wait up to a second before thinking about current
* state. */
playing->slot = addfd(playing->fd, POLLIN);
else if(playing)
playing->slot = -1;
- if(playable()) {
- /* We want to play some audio. If the device is closed then we attempt
- * to open it. */
- if(device_state == device_closed)
- activate();
- /* If the device is (now) open then we will wait up until it is ready for
- * more. If something went wrong then we should have device_error
- * instead, but the post-poll code will cope even if it's
- * device_closed. */
- if(device_state == device_open)
- backend->beforepoll(&timeout);
- }
/* If any other tracks don't have a full buffer, try to read sample data
* from them. We do this last of all, so that if we run out of slots,
* nothing important can't be monitored. */
} else
t->slot = -1;
}
+ sigpipe_slot = addfd(sigpipe[1], POLLIN);
/* Wait for something interesting to happen */
n = poll(fds, fdno, timeout);
if(n < 0) {
if(errno == EINTR) continue;
fatal(errno, "error calling poll");
}
- /* Play some sound before doing anything else */
- if(playable()) {
- /* We want to play some audio */
- if(device_state == device_open) {
- if(backend->ready())
- speaker_play(3 * FRAMES);
- } else {
- /* We must be in _closed or _error, and it should be the latter, but we
- * cope with either.
- *
- * We most likely timed out, so now is a good time to retry.
- * speaker_play() knows to re-activate the device if necessary.
- */
- speaker_play(3 * FRAMES);
- }
- }
/* Perhaps a connection has arrived */
if(fds[listen_slot].revents & POLLIN) {
struct sockaddr_un addr;
if(n > 0)
switch(sm.type) {
case SM_PLAY:
- if(playing) fatal(0, "got SM_PLAY but already playing something");
+ if(playing)
+ fatal(0, "got SM_PLAY but already playing something");
t = findtrack(sm.id, 1);
D(("SM_PLAY %s fd %d", t->id, t->fd));
if(t->fd == -1)
error(0, "cannot play track because no connection arrived");
playing = t;
- /* We attempt to play straight away rather than going round the loop.
- * speaker_play() is clever enough to perform any activation that is
- * required. */
- speaker_play(3 * FRAMES);
- report();
+ force_report = 1;
break;
case SM_PAUSE:
D(("SM_PAUSE"));
paused = 1;
- report();
+ force_report = 1;
break;
case SM_RESUME:
D(("SM_RESUME"));
- if(paused) {
- paused = 0;
- /* As for SM_PLAY we attempt to play straight away. */
- if(playing)
- speaker_play(3 * FRAMES);
- }
- report();
+ paused = 0;
+ force_report = 1;
break;
case SM_CANCEL:
D(("SM_CANCEL %s", sm.id));
t = removetrack(sm.id);
if(t) {
+ pthread_mutex_lock(&lock);
if(t == playing) {
/* scratching the playing track */
sm.type = SM_FINISHED;
}
strcpy(sm.id, t->id);
destroy(t);
+ pthread_mutex_unlock(&lock);
} else {
/* Probably scratching the playing track well before it's got
* going, but could indicate a bug, so we log this as an error. */
error(0, "SM_CANCEL for unknown track %s", sm.id);
}
speaker_send(1, &sm);
- report();
+ force_report = 1;
break;
case SM_RELOAD:
D(("SM_RELOAD"));
- if(config_read(1)) error(0, "cannot read configuration");
+ if(config_read(1))
+ error(0, "cannot read configuration");
info("reloaded configuration");
break;
default:
&& t->slot != -1
&& (fds[t->slot].revents & (POLLIN | POLLHUP)))
speaker_fill(t);
- /* Maybe we finished playing a track somewhere in the above */
- maybe_finished();
- /* If we don't need the sound device for now then close it for the benefit
- * of anyone else who wants it. */
- if((!playing || paused) && device_state == device_open)
- idle();
- /* If we've not reported out state for a second do so now. */
- if(time(0) > last_report)
+ /* Drain the signal pipe. We don't care about its contents, merely that it
+ * interrupted poll(). */
+ if(fds[sigpipe_slot].revents & POLLIN) {
+ char buffer[64];
+
+ read(sigpipe[0], buffer, sizeof buffer);
+ }
+ if(playing && playing->used == 0 && playing->eof) {
+ /* The playing track is done. Tell the server, and destroy it. */
+ memset(&sm, 0, sizeof sm);
+ sm.type = SM_FINISHED;
+ strcpy(sm.id, playing->id);
+ speaker_send(1, &sm);
+ removetrack(playing->id);
+ pthread_mutex_lock(&lock);
+ destroy(playing);
+ playing = 0;
+ pthread_mutex_unlock(&lock);
+ /* The server will presumalby send as an SM_PLAY by return */
+ }
+ /* Impose any state change required by the above */
+ if(playable()) {
+ if(!activated) {
+ activated = 1;
+ backend->activate();
+ }
+ } else {
+ if(activated) {
+ activated = 0;
+ backend->deactivate();
+ }
+ }
+ /* If we've not reported our state for a second do so now. */
+ if(force_report || time(0) > last_report)
report();
}
}
struct speaker_message sm;
const char *d;
char *dir;
+ struct rlimit rl[1];
set_progname(argv);
if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
openlog(progname, LOG_PID, LOG_DAEMON);
log_default = &log_syslog;
}
+ config_uaudio_apis = uaudio_apis;
if(config_read(1)) fatal(0, "cannot read configuration");
- bpf = bytes_per_frame(&config->sample_format);
/* ignore SIGPIPE */
signal(SIGPIPE, SIG_IGN);
- /* reap kids */
- signal(SIGCHLD, reap);
/* set nice value */
xnice(config->nice_speaker);
/* change user */
become_mortal();
/* make sure we're not root, whatever the config says */
- if(getuid() == 0 || geteuid() == 0) fatal(0, "do not run as root");
- /* identify the backend used to play */
- for(n = 0; backends[n]; ++n)
- if(backends[n]->backend == config->api)
- break;
- if(!backends[n])
- fatal(0, "unsupported api %d", config->api);
- backend = backends[n];
+ if(getuid() == 0 || geteuid() == 0)
+ fatal(0, "do not run as root");
+ /* Make sure we can't have more than NFDS files open (it would bust our
+ * poll() array) */
+ if(getrlimit(RLIMIT_NOFILE, rl) < 0)
+ fatal(errno, "getrlimit RLIMIT_NOFILE");
+ if(rl->rlim_cur > NFDS) {
+ rl->rlim_cur = NFDS;
+ if(setrlimit(RLIMIT_NOFILE, rl) < 0)
+ fatal(errno, "setrlimit to reduce RLIMIT_NOFILE to %lu",
+ (unsigned long)rl->rlim_cur);
+ info("set RLIM_NOFILE to %lu", (unsigned long)rl->rlim_cur);
+ } else
+ info("RLIM_NOFILE is %lu", (unsigned long)rl->rlim_cur);
+ /* create a pipe between the backend callback and the poll() loop */
+ xpipe(sigpipe);
+ nonblock(sigpipe[0]);
+ /* set up audio backend */
+ uaudio_set_format(config->sample_format.rate,
+ config->sample_format.channels,
+ config->sample_format.bits,
+ config->sample_format.bits != 8);
+ /* TODO other parameters! */
+ backend = uaudio_find(config->api);
/* backend-specific initialization */
- backend->init();
+ backend->start(speaker_callback, NULL);
/* create the socket directory */
byte_xasprintf(&dir, "%s/speaker", config->home);
unlink(dir); /* might be a leftover socket */
+++ /dev/null
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005-2008 Richard Kettlewell
- *
- * 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 3 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, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker.h
- * @brief Speaker process
- */
-#ifndef SPEAKER_H
-#define SPEAKER_H
-
-#ifdef WORDS_BIGENDIAN
-# define MACHINE_AO_FMT AO_FMT_BIG
-#else
-# define MACHINE_AO_FMT AO_FMT_LITTLE
-#endif
-
-/** @brief Minimum number of frames to try to play at once
- *
- * The main loop will only attempt to play any audio when this many
- * frames are available (or the current track has reached the end).
- * The actual number of frames it attempts to play will often be
- * larger than this (up to three times).
- *
- * For ALSA we request a buffer of three times this size and set the low
- * watermark to this amount. The goal is then to keep between 1 and 3 times
- * this many frames in play.
- *
- * For other we attempt to play up to three times this many frames per
- * shot. In practice we will often only send much less than this.
- */
-#define FRAMES 4096
-
-/** @brief Bytes to send per network packet
- *
- * This is the maximum number of bytes we pass to write(2); to determine actual
- * packet sizes, add a UDP header and an IP header (and a link layer header if
- * it's the link layer size you care about).
- *
- * Don't make this too big or arithmetic will start to overflow.
- */
-#define NETWORK_BYTES (1500-8/*UDP*/-40/*IP*/-8/*conservatism*/)
-
-/** @brief Maximum number of FDs to poll for */
-#define NFDS 256
-
-/** @brief Track structure
- *
- * Known tracks are kept in a linked list. Usually there will be at most two
- * of these but rearranging the queue can cause there to be more.
- */
-struct track {
- /** @brief Next track */
- struct track *next;
-
- /** @brief Input file descriptor */
- int fd; /* input FD */
-
- /** @brief Track ID */
- char id[24];
-
- /** @brief Start position of data in buffer */
- size_t start;
-
- /** @brief Number of bytes of data in buffer */
- size_t used;
-
- /** @brief Set @c fd is at EOF */
- int eof;
-
- /** @brief Total number of frames played */
- unsigned long long played;
-
- /** @brief Slot in @ref fds */
- int slot;
-
- /** @brief Set when playable
- *
- * A track becomes playable whenever it fills its buffer or reaches EOF; it
- * stops being playable when it entirely empties its buffer. Tracks start
- * out life not playable.
- */
- int playable;
-
- /** @brief Input buffer
- *
- * 1Mbyte is enough for nearly 6s of 44100Hz 16-bit stereo
- */
- char buffer[1048576];
-};
-
-/** @brief Structure of a backend */
-struct speaker_backend {
- /** @brief Which backend this is
- *
- * @c -1 terminates the list.
- */
- int backend;
-
- /** @brief Flags
- *
- * This field is currently not used and must be 0.
- */
- unsigned flags;
-
- /** @brief Initialization
- *
- * Called once at startup. This is responsible for one-time setup
- * operations, for instance opening a network socket to transmit to.
- *
- * When writing to a native sound API this might @b not imply opening the
- * native sound device - that might be done by @c activate below.
- */
- void (*init)(void);
-
- /** @brief Activation
- * @return 0 on success, non-0 on error
- *
- * Called to activate the output device.
- *
- * On input @ref device_state may be anything. If it is @ref
- * device_open then the device is already open but might be using
- * the wrong sample format. The device should be reconfigured to
- * use the right sample format.
- *
- * If it is @ref device_error then a retry is underway and an
- * attempt to recover or re-open the device (with the right sample
- * format) should be made.
- *
- * If it is @ref device_closed then the device should be opened with
- * the right sample format.
- *
- * Some devices are effectively always open and have no error state, in which
- * case this callback can be NULL. Note that @ref device_state still
- * switches between @ref device_open and @ref device_closed in this case.
- */
- void (*activate)(void);
-
- /** @brief Play sound
- * @param frames Number of frames to play
- * @return Number of frames actually played
- *
- * If an error occurs (and it is not immediately recovered) this
- * should set @ref device_state to @ref device_error.
- */
- size_t (*play)(size_t frames);
-
- /** @brief Deactivation
- *
- * Called to deactivate the sound device. This is the inverse of @c
- * activate above.
- *
- * For sound devices that are open all the time and have no error
- * state, this callback can be NULL. Note that @ref device_state
- * still switches between @ref device_open and @ref device_closed in
- * this case.
- */
- void (*deactivate)(void);
-
- /** @brief Called before poll()
- * @param timeoutp Pointer to timeout
- *
- * Called before the call to poll().
- *
- * If desirable, should call addfd() to update the FD array and stash the
- * slot number somewhere safe. This will only be called if @ref device_state
- * is @ref device_open.
- *
- * @p timeoutp points to the poll timeout value in milliseconds. It may be
- * reduced, but never increased.
- *
- * NB you can NOT assume that @c beforepoll is always called before @c play.
- */
- void (*beforepoll)(int *timeoutp);
-
- /** @brief Called after poll()
- * @return 1 if output device ready for play, 0 otherwise
- *
- * Called after the call to poll(). This will only be called if
- * @ref device_state = @ref device_open.
- *
- * The return value should be 1 if the device was ready to play, or
- * 0 if it was not.
- */
- int (*ready)(void);
-};
-
-/** @brief Possible device states */
-enum device_states {
- /** @brief The device is closed */
- device_closed,
-
- /** @brief The device is open and ready to receive sound
- *
- * The current device sample format is potentially part of this state.
- */
- device_open,
-
- /** @brief An error has occurred on the device
- *
- * This state is used to ensure that a small interval is left
- * between retrying the device. If errors just set @ref
- * device_closed then the main loop would busy-wait on broken output
- * devices.
- *
- * The current device sample format is potentially part of this state.
- */
- device_error
-};
-
-extern enum device_states device_state;
-extern struct track *tracks;
-extern struct track *playing;
-
-extern const struct speaker_backend network_backend;
-extern const struct speaker_backend alsa_backend;
-extern const struct speaker_backend command_backend;
-extern const struct speaker_backend coreaudio_backend;
-extern const struct speaker_backend oss_backend;
-
-extern struct pollfd fds[NFDS];
-extern int fdno;
-extern size_t bpf;
-extern int idled;
-
-int addfd(int fd, int events);
-void abandon(void);
-
-#endif /* SPEAKER_H */
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
/*
* This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
+ * Copyright (C) 2004, 2005, 2007-2009 Richard Kettlewell
*
* 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
static struct addrinfo *current_listen_addrinfo;
static int current_listen_fd;
+/** @brief Current audio API */
+const struct uaudio *api;
+
void quit(ev_source *ev) {
info("shutting down...");
quitting(ev);
int ret = 0;
D(("reconfigure(%d)", reload));
+ if(api) {
+ if(api->close_mixer)
+ api->close_mixer();
+ api = NULL;
+ }
if(reload) {
need_another_rescan = trackdb_rescan_cancel();
trackdb_close();
} else
/* We only allow for upgrade at startup */
trackdb_open(TRACKDB_CAN_UPGRADE);
+ api = uaudio_find(config->api);
+ if(api->open_mixer)
+ api->open_mixer();
if(need_another_rescan)
trackdb_rescan(ev, 1/*check*/, 0, 0);
/* Arrange timeouts for schedule actions */