diff options
-rw-r--r-- | configure.ac | 39 | ||||
-rw-r--r-- | doc/mpd.conf.5 | 10 | ||||
-rw-r--r-- | doc/mpdconf.example | 1 | ||||
-rw-r--r-- | src/audioOutputs/audioOutput_shout.c | 75 | ||||
-rw-r--r-- | src/audioOutputs/audioOutput_shout.h | 64 | ||||
-rw-r--r-- | src/audioOutputs/audioOutput_shout_ogg.c | 93 |
6 files changed, 209 insertions, 73 deletions
diff --git a/configure.ac b/configure.ac index c3db4cbc1..ffe161307 100644 --- a/configure.ac +++ b/configure.ac @@ -73,7 +73,7 @@ if test -z "$prefix" || test "x$prefix" = xNONE; then 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_ogg,[ --disable-shout_ogg disable support for ogg streaming through shout (default: enable)],[enable_shout_ogg=$enableval],[enable_shout_ogg=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]) @@ -185,14 +185,25 @@ case $host in enable_osx=yes ;; esac -if test x$enable_shout = xyes; then +if test x$enable_shout_ogg = xyes || x$enable_shout_mp3 = xyes; then + XIPH_PATH_SHOUT([enable_shout=yes;AC_DEFINE(HAVE_SHOUT, 1, [Define to enable libshout support]) MPD_LIBS="$MPD_LIBS $SHOUT_LIBS" MPD_CFLAGS="$MPD_CFLAGS $SHOUT_CFLAGS"], [AC_MSG_WARN(libshout not found -- disabling shout support);enable_shout=no]) +fi + +if test x$enable_shout_ogg = xyes; then if test x$enable_oggvorbis = xno; then - AC_MSG_WARN([disabling shout streaming support because vorbis is not enabled]) - enable_shout=no + AC_MSG_WARN([disabling ogg shout streaming support because vorbis is not enabled]) + enable_shout_ogg=no fi if test x$use_tremor = xyes; then - AC_MSG_WARN([disabling shout streaming support because tremor does not support vorbis encoding]) - enable_shout=no + AC_MSG_WARN([disabling ogg shout streaming support because tremor does not support vorbis encoding]) + enable_shout_ogg=no + fi + if test x$enable_shout = xno; then + AC_MSG_WARN([disabling ogg shout streaming support because libshout is not found]) + enable_shout_ogg=no + fi + if test x$enable_shout_ogg = xyes; then + AC_DEFINE(HAVE_SHOUT_OGG, 1, [Define to enable ogg streaming support]) fi fi @@ -200,12 +211,8 @@ 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 -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_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]) + 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 PKG_PROG_PKG_CONFIG @@ -535,7 +542,7 @@ elif test x$enable_oggvorbis = xyes; then MPD_LIBS="$MPD_LIBS $OGG_LIBS $VORBIS_LIBS $VORBISFILE_LIBS" MPD_CFLAGS="$MPD_CFLAGS $OGG_CFLAGS $VORBIS_CFLAGS" - if test x$enable_shout = xyes; then + if test x$enable_shout_ogg = xyes; then MPD_LIBS="$MPD_LIBS $VORBISENC_LIBS" MPD_CFLAGS="$MPD_CFLAGS $VORBISFILE_CFLAGS $VORBISENC_CFLAGS" fi @@ -740,17 +747,17 @@ else echo " Media MVP support .............disabled" fi -if test x$enable_shout = xyes; then - echo " Shout streaming support .......enabled" +if test x$enable_shout_ogg = xyes; then + echo " Shout ogg streaming support ...enabled" else - echo " Shout streaming support .......disabled" + echo " Shout ogg 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_ogg = xno && test x$enable_alsa = xno && test x$enable_osx = xno && test x$enable_pulse = xno && diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index 371c0d774..5a77d4944 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -365,9 +365,11 @@ This specifies the icecast mountpoint to use. This specifies the password to use when logging in to the icecast server. .TP .B quality <quality> -This specifies the ogg encoding quality to use. The value must be between 0 +This specifies the encoding quality to use. The value must be between 0 and 10. Fractional values, such as 2.5, are permitted. Either the quality or -the bitrate parameter must be specified, but not both. +the bitrate parameter must be specified, but not both. For Ogg, a +higher quality number produces higher quality output. For MP3, it's +just the opposite, with lower numbers producing higher quality output. .TP .B bitrate <kbps> This specifies the bitrate to use for encoding. Either the quality or the @@ -378,6 +380,10 @@ This specifies the sample rate, bits per sample, and number of channels to use for encoding. .SH OPTIONAL SHOUT OUTPUT PARAMETERS .TP +.B encoding <encoding> +This specifies which output encoding to use. Should be either "ogg" +or "mp3". The default is "ogg". +.TP .B user <username> This specifies the username to use when logging in to the icecast server. The default is "source". diff --git a/doc/mpdconf.example b/doc/mpdconf.example index be04adb91..995bba3aa 100644 --- a/doc/mpdconf.example +++ b/doc/mpdconf.example @@ -112,6 +112,7 @@ error_file "~/.mpd/mpd.error" # #audio_output { # type "shout" +# encoding "ogg" # name "My Shout Stream" # host "localhost" # port "8000" diff --git a/src/audioOutputs/audioOutput_shout.c b/src/audioOutputs/audioOutput_shout.c index 5bd2b575c..1e60e3546 100644 --- a/src/audioOutputs/audioOutput_shout.c +++ b/src/audioOutputs/audioOutput_shout.c @@ -20,6 +20,7 @@ #ifdef HAVE_SHOUT +#include "../list.h" #include "../utils.h" #define CONN_ATTEMPT_INTERVAL 60 @@ -28,6 +29,33 @@ #define SHOUT_BUF_SIZE 8192 static int shout_init_count; +static List *shout_encoder_plugin_list; + +mpd_unused +static void finish_shout_encoder_plugins(void) +{ + freeList(shout_encoder_plugin_list); +} + +static void init_shout_encoder_plugins(void) +{ + shout_encoder_plugin_list = makeList(NULL, 0); +} + +static void load_shout_encoder_plugin(shout_encoder_plugin * plugin) +{ + if (!plugin->name) + return; + insertInList(shout_encoder_plugin_list, plugin->name, plugin); +} + +mpd_unused +static void unload_shout_encoder_plugin(shout_encoder_plugin * plugin) +{ + if (!plugin->name) + return; + deleteFromList(shout_encoder_plugin_list, plugin->name); +} static void clear_shout_buffer(struct shout_data * sd) { @@ -82,6 +110,12 @@ static void free_shout_data(struct shout_data *sd) } \ } +static void load_shout_plugins(void) +{ + init_shout_encoder_plugins(); + load_shout_encoder_plugin(&shout_ogg_encoder); +} + static int my_shout_init_driver(struct audio_output *audio_output, ConfigParam * param) { @@ -91,10 +125,14 @@ static int my_shout_init_driver(struct audio_output *audio_output, char *host; char *mount; char *passwd; + const char *encoding; const char *user; char *name; BlockParam *block_param; int public; + void *data = NULL; + + load_shout_plugins(); sd = new_shout_data(); @@ -173,6 +211,25 @@ static int my_shout_init_driver(struct audio_output *audio_output, check_block_param("format"); sd->audio_format = audio_output->reqAudioFormat; + block_param = getBlockParam(param, "encoding"); + if (block_param) { + if (0 == strncasecmp(block_param->value, "mp3", 3)) + encoding = block_param->value; + else if (0 == strncasecmp(block_param->value, "ogg", 3)) + encoding = block_param->value; + else + FATAL("shout encoding \"%s\" is not \"ogg\" or " + "\"mp3\", line %i\n", block_param->value, + block_param->line); + } else { + encoding = "ogg"; + } + if (!findInList(shout_encoder_plugin_list, encoding, &data)) { + FATAL("couldn't find shout encoder plugin for \"%s\" " + "at line %i\n", encoding, block_param->line); + } + sd->encoder = (shout_encoder_plugin *) data; + if (shout_set_host(sd->shout_conn, host) != SHOUTERR_SUCCESS || shout_set_port(sd->shout_conn, port) != SHOUTERR_SUCCESS || shout_set_password(sd->shout_conn, passwd) != SHOUTERR_SUCCESS || @@ -181,7 +238,7 @@ static int my_shout_init_driver(struct audio_output *audio_output, shout_set_user(sd->shout_conn, user) != SHOUTERR_SUCCESS || shout_set_public(sd->shout_conn, public) != SHOUTERR_SUCCESS || shout_set_nonblocking(sd->shout_conn, 1) != SHOUTERR_SUCCESS || - shout_set_format(sd->shout_conn, SHOUT_FORMAT_VORBIS) + shout_set_format(sd->shout_conn, sd->encoder->shout_format) != SHOUTERR_SUCCESS || shout_set_protocol(sd->shout_conn, SHOUT_PROTOCOL_HTTP) != SHOUTERR_SUCCESS || @@ -237,7 +294,7 @@ static int my_shout_init_driver(struct audio_output *audio_output, audio_output->data = sd; - return 0; + return sd->encoder->init_func(sd); } static int handle_shout_error(struct shout_data *sd, int err) @@ -278,10 +335,10 @@ static int write_page(struct shout_data *sd) return 0; } -static void close_shout_conn(struct shout_data *sd) +static void close_shout_conn(struct shout_data * sd) { if (sd->opened) { - if (shout_ogg_encoder_clear_encoder(sd)) + if (sd->encoder->clear_encoder_func(sd)) write_page(sd); } @@ -300,6 +357,7 @@ static void my_shout_finish_driver(struct audio_output *audio_output) close_shout_conn(sd); + sd->encoder->finish_func(sd); free_shout_data(sd); shout_init_count--; @@ -395,7 +453,7 @@ static int open_shout_conn(struct audio_output *audio_output) if (status != 0) return status; - if (init_encoder(sd) < 0) { + if (sd->encoder->init_encoder_func(sd) < 0) { shout_close(sd->shout_conn); return -1; } @@ -436,7 +494,7 @@ static void send_metadata(struct shout_data * sd) if (!sd->opened || !sd->tag) return; - if (shout_ogg_encoder_send_metadata(sd, song, size)) { + if (sd->encoder->send_metadata_func(sd, song, size)) { shout_metadata_add(sd->shout_meta, "song", song); if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn, sd->shout_meta)) { @@ -473,7 +531,10 @@ static int my_shout_play(struct audio_output *audio_output, } } - shout_ogg_encoder_encode(sd, chunk, size); + if (sd->encoder->encode_func(sd, chunk, size)) { + my_shout_close_device(audio_output); + return -1; + } if (write_page(sd) < 0) { my_shout_close_device(audio_output); diff --git a/src/audioOutputs/audioOutput_shout.h b/src/audioOutputs/audioOutput_shout.h index eee2ccad2..c21ddff40 100644 --- a/src/audioOutputs/audioOutput_shout.h +++ b/src/audioOutputs/audioOutput_shout.h @@ -27,7 +27,38 @@ #include "../timer.h" #include <shout/shout.h> -#include <vorbis/vorbisenc.h> + +#define DISABLED_SHOUT_ENCODER_PLUGIN(plugin) shout_encoder_plugin plugin; + +typedef struct shout_data shout_data; + +typedef int (*shout_encoder_clear_encoder_func) (shout_data * sd); +typedef int (*shout_encoder_encode_func) (shout_data * sd, + const char * chunk, + size_t len); +typedef void (*shout_encoder_finish_func) (shout_data * sd); +typedef int (*shout_encoder_init_func) (shout_data * sd); +typedef int (*shout_encoder_init_encoder_func) (shout_data * sd); +/* Called when there is a new MpdTag to encode into the stream. If + this function returns non-zero, then the resulting song will be + passed to the shout server as metadata. This allows the Ogg + encoder to send metadata via Vorbis comments in the stream, while + an MP3 encoder can use the Shout Server's metadata API. */ +typedef int (*shout_encoder_send_metadata_func) (shout_data * sd, + char * song, + size_t size); + +typedef struct _shout_encoder_plugin { + const char *name; + unsigned int shout_format; + + shout_encoder_clear_encoder_func clear_encoder_func; + shout_encoder_encode_func encode_func; + shout_encoder_finish_func finish_func; + shout_encoder_init_func init_func; + shout_encoder_init_encoder_func init_encoder_func; + shout_encoder_send_metadata_func send_metadata_func; +} shout_encoder_plugin; typedef struct _shout_buffer { unsigned char *data; @@ -35,26 +66,13 @@ typedef struct _shout_buffer { size_t max_len; } shout_buffer; -typedef struct _ogg_vorbis_data { - 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; -} ogg_vorbis_data; - struct shout_data { shout_t *shout_conn; shout_metadata_t *shout_meta; int shout_error; - ogg_vorbis_data od; + shout_encoder_plugin *encoder; + void *encoder_data; float quality; int bitrate; @@ -76,19 +94,7 @@ struct shout_data { shout_buffer buf; }; -void copy_tag_to_vorbis_comment(struct shout_data *sd); - -int send_ogg_vorbis_header(struct shout_data *sd); - -int shout_ogg_encoder_clear_encoder(struct shout_data *sd); - -int init_encoder(struct shout_data *sd); - -int shout_ogg_encoder_send_metadata(struct shout_data * sd, - char *song, size_t size); - -void shout_ogg_encoder_encode(struct shout_data *sd, - const char *chunk, size_t len); +extern shout_encoder_plugin shout_ogg_encoder; #endif diff --git a/src/audioOutputs/audioOutput_shout_ogg.c b/src/audioOutputs/audioOutput_shout_ogg.c index d0d1b6880..5e0adf4d0 100644 --- a/src/audioOutputs/audioOutput_shout_ogg.c +++ b/src/audioOutputs/audioOutput_shout_ogg.c @@ -18,9 +18,24 @@ #include "audioOutput_shout.h" -#ifdef HAVE_SHOUT +#ifdef HAVE_SHOUT_OGG #include "../utils.h" +#include <vorbis/vorbisenc.h> + +typedef struct _ogg_vorbis_data { + 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; +} ogg_vorbis_data; static void add_tag(ogg_vorbis_data *od, const char *name, char *value) { @@ -31,9 +46,9 @@ static void add_tag(ogg_vorbis_data *od, const char *name, char *value) } } -void copy_tag_to_vorbis_comment(struct shout_data *sd) +static void copy_tag_to_vorbis_comment(struct shout_data *sd) { - ogg_vorbis_data *od = &sd->od; + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; if (sd->tag) { int i; @@ -49,6 +64,7 @@ void copy_tag_to_vorbis_comment(struct shout_data *sd) case TAG_ITEM_TITLE: add_tag(od, "TITLE", sd->tag->items[i]->value); break; + default: break; } @@ -83,7 +99,7 @@ static int copy_ogg_buffer_to_shout_buffer(ogg_page *og, static int flush_ogg_buffer(struct shout_data *sd) { shout_buffer *buf = &sd->buf; - ogg_vorbis_data *od = &sd->od; + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; int ret = 0; if (ogg_stream_flush(&od->os, &od->og)) @@ -92,9 +108,9 @@ static int flush_ogg_buffer(struct shout_data *sd) return ret; } -int send_ogg_vorbis_header(struct shout_data *sd) +static int send_ogg_vorbis_header(struct shout_data *sd) { - ogg_vorbis_data *od = &sd->od; + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; vorbis_analysis_headerout(&od->vd, &od->vc, &od->header_main, @@ -121,9 +137,9 @@ static void finish_encoder(ogg_vorbis_data *od) } } -int shout_ogg_encoder_clear_encoder(struct shout_data *sd) +static int shout_ogg_encoder_clear_encoder(struct shout_data *sd) { - ogg_vorbis_data *od = &sd->od; + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; int ret; finish_encoder(od); @@ -139,9 +155,30 @@ int shout_ogg_encoder_clear_encoder(struct shout_data *sd) return ret; } +static void shout_ogg_encoder_finish(struct shout_data *sd) +{ + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; + + if (od) { + free(od); + sd->encoder_data = NULL; + } +} + +static int shout_ogg_encoder_init(struct shout_data *sd) +{ + ogg_vorbis_data *od; + + if (NULL == (od = xmalloc(sizeof(ogg_vorbis_data)))) + FATAL("error initializing ogg vorbis encoder data\n"); + sd->encoder_data = od; + + return 0; +} + static int reinit_encoder(struct shout_data *sd) { - ogg_vorbis_data *od = &sd->od; + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; vorbis_info_init(&od->vi); @@ -150,7 +187,7 @@ static int reinit_encoder(struct shout_data *sd) sd->audio_format.channels, sd->audio_format.sampleRate, sd->quality * 0.1)) { - ERROR("problem setting up vorbis encoder for shout\n"); + ERROR("error initializing vorbis vbr\n"); vorbis_info_clear(&od->vi); return -1; } @@ -159,7 +196,7 @@ static int reinit_encoder(struct shout_data *sd) sd->audio_format.channels, sd->audio_format.sampleRate, -1.0, sd->bitrate * 1000, -1.0)) { - ERROR("problem setting up vorbis encoder for shout\n"); + ERROR("error initializing vorbis encoder\n"); vorbis_info_clear(&od->vi); return -1; } @@ -173,7 +210,7 @@ static int reinit_encoder(struct shout_data *sd) return 0; } -int init_encoder(struct shout_data *sd) +static int shout_ogg_encoder_init_encoder(struct shout_data *sd) { if (reinit_encoder(sd)) return -1; @@ -186,11 +223,11 @@ int init_encoder(struct shout_data *sd) return 0; } -int shout_ogg_encoder_send_metadata(struct shout_data * sd, - mpd_unused char * song, - mpd_unused size_t size) +static int shout_ogg_encoder_send_metadata(struct shout_data *sd, + mpd_unused char * song, + mpd_unused size_t size) { - ogg_vorbis_data *od = &sd->od; + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; shout_ogg_encoder_clear_encoder(sd); if (reinit_encoder(sd)) @@ -212,8 +249,8 @@ int shout_ogg_encoder_send_metadata(struct shout_data * sd, return 0; } -void shout_ogg_encoder_encode(struct shout_data *sd, - const char *chunk, size_t size) +static int shout_ogg_encoder_encode(struct shout_data *sd, + const char *chunk, size_t size) { shout_buffer *buf = &sd->buf; unsigned int i; @@ -221,7 +258,7 @@ void shout_ogg_encoder_encode(struct shout_data *sd, float **vorbbuf; unsigned int samples; int bytes = sd->audio_format.bits / 8; - ogg_vorbis_data *od = &sd->od; + ogg_vorbis_data *od = (ogg_vorbis_data *)sd->encoder_data; samples = size / (bytes * sd->audio_format.channels); vorbbuf = vorbis_analysis_buffer(&od->vd, samples); @@ -248,6 +285,24 @@ void shout_ogg_encoder_encode(struct shout_data *sd, if (ogg_stream_pageout(&od->os, &od->og)) copy_ogg_buffer_to_shout_buffer(&od->og, buf); + + return 0; } +shout_encoder_plugin shout_ogg_encoder = { + "ogg", + SHOUT_FORMAT_VORBIS, + + shout_ogg_encoder_clear_encoder, + shout_ogg_encoder_encode, + shout_ogg_encoder_finish, + shout_ogg_encoder_init, + shout_ogg_encoder_init_encoder, + shout_ogg_encoder_send_metadata, +}; + +#else + +DISABLED_SHOUT_ENCODER_PLUGIN(shout_ogg_encoder); + #endif |