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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <lame/lame.h> + +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 <stdio.h> +#include <lame/lame.h> +], [ 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 ([EMAIL PROTECTED]) + * 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 <shout/shout.h> +#include <lame/lame.h> + +#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 ------------------------------------------------------------------------- This SF.Net email is sponsored by the Moblin Your Move Developer's challenge Build the coolest Linux based applications with Moblin SDK & win great prizes Grand prize is a trip for two to an Open Source event anywhere in the world http://moblin-contest.org/redirect.php?banner_id=100&url=/ _______________________________________________ Musicpd-dev-team mailing list Musicpd-dev-team@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/musicpd-dev-team