aboutsummaryrefslogtreecommitdiffstats
path: root/src/audioOutput
diff options
context:
space:
mode:
Diffstat (limited to 'src/audioOutput')
-rw-r--r--src/audioOutput/audioOutput_ao.c243
-rw-r--r--src/audioOutput/audioOutput_oss.c199
-rw-r--r--src/audioOutput/audioOutput_shout.c581
3 files changed, 1023 insertions, 0 deletions
diff --git a/src/audioOutput/audioOutput_ao.c b/src/audioOutput/audioOutput_ao.c
new file mode 100644
index 000000000..366c42c1d
--- /dev/null
+++ b/src/audioOutput/audioOutput_ao.c
@@ -0,0 +1,243 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../audioOutput.h"
+#include "../conf.h"
+#include "../log.h"
+#include "../sig_handlers.h"
+
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+
+#include <ao/ao.h>
+
+static int driverInitCount = 0;
+
+typedef struct _AoData {
+ int writeSize;
+ int driverId;
+ ao_option * options;
+ ao_device * device;
+} AoData;
+
+static AoData * newAoData() {
+ AoData * ret = malloc(sizeof(AoData));
+ ret->device = NULL;
+ ret->options = NULL;
+
+ return ret;
+}
+
+static void audioOutputAo_error() {
+ if(errno==AO_ENOTLIVE) {
+ ERROR("not a live ao device\n");
+ }
+ else if(errno==AO_EOPENDEVICE) {
+ ERROR("not able to open audio device\n");
+ }
+ else if(errno==AO_EBADOPTION) {
+ ERROR("bad driver option\n");
+ }
+}
+
+static int audioOutputAo_initDriver(AudioOutput * audioOutput,
+ ConfigParam * param)
+{
+ ao_info * ai;
+ char * dup;
+ char * stk1;
+ char * stk2;
+ char * n1;
+ char * key;
+ char * value;
+ char * test;
+ AoData * ad = newAoData();
+ BlockParam * blockParam;
+
+ audioOutput->data = ad;
+
+ if((blockParam = getBlockParam(param, "write_size"))) {
+ ad->writeSize = strtol(blockParam->value, &test, 10);
+ if (*test!='\0') {
+ ERROR("\"%s\" is not a valid write size at line %i\n",
+ blockParam->value, blockParam->line);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else ad->writeSize = 1024;
+
+ if(driverInitCount == 0) {
+ ao_initialize();
+ }
+ driverInitCount++;
+
+ blockParam = getBlockParam(param, "driver");
+
+ if(!blockParam || 0 == strcmp(blockParam->value,"default")) {
+ ad->driverId = ao_default_driver_id();
+ }
+ else if((ad->driverId =
+ ao_driver_id(blockParam->value))<0) {
+ ERROR("\"%s\" is not a valid ao driver at line %i\n",
+ blockParam->value, blockParam->line);
+ exit(EXIT_FAILURE);
+ }
+
+ if((ai = ao_driver_info(ad->driverId))==NULL) {
+ ERROR("problems getting driver info for device defined at "
+ "line %i\n", param->line);
+ ERROR("you may not have permission to the audio device\n");
+ exit(EXIT_FAILURE);
+ }
+
+ blockParam = getBlockParam(param, "options");
+
+ if(blockParam) {
+ dup = strdup(blockParam->value);
+ }
+ else dup = strdup("");
+
+ if(strlen(dup)) {
+ stk1 = NULL;
+ n1 = strtok_r(dup,";",&stk1);
+ while(n1) {
+ stk2 = NULL;
+ key = strtok_r(n1,"=",&stk2);
+ if(!key) {
+ ERROR("problems parsing "
+ "ao_driver_options \"%s\"\n", n1);
+ exit(EXIT_FAILURE);
+ }
+ /*found = 0;
+ for(i=0;i<ai->option_count;i++) {
+ if(strcmp(ai->options[i],key)==0) {
+ found = 1;
+ break;
+ }
+ }
+ if(!found) {
+ ERROR("\"%s\" is not an option for "
+ "\"%s\" ao driver\n",key,
+ ai->short_name);
+ exit(EXIT_FAILURE);
+ }*/
+ value = strtok_r(NULL,"",&stk2);
+ if(!value) {
+ ERROR("problems parsing "
+ "ao_driver_options \"%s\"\n", n1);
+ exit(EXIT_FAILURE);
+ }
+ ao_append_option(&ad->options,key,value);
+ n1 = strtok_r(NULL,";",&stk1);
+ }
+ }
+ free(dup);
+
+ return 0;
+}
+
+static void freeAoData(AoData * ad) {
+ ao_free_options(ad->options);
+ free(ad);
+}
+
+static void audioOutputAo_finishDriver(AudioOutput * audioOutput) {
+ AoData * ad = (AoData *)audioOutput->data;
+ freeAoData(ad);
+
+ driverInitCount--;
+
+ if(driverInitCount == 0) ao_shutdown();
+}
+
+static void audioOutputAo_closeDevice(AudioOutput * audioOutput) {
+ AoData * ad = (AoData *) audioOutput->data;
+
+ if(ad->device) {
+ blockSignals();
+ ao_close(ad->device);
+ ad->device = NULL;
+ unblockSignals();
+ }
+
+ audioOutput->open = 0;
+}
+
+static int audioOutputAo_openDevice(AudioOutput * audioOutput,
+ AudioFormat * audioFormat)
+{
+ ao_sample_format format;
+ AoData * ad = (AoData *)audioOutput->data;
+
+ if(ad->device) {
+ audioOutputAo_closeDevice(audioOutput);
+ }
+
+ format.bits = audioFormat->bits;
+ format.rate = audioFormat->sampleRate;
+ format.byte_format = AO_FMT_NATIVE;
+ format.channels = audioFormat->channels;
+
+ blockSignals();
+ ad->device = ao_open_live(ad->driverId, &format, ad->options);
+ unblockSignals();
+
+ if(ad->device==NULL) return -1;
+
+ audioOutput->open = 1;
+
+ return 0;
+}
+
+
+static int audioOutputAo_play(AudioOutput * audioOutput, char * playChunk,
+ int size)
+{
+ int send;
+ AoData * ad = (AoData *)audioOutput->data;
+
+ if(ad->device==NULL) return -1;
+
+ while(size>0) {
+ send = ad->writeSize > size ? size : ad->writeSize;
+
+ if(ao_play(ad->device, playChunk, send)==0) {
+ audioOutputAo_error();
+ ERROR("closing audio device due to write error\n");
+ audioOutputAo_closeDevice(audioOutput);
+ return -1;
+ }
+
+ playChunk+=send;
+ size-=send;
+ }
+
+ return 0;
+}
+
+AudioOutputPlugin aoPlugin =
+{
+ "ao",
+ audioOutputAo_initDriver,
+ audioOutputAo_finishDriver,
+ audioOutputAo_openDevice,
+ audioOutputAo_play,
+ audioOutputAo_closeDevice,
+ NULL /* sendMetadataFunc */
+};
diff --git a/src/audioOutput/audioOutput_oss.c b/src/audioOutput/audioOutput_oss.c
new file mode 100644
index 000000000..c8e86b7a4
--- /dev/null
+++ b/src/audioOutput/audioOutput_oss.c
@@ -0,0 +1,199 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * OSS audio output (c) 2004 by Eric Wong <eric@petta-tech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../audioOutput.h"
+
+#include <stdlib.h>
+
+#ifdef HAVE_OSS
+
+#include "../conf.h"
+#include "../log.h"
+#include "../sig_handlers.h"
+
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#if defined(__OpenBSD__) || defined(__NetBSD__)
+# include <soundcard.h>
+#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+# include <sys/soundcard.h>
+#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
+
+typedef struct _OssData {
+ int fd;
+ char * device;
+} OssData;
+
+static OssData * newOssData() {
+ OssData * ret = malloc(sizeof(OssData));
+
+ ret->device = NULL;
+ ret->fd = -1;
+
+ return ret;
+}
+
+static void freeOssData(OssData * od) {
+ if(od->device) free(od->device);
+
+ free(od);
+}
+
+static int oss_initDriver(AudioOutput * audioOutput, ConfigParam * param) {
+ BlockParam * bp = getBlockParam(param, "device");
+ OssData * od = newOssData();
+
+ audioOutput->data = od;
+
+ if(!bp) {
+ int fd;
+
+ if(0 <= (fd = open("/dev/sound/dsp", O_WRONLY))) {
+ od->device = strdup("/dev/sound/dsp");
+ close(fd);
+ }
+ else if(0 <= (fd = open("/dev/dsp", O_WRONLY))) {
+ od->device = strdup("/dev/dsp");
+ close(fd);
+ }
+ else {
+ ERROR("Error trying to open default OSS device "
+ "specified at line %i\n", param->line);
+ ERROR("Specify a OSS device and/or check your "
+ "permissions\n");
+ exit(EXIT_FAILURE);
+ }
+
+ od->fd = -1;
+
+ return 0;
+ }
+
+ od->device = strdup(bp->value);
+
+ return 0;
+}
+
+static void oss_finishDriver(AudioOutput * audioOutput) {
+ OssData * od = audioOutput->data;
+
+ freeOssData(od);
+}
+
+static int oss_openDevice(AudioOutput * audioOutput, AudioFormat * audioFormat)
+{
+ OssData * od = audioOutput->data;
+#ifdef WORDS_BIGENDIAN
+ int i = AFMT_S16_BE;
+#else
+ int i = AFMT_S16_LE;
+#endif
+
+ if((od->fd = open(od->device, O_WRONLY)) < 0)
+ goto fail;
+ if(ioctl(od->fd, SNDCTL_DSP_SETFMT, &i))
+ goto fail;
+ if(ioctl(od->fd, SNDCTL_DSP_CHANNELS, &audioFormat->channels))
+ goto fail;
+ if(ioctl(od->fd, SNDCTL_DSP_SPEED, &audioFormat->sampleRate))
+ goto fail;
+ if(ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, &audioFormat->bits))
+ goto fail;
+ /*i = 1; if (ioctl(od->fd,SNDCTL_DSP_STEREO,&i)) err != 32; */
+
+ audioOutput->open = 1;
+
+ return 0;
+
+fail:
+ if(od->fd >= 0) close(od->fd);
+ audioOutput->open = 0;
+ ERROR("Error opening OSS device \"%s\": %s\n", od->device,
+ strerror(errno));
+ return -1;
+}
+
+static void oss_closeDevice(AudioOutput * audioOutput) {
+ OssData * od = audioOutput->data;
+
+ if(od->fd >= 0) {
+ close(od->fd);
+ od->fd = -1;
+ }
+
+ audioOutput->open = 0;
+}
+
+static int oss_playAudio(AudioOutput * audioOutput, char * playChunk,
+ int size)
+{
+ OssData * od = audioOutput->data;
+ int ret;
+
+ while (size > 0) {
+ ret = write(od->fd, playChunk, size);
+ if(ret<0) {
+ ERROR("closing audio device due to write error\n");
+ oss_closeDevice(audioOutput);
+ return -1;
+ }
+ playChunk += ret;
+ size -= ret;
+ }
+
+ return 0;
+}
+
+AudioOutputPlugin ossPlugin =
+{
+ "oss",
+ oss_initDriver,
+ oss_finishDriver,
+ oss_openDevice,
+ oss_playAudio,
+ oss_closeDevice,
+ NULL /* sendMetadataFunc */
+};
+
+#else /* HAVE OSS */
+
+AudioOutputPlugin ossPlugin =
+{
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL /* sendMetadataFunc */
+};
+
+#endif /* HAVE_OSS */
+
+
diff --git a/src/audioOutput/audioOutput_shout.c b/src/audioOutput/audioOutput_shout.c
new file mode 100644
index 000000000..8b8e789fb
--- /dev/null
+++ b/src/audioOutput/audioOutput_shout.c
@@ -0,0 +1,581 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "../audioOutput.h"
+
+#include <stdlib.h>
+
+#ifdef HAVE_SHOUT
+
+#include "../conf.h"
+#include "../log.h"
+#include "../sig_handlers.h"
+#include "../pcm_utils.h"
+
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+
+#include <shout/shout.h>
+#include <vorbis/vorbisenc.h>
+#include <vorbis/codec.h>
+
+static int shoutInitCount = 0;
+
+/* lots of this code blatantly stolent from bossogg/bossao2 */
+
+typedef struct _ShoutData {
+ shout_t * shoutConn;
+
+ ogg_stream_state os;
+ ogg_page og;
+ ogg_packet op;
+ ogg_packet header_main;
+ ogg_packet header_comments;
+ ogg_packet header_codebooks;
+
+ vorbis_dsp_state vd;
+ vorbis_block vb;
+ vorbis_info vi;
+ vorbis_comment vc;
+
+ float quality;
+ int bitrate;
+ AudioFormat outAudioFormat;
+ AudioFormat inAudioFormat;
+
+ char * convBuffer;
+ size_t convBufferLen;
+ /* shoud we convert the audio to a different format? */
+ int audioFormatConvert;
+
+ int opened;
+
+ MpdTag * tag;
+ int tagToSend;
+} ShoutData;
+
+static ShoutData * newShoutData() {
+ ShoutData * ret = malloc(sizeof(ShoutData));
+
+ ret->shoutConn = shout_new();
+ ret->convBuffer = NULL;
+ ret->convBufferLen = 0;
+ ret->opened = 0;
+ ret->tag = NULL;
+ ret->tagToSend = 0;
+ ret->bitrate = -1;
+ ret->quality = -1.0;
+
+ return ret;
+}
+
+static void freeShoutData(ShoutData * sd) {
+ if(sd->shoutConn) shout_free(sd->shoutConn);
+ if(sd->tag) freeMpdTag(sd->tag);
+ if(sd->convBuffer) free(sd->convBuffer);
+
+ free(sd);
+}
+
+#define checkBlockParam(name) { \
+ blockParam = getBlockParam(param, name); \
+ if(!blockParam) { \
+ ERROR("no \"%s\" defined for shout device defined at line " \
+ "%i\n", name, param->line); \
+ exit(EXIT_FAILURE); \
+ } \
+}
+
+static int myShout_initDriver(AudioOutput * audioOutput, ConfigParam * param) {
+ ShoutData * sd;
+ char * test;
+ int port;
+ char * host;
+ char * mount;
+ char * passwd;
+ char * user;
+ char * name;
+ BlockParam * blockParam;
+
+ sd = newShoutData();
+
+ if(shoutInitCount == 0) shout_init();
+
+ shoutInitCount++;
+
+ checkBlockParam("host");
+ host = blockParam->value;
+
+ checkBlockParam("mount");
+ mount = blockParam->value;
+
+ checkBlockParam("port");
+
+ port = strtol(blockParam->value, &test, 10);
+
+ if(*test != '\0' || port <= 0) {
+ ERROR("shout port \"%s\" is not a positive integer, line %i\n",
+ blockParam->value, blockParam->line);
+ exit(EXIT_FAILURE);
+ }
+
+ checkBlockParam("password");
+ passwd = blockParam->value;
+
+ checkBlockParam("name");
+ name = blockParam->value;
+
+ checkBlockParam("user");
+ user = blockParam->value;
+
+ blockParam = getBlockParam(param, "quality");
+
+ if(blockParam) {
+ int line = blockParam->line;
+
+ sd->quality = strtod(blockParam->value, &test);
+
+ if(*test != '\0' || sd->quality < 0.0 || sd->quality > 10.0) {
+ ERROR("shout quality \"%s\" is not a number in the "
+ "rage 0-10, line %i\n", blockParam->value,
+ blockParam->line);
+ exit(EXIT_FAILURE);
+ }
+
+ blockParam = getBlockParam(param, "bitrate");
+
+ if(blockParam) {
+ ERROR("quality (line %i) and bitrate (line %i) are "
+ "both defined for shout output\n", line,
+ blockParam->line);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else {
+ blockParam = getBlockParam(param, "bitrate");
+
+ if(!blockParam) {
+ ERROR("neither bitrate nor quality defined for shout "
+ "output at line %i\n", param->line);
+ exit(EXIT_FAILURE);
+ }
+
+ sd->bitrate = strtol(blockParam->value, &test, 10);
+
+ if(*test != '\0' || sd->bitrate <= 0) {
+ ERROR("bitrate at line %i should be a positve integer "
+ "\n", blockParam->line);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ checkBlockParam("format");
+
+ if(0 != parseAudioConfig(&(sd->outAudioFormat), blockParam->value)) {
+ ERROR("error parsing format at line %i\n", blockParam->line);
+ exit(EXIT_FAILURE);
+ }
+
+ if(shout_set_host(sd->shoutConn, host) != SHOUTERR_SUCCESS ||
+ shout_set_port(sd->shoutConn, port) != SHOUTERR_SUCCESS ||
+ shout_set_password(sd->shoutConn, passwd) != SHOUTERR_SUCCESS ||
+ shout_set_mount(sd->shoutConn, mount) != SHOUTERR_SUCCESS ||
+ shout_set_name(sd->shoutConn, name) != SHOUTERR_SUCCESS ||
+ shout_set_user(sd->shoutConn, user) != SHOUTERR_SUCCESS ||
+ shout_set_format(sd->shoutConn, SHOUT_FORMAT_VORBIS)
+ != SHOUTERR_SUCCESS ||
+ shout_set_protocol(sd->shoutConn, SHOUT_PROTOCOL_HTTP)
+ != SHOUTERR_SUCCESS ||
+ shout_set_agent(sd->shoutConn, "MPD") != SHOUTERR_SUCCESS)
+ {
+ ERROR("error configuring shout: %s\n",
+ shout_get_error(sd->shoutConn));
+ exit(EXIT_FAILURE);
+ }
+
+ {
+ char temp[11];
+ memset(temp, 0, sizeof(temp));
+
+ snprintf(temp, sizeof(temp), "%d", sd->outAudioFormat.channels);
+ shout_set_audio_info(sd->shoutConn, SHOUT_AI_CHANNELS, temp);
+
+ snprintf(temp, sizeof(temp), "%d",
+ sd->outAudioFormat.sampleRate);
+ shout_set_audio_info(sd->shoutConn, SHOUT_AI_SAMPLERATE, temp);
+
+ if(sd->quality >= 0) {
+ snprintf(temp, sizeof(temp), "%2.2f", sd->quality);
+ shout_set_audio_info(sd->shoutConn, SHOUT_AI_QUALITY,
+ temp);
+ }
+ else {
+ snprintf(temp, sizeof(temp), "%d", sd->bitrate);
+ shout_set_audio_info(sd->shoutConn, SHOUT_AI_BITRATE,
+ temp);
+ }
+ }
+
+ audioOutput->data = sd;
+
+ return 0;
+}
+
+/*static void finishEncoder(ShoutData * sd) {
+ vorbis_analysis_wrote(&sd->vd, 0);
+
+ while(vorbis_analysis_blockout(&sd->vd, &sd->vb) == 1) {
+ vorbis_analysis(&sd->vb, NULL);
+ vorbis_bitrate_addblock(&sd->vb);
+ while(vorbis_bitrate_flushpacket(&sd->vd, &sd->op)) {
+ ogg_stream_packetin(&sd->os, &sd->op);
+ }
+ }
+}
+
+static void flushEncoder(ShoutData * sd) {
+ while(1 == ogg_stream_pageout(&sd->os, &sd->og));
+}*/
+
+static void clearEncoder(ShoutData * sd) {
+ /*finishEncoder(sd);
+ flushEncoder(sd);*/
+
+ vorbis_comment_clear(&sd->vc);
+ ogg_stream_clear(&sd->os);
+ vorbis_block_clear(&sd->vb);
+ vorbis_dsp_clear(&sd->vd);
+ vorbis_info_clear(&sd->vi);
+}
+
+static void myShout_closeShoutConn(ShoutData * sd) {
+ if(sd->opened) {
+ clearEncoder(sd);
+
+ if(shout_close(sd->shoutConn) != SHOUTERR_SUCCESS) {
+ ERROR("problem closing connection to shout server: "
+ "%s\n", shout_get_error(sd->shoutConn));
+ }
+ }
+
+ sd->opened = 0;
+}
+
+static void myShout_finishDriver(AudioOutput * audioOutput) {
+ ShoutData * sd = (ShoutData *)audioOutput->data;
+
+ myShout_closeShoutConn(sd);
+
+ freeShoutData(sd);
+
+ shoutInitCount--;
+
+ if(shoutInitCount == 0) shout_shutdown();
+}
+
+static void myShout_closeDevice(AudioOutput * audioOutput) {
+ audioOutput->open = 0;
+}
+
+static int myShout_handleError(ShoutData * sd, int err) {
+ switch(err) {
+ case SHOUTERR_SUCCESS:
+ break;
+ case SHOUTERR_UNCONNECTED:
+ case SHOUTERR_SOCKET:
+ ERROR("Lost shout connection\n");
+ return -1;
+ default:
+ ERROR("shout: error: %s\n", shout_get_error(sd->shoutConn));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int write_page(ShoutData * sd) {
+ int err = 0;
+
+ shout_sync(sd->shoutConn);
+ err = shout_send(sd->shoutConn, sd->og.header, sd->og.header_len);
+ if(myShout_handleError(sd, err) < 0) return -1;
+ err = shout_send(sd->shoutConn, sd->og.body, sd->og.body_len);
+ if(myShout_handleError(sd, err) < 0) return -1;
+
+ return 0;
+}
+
+#define addTag(name, value) { \
+ if(value) vorbis_comment_add_tag(&(sd->vc), name, value); \
+}
+
+static void copyTagToVorbisComment(ShoutData * sd) {
+ if(sd->tag) {
+ addTag("ARTIST", sd->tag->artist);
+ addTag("ALBUM", sd->tag->album);
+ addTag("TITLE", sd->tag->title);
+ }
+}
+
+static int initEncoder(ShoutData * sd) {
+ vorbis_info_init(&(sd->vi));
+
+ if(sd->quality >= 0) {
+ if( 0 != vorbis_encode_init_vbr(&(sd->vi),
+ sd->outAudioFormat.channels,
+ sd->outAudioFormat.sampleRate, sd->quality*0.1) )
+ {
+ ERROR("problem seting up vorbis encoder for shout\n");
+ vorbis_info_clear(&(sd->vi));
+ return -1;
+ }
+ }
+ else {
+ if( 0 != vorbis_encode_init(&(sd->vi),
+ sd->outAudioFormat.channels,
+ sd->outAudioFormat.sampleRate, -1.0,
+ sd->bitrate*1000, -1.0) )
+ {
+ ERROR("problem seting up vorbis encoder for shout\n");
+ vorbis_info_clear(&(sd->vi));
+ return -1;
+ }
+ }
+
+ vorbis_analysis_init(&(sd->vd), &(sd->vi));
+ vorbis_block_init (&(sd->vd), &(sd->vb));
+
+ ogg_stream_init(&(sd->os), rand());
+
+ vorbis_comment_init(&(sd->vc));
+
+ return 0;
+}
+
+static int myShout_openShoutConn(AudioOutput * audioOutput) {
+ ShoutData * sd = (ShoutData *)audioOutput->data;
+
+ if(shout_open(sd->shoutConn) != SHOUTERR_SUCCESS)
+ {
+ ERROR("problem opening connection to shout server: %s\n",
+ shout_get_error(sd->shoutConn));
+
+ return -1;
+ }
+
+ if(initEncoder(sd) < 0) {
+ shout_close(sd->shoutConn);
+ return -1;
+ }
+
+ copyTagToVorbisComment(sd);
+
+ vorbis_analysis_headerout(&(sd->vd), &(sd->vc), &(sd->header_main),
+ &(sd->header_comments), &(sd->header_codebooks));
+
+ ogg_stream_packetin(&(sd->os), &(sd->header_main));
+ ogg_stream_packetin(&(sd->os), &(sd->header_comments));
+ ogg_stream_packetin(&(sd->os), &(sd->header_codebooks));
+
+ sd->opened = 1;
+ sd->tagToSend = 0;
+
+ while(ogg_stream_flush(&(sd->os), &(sd->og)))
+ {
+ if(write_page(sd) < 0) {
+ myShout_closeShoutConn(sd);
+ return -1;
+ }
+ }
+
+ /*if(sd->tag) freeMpdTag(sd->tag);
+ sd->tag = NULL;*/
+
+ return 0;
+}
+
+static int myShout_openDevice(AudioOutput * audioOutput,
+ AudioFormat * audioFormat)
+{
+ ShoutData * sd = (ShoutData *)audioOutput->data;
+
+ memcpy(&(sd->inAudioFormat), audioFormat, sizeof(AudioFormat));
+
+ if(0 == memcmp(&(sd->inAudioFormat), &(sd->outAudioFormat),
+ sizeof(AudioFormat)))
+ {
+ sd->audioFormatConvert = 0;
+ }
+ else sd->audioFormatConvert = 1;
+
+ audioOutput->open = 1;
+
+ if(sd->opened) return 0;
+
+ if(myShout_openShoutConn(audioOutput) < 0) {
+ audioOutput->open = 0;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void myShout_convertAudioFormat(ShoutData * sd, char ** chunkArgPtr,
+ int * sizeArgPtr)
+{
+ int size = pcm_sizeOfOutputBufferForAudioFormatConversion(
+ &(sd->inAudioFormat), *sizeArgPtr,
+ &(sd->outAudioFormat));
+
+ if(size > sd->convBufferLen) {
+ sd->convBuffer = realloc(sd->convBuffer, size);
+ sd->convBufferLen = size;
+ }
+
+ pcm_convertAudioFormat(&(sd->inAudioFormat), *chunkArgPtr, *sizeArgPtr,
+ &(sd->outAudioFormat), sd->convBuffer);
+
+ *sizeArgPtr = size;
+ *chunkArgPtr = sd->convBuffer;
+}
+
+static void myShout_sendMetadata(ShoutData * sd) {
+ ogg_int64_t granulepos = sd->vd.granulepos;
+
+ if(!sd->opened || !sd->tag) return;
+
+ clearEncoder(sd);
+ if(initEncoder(sd) < 0) return;
+
+ sd->vd.granulepos = granulepos;
+
+ copyTagToVorbisComment(sd);
+
+ vorbis_analysis_headerout(&(sd->vd), &(sd->vc), &(sd->header_main),
+ &(sd->header_comments), &(sd->header_codebooks));
+
+ ogg_stream_packetin(&(sd->os), &(sd->header_main));
+ ogg_stream_packetin(&(sd->os), &(sd->header_comments));
+ ogg_stream_packetin(&(sd->os), &(sd->header_codebooks));
+
+ while(ogg_stream_flush(&(sd->os), &(sd->og)))
+ {
+ if(write_page(sd) < 0) {
+ myShout_closeShoutConn(sd);
+ return;
+ }
+ }
+
+ /*if(sd->tag) freeMpdTag(sd->tag);
+ sd->tag = NULL;*/
+ sd->tagToSend = 0;
+}
+
+static int myShout_play(AudioOutput * audioOutput, char * playChunk, int size) {
+ int i,j;
+ ShoutData * sd = (ShoutData *)audioOutput->data;
+ float ** vorbbuf;
+ int samples;
+ int bytes = sd->outAudioFormat.bits/8;
+
+ if(sd->opened && sd->tagToSend) myShout_sendMetadata(sd);
+
+ if(!sd->opened) {
+ if(myShout_openShoutConn(audioOutput) < 0) {
+ return -1;
+ }
+ }
+
+ if(sd->audioFormatConvert) {
+ myShout_convertAudioFormat(sd, &playChunk, &size);
+ }
+
+ samples = size/(bytes*sd->outAudioFormat.channels);
+
+ /* this is for only 16-bit audio */
+
+ vorbbuf = vorbis_analysis_buffer(&(sd->vd), samples);
+
+ for(i=0; i<samples; i++) {
+ for(j=0; j<sd->outAudioFormat.channels; j++) {
+ vorbbuf[j][i] = (*((mpd_sint16 *)playChunk)) / 32768.0;
+ playChunk += bytes;
+ }
+ }
+
+ vorbis_analysis_wrote(&(sd->vd), samples);
+
+ while(1 == vorbis_analysis_blockout(&(sd->vd), &(sd->vb))) {
+ vorbis_analysis(&(sd->vb), NULL);
+ vorbis_bitrate_addblock(&(sd->vb));
+
+ while(vorbis_bitrate_flushpacket(&(sd->vd), &(sd->op))) {
+ ogg_stream_packetin(&(sd->os), &(sd->op));
+ }
+ }
+
+
+ while(ogg_stream_pageout(&(sd->os), &(sd->og)) != 0) {
+ if(write_page(sd) < 0) {
+ myShout_closeShoutConn(sd);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void myShout_setTag(AudioOutput * audioOutput, MpdTag * tag) {
+ ShoutData * sd = (ShoutData *)audioOutput->data;
+
+ if(sd->tag) freeMpdTag(sd->tag);
+ sd->tag = NULL;
+ sd->tagToSend = 0;
+
+ if(!tag) return;
+
+ sd->tag = mpdTagDup(tag);
+ sd->tagToSend = 1;
+}
+
+AudioOutputPlugin shoutPlugin =
+{
+ "shout",
+ myShout_initDriver,
+ myShout_finishDriver,
+ myShout_openDevice,
+ myShout_play,
+ myShout_closeDevice,
+ myShout_setTag
+};
+
+#else
+
+AudioOutputPlugin shoutPlugin =
+{
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+#endif