From b5f6593ed4967c9bb9ab74e7dbd695208696e04d Mon Sep 17 00:00:00 2001 From: Eric Wollesen Date: Mon, 25 Aug 2008 15:36:55 -0600 Subject: [PATCH] Creation of shout_mp3 audio output plugin. Basically I just copied the existing shout plugin and replaced ogg with lame. Uses lame for mp3 encoding. Next step is to pull common functionality out of each shout plugin and share it between them. Configuration options for "shout_mp3" are the same as for "shout". --- configure.ac | 33 ++ m4/lame.m4 | 108 +++++ src/Makefile.am | 1 + src/audio.c | 1 + src/audioOutput.h | 1 + src/audioOutputs/audioOutput_shout_mp3.c | 676 ++++++++++++++++++++++++++++++ 6 files changed, 820 insertions(+), 0 deletions(-) create mode 100644 m4/lame.m4 create mode 100644 src/audioOutputs/audioOutput_shout_mp3.c diff --git a/configure.ac b/configure.ac index 6067bc4..f7ecc6c 100644 --- a/configure.ac +++ b/configure.ac @@ -71,6 +71,8 @@ fi AC_ARG_ENABLE(ao,[ --enable-ao enable support for libao (default: disable)],[enable_ao=$enableval],[enable_ao=no]) AC_ARG_ENABLE(shout,[ --disable-shout disable support for streaming through shout (default: enable)],[enable_shout=$enableval],[enable_shout=yes]) +AC_ARG_ENABLE(shout_mp3,[ --disable-shout_mp3 disable support for mp3 +streaming through shout (default: enable)],[enable_shout_mp3=$enableval],[enable_shout_mp3=yes]) AC_ARG_ENABLE(iconv,[ --disable-iconv disable iconv support (default: enable)],[enable_iconv=$enableval],[enable_iconv=yes]) AC_ARG_ENABLE(ipv6,[ --disable-ipv6 disable IPv6 support (default: enable)],[enable_ipv6=$enableval],[enable_ipv6=yes]) AC_ARG_ENABLE(tcp,[ --disable-tcp disable support for clients connecting via TCP (default: enable)],[enable_tcp=$enableval],[enable_tcp=yes]) @@ -85,6 +87,7 @@ AC_ARG_ENABLE(oggvorbis,[ --disable-oggvorbis disable Ogg Vorbis support (d AC_ARG_ENABLE(oggflac,[ --disable-oggflac disable OggFLAC support (default: enable)],[enable_oggflac=$enableval],enable_oggflac=yes) AC_ARG_ENABLE(flac,[ --disable-flac disable flac support (default: enable)],[enable_flac=$enableval],[enable_flac=yes]) AC_ARG_ENABLE(mp3,[ --disable-mp3 disable mp3 support (default: enable)],[enable_mp3=$enableval],[enable_mp3=yes]) +AC_ARG_ENABLE(lame,[ --disable-lame disable lame support (default: enable)],[enable_lame=$enableval],[enable_lame=yes]) AC_ARG_ENABLE(aac,[ --disable-aac disable AAC support (default: enable)],[enable_aac=$enableval],[enable_aac=yes]) AC_ARG_ENABLE(audiofile,[ --disable-audiofile disable audiofile support, disables wave support (default: enable)],[enable_audiofile=$enableval],[enable_audiofile=yes]) AC_ARG_ENABLE(mod,[ --enable-mod enable MOD support (default: disable)],[enable_mod=$enableval],[enable_mod=yes]) @@ -193,6 +196,13 @@ if test x$enable_shout = xyes; then fi fi +if test x$enable_shout_mp3 = xyes; then + if test x$enable_lame = xno; then + AC_MSG_WARN([disabling shout_mp3 streaming support because lame is not enabled]) + enable_shout_mp3=no + fi +fi + if test x$enable_ao = xyes; then XIPH_PATH_AO([AC_DEFINE(HAVE_AO, 1, [Define to play with ao]) MPD_LIBS="$MPD_LIBS $AO_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AO_CFLAGS"], enable_ao=no) fi @@ -201,6 +211,10 @@ if test x$enable_shout = xyes; then XIPH_PATH_SHOUT([AC_DEFINE(HAVE_SHOUT, 1, [Define to enable libshout support]) MPD_LIBS="$MPD_LIBS $SHOUT_LIBS" MPD_CFLAGS="$MPD_CFLAGS $SHOUT_CFLAGS"], enable_shout=no) fi +if test x$enable_shout_mp3 = xyes; then + XIPH_PATH_SHOUT([AC_DEFINE(HAVE_SHOUT_MP3, 1, [Define to enable mp3 libshout support]) MPD_LIBS="$MPD_LIBS $SHOUT_LIBS" MPD_CFLAGS="$MPD_CFLAGS $SHOUT_CFLAGS"], enable_shout_mp3=no) +fi + if test x$enable_oss = xyes; then AC_CHECK_HEADER(sys/soundcard.h,[enable_oss=yes;AC_DEFINE(HAVE_OSS,1,[Define to enable OSS])],[AC_MSG_WARN(Soundcard headers not found -- disabling OSS support);enable_oss=no]) fi @@ -354,6 +368,12 @@ if test x$enable_mp3 = xyes; then fi fi +if test x$enable_lame = xyes; then + AM_PATH_LAME([MPD_LIBS="$MPD_LIBS $LAME_LIBS" MPD_CFLAGS="$MPD_CFLAGS $LAME_CFLAGS"], + [enable_lame=no;AC_MSG_WARN(You need lame -- disabling lame support)]) +fi + + if test x$enable_mpc = xyes; then if test "x$mpcdec_libraries" != "x" ; then MPCDEC_LIBS="-L$mpcdec_libraries" @@ -743,11 +763,18 @@ else echo " Shout streaming support .......disabled" fi +if test x$enable_shout_mp3 = xyes; then + echo " Shout mp3 streaming support ...enabled" +else + echo " Shout mp3 streaming support ...disabled" +fi + echo "" if test x$enable_ao = xno && test x$enable_oss = xno && test x$enable_shout = xno && + test x$enable_shout_mp3 = xno && test x$enable_alsa = xno && test x$enable_osx = xno && test x$enable_pulse = xno && @@ -771,6 +798,12 @@ else echo " mp3 support ...................disabled" fi +if test x$enable_lame = xyes; then + echo " lame support ..................enabled" +else + echo " lame support ..................disabled" +fi + if test x$enable_oggvorbis = xyes; then echo " Ogg Vorbis support ............enabled" if test x$use_tremor = xyes; then diff --git a/m4/lame.m4 b/m4/lame.m4 new file mode 100644 index 0000000..5ebf550 --- /dev/null +++ b/m4/lame.m4 @@ -0,0 +1,108 @@ +dnl borrowed from oddsock.org +dnl AM_PATH_LAME([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for liblame, and define LAME_CFLAGS and LAME_LIBS +dnl +AC_DEFUN([AM_PATH_LAME], +[dnl +dnl Get the cflags and libraries +dnl +AC_ARG_WITH(lame,[ --with-lame=PFX Prefix where liblame is installed (optional)], lame_prefix="$withval", lame_prefix="") +AC_ARG_WITH(lame-libraries,[ --with-lame-libraries=DIR Directory where liblame library is installed (optional)], lame_libraries="$withval", lame_libraries="") +AC_ARG_WITH(lame-includes,[ --with-lame-includes=DIR Directory where liblame header files are installed (optional)], lame_includes="$withval", lame_includes="") +AC_ARG_ENABLE(lametest, [ --disable-lametest Do not try to compile and run a test liblame program],, enable_lametest=yes) + +if test "x$lame_prefix" != "xno" ; then + + if test "x$lame_libraries" != "x" ; then + LAME_LIBS="-L$lame_libraries" + elif test "x$lame_prefix" != "x" ; then + LAME_LIBS="-L$lame_prefix/lib" + elif test "x$prefix" != "xNONE" ; then + LAME_LIBS="-L$prefix/lib" + fi + + LAME_LIBS="$LAME_LIBS -lmp3lame -lm" + + if test "x$lame_includes" != "x" ; then + LAME_CFLAGS="-I$lame_includes" + elif test "x$lame_prefix" != "x" ; then + LAME_CFLAGS="-I$lame_prefix/include" + elif test "x$prefix" != "xNONE"; then + LAME_CFLAGS="-I$prefix/include" + fi + + AC_MSG_CHECKING(for liblame) + no_lame="" + + + if test "x$enable_lametest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $LAME_CFLAGS" + LIBS="$LIBS $LAME_LIBS" +dnl +dnl Now check if the installed liblame is sufficiently new. +dnl + rm -f conf.lametest + AC_TRY_RUN([ +#include +#include +#include +#include + +int main () +{ + system("touch conf.lametest"); + return 0; +} + +],, no_lame=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_lame" = "x" ; then + AC_MSG_RESULT(yes) + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT(no) + if test -f conf.lametest ; then + : + else + echo "*** Could not run liblame test program, checking why..." + CFLAGS="$CFLAGS $LAME_CFLAGS" + LIBS="$LIBS $LAME_LIBS" + AC_TRY_LINK([ +#include +#include +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding liblame or finding the wrong" + echo "*** version of liblame. If it is not finding liblame, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means liblame was incorrectly installed" + echo "*** or that you have moved liblame since it was installed." ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + LAME_CFLAGS="" + LAME_LIBS="" + ifelse([$2], , :, [$2]) + fi + AC_DEFINE(HAVE_LAME, 1, [Define if you have liblame.]) + use_lame="1" +else + LAME_CFLAGS="" + LAME_LIBS="" +fi + AC_SUBST(LAME_CFLAGS) + AC_SUBST(LAME_LIBS) + rm -f conf.lametest +]) + diff --git a/src/Makefile.am b/src/Makefile.am index 6c52dde..512e84e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,6 +3,7 @@ SUBDIRS = $(MP4FF_SUBDIR) mpd_audioOutputs = \ audioOutputs/audioOutput_shout.c \ + audioOutputs/audioOutput_shout_mp3.c \ audioOutputs/audioOutput_null.c \ audioOutputs/audioOutput_fifo.c \ audioOutputs/audioOutput_alsa.c \ diff --git a/src/audio.c b/src/audio.c index 34b74e6..3334ff0 100644 --- a/src/audio.c +++ b/src/audio.c @@ -85,6 +85,7 @@ void loadAudioDrivers(void) { initAudioOutputPlugins(); loadAudioOutputPlugin(&shoutPlugin); + loadAudioOutputPlugin(&shoutMp3Plugin); loadAudioOutputPlugin(&nullPlugin); loadAudioOutputPlugin(&fifoPlugin); loadAudioOutputPlugin(&alsaPlugin); diff --git a/src/audioOutput.h b/src/audioOutput.h index 7574f5a..b98334e 100644 --- a/src/audioOutput.h +++ b/src/audioOutput.h @@ -109,6 +109,7 @@ void sendMetadataToAudioOutput(AudioOutput * audioOutput, MpdTag * tag); void printAllOutputPluginTypes(FILE * fp); extern AudioOutputPlugin shoutPlugin; +extern AudioOutputPlugin shoutMp3Plugin; extern AudioOutputPlugin nullPlugin; extern AudioOutputPlugin fifoPlugin; extern AudioOutputPlugin alsaPlugin; diff --git a/src/audioOutputs/audioOutput_shout_mp3.c b/src/audioOutputs/audioOutput_shout_mp3.c new file mode 100644 index 0000000..da76a21 --- /dev/null +++ b/src/audioOutputs/audioOutput_shout_mp3.c @@ -0,0 +1,676 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * 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" + +#ifdef HAVE_SHOUT_MP3 + +#include "../conf.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../timer.h" + +#include +#include + +#define CONN_ATTEMPT_INTERVAL 60 +#define DEFAULT_CONN_TIMEOUT 2 +#define MP3_BUF_SIZE 1048576 + +static int shoutInitCount; + +/* lots of this code blatantly stolent from bossogg/bossao2 */ + +typedef struct _ShoutData { + shout_t *shoutConn; + int shoutError; + shout_metadata_t *shoutMeta; + + lame_global_flags *gfp; + unsigned char mp3buf[MP3_BUF_SIZE]; + unsigned int mp3buf_full; + + float quality; + int bitrate; + + int opened; + + MpdTag *tag; + int tagToSend; + + int timeout; + int connAttempts; + time_t lastAttempt; + + Timer *timer; + + /* just a pointer to audioOutput->outAudioFormat */ + AudioFormat *audioFormat; +} ShoutData; + +static ShoutData *newShoutData(void) +{ + ShoutData *ret = xmalloc(sizeof(ShoutData)); + + ret->shoutConn = shout_new(); + ret->shoutMeta = shout_metadata_new(); + ret->opened = 0; + ret->tag = NULL; + ret->tagToSend = 0; + ret->bitrate = -1; + ret->quality = -2.0; + ret->timeout = DEFAULT_CONN_TIMEOUT; + ret->connAttempts = 0; + ret->lastAttempt = 0; + ret->audioFormat = NULL; + ret->timer = NULL; + + return ret; +} + +static void freeShoutData(ShoutData * sd) +{ + if (sd->shoutMeta) + shout_metadata_free(sd->shoutMeta); + if (sd->shoutConn) + shout_free(sd->shoutConn); + if (sd->tag) + freeMpdTag(sd->tag); + if (sd->timer) + timer_free(sd->timer); + + free(sd); +} + +#define checkBlockParam(name) { \ + blockParam = getBlockParam(param, name); \ + if (!blockParam) { \ + FATAL("no \"%s\" defined for shout_mp3 device defined at line " \ + "%i\n", name, param->line); \ + } \ +} + +static int myShoutMp3_initDriver(AudioOutput * audioOutput, + ConfigParam * param) +{ + ShoutData *sd; + char *test; + int port; + char *host; + char *mount; + char *passwd; + const char *user; + char *name; + BlockParam *blockParam; + int public; + + 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) { + FATAL("shout port \"%s\" is not a positive integer, line %i\n", + blockParam->value, blockParam->line); + } + + checkBlockParam("password"); + passwd = blockParam->value; + + checkBlockParam("name"); + name = blockParam->value; + + public = getBoolBlockParam(param, "public", 1); + if (public == CONF_BOOL_UNSET) + public = 0; + + blockParam = getBlockParam(param, "user"); + if (blockParam) + user = blockParam->value; + else + user = "source"; + + blockParam = getBlockParam(param, "quality"); + + if (blockParam) { + int line = blockParam->line; + + sd->quality = strtod(blockParam->value, &test); + + if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) { + FATAL("shout quality \"%s\" is not a number in the " + "range -1 to 10, line %i\n", blockParam->value, + blockParam->line); + } + + blockParam = getBlockParam(param, "bitrate"); + + if (blockParam) { + FATAL("quality (line %i) and bitrate (line %i) are " + "both defined for shout_mp3 output\n", line, + blockParam->line); + } + } else { + blockParam = getBlockParam(param, "bitrate"); + + if (!blockParam) { + FATAL("neither bitrate nor quality defined for shout_mp3 " + "output at line %i\n", param->line); + } + + sd->bitrate = strtol(blockParam->value, &test, 10); + + if (*test != '\0' || sd->bitrate <= 0) { + FATAL("bitrate at line %i should be a positive integer " + "\n", blockParam->line); + } + } + + checkBlockParam("format"); + sd->audioFormat = &audioOutput->outAudioFormat; + + 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_public(sd->shoutConn, public) != SHOUTERR_SUCCESS || + shout_set_nonblocking(sd->shoutConn, 1) != SHOUTERR_SUCCESS || + shout_set_format(sd->shoutConn, SHOUT_FORMAT_MP3) + != SHOUTERR_SUCCESS || + shout_set_protocol(sd->shoutConn, SHOUT_PROTOCOL_HTTP) + != SHOUTERR_SUCCESS || + shout_set_agent(sd->shoutConn, "MPD") != SHOUTERR_SUCCESS) { + FATAL("error configuring shout_mp3 defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + /* optional paramters */ + blockParam = getBlockParam(param, "timeout"); + if (blockParam) { + sd->timeout = (int)strtol(blockParam->value, &test, 10); + if (*test != '\0' || sd->timeout <= 0) { + FATAL("shout timeout is not a positive integer, " + "line %i\n", blockParam->line); + } + } + + blockParam = getBlockParam(param, "genre"); + if (blockParam && shout_set_genre(sd->shoutConn, blockParam->value)) { + FATAL("error configuring shout_mp3 defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + blockParam = getBlockParam(param, "description"); + if (blockParam && shout_set_description(sd->shoutConn, + blockParam->value)) { + FATAL("error configuring shout_mp3 defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + { + char temp[11]; + memset(temp, 0, sizeof(temp)); + + snprintf(temp, sizeof(temp), "%d", sd->audioFormat->channels); + shout_set_audio_info(sd->shoutConn, SHOUT_AI_CHANNELS, temp); + + snprintf(temp, sizeof(temp), "%d", sd->audioFormat->sampleRate); + + shout_set_audio_info(sd->shoutConn, SHOUT_AI_SAMPLERATE, temp); + + if (sd->quality >= -1.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 int myShoutMp3_handleError(ShoutData * sd, int err) +{ + switch (err) { + case SHOUTERR_SUCCESS: + break; + case SHOUTERR_UNCONNECTED: + case SHOUTERR_SOCKET: + ERROR("Lost shout_mp3 connection to %s:%i: %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + shout_get_error(sd->shoutConn)); + sd->shoutError = 1; + return -1; + default: + ERROR("shout_mp3: connection to %s:%i error: %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + shout_get_error(sd->shoutConn)); + sd->shoutError = 1; + return -1; + } + + return 0; +} + +static int write_page(ShoutData * sd) +{ + int err; + + shout_sync(sd->shoutConn); + err = shout_send(sd->shoutConn, sd->mp3buf, sd->mp3buf_full); + if (myShoutMp3_handleError(sd, err) < 0) + return -1; + + return 0; +} + +static void finishEncoder(ShoutData * sd) +{ + /* Does lame require anything to be done here? */ +} + +static int flushEncoder(ShoutData * sd) +{ + /* Does lame require anything to be done here? */ + return 0; +} + +static void clearEncoder(ShoutData * sd) +{ + finishEncoder(sd); + while (1 == flushEncoder(sd)) { + if (!sd->shoutError) + write_page(sd); + } + + lame_close(sd->gfp); +} + +static void myShoutMp3_closeShoutConn(ShoutData * sd) +{ + if (sd->opened) + clearEncoder(sd); + + if (shout_get_connected(sd->shoutConn) != SHOUTERR_UNCONNECTED && + shout_close(sd->shoutConn) != SHOUTERR_SUCCESS) { + ERROR("problem closing connection to shout_mp3 server: %s\n", + shout_get_error(sd->shoutConn)); + } + + sd->opened = 0; +} + +static void myShoutMp3_finishDriver(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + myShoutMp3_closeShoutConn(sd); + + freeShoutData(sd); + + shoutInitCount--; + + if (shoutInitCount == 0) + shout_shutdown(); +} + +static void myShoutMp3_dropBufferedAudio(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *)audioOutput->data; + timer_reset(sd->timer); + + /* needs to be implemented for shout_mp3 */ +} + +static void myShoutMp3_closeDevice(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + myShoutMp3_closeShoutConn(sd); + + if (sd->timer) { + timer_free(sd->timer); + sd->timer = NULL; + } + + audioOutput->open = 0; +} + +static void sendMetadata(ShoutData *sd) +{ + const size_t tag_size = 1024; + char song[tag_size]; + char artist[tag_size]; + char title[tag_size]; + + if (sd->tag) { + int i; + + for (i = 0; i < sd->tag->numOfItems; i++) { + switch (sd->tag->items[i].type) { + case TAG_ITEM_ARTIST: + snprintf(artist, tag_size, "%s", + sd->tag->items[i].value); + break; + case TAG_ITEM_TITLE: + snprintf(title, tag_size, "%s", + sd->tag->items[i].value); + break; + } + } + snprintf(song, tag_size, "%s - %s", title, artist); + shout_metadata_add(sd->shoutMeta, "song", song); + if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shoutConn, + sd->shoutMeta)) { + ERROR("error setting shout_mp3 metadata\n"); + return; + } + } +} + +static int initEncoder(ShoutData * sd) +{ + if (NULL == (sd->gfp = lame_init())) { + ERROR("problem setting up lame encoder for shout_mp3\n"); + lame_close(sd->gfp); /* necessary? */ + return -1; + } + + if (sd->quality >= -1.0) { + if (0 != lame_set_VBR(sd->gfp, vbr_rh)) { + ERROR("problem setting up lame encoder for shout_mp3\n"); + lame_close(sd->gfp); + return -1; + } + if (0 != lame_set_VBR_q(sd->gfp, sd->quality)) { + ERROR("problem setting up lame encoder for shout_mp3\n"); + lame_close(sd->gfp); + return -1; + } + } else { + if (0 != lame_set_brate(sd->gfp, sd->bitrate)) { + ERROR("problem setting up lame encoder for shout_mp3\n"); + lame_close(sd->gfp); + return -1; + } + } + + if (0 != lame_set_num_channels(sd->gfp, + sd->audioFormat->channels)) { + ERROR("problem setting up lame encoder for shout\n"); + lame_close(sd->gfp); + return -1; + } + + if (0 != lame_set_in_samplerate(sd->gfp, + sd->audioFormat->sampleRate)) { + ERROR("problem setting up lame encoder for shout\n"); + lame_close(sd->gfp); + return -1; + } + + if (0 > lame_init_params(sd->gfp)) { + ERROR("problem setting up lame encoder for shout\n"); + lame_close(sd->gfp); + return -1; + } + + return 0; +} + +static int myShoutMp3_connect(ShoutData *sd) +{ + time_t t = time(NULL); + int state = shout_get_connected(sd->shoutConn); + + /* already connected */ + if (state == SHOUTERR_CONNECTED) + return 0; + + /* waiting to connect */ + if (state == SHOUTERR_BUSY && sd->connAttempts != 0) { + /* timeout waiting to connect */ + if ((t - sd->lastAttempt) > sd->timeout) { + ERROR("timeout connecting to shout server %s:%i " + "(attempt %i)\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + sd->connAttempts); + return -1; + } + + return 1; + } + + /* we're in some funky state, so just reset it to unconnected */ + if (state != SHOUTERR_UNCONNECTED) + shout_close(sd->shoutConn); + + /* throttle new connection attempts */ + if (sd->connAttempts != 0 && + (t - sd->lastAttempt) <= CONN_ATTEMPT_INTERVAL) { + return -1; + } + + /* initiate a new connection */ + + sd->connAttempts++; + sd->lastAttempt = t; + + state = shout_open(sd->shoutConn); + switch (state) { + case SHOUTERR_SUCCESS: + case SHOUTERR_CONNECTED: + return 0; + case SHOUTERR_BUSY: + return 1; + default: + ERROR("problem opening connection to shout server %s:%i " + "(attempt %i): %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + sd->connAttempts, shout_get_error(sd->shoutConn)); + return -1; + } +} + +static int myShoutMp3_openShoutConn(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + int status; + + status = myShoutMp3_connect(sd); + if (status != 0) + return status; + + if (initEncoder(sd) < 0) { + shout_close(sd->shoutConn); + return -1; + } + + sd->shoutError = 0; + + sendMetadata(sd); + + sd->opened = 1; + sd->tagToSend = 0; + + while (lame_encode_flush(sd->gfp, sd->mp3buf, sd->mp3buf_full)) { + if (write_page(sd) < 0) { + myShoutMp3_closeShoutConn(sd); + return -1; + } + } + + sd->connAttempts = 0; + + return 0; +} + +static int myShoutMp3_openDevice(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + if (!sd->opened && myShoutMp3_openShoutConn(audioOutput) < 0) + return -1; + + if (sd->timer) + timer_free(sd->timer); + + sd->timer = timer_new(&audioOutput->outAudioFormat); + + audioOutput->open = 1; + + return 0; +} + +static void myShoutMp3_sendMetadata(ShoutData * sd) +{ + if (!sd->opened || !sd->tag) + return; + + clearEncoder(sd); + if (initEncoder(sd) < 0) + return; + + sendMetadata(sd); + + while (lame_encode_flush(sd->gfp, sd->mp3buf, sd->mp3buf_full)) { + if (write_page(sd) < 0) { + myShoutMp3_closeShoutConn(sd); + return -1; + } + } + + /*if(sd->tag) freeMpdTag(sd->tag); + sd->tag = NULL; */ + sd->tagToSend = 0; +} + +static int myShoutMp3_play(AudioOutput * audioOutput, + const char *playChunk, size_t size) +{ + unsigned int i; + int j; + ShoutData *sd = (ShoutData *) audioOutput->data; + float lamebuf[2][50000]; /* make me dynamic? */ + unsigned int samples; + int bytes = sd->audioFormat->bits / 8; + int status; + + if (!sd->timer->started) + timer_start(sd->timer); + + timer_add(sd->timer, size); + + if (sd->opened && sd->tagToSend) + myShoutMp3_sendMetadata(sd); + + if (!sd->opened) { + status = myShoutMp3_openShoutConn(audioOutput); + if (status < 0) { + myShoutMp3_closeDevice(audioOutput); + return -1; + } else if (status > 0) { + timer_sync(sd->timer); + return 0; + } + } + + samples = size / (bytes * sd->audioFormat->channels); + + /* this is for only 16-bit audio */ + + for (i = 0; i < samples; i++) { + for (j = 0; j < sd->audioFormat->channels; j++) { + lamebuf[j][i] = (*((mpd_sint16 *) playChunk)); + playChunk += bytes; + } + } + + if (0 > (sd->mp3buf_full = + (lame_encode_buffer_float(sd->gfp, lamebuf[0], lamebuf[1], + samples, sd->mp3buf, + MP3_BUF_SIZE)))) { + ERROR("problem encoding lame buffer for shout_mp3\n"); + lame_close(sd->gfp); + myShoutMp3_closeShoutConn(sd); + return -1; + } + + if (write_page(sd) < 0) { + myShoutMp3_closeDevice(audioOutput); + return -1; + } + + return 0; +} + +static void myShoutMp3_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 shoutMp3Plugin = { + "shout_mp3", + NULL, + myShoutMp3_initDriver, + myShoutMp3_finishDriver, + myShoutMp3_openDevice, + myShoutMp3_play, + myShoutMp3_dropBufferedAudio, + myShoutMp3_closeDevice, + myShoutMp3_setTag, +}; + +#else + +DISABLED_AUDIO_OUTPUT_PLUGIN(shoutMp3Plugin) +#endif -- 1.5.4.3