diff options
Diffstat (limited to 'src')
333 files changed, 22287 insertions, 8371 deletions
diff --git a/src/AudioCompress/compress.c b/src/AudioCompress/compress.c new file mode 100644 index 000000000..bf72b2eab --- /dev/null +++ b/src/AudioCompress/compress.c @@ -0,0 +1,184 @@ +/* compress.c + * Compressor logic + * + * (c)2007 busybee (http://beesbuzz.biz/ + * Licensed under the terms of the LGPL. See the file COPYING for details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "compress.h" + +struct Compressor { + //! The compressor's preferences + struct CompressorConfig prefs; + + //! History of the peak values + int *peaks; + + //! History of the gain values + int *gain; + + //! History of clip amounts + int *clipped; + + unsigned int pos; + unsigned int bufsz; +}; + +struct Compressor *Compressor_new(unsigned int history) +{ + struct Compressor *obj = malloc(sizeof(struct Compressor)); + + obj->prefs.target = TARGET; + obj->prefs.maxgain = GAINMAX; + obj->prefs.smooth = GAINSMOOTH; + + obj->peaks = obj->gain = obj->clipped = NULL; + obj->bufsz = 0; + obj->pos = 0; + + Compressor_setHistory(obj, history); + + return obj; +} + +void Compressor_delete(struct Compressor *obj) +{ + if (obj->peaks) + free(obj->peaks); + if (obj->gain) + free(obj->gain); + if (obj->clipped) + free(obj->clipped); + free(obj); +} + +static int *resizeArray(int *data, int newsz, int oldsz) +{ + data = realloc(data, newsz*sizeof(int)); + if (newsz > oldsz) + memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz)); + return data; +} + +void Compressor_setHistory(struct Compressor *obj, unsigned int history) +{ + if (!history) + history = BUCKETS; + + obj->peaks = resizeArray(obj->peaks, history, obj->bufsz); + obj->gain = resizeArray(obj->gain, history, obj->bufsz); + obj->clipped = resizeArray(obj->clipped, history, obj->bufsz); + obj->bufsz = history; +} + +struct CompressorConfig *Compressor_getConfig(struct Compressor *obj) +{ + return &obj->prefs; +} + +void Compressor_Process_int16(struct Compressor *obj, int16_t *audio, + unsigned int count) +{ + struct CompressorConfig *prefs = Compressor_getConfig(obj); + int16_t *ap; + unsigned int i; + int *peaks = obj->peaks; + int curGain = obj->gain[obj->pos]; + int newGain; + int peakVal = 1; + int peakPos = 0; + int slot = (obj->pos + 1) % obj->bufsz; + int *clipped = obj->clipped + slot; + unsigned int ramp = count; + int delta; + + ap = audio; + for (i = 0; i < count; i++) + { + int val = *ap++; + if (val < 0) + val = -val; + if (val > peakVal) + { + peakVal = val; + peakPos = i; + } + } + peaks[slot] = peakVal; + + + for (i = 0; i < obj->bufsz; i++) + { + if (peaks[i] > peakVal) + { + peakVal = peaks[i]; + peakPos = 0; + } + } + + //! Determine target gain + newGain = (1 << 10)*prefs->target/peakVal; + + //! Adjust the gain with inertia from the previous gain value + newGain = (curGain*((1 << prefs->smooth) - 1) + newGain) + >> prefs->smooth; + + //! Make sure it's no more than the maximum gain value + if (newGain > (prefs->maxgain << 10)) + newGain = prefs->maxgain << 10; + + //! Make sure it's no less than 1:1 + if (newGain < (1 << 10)) + newGain = 1 << 10; + + //! Make sure the adjusted gain won't cause clipping + if ((peakVal*newGain >> 10) > 32767) + { + newGain = (32767 << 10)/peakVal; + //! Truncate the ramp time + ramp = peakPos; + } + + //! Record the new gain + obj->gain[slot] = newGain; + + if (!ramp) + ramp = 1; + if (!curGain) + curGain = 1 << 10; + delta = (newGain - curGain) / (int)ramp; + + ap = audio; + *clipped = 0; + for (i = 0; i < count; i++) + { + int sample; + + //! Amplify the sample + sample = *ap*curGain >> 10; + if (sample < -32768) + { + *clipped += -32768 - sample; + sample = -32768; + } else if (sample > 32767) + { + *clipped += sample - 32767; + sample = 32767; + } + *ap++ = sample; + + //! Adjust the gain + if (i < ramp) + curGain += delta; + else + curGain = newGain; + } + + obj->pos = slot; +} + diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h new file mode 100644 index 000000000..cc875c6da --- /dev/null +++ b/src/AudioCompress/compress.h @@ -0,0 +1,40 @@ +/*! compress.h + * interface to audio compression + * + * (c)2007 busybee (http://beesbuzz.biz/) + * Licensed under the terms of the LGPL. See the file COPYING for details. + */ + +#ifndef COMPRESS_H +#define COMPRESS_H + +#include <sys/types.h> + +//! Configuration values for the compressor object +struct CompressorConfig { + int target; + int maxgain; + int smooth; +}; + +struct Compressor; + +//! Create a new compressor (use history value of 0 for default) +struct Compressor *Compressor_new(unsigned int history); + +//! Delete a compressor +void Compressor_delete(struct Compressor *); + +//! Set the history length +void Compressor_setHistory(struct Compressor *, unsigned int history); + +//! Get the configuration for a compressor +struct CompressorConfig *Compressor_getConfig(struct Compressor *); + +//! Process 16-bit signed data +void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count); + +//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed + +//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring) +#endif diff --git a/src/AudioCompress/config.h b/src/AudioCompress/config.h new file mode 100644 index 000000000..25615ee68 --- /dev/null +++ b/src/AudioCompress/config.h @@ -0,0 +1,19 @@ +/* config.h +** Default values for the configuration, and also a few random debug things +*/ + +#ifndef AC_CONFIG_H +#define AC_CONFIG_H + +/*** Version information ***/ +#define ACVERSION "2.0" + +/*** Default configuration stuff ***/ +#define TARGET 16384 /*!< Target level (on a scale of 0-32767) */ + +#define GAINMAX 32 /*!< The maximum amount to amplify by */ +#define GAINSMOOTH 8 /*!< How much inertia ramping has*/ +#define BUCKETS 400 /*!< How long of a history to use by default */ + +#endif + diff --git a/src/aiff.c b/src/aiff.c index d4bec628b..7fbb7eb92 100644 --- a/src/aiff.c +++ b/src/aiff.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "aiff.h" #include <glib.h> diff --git a/src/archive/bz2_plugin.c b/src/archive/bz2_archive_plugin.c index 0ef042e90..b3a9027af 100644 --- a/src/archive/bz2_plugin.c +++ b/src/archive/bz2_archive_plugin.c @@ -21,9 +21,10 @@ * single bz2 archive handling (requires libbz2) */ +#include "config.h" +#include "archive/bz2_archive_plugin.h" #include "archive_api.h" #include "input_plugin.h" -#include "config.h" #include <stdint.h> #include <stddef.h> @@ -32,30 +33,40 @@ #include <bzlib.h> #ifdef HAVE_OLDER_BZIP2 -#define BZ2_bzDecompressInit bzDecompressInit -#define BZ2_bzDecompress bzDecompress +#define BZ2_bzDecompressInit bzDecompressInit +#define BZ2_bzDecompress bzDecompress #endif #define BZ_BUFSIZE 5000 -typedef struct { - char *name; - bool reset; +struct bz2_archive_file { + struct archive_file base; + + char *name; + bool reset; struct input_stream istream; - int last_bz_result; - int last_parent_result; - bz_stream bzstream; - char *buffer; -} bz2_context; + bool eof; + + bz_stream bzstream; + char *buffer; +}; static const struct input_plugin bz2_inputplugin; +static inline GQuark +bz2_quark(void) +{ + return g_quark_from_static_string("bz2"); +} + /* single archive handling allocation helpers */ static bool -bz2_alloc(bz2_context *data) +bz2_alloc(struct bz2_archive_file *data, GError **error_r) { + int ret; + data->bzstream.bzalloc = NULL; data->bzstream.bzfree = NULL; data->bzstream.opaque = NULL; @@ -64,19 +75,21 @@ bz2_alloc(bz2_context *data) data->bzstream.next_in = (void *) data->buffer; data->bzstream.avail_in = 0; - if (BZ2_bzDecompressInit(&data->bzstream, 0, 0) != BZ_OK) { + ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0); + if (ret != BZ_OK) { g_free(data->buffer); g_free(data); + + g_set_error(error_r, bz2_quark(), ret, + "BZ2_bzDecompressInit() has failed"); return false; } - data->last_bz_result = BZ_OK; - data->last_parent_result = 0; return true; } static void -bz2_destroy(bz2_context *data) +bz2_destroy(struct bz2_archive_file *data) { BZ2_bzDecompressEnd(&data->bzstream); g_free(data->buffer); @@ -85,61 +98,56 @@ bz2_destroy(bz2_context *data) /* archive open && listing routine */ static struct archive_file * -bz2_open(char * pathname) +bz2_open(const char *pathname, GError **error_r) { - bz2_context *context; - char *name; + struct bz2_archive_file *context; int len; - context = g_malloc(sizeof(bz2_context)); - if (!context) { - return NULL; - } + context = g_malloc(sizeof(*context)); + archive_file_init(&context->base, &bz2_archive_plugin); + //open archive - if (!input_stream_open(&context->istream, pathname)) { - g_warning("failed to open an bzip2 archive %s\n",pathname); - g_free(context); - return NULL; - } - //capture filename - name = strrchr(pathname, '/'); - if (name == NULL) { - g_warning("failed to get bzip2 name from %s\n",pathname); + if (!input_stream_open(&context->istream, pathname, error_r)) { g_free(context); return NULL; } - context->name = g_strdup(name+1); + + context->name = g_path_get_basename(pathname); + //remove suffix len = strlen(context->name); if (len > 4) { - context->name[len-4] = 0; //remove .bz2 suffix + context->name[len - 4] = 0; //remove .bz2 suffix } - return (struct archive_file *) context; + + return &context->base; } static void bz2_scan_reset(struct archive_file *file) { - bz2_context *context = (bz2_context *) file; + struct bz2_archive_file *context = (struct bz2_archive_file *) file; context->reset = true; } static char * bz2_scan_next(struct archive_file *file) { - bz2_context *context = (bz2_context *) file; + struct bz2_archive_file *context = (struct bz2_archive_file *) file; char *name = NULL; + if (context->reset) { name = context->name; context->reset = false; } + return name; } static void bz2_close(struct archive_file *file) { - bz2_context *context = (bz2_context *) file; + struct bz2_archive_file *context = (struct bz2_archive_file *) file; g_free(context->name); @@ -151,35 +159,36 @@ bz2_close(struct archive_file *file) static bool bz2_open_stream(struct archive_file *file, struct input_stream *is, - G_GNUC_UNUSED const char *path) + G_GNUC_UNUSED const char *path, GError **error_r) { - bz2_context *context = (bz2_context *) file; + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + //setup file ops is->plugin = &bz2_inputplugin; //insert back reference is->data = context; is->seekable = false; - if (!bz2_alloc(context)) { - g_warning("alloc bz2 failed\n"); + if (!bz2_alloc(context, error_r)) return false; - } + + context->eof = false; + return true; } static void bz2_is_close(struct input_stream *is) { - bz2_context *context = (bz2_context *) is->data; + struct bz2_archive_file *context = (struct bz2_archive_file *) is->data; bz2_destroy(context); is->data = NULL; bz2_close((struct archive_file *)context); } -static int -bz2_fillbuffer(bz2_context *context, - size_t numBytes) +static bool +bz2_fillbuffer(struct bz2_archive_file *context, GError **error_r) { size_t count; bz_stream *bzstream; @@ -187,76 +196,65 @@ bz2_fillbuffer(bz2_context *context, bzstream = &context->bzstream; if (bzstream->avail_in > 0) - return 0; + return true; count = input_stream_read(&context->istream, - context->buffer, BZ_BUFSIZE); - - if (count == 0) { - if (bzstream->avail_out == numBytes) - return -1; - if (!input_stream_eof(&context->istream)) - context->last_parent_result = 1; - } else { - bzstream->next_in = context->buffer; - bzstream->avail_in = count; - } + context->buffer, BZ_BUFSIZE, + error_r); + if (count == 0) + return false; - return 0; + bzstream->next_in = context->buffer; + bzstream->avail_in = count; + return true; } static size_t -bz2_is_read(struct input_stream *is, void *ptr, size_t size) +bz2_is_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) { - bz2_context *context = (bz2_context *) is->data; + struct bz2_archive_file *context = (struct bz2_archive_file *) is->data; bz_stream *bzstream; int bz_result; - size_t numBytes = size; - size_t bytesRead = 0; + size_t nbytes = 0; - if (context->last_bz_result != BZ_OK) - return 0; - if (context->last_parent_result != 0) + if (context->eof) return 0; bzstream = &context->bzstream; bzstream->next_out = ptr; - bzstream->avail_out = numBytes; + bzstream->avail_out = length; - while (bzstream->avail_out != 0) { - if (bz2_fillbuffer(context, numBytes) != 0) - break; + do { + if (!bz2_fillbuffer(context, error_r)) + return 0; bz_result = BZ2_bzDecompress(bzstream); - if (context->last_bz_result != BZ_OK - && bzstream->avail_out == numBytes) { - context->last_bz_result = bz_result; + if (bz_result == BZ_STREAM_END) { + context->eof = true; break; } - if (bz_result == BZ_STREAM_END) { - context->last_bz_result = bz_result; - break; + if (bz_result != BZ_OK) { + g_set_error(error_r, bz2_quark(), bz_result, + "BZ2_bzDecompress() has failed"); + return 0; } - } + } while (bzstream->avail_out == length); - bytesRead = numBytes - bzstream->avail_out; - is->offset += bytesRead; + nbytes = length - bzstream->avail_out; + is->offset += nbytes; - return bytesRead; + return nbytes; } static bool bz2_is_eof(struct input_stream *is) { - bz2_context *context = (bz2_context *) is->data; - - if (context->last_bz_result == BZ_STREAM_END) { - return true; - } + struct bz2_archive_file *context = (struct bz2_archive_file *) is->data; - return false; + return context->eof; } /* exported structures */ @@ -272,7 +270,7 @@ static const struct input_plugin bz2_inputplugin = { .eof = bz2_is_eof, }; -const struct archive_plugin bz2_plugin = { +const struct archive_plugin bz2_archive_plugin = { .name = "bz2", .open = bz2_open, .scan_reset = bz2_scan_reset, diff --git a/src/input/lastfm_input_plugin.h b/src/archive/bz2_archive_plugin.h index d0eaf5a55..42b0c48ed 100644 --- a/src/input/lastfm_input_plugin.h +++ b/src/archive/bz2_archive_plugin.h @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef LASTFM_INPUT_PLUGIN_H -#define LASTFM_INPUT_PLUGIN_H +#ifndef MPD_ARCHIVE_BZ2_H +#define MPD_ARCHIVE_BZ2_H -extern const struct input_plugin lastfm_input_plugin; +extern const struct archive_plugin bz2_archive_plugin; #endif diff --git a/src/archive/iso_plugin.c b/src/archive/iso9660_archive_plugin.c index 9063af0fc..780268df8 100644 --- a/src/archive/iso_plugin.c +++ b/src/archive/iso9660_archive_plugin.c @@ -21,6 +21,8 @@ * iso archive handling (requires cdio, and iso9660) */ +#include "config.h" +#include "archive/iso9660_archive_plugin.h" #include "archive_api.h" #include "input_plugin.h" @@ -32,21 +34,29 @@ #define CEILING(x, y) ((x+(y-1))/y) -typedef struct { +struct iso9660_archive_file { + struct archive_file base; + iso9660_t *iso; iso9660_stat_t *statbuf; size_t cur_ofs; size_t max_blocks; GSList *list; GSList *iter; -} iso_context; +}; + +static const struct input_plugin iso9660_input_plugin; -static const struct input_plugin iso_inputplugin; +static inline GQuark +iso9660_quark(void) +{ + return g_quark_from_static_string("iso9660"); +} /* archive open && listing routine */ static void -listdir_recur(const char *psz_path, iso_context *context) +listdir_recur(const char *psz_path, struct iso9660_archive_file *context) { iso9660_t *iso = context->iso; CdioList_t *entlist; @@ -80,36 +90,44 @@ listdir_recur(const char *psz_path, iso_context *context) } static struct archive_file * -iso_open(char * pathname) +iso9660_archive_open(const char *pathname, GError **error_r) { - iso_context *context = g_malloc(sizeof(iso_context)); + struct iso9660_archive_file *context = + g_new(struct iso9660_archive_file, 1); + + archive_file_init(&context->base, &iso9660_archive_plugin); context->list = NULL; /* open archive */ context->iso = iso9660_open (pathname); if (context->iso == NULL) { - g_warning("iso %s open failed\n", pathname); + g_set_error(error_r, iso9660_quark(), 0, + "Failed to open ISO9660 file %s", pathname); return NULL; } listdir_recur("/", context); - return (struct archive_file *)context; + return &context->base; } static void -iso_scan_reset(struct archive_file *file) +iso9660_archive_scan_reset(struct archive_file *file) { - iso_context *context = (iso_context *) file; + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + //reset iterator context->iter = context->list; } static char * -iso_scan_next(struct archive_file *file) +iso9660_archive_scan_next(struct archive_file *file) { - iso_context *context = (iso_context *) file; + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + char *data = NULL; if (context->iter != NULL) { ///fetch data and goto next @@ -120,9 +138,11 @@ iso_scan_next(struct archive_file *file) } static void -iso_close(struct archive_file *file) +iso9660_archive_close(struct archive_file *file) { - iso_context *context = (iso_context *) file; + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + GSList *tmp; if (context->list) { //free list @@ -139,12 +159,14 @@ iso_close(struct archive_file *file) /* single archive handling */ static bool -iso_open_stream(struct archive_file *file, struct input_stream *is, - const char *pathname) +iso9660_archive_open_stream(struct archive_file *file, struct input_stream *is, + const char *pathname, GError **error_r) { - iso_context *context = (iso_context *) file; + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + //setup file ops - is->plugin = &iso_inputplugin; + is->plugin = &iso9660_input_plugin; //insert back reference is->data = context; //we are not seekable @@ -153,7 +175,8 @@ iso_open_stream(struct archive_file *file, struct input_stream *is, context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname); if (context->statbuf == NULL) { - g_warning("file %s not found in iso\n", pathname); + g_set_error(error_r, iso9660_quark(), 0, + "not found in the ISO file: %s", pathname); return false; } context->cur_ofs = 0; @@ -162,19 +185,19 @@ iso_open_stream(struct archive_file *file, struct input_stream *is, } static void -iso_is_close(struct input_stream *is) +iso9660_input_close(struct input_stream *is) { - iso_context *context = (iso_context *) is->data; + struct iso9660_archive_file *context = is->data; g_free(context->statbuf); - iso_close((struct archive_file *)context); + iso9660_archive_close((struct archive_file *)context); } static size_t -iso_is_read(struct input_stream *is, void *ptr, size_t size) +iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) { - iso_context *context = (iso_context *) is->data; + struct iso9660_archive_file *context = (struct iso9660_archive_file *) is->data; int toread, readed = 0; int no_blocks, cur_block; size_t left_bytes = context->statbuf->size - context->cur_ofs; @@ -196,9 +219,10 @@ iso_is_read(struct input_stream *is, void *ptr, size_t size) context->statbuf->lsn + cur_block, no_blocks); if (readed != no_blocks * ISO_BLOCKSIZE) { - g_warning("error reading ISO file at lsn %lu\n", - (long unsigned int) cur_block ); - return -1; + g_set_error(error_r, iso9660_quark(), 0, + "error reading ISO file at lsn %lu", + (long unsigned int) cur_block); + return 0; } if (left_bytes < size) { readed = left_bytes; @@ -209,31 +233,32 @@ iso_is_read(struct input_stream *is, void *ptr, size_t size) } static bool -iso_is_eof(struct input_stream *is) +iso9660_input_eof(struct input_stream *is) { - iso_context *context = (iso_context *) is->data; + struct iso9660_archive_file *context = is->data; + return (context->cur_ofs == context->statbuf->size); } /* exported structures */ -static const char *const iso_extensions[] = { +static const char *const iso9660_archive_extensions[] = { "iso", NULL }; -static const struct input_plugin iso_inputplugin = { - .close = iso_is_close, - .read = iso_is_read, - .eof = iso_is_eof, +static const struct input_plugin iso9660_input_plugin = { + .close = iso9660_input_close, + .read = iso9660_input_read, + .eof = iso9660_input_eof, }; -const struct archive_plugin iso_plugin = { +const struct archive_plugin iso9660_archive_plugin = { .name = "iso", - .open = iso_open, - .scan_reset = iso_scan_reset, - .scan_next = iso_scan_next, - .open_stream = iso_open_stream, - .close = iso_close, - .suffixes = iso_extensions + .open = iso9660_archive_open, + .scan_reset = iso9660_archive_scan_reset, + .scan_next = iso9660_archive_scan_next, + .open_stream = iso9660_archive_open_stream, + .close = iso9660_archive_close, + .suffixes = iso9660_archive_extensions }; diff --git a/src/archive/iso9660_archive_plugin.h b/src/archive/iso9660_archive_plugin.h new file mode 100644 index 000000000..21730c537 --- /dev/null +++ b/src/archive/iso9660_archive_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ISO9660_H +#define MPD_ARCHIVE_ISO9660_H + +extern const struct archive_plugin iso9660_archive_plugin; + +#endif diff --git a/src/archive/zip_plugin.c b/src/archive/zzip_archive_plugin.c index 243d46418..43c880aab 100644 --- a/src/archive/zip_plugin.c +++ b/src/archive/zzip_archive_plugin.c @@ -21,6 +21,8 @@ * zip archive handling (requires zziplib) */ +#include "config.h" +#include "archive/zzip_archive_plugin.h" #include "archive_api.h" #include "archive_api.h" #include "input_plugin.h" @@ -29,29 +31,40 @@ #include <glib.h> #include <string.h> -typedef struct { +struct zzip_archive { + struct archive_file base; + ZZIP_DIR *dir; ZZIP_FILE *file; size_t length; GSList *list; GSList *iter; -} zip_context; +}; + +static const struct input_plugin zzip_input_plugin; -static const struct input_plugin zip_inputplugin; +static inline GQuark +zzip_quark(void) +{ + return g_quark_from_static_string("zzip"); +} /* archive open && listing routine */ static struct archive_file * -zip_open(char * pathname) +zzip_archive_open(const char *pathname, GError **error_r) { - zip_context *context = g_malloc(sizeof(zip_context)); + struct zzip_archive *context = g_malloc(sizeof(*context)); ZZIP_DIRENT dirent; + archive_file_init(&context->base, &zzip_archive_plugin); + // open archive context->list = NULL; context->dir = zzip_dir_open(pathname, NULL); if (context->dir == NULL) { - g_warning("zipfile %s open failed\n", pathname); + g_set_error(error_r, zzip_quark(), 0, + "Failed to open ZIP file %s", pathname); return NULL; } @@ -63,21 +76,21 @@ zip_open(char * pathname) } } - return (struct archive_file *)context; + return &context->base; } static void -zip_scan_reset(struct archive_file *file) +zzip_archive_scan_reset(struct archive_file *file) { - zip_context *context = (zip_context *) file; + struct zzip_archive *context = (struct zzip_archive *) file; //reset iterator context->iter = context->list; } static char * -zip_scan_next(struct archive_file *file) +zzip_archive_scan_next(struct archive_file *file) { - zip_context *context = (zip_context *) file; + struct zzip_archive *context = (struct zzip_archive *) file; char *data = NULL; if (context->iter != NULL) { ///fetch data and goto next @@ -88,9 +101,9 @@ zip_scan_next(struct archive_file *file) } static void -zip_close(struct archive_file *file) +zzip_archive_close(struct archive_file *file) { - zip_context *context = (zip_context *) file; + struct zzip_archive *context = (struct zzip_archive *) file; if (context->list) { //free list for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) @@ -106,14 +119,14 @@ zip_close(struct archive_file *file) /* single archive handling */ static bool -zip_open_stream(struct archive_file *file, struct input_stream *is, - const char *pathname) +zzip_archive_open_stream(struct archive_file *file, struct input_stream *is, + const char *pathname, GError **error_r) { - zip_context *context = (zip_context *) file; + struct zzip_archive *context = (struct zzip_archive *) file; ZZIP_STAT z_stat; //setup file ops - is->plugin = &zip_inputplugin; + is->plugin = &zzip_input_plugin; //insert back reference is->data = context; //we are seekable (but its not recommendent to do so) @@ -121,7 +134,8 @@ zip_open_stream(struct archive_file *file, struct input_stream *is, context->file = zzip_file_open(context->dir, pathname, 0); if (!context->file) { - g_warning("file %s not found in the zipfile\n", pathname); + g_set_error(error_r, zzip_quark(), 0, + "not found in the ZIP file: %s", pathname); return false; } zzip_file_stat(context->file, &z_stat); @@ -130,41 +144,45 @@ zip_open_stream(struct archive_file *file, struct input_stream *is, } static void -zip_is_close(struct input_stream *is) +zzip_input_close(struct input_stream *is) { - zip_context *context = (zip_context *) is->data; + struct zzip_archive *context = (struct zzip_archive *) is->data; zzip_file_close (context->file); - zip_close((struct archive_file *)context); + zzip_archive_close((struct archive_file *)context); } static size_t -zip_is_read(struct input_stream *is, void *ptr, size_t size) +zzip_input_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { - zip_context *context = (zip_context *) is->data; + struct zzip_archive *context = (struct zzip_archive *) is->data; int ret; ret = zzip_file_read(context->file, ptr, size); if (ret < 0) { - g_warning("error %d reading zipfile\n", ret); + g_set_error(error_r, zzip_quark(), ret, + "zzip_file_read() has failed"); return 0; } return ret; } static bool -zip_is_eof(struct input_stream *is) +zzip_input_eof(struct input_stream *is) { - zip_context *context = (zip_context *) is->data; + struct zzip_archive *context = (struct zzip_archive *) is->data; return ((size_t) zzip_tell(context->file) == context->length); } static bool -zip_is_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence) +zzip_input_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) { - zip_context *context = (zip_context *) is->data; + struct zzip_archive *context = (struct zzip_archive *) is->data; zzip_off_t ofs = zzip_seek(context->file, offset, whence); if (ofs != -1) { + g_set_error(error_r, zzip_quark(), ofs, + "zzip_seek() has failed"); is->offset = ofs; return true; } @@ -173,24 +191,24 @@ zip_is_seek(G_GNUC_UNUSED struct input_stream *is, /* exported structures */ -static const char *const zip_extensions[] = { +static const char *const zzip_archive_extensions[] = { "zip", NULL }; -static const struct input_plugin zip_inputplugin = { - .close = zip_is_close, - .read = zip_is_read, - .eof = zip_is_eof, - .seek = zip_is_seek, +static const struct input_plugin zzip_input_plugin = { + .close = zzip_input_close, + .read = zzip_input_read, + .eof = zzip_input_eof, + .seek = zzip_input_seek, }; -const struct archive_plugin zip_plugin = { - .name = "zip", - .open = zip_open, - .scan_reset = zip_scan_reset, - .scan_next = zip_scan_next, - .open_stream = zip_open_stream, - .close = zip_close, - .suffixes = zip_extensions +const struct archive_plugin zzip_archive_plugin = { + .name = "zzip", + .open = zzip_archive_open, + .scan_reset = zzip_archive_scan_reset, + .scan_next = zzip_archive_scan_next, + .open_stream = zzip_archive_open_stream, + .close = zzip_archive_close, + .suffixes = zzip_archive_extensions }; diff --git a/src/archive/zzip_archive_plugin.h b/src/archive/zzip_archive_plugin.h new file mode 100644 index 000000000..07a8a2eca --- /dev/null +++ b/src/archive/zzip_archive_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_ZZIP_H +#define MPD_ARCHIVE_ZZIP_H + +extern const struct archive_plugin zzip_archive_plugin; + +#endif diff --git a/src/archive_api.c b/src/archive_api.c index 153afa361..574960558 100644 --- a/src/archive_api.c +++ b/src/archive_api.c @@ -17,6 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ +#include "archive_api.h" + #include <stdio.h> #include <string.h> @@ -26,8 +29,6 @@ #include <errno.h> #include <glib.h> -#include "archive_api.h" - /** * * archive_lookup is used to determine if part of pathname refers to an regular diff --git a/src/archive_api.h b/src/archive_api.h index 2efcc1e6a..20a4f9277 100644 --- a/src/archive_api.h +++ b/src/archive_api.h @@ -27,72 +27,11 @@ */ #include "archive_internal.h" +#include "archive_plugin.h" #include "input_stream.h" #include <stdbool.h> -struct archive_file; - -struct archive_plugin { - const char *name; - - /** - * optional, set this to NULL if the archive plugin doesn't - * have/need one this must false if there is an error and - * true otherwise - */ - bool (*init)(void); - - /** - * optional, set this to NULL if the archive plugin doesn't - * have/need one - */ - void (*finish)(void); - - /** - * tryes to open archive file and associates handle with archive - * returns pointer to handle used is all operations with this archive - * or NULL when opening fails - */ - struct archive_file *(*open)(char * pathname); - - /** - * reset routine will move current read index in archive to default - * position and then the filenames from archives can be read - * via scan_next routine - */ - void (*scan_reset)(struct archive_file *); - - /** - * the read method will return corresponding files from archive - * (as pathnames) and move read index to next file. When there is no - * next file it return NULL. - */ - char *(*scan_next)(struct archive_file *); - - /** - * Opens an input_stream of a file within the archive. - * - * If this function succeeds, then the #input_stream "owns" - * the archive file and will automatically close it. - * - * @param path the path within the archive - */ - bool (*open_stream)(struct archive_file *, struct input_stream *is, - const char *path); - - /** - * closes archive file. - */ - void (*close)(struct archive_file *); - - /** - * suffixes handled by this plugin. - * last element in these arrays must always be a NULL - */ - const char *const*suffixes; -}; - bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix); #endif diff --git a/src/archive_internal.h b/src/archive_internal.h index 130d25d65..3d973381e 100644 --- a/src/archive_internal.h +++ b/src/archive_internal.h @@ -21,7 +21,14 @@ #define MPD_ARCHIVE_INTERNAL_H struct archive_file { - int placeholder; + const struct archive_plugin *plugin; }; +static inline void +archive_file_init(struct archive_file *archive_file, + const struct archive_plugin *plugin) +{ + archive_file->plugin = plugin; +} + #endif diff --git a/src/archive_list.c b/src/archive_list.c index 8228fc961..191cb004a 100644 --- a/src/archive_list.c +++ b/src/archive_list.c @@ -17,50 +17,44 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "archive_list.h" -#include "archive_api.h" +#include "archive_plugin.h" #include "utils.h" -#include "config.h" +#include "archive/bz2_archive_plugin.h" +#include "archive/iso9660_archive_plugin.h" +#include "archive/zzip_archive_plugin.h" #include <string.h> #include <glib.h> -extern const struct archive_plugin bz2_plugin; -extern const struct archive_plugin zip_plugin; -extern const struct archive_plugin iso_plugin; - static const struct archive_plugin *const archive_plugins[] = { #ifdef HAVE_BZ2 - &bz2_plugin, + &bz2_archive_plugin, #endif -#ifdef HAVE_ZIP - &zip_plugin, +#ifdef HAVE_ZZIP + &zzip_archive_plugin, #endif -#ifdef HAVE_ISO - &iso_plugin, +#ifdef HAVE_ISO9660 + &iso9660_archive_plugin, #endif NULL }; -enum { - num_archive_plugins = G_N_ELEMENTS(archive_plugins)-1, -}; - /** which plugins have been initialized successfully? */ -static bool archive_plugins_enabled[num_archive_plugins+1]; +static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1]; const struct archive_plugin * archive_plugin_from_suffix(const char *suffix) { - unsigned i; - if (suffix == NULL) return NULL; - for (i=0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (archive_plugins_enabled[i] && - stringFoundInStringArray(plugin->suffixes, suffix)) { + plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { ++i; return plugin; } @@ -71,7 +65,7 @@ archive_plugin_from_suffix(const char *suffix) const struct archive_plugin * archive_plugin_from_name(const char *name) { - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (archive_plugins_enabled[i] && strcmp(plugin->name, name) == 0) @@ -84,7 +78,7 @@ void archive_plugin_print_all_suffixes(FILE * fp) { const char *const*suffixes; - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (!archive_plugins_enabled[i]) continue; @@ -101,7 +95,7 @@ void archive_plugin_print_all_suffixes(FILE * fp) void archive_plugin_init_all(void) { - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (plugin->init == NULL || archive_plugins[i]->init()) archive_plugins_enabled[i] = true; @@ -110,7 +104,7 @@ void archive_plugin_init_all(void) void archive_plugin_deinit_all(void) { - for (unsigned i = 0; i < num_archive_plugins; ++i) { + for (unsigned i = 0; archive_plugins[i] != NULL; ++i) { const struct archive_plugin *plugin = archive_plugins[i]; if (archive_plugins_enabled[i] && plugin->finish != NULL) archive_plugins[i]->finish(); diff --git a/src/archive_list.h b/src/archive_list.h index 55278fbc4..2534b2b18 100644 --- a/src/archive_list.h +++ b/src/archive_list.h @@ -20,8 +20,6 @@ #ifndef MPD_ARCHIVE_LIST_H #define MPD_ARCHIVE_LIST_H -#include "archive_api.h" - #include <stdio.h> struct archive_plugin; diff --git a/src/archive_plugin.c b/src/archive_plugin.c new file mode 100644 index 000000000..86334709b --- /dev/null +++ b/src/archive_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "archive_plugin.h" +#include "archive_internal.h" + +#include <assert.h> + +struct archive_file * +archive_file_open(const struct archive_plugin *plugin, const char *path, + GError **error_r) +{ + struct archive_file *file; + + assert(plugin != NULL); + assert(plugin->open != NULL); + assert(path != NULL); + assert(error_r == NULL || *error_r == NULL); + + file = plugin->open(path, error_r); + + if (file != NULL) { + assert(file->plugin != NULL); + assert(file->plugin->close != NULL); + assert(file->plugin->scan_reset != NULL); + assert(file->plugin->scan_next != NULL); + assert(file->plugin->open_stream != NULL); + assert(error_r == NULL || *error_r == NULL); + } else { + assert(error_r == NULL || *error_r != NULL); + } + + return file; +} + +void +archive_file_close(struct archive_file *file) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->close != NULL); + + file->plugin->close(file); +} + +void +archive_file_scan_reset(struct archive_file *file) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->scan_reset != NULL); + assert(file->plugin->scan_next != NULL); + + file->plugin->scan_reset(file); +} + +char * +archive_file_scan_next(struct archive_file *file) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->scan_next != NULL); + + return file->plugin->scan_next(file); +} + +bool +archive_file_open_stream(struct archive_file *file, struct input_stream *is, + const char *path, GError **error_r) +{ + assert(file != NULL); + assert(file->plugin != NULL); + assert(file->plugin->open_stream != NULL); + + return file->plugin->open_stream(file, is, path, error_r); +} diff --git a/src/archive_plugin.h b/src/archive_plugin.h new file mode 100644 index 000000000..52629f2e1 --- /dev/null +++ b/src/archive_plugin.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ARCHIVE_PLUGIN_H +#define MPD_ARCHIVE_PLUGIN_H + +#include <glib.h> + +#include <stdbool.h> + +struct input_stream; +struct archive_file; + +struct archive_plugin { + const char *name; + + /** + * optional, set this to NULL if the archive plugin doesn't + * have/need one this must false if there is an error and + * true otherwise + */ + bool (*init)(void); + + /** + * optional, set this to NULL if the archive plugin doesn't + * have/need one + */ + void (*finish)(void); + + /** + * tryes to open archive file and associates handle with archive + * returns pointer to handle used is all operations with this archive + * or NULL when opening fails + */ + struct archive_file *(*open)(const char *path_fs, GError **error_r); + + /** + * reset routine will move current read index in archive to default + * position and then the filenames from archives can be read + * via scan_next routine + */ + void (*scan_reset)(struct archive_file *); + + /** + * the read method will return corresponding files from archive + * (as pathnames) and move read index to next file. When there is no + * next file it return NULL. + */ + char *(*scan_next)(struct archive_file *); + + /** + * Opens an input_stream of a file within the archive. + * + * If this function succeeds, then the #input_stream "owns" + * the archive file and will automatically close it. + * + * @param path the path within the archive + * @param error_r location to store the error occuring, or + * NULL to ignore errors + */ + bool (*open_stream)(struct archive_file *, struct input_stream *is, + const char *path, GError **error_r); + + /** + * closes archive file. + */ + void (*close)(struct archive_file *); + + /** + * suffixes handled by this plugin. + * last element in these arrays must always be a NULL + */ + const char *const*suffixes; +}; + +struct archive_file * +archive_file_open(const struct archive_plugin *plugin, const char *path, + GError **error_r); + +void +archive_file_close(struct archive_file *file); + +void +archive_file_scan_reset(struct archive_file *file); + +char * +archive_file_scan_next(struct archive_file *file); + +bool +archive_file_open_stream(struct archive_file *file, struct input_stream *is, + const char *path, GError **error_r); + +#endif diff --git a/src/audio.c b/src/audio.c index d48558e46..1d234bf5b 100644 --- a/src/audio.c +++ b/src/audio.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "audio.h" #include "audio_format.h" #include "audio_parser.h" @@ -35,9 +36,8 @@ static struct audio_format configured_audio_format; void getOutputAudioFormat(const struct audio_format *inAudioFormat, struct audio_format *outAudioFormat) { - *outAudioFormat = audio_format_defined(&configured_audio_format) - ? configured_audio_format - : *inAudioFormat; + *outAudioFormat = *inAudioFormat; + audio_format_mask_apply(outAudioFormat, &configured_audio_format); } void initAudioConfig(void) @@ -46,17 +46,12 @@ void initAudioConfig(void) GError *error = NULL; bool ret; - if (NULL == param || NULL == param->value) + if (param == NULL) return; ret = audio_format_parse(&configured_audio_format, param->value, - &error); + true, &error); if (!ret) g_error("error parsing \"%s\" at line %i: %s", CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message); } - -void finishAudioConfig(void) -{ - audio_format_clear(&configured_audio_format); -} diff --git a/src/audio.h b/src/audio.h index 4d80ee0e6..4b80cfd27 100644 --- a/src/audio.h +++ b/src/audio.h @@ -30,6 +30,4 @@ void getOutputAudioFormat(const struct audio_format *inFormat, /* make sure initPlayerData is called before this function!! */ void initAudioConfig(void); -void finishAudioConfig(void); - #endif diff --git a/src/audio_check.c b/src/audio_check.c new file mode 100644 index 000000000..a843975fa --- /dev/null +++ b/src/audio_check.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "audio_check.h" +#include "audio_format.h" + +#include <assert.h> + +bool +audio_check_sample_rate(unsigned long sample_rate, GError **error_r) +{ + if (!audio_valid_sample_rate(sample_rate)) { + g_set_error(error_r, audio_format_quark(), 0, + "Invalid sample rate: %lu", sample_rate); + return false; + } + + return true; +} + +bool +audio_check_sample_format(enum sample_format sample_format, GError **error_r) +{ + if (!audio_valid_sample_format(sample_format)) { + g_set_error(error_r, audio_format_quark(), 0, + "Invalid sample format: %u", sample_format); + return false; + } + + return true; +} + +bool +audio_check_channel_count(unsigned channels, GError **error_r) +{ + if (!audio_valid_channel_count(channels)) { + g_set_error(error_r, audio_format_quark(), 0, + "Invalid channel count: %u", channels); + return false; + } + + return true; +} + +bool +audio_format_init_checked(struct audio_format *af, unsigned long sample_rate, + enum sample_format sample_format, unsigned channels, + GError **error_r) +{ + if (audio_check_sample_rate(sample_rate, error_r) && + audio_check_sample_format(sample_format, error_r) && + audio_check_channel_count(channels, error_r)) { + audio_format_init(af, sample_rate, sample_format, channels); + assert(audio_format_valid(af)); + return true; + } else + return false; +} diff --git a/src/audio_check.h b/src/audio_check.h new file mode 100644 index 000000000..197dedd48 --- /dev/null +++ b/src/audio_check.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AUDIO_CHECK_H +#define MPD_AUDIO_CHECK_H + +#include "audio_format.h" + +#include <glib.h> +#include <stdbool.h> + +/** + * The GLib quark used for errors reported by this library. + */ +static inline GQuark +audio_format_quark(void) +{ + return g_quark_from_static_string("audio_format"); +} + +bool +audio_check_sample_rate(unsigned long sample_rate, GError **error_r); + +bool +audio_check_sample_format(unsigned sample_format, GError **error_r); + +bool +audio_check_channel_count(unsigned sample_format, GError **error_r); + +/** + * Wrapper for audio_format_init(), which checks all attributes. + */ +bool +audio_format_init_checked(struct audio_format *af, unsigned long sample_rate, + enum sample_format sample_format, unsigned channels, + GError **error_r); + +#endif diff --git a/src/audio_format.c b/src/audio_format.c new file mode 100644 index 000000000..33cd90f58 --- /dev/null +++ b/src/audio_format.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "audio_format.h" + +#include <assert.h> +#include <stdio.h> + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define REVERSE_ENDIAN_SUFFIX "_le" +#else +#define REVERSE_ENDIAN_SUFFIX "_be" +#endif + +const char * +sample_format_to_string(enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + return "?"; + + case SAMPLE_FORMAT_S8: + return "8"; + + case SAMPLE_FORMAT_S16: + return "16"; + + case SAMPLE_FORMAT_S24_P32: + return "24"; + + case SAMPLE_FORMAT_S32: + return "32"; + } + + /* unreachable */ + assert(false); + return "?"; +} + +const char * +audio_format_to_string(const struct audio_format *af, + struct audio_format_string *s) +{ + assert(af != NULL); + assert(s != NULL); + + snprintf(s->buffer, sizeof(s->buffer), "%u:%s%s:%u", + af->sample_rate, sample_format_to_string(af->format), + af->reverse_endian ? REVERSE_ENDIAN_SUFFIX : "", + af->channels); + + return s->buffer; +} diff --git a/src/audio_format.h b/src/audio_format.h index 64087d070..6e7d50c46 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -23,25 +23,118 @@ #include <stdint.h> #include <stdbool.h> +enum sample_format { + SAMPLE_FORMAT_UNDEFINED = 0, + + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_S16, + + /** + * Signed 24 bit integer samples, packed in 32 bit integers + * (the most significant byte is filled with the sign bit). + */ + SAMPLE_FORMAT_S24_P32, + + SAMPLE_FORMAT_S32, +}; + +/** + * This structure describes the format of a raw PCM stream. + */ struct audio_format { + /** + * The sample rate in Hz. A better name for this attribute is + * "frame rate", because technically, you have two samples per + * frame in stereo sound. + */ uint32_t sample_rate; - uint8_t bits; + + /** + * The format samples are stored in. See the #sample_format + * enum for valid values. + */ + uint8_t format; + + /** + * The number of channels. Only mono (1) and stereo (2) are + * fully supported currently. + */ uint8_t channels; + + /** + * If zero, then samples are stored in host byte order. If + * nonzero, then samples are stored in the reverse host byte + * order. + */ + uint8_t reverse_endian; +}; + +/** + * Buffer for audio_format_string(). + */ +struct audio_format_string { + char buffer[24]; }; +/** + * Clears the #audio_format object, i.e. sets all attributes to an + * undefined (invalid) value. + */ static inline void audio_format_clear(struct audio_format *af) { af->sample_rate = 0; - af->bits = 0; + af->format = SAMPLE_FORMAT_UNDEFINED; af->channels = 0; + af->reverse_endian = 0; } +/** + * Initializes an #audio_format object, i.e. sets all + * attributes to valid values. + */ +static inline void audio_format_init(struct audio_format *af, + uint32_t sample_rate, + enum sample_format format, uint8_t channels) +{ + af->sample_rate = sample_rate; + af->format = (uint8_t)format; + af->channels = channels; + af->reverse_endian = 0; +} + +/** + * Checks whether the specified #audio_format object has a defined + * value. + */ static inline bool audio_format_defined(const struct audio_format *af) { return af->sample_rate != 0; } /** + * Checks whether the specified #audio_format object is full, i.e. all + * attributes are defined. This is more complete than + * audio_format_defined(), but slower. + */ +static inline bool +audio_format_fully_defined(const struct audio_format *af) +{ + return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED && + af->channels != 0; +} + +/** + * Checks whether the specified #audio_format object has at least one + * defined value. + */ +static inline bool +audio_format_mask_defined(const struct audio_format *af) +{ + return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED || + af->channels != 0; +} + +/** * Checks whether the sample rate is valid. * * @param sample_rate the sample rate in Hz @@ -58,9 +151,20 @@ audio_valid_sample_rate(unsigned sample_rate) * @param bits the number of significant bits per sample */ static inline bool -audio_valid_sample_format(unsigned bits) +audio_valid_sample_format(enum sample_format format) { - return bits == 16 || bits == 24 || bits == 32 || bits == 8; + switch (format) { + case SAMPLE_FORMAT_S8: + case SAMPLE_FORMAT_S16: + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + return true; + + case SAMPLE_FORMAT_UNDEFINED: + break; + } + + return false; } /** @@ -79,16 +183,44 @@ audio_valid_channel_count(unsigned channels) static inline bool audio_format_valid(const struct audio_format *af) { return audio_valid_sample_rate(af->sample_rate) && - audio_valid_sample_format(af->bits) && + audio_valid_sample_format((enum sample_format)af->format) && audio_valid_channel_count(af->channels); } +/** + * Returns false if the format mask is not valid for playback with + * MPD. This function performs some basic validity checks. + */ +static inline bool audio_format_mask_valid(const struct audio_format *af) +{ + return (af->sample_rate == 0 || + audio_valid_sample_rate(af->sample_rate)) && + (af->format == SAMPLE_FORMAT_UNDEFINED || + audio_valid_sample_format((enum sample_format)af->format)) && + (af->channels == 0 || audio_valid_channel_count(af->channels)); +} + static inline bool audio_format_equals(const struct audio_format *a, const struct audio_format *b) { return a->sample_rate == b->sample_rate && - a->bits == b->bits && - a->channels == b->channels; + a->format == b->format && + a->channels == b->channels && + a->reverse_endian == b->reverse_endian; +} + +static inline void +audio_format_mask_apply(struct audio_format *af, + const struct audio_format *mask) +{ + if (mask->sample_rate != 0) + af->sample_rate = mask->sample_rate; + + if (mask->format != SAMPLE_FORMAT_UNDEFINED) + af->format = mask->format; + + if (mask->channels != 0) + af->channels = mask->channels; } /** @@ -96,28 +228,62 @@ static inline bool audio_format_equals(const struct audio_format *a, */ static inline unsigned audio_format_sample_size(const struct audio_format *af) { - if (af->bits <= 8) + switch (af->format) { + case SAMPLE_FORMAT_S8: return 1; - else if (af->bits <= 16) + + case SAMPLE_FORMAT_S16: return 2; - else + + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: return 4; + + case SAMPLE_FORMAT_UNDEFINED: + break; + } + + return 0; } +/** + * Returns the size of each full frame in bytes. + */ static inline unsigned audio_format_frame_size(const struct audio_format *af) { return audio_format_sample_size(af) * af->channels; } +/** + * Returns the floating point factor which converts a time span to a + * storage size in bytes. + */ static inline double audio_format_time_to_size(const struct audio_format *af) { return af->sample_rate * audio_format_frame_size(af); } -static inline double audioFormatSizeToTime(const struct audio_format *af) -{ - return 1.0 / audio_format_time_to_size(af); -} +/** + * Renders a #sample_format enum into a string, e.g. for printing it + * in a log file. + * + * @param format a #sample_format enum value + * @return the string + */ +const char * +sample_format_to_string(enum sample_format format); + +/** + * Renders the #audio_format object into a string, e.g. for printing + * it in a log file. + * + * @param af the #audio_format object + * @param s a buffer to print into + * @return the string, or NULL if the #audio_format object is invalid + */ +const char * +audio_format_to_string(const struct audio_format *af, + struct audio_format_string *s); #endif diff --git a/src/audio_parser.c b/src/audio_parser.c index 906b0f819..210ea7a62 100644 --- a/src/audio_parser.c +++ b/src/audio_parser.c @@ -22,9 +22,12 @@ * */ +#include "config.h" #include "audio_parser.h" #include "audio_format.h" +#include "audio_check.h" +#include <assert.h> #include <stdlib.h> /** @@ -36,64 +39,154 @@ audio_parser_quark(void) return g_quark_from_static_string("audio_parser"); } -bool -audio_format_parse(struct audio_format *dest, const char *src, GError **error) +static bool +parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r, + const char **endptr_r, GError **error_r) { - char *endptr; unsigned long value; + char *endptr; - audio_format_clear(dest); - - /* parse sample rate */ + if (mask && *src == '*') { + *sample_rate_r = 0; + *endptr_r = src + 1; + return true; + } value = strtoul(src, &endptr, 10); if (endptr == src) { - g_set_error(error, audio_parser_quark(), 0, - "Sample rate missing"); + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the sample rate"); return false; - } else if (*endptr != ':') { - g_set_error(error, audio_parser_quark(), 0, - "Sample format missing"); + } else if (!audio_check_sample_rate(value, error_r)) + return false; + + *sample_rate_r = value; + *endptr_r = endptr; + return true; +} + +static bool +parse_sample_format(const char *src, bool mask, + enum sample_format *sample_format_r, + const char **endptr_r, GError **error_r) +{ + unsigned long value; + char *endptr; + enum sample_format sample_format; + + if (mask && *src == '*') { + *sample_format_r = SAMPLE_FORMAT_UNDEFINED; + *endptr_r = src + 1; + return true; + } + + value = strtoul(src, &endptr, 10); + if (endptr == src) { + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the sample format"); return false; - } else if (!audio_valid_sample_rate(value)) { - g_set_error(error, audio_parser_quark(), 0, - "Invalid sample rate: %lu", value); + } + + switch (value) { + case 8: + sample_format = SAMPLE_FORMAT_S8; + break; + + case 16: + sample_format = SAMPLE_FORMAT_S16; + break; + + case 24: + sample_format = SAMPLE_FORMAT_S24_P32; + break; + + case 32: + sample_format = SAMPLE_FORMAT_S32; + break; + + default: + g_set_error(error_r, audio_parser_quark(), 0, + "Invalid sample format: %lu", value); return false; } - dest->sample_rate = value; + assert(audio_valid_sample_format(sample_format)); - /* parse sample format */ + *sample_format_r = sample_format; + *endptr_r = endptr; + return true; +} + +static bool +parse_channel_count(const char *src, bool mask, uint8_t *channels_r, + const char **endptr_r, GError **error_r) +{ + unsigned long value; + char *endptr; + + if (mask && *src == '*') { + *channels_r = 0; + *endptr_r = src + 1; + return true; + } - src = endptr + 1; value = strtoul(src, &endptr, 10); if (endptr == src) { - g_set_error(error, audio_parser_quark(), 0, - "Sample format missing"); + g_set_error(error_r, audio_parser_quark(), 0, + "Failed to parse the channel count"); return false; - } else if (*endptr != ':') { - g_set_error(error, audio_parser_quark(), 0, - "Channel count missing"); + } else if (!audio_check_channel_count(value, error_r)) return false; - } else if (!audio_valid_sample_format(value)) { - g_set_error(error, audio_parser_quark(), 0, - "Invalid sample format: %lu", value); + + *channels_r = value; + *endptr_r = endptr; + return true; +} + +bool +audio_format_parse(struct audio_format *dest, const char *src, + bool mask, GError **error_r) +{ + uint32_t rate; + enum sample_format sample_format; + uint8_t channels; + + audio_format_clear(dest); + + /* parse sample rate */ + + if (!parse_sample_rate(src, mask, &rate, &src, error_r)) + return false; + + if (*src++ != ':') { + g_set_error(error_r, audio_parser_quark(), 0, + "Sample format missing"); return false; } - dest->bits = value; + /* parse sample format */ + + if (!parse_sample_format(src, mask, &sample_format, &src, error_r)) + return false; + + if (*src++ != ':') { + g_set_error(error_r, audio_parser_quark(), 0, + "Channel count missing"); + return false; + } /* parse channel count */ - src = endptr + 1; - value = strtoul(src, &endptr, 10); - if (*endptr != 0 || !audio_valid_channel_count(value)) { - g_set_error(error, audio_parser_quark(), 0, - "Invalid channel count: %s", src); + if (!parse_channel_count(src, mask, &channels, &src, error_r)) + return false; + + if (*src != 0) { + g_set_error(error_r, audio_parser_quark(), 0, + "Extra data after channel count: %s", src); return false; } - dest->channels = value; + audio_format_init(dest, rate, sample_format, channels); return true; } diff --git a/src/audio_parser.h b/src/audio_parser.h index 30a927456..d50c17489 100644 --- a/src/audio_parser.h +++ b/src/audio_parser.h @@ -37,11 +37,13 @@ struct audio_format; * * @param dest the destination #audio_format struct * @param src the input string - * @param error location to store the error occuring, or NULL to + * @param mask if true, then "*" is allowed for any number of items + * @param error_r location to store the error occuring, or NULL to * ignore errors * @return true on success */ bool -audio_format_parse(struct audio_format *dest, const char *src, GError **error); +audio_format_parse(struct audio_format *dest, const char *src, + bool mask, GError **error_r); #endif diff --git a/src/buffer.c b/src/buffer.c index 24715a744..898197492 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "buffer.h" #include "chunk.h" #include "poison.h" diff --git a/src/buffer2array.c b/src/buffer2array.c deleted file mode 100644 index b6029d754..000000000 --- a/src/buffer2array.c +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "buffer2array.h" - -#include <glib.h> - -#include <string.h> - -int buffer2array(char *buffer, char *array[], const int max) -{ - int i = 0; - char *c = buffer; - - while (*c != '\0' && i < max) { - if (*c == '\"') { - array[i++] = ++c; - while (*c != '\0') { - if (*c == '\"') { - *(c++) = '\0'; - break; - } - else if (*(c++) == '\\' && *c != '\0') { - memmove(c - 1, c, strlen(c) + 1); - } - } - } else { - c = g_strchug(c); - if (*c == '\0') - return i; - - array[i++] = c++; - - while (!g_ascii_isspace(*c) && *c != '\0') - ++c; - } - if (*c == '\0') - return i; - *(c++) = '\0'; - - c = g_strchug(c); - } - return i; -} - -#ifdef UNIT_TEST - -#include <stdio.h> -#include <string.h> -#include <assert.h> - -int main() -{ - char *a[4] = { NULL }; - char *b; - int max; - - b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir/name \"test\"", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir/name \"test\" something else", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"/some/dir\\\\name\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir\\name", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"/some/dir name\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("/some/dir name", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"\\\"/some/dir\\\"\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("\"/some/dir\"", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"\\\"/some/dir\\\" x\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("\"/some/dir\" x", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"single quote\\'d from php magicquotes\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("single quote\'d from php magicquotes", a[1]) ); - assert( !a[2] ); - - b = strdup("lsinfo \"double quote\\\"d from php magicquotes\""); - assert(b); - max = buffer2array(b, a, 4); - assert( !strcmp("lsinfo", a[0]) ); - assert( !strcmp("double quote\"d from php magicquotes", a[1]) ); - assert( !a[2] ); - - return 0; -} - -#endif diff --git a/src/check.h b/src/check.h new file mode 100644 index 000000000..6fdb2535e --- /dev/null +++ b/src/check.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CHECK_H +#define MPD_CHECK_H + +/* + * All sources must include config.h on the first line to ensure that + * Large File Support is configured properly. This header checks + * whether this has happened. + * + * Usage: include this header before you use any of the above types. + * It will stop the compiler if something went wrong. + * + * This is Linux/glibc specific, and only enabled in the debug build, + * so bugs in this headers don't affect users with production builds. + * + */ + +#ifndef PACKAGE_VERSION +#error config.h missing +#endif + +#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \ + defined(_FEATURES_H) && defined(__i386__) && \ + !defined(__USE_FILE_OFFSET64) +/* on i386, check if LFS is enabled */ +#error config.h was included too late +#endif + +#endif diff --git a/src/chunk.c b/src/chunk.c index 3ac190633..9cfaa010a 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "chunk.h" #include "audio_format.h" #include "tag.h" diff --git a/src/client.c b/src/client.c index 6a256998f..dae7b8d20 100644 --- a/src/client.c +++ b/src/client.c @@ -17,110 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "client.h" -#include "fifo_buffer.h" -#include "command.h" -#include "conf.h" -#include "listen.h" -#include "socket_util.h" -#include "permission.h" -#include "event_pipe.h" -#include "idle.h" -#include "main.h" #include "config.h" - -#include <glib.h> -#include <assert.h> -#include <unistd.h> -#include <string.h> -#include <stdlib.h> -#include <stdio.h> -#include <errno.h> - -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#endif - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "client" -#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO - -static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; - -#define CLIENT_LIST_MODE_BEGIN "command_list_begin" -#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" -#define CLIENT_LIST_MODE_END "command_list_end" -#define CLIENT_TIMEOUT_DEFAULT (60) -#define CLIENT_MAX_CONNECTIONS_DEFAULT (10) -#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) -#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) - -/* set this to zero to indicate we have no possible clients */ -static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */ -static int client_timeout; -static size_t client_max_command_list_size; -static size_t client_max_output_buffer_size; - -struct deferred_buffer { - size_t size; - char data[sizeof(long)]; -}; - -struct client { - GIOChannel *channel; - guint source_id; - - /** the buffer for reading lines from the #channel */ - struct fifo_buffer *input; - - unsigned permission; - - /** the uid of the client process, or -1 if unknown */ - int uid; - - /** - * How long since the last activity from this client? - */ - GTimer *last_activity; - - GSList *cmd_list; /* for when in list mode */ - int cmd_list_OK; /* print OK after each command execution */ - size_t cmd_list_size; /* mem cmd_list consumes */ - GQueue *deferred_send; /* for output if client is slow */ - size_t deferred_bytes; /* mem deferred_send consumes */ - unsigned int num; /* client number */ - - char send_buf[4096]; - size_t send_buf_used; /* bytes used this instance */ - - /** is this client waiting for an "idle" response? */ - bool idle_waiting; - - /** idle flags pending on this client, to be sent as soon as - the client enters "idle" */ - unsigned idle_flags; - - /** idle flags that the client wants to receive */ - unsigned idle_subscriptions; -}; - -static GList *clients; -static unsigned num_clients; -static guint expire_source_id; - -static void client_write_deferred(struct client *client); - -static void client_write_output(struct client *client); - -static void client_manager_expire(void); - -static gboolean -client_in_event(GIOChannel *source, GIOCondition condition, gpointer data); +#include "client_internal.h" bool client_is_expired(const struct client *client) { @@ -141,782 +39,3 @@ void client_set_permission(struct client *client, unsigned permission) { client->permission = permission; } - -/** - * An idle event which calls client_manager_expire(). - */ -static gboolean -client_manager_expire_event(G_GNUC_UNUSED gpointer data) -{ - expire_source_id = 0; - client_manager_expire(); - return false; -} - -static inline void client_set_expired(struct client *client) -{ - if (expire_source_id == 0 && !client_is_expired(client)) - /* delayed deletion */ - expire_source_id = g_idle_add(client_manager_expire_event, - NULL); - - if (client->source_id != 0) { - g_source_remove(client->source_id); - client->source_id = 0; - } - - if (client->channel != NULL) { - g_io_channel_unref(client->channel); - client->channel = NULL; - } -} - -static void client_init(struct client *client, int fd) -{ - static unsigned int next_client_num; - - assert(fd >= 0); - - client->cmd_list_size = 0; - client->cmd_list_OK = -1; - -#ifndef G_OS_WIN32 - client->channel = g_io_channel_unix_new(fd); -#else - client->channel = g_io_channel_win32_new_socket(fd); -#endif - /* GLib is responsible for closing the file descriptor */ - g_io_channel_set_close_on_unref(client->channel, true); - /* NULL encoding means the stream is binary safe; the MPD - protocol is UTF-8 only, but we are doing this call anyway - to prevent GLib from messing around with the stream */ - g_io_channel_set_encoding(client->channel, NULL, NULL); - /* we prefer to do buffering */ - g_io_channel_set_buffered(client->channel, false); - - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - - client->input = fifo_buffer_new(4096); - - client->cmd_list = NULL; - client->deferred_send = g_queue_new(); - client->deferred_bytes = 0; - client->num = next_client_num++; - client->send_buf_used = 0; - - client->permission = getDefaultPermissions(); - - (void)write(fd, GREETING, sizeof(GREETING) - 1); -} - -static void free_cmd_list(GSList *list) -{ - for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - - g_slist_free(list); -} - -static void new_cmd_list_ptr(struct client *client, char *s) -{ - client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); -} - -static void -deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct deferred_buffer *buffer = data; - g_free(buffer); -} - -static void client_close(struct client *client) -{ - assert(num_clients > 0); - assert(clients != NULL); - - clients = g_list_remove(clients, client); - --num_clients; - - client_set_expired(client); - - g_timer_destroy(client->last_activity); - - if (client->cmd_list) { - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - } - - g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL); - g_queue_free(client->deferred_send); - - fifo_buffer_free(client->input); - - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] closed", client->num); - g_free(client); -} - -void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) -{ - struct client *client; - char *remote; - - if (num_clients >= client_max_connections) { - g_warning("Max Connections Reached!"); - close(fd); - return; - } - - client = g_new0(struct client, 1); - clients = g_list_prepend(clients, client); - ++num_clients; - - client_init(client, fd); - client->uid = uid; - - client->last_activity = g_timer_new(); - - remote = sockaddr_to_string(sa, sa_length, NULL); - g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, - "[%u] opened from %s", client->num, remote); - g_free(remote); -} - -static int client_process_line(struct client *client, char *line) -{ - int ret = 1; - - if (strcmp(line, "noidle") == 0) { - if (client->idle_waiting) { - /* send empty idle response and leave idle mode */ - client->idle_waiting = false; - command_success(client); - client_write_output(client); - } - - /* do nothing if the client wasn't idling: the client - has already received the full idle response from - client_idle_notify(), which he can now evaluate */ - - return 0; - } else if (client->idle_waiting) { - /* during idle mode, clients must not send anything - except "noidle" */ - g_warning("[%u] command \"%s\" during idle", - client->num, line); - return COMMAND_RETURN_CLOSE; - } - - if (client->cmd_list_OK >= 0) { - if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { - g_debug("[%u] process command list", - client->num); - - /* for scalability reasons, we have prepended - each new command; now we have to reverse it - to restore the correct order */ - client->cmd_list = g_slist_reverse(client->cmd_list); - - ret = command_process_list(client, - client->cmd_list_OK, - client->cmd_list); - g_debug("[%u] process command " - "list returned %i", client->num, ret); - - if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - - if (ret == 0) - command_success(client); - - client_write_output(client); - free_cmd_list(client->cmd_list); - client->cmd_list = NULL; - client->cmd_list_OK = -1; - } else { - size_t len = strlen(line) + 1; - client->cmd_list_size += len; - if (client->cmd_list_size > - client_max_command_list_size) { - g_warning("[%u] command list size (%lu) " - "is larger than the max (%lu)", - client->num, - (unsigned long)client->cmd_list_size, - (unsigned long)client_max_command_list_size); - return COMMAND_RETURN_CLOSE; - } else - new_cmd_list_ptr(client, line); - } - } else { - if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { - client->cmd_list_OK = 0; - ret = 1; - } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { - client->cmd_list_OK = 1; - ret = 1; - } else { - g_debug("[%u] process command \"%s\"", - client->num, line); - ret = command_process(client, line); - g_debug("[%u] command returned %i", - client->num, ret); - - if (ret == COMMAND_RETURN_CLOSE || - client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - - if (ret == 0) - command_success(client); - - client_write_output(client); - } - } - - return ret; -} - -static char * -client_read_line(struct client *client) -{ - const char *p, *newline; - size_t length; - char *line; - - p = fifo_buffer_read(client->input, &length); - if (p == NULL) - return NULL; - - newline = memchr(p, '\n', length); - if (newline == NULL) - return NULL; - - line = g_strndup(p, newline - p); - fifo_buffer_consume(client->input, newline - p + 1); - - return g_strchomp(line); -} - -static int client_input_received(struct client *client, size_t bytesRead) -{ - char *line; - int ret; - - fifo_buffer_append(client->input, bytesRead); - - /* process all lines */ - - while ((line = client_read_line(client)) != NULL) { - ret = client_process_line(client, line); - g_free(line); - - if (ret == COMMAND_RETURN_KILL || - ret == COMMAND_RETURN_CLOSE) - return ret; - if (client_is_expired(client)) - return COMMAND_RETURN_CLOSE; - } - - return 0; -} - -static int client_read(struct client *client) -{ - char *p; - size_t max_length; - GError *error = NULL; - GIOStatus status; - gsize bytes_read; - - assert(client != NULL); - assert(client->channel != NULL); - - p = fifo_buffer_write(client->input, &max_length); - if (p == NULL) { - g_warning("[%u] buffer overflow", client->num); - return COMMAND_RETURN_CLOSE; - } - - status = g_io_channel_read_chars(client->channel, p, max_length, - &bytes_read, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return client_input_received(client, bytes_read); - - case G_IO_STATUS_AGAIN: - /* try again later, after select() */ - return 0; - - case G_IO_STATUS_EOF: - /* peer disconnected */ - return COMMAND_RETURN_CLOSE; - - case G_IO_STATUS_ERROR: - /* I/O error */ - g_warning("failed to read from client %d: %s", - client->num, error->message); - g_error_free(error); - return COMMAND_RETURN_CLOSE; - } - - /* unreachable */ - return COMMAND_RETURN_CLOSE; -} - -static gboolean -client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data); - -static gboolean -client_in_event(G_GNUC_UNUSED GIOChannel *source, - GIOCondition condition, - gpointer data) -{ - struct client *client = data; - int ret; - - assert(!client_is_expired(client)); - - if (condition != G_IO_IN) { - client_set_expired(client); - return false; - } - - g_timer_start(client->last_activity); - - ret = client_read(client); - switch (ret) { - case COMMAND_RETURN_KILL: - client_close(client); - g_main_loop_quit(main_loop); - return false; - - case COMMAND_RETURN_CLOSE: - client_close(client); - return false; - } - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - if (!g_queue_is_empty(client->deferred_send)) { - /* deferred buffers exist: schedule write */ - client->source_id = g_io_add_watch(client->channel, - G_IO_OUT|G_IO_ERR|G_IO_HUP, - client_out_event, client); - return false; - } - - /* read more */ - return true; -} - -static gboolean -client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, - gpointer data) -{ - struct client *client = data; - - assert(!client_is_expired(client)); - - if (condition != G_IO_OUT) { - client_set_expired(client); - return false; - } - - client_write_deferred(client); - - if (client_is_expired(client)) { - client_close(client); - return false; - } - - g_timer_start(client->last_activity); - - if (g_queue_is_empty(client->deferred_send)) { - /* done sending deferred buffers exist: schedule - read */ - client->source_id = g_io_add_watch(client->channel, - G_IO_IN|G_IO_ERR|G_IO_HUP, - client_in_event, client); - return false; - } - - /* write more */ - return true; -} - -void client_manager_init(void) -{ - client_timeout = config_get_positive(CONF_CONN_TIMEOUT, - CLIENT_TIMEOUT_DEFAULT); - client_max_connections = - config_get_positive(CONF_MAX_CONN, - CLIENT_MAX_CONNECTIONS_DEFAULT); - client_max_command_list_size = - config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, - CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) - * 1024; - - client_max_output_buffer_size = - config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, - CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) - * 1024; -} - -static void client_close_all(void) -{ - while (clients != NULL) { - struct client *client = clients->data; - - client_close(client); - } - - assert(num_clients == 0); -} - -void client_manager_deinit(void) -{ - client_close_all(); - - client_max_connections = 0; - - if (expire_source_id != 0) - g_source_remove(expire_source_id); -} - -static void -client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) -{ - struct client *client = data; - - if (client_is_expired(client)) { - g_debug("[%u] expired", client->num); - client_close(client); - } else if (!client->idle_waiting && /* idle clients - never expire */ - (int)g_timer_elapsed(client->last_activity, NULL) > - client_timeout) { - g_debug("[%u] timeout", client->num); - client_close(client); - } -} - -static void -client_manager_expire(void) -{ - g_list_foreach(clients, client_check_expired_callback, NULL); -} - -static size_t -client_write_deferred_buffer(struct client *client, - const struct deferred_buffer *buffer) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(buffer != NULL); - - status = g_io_channel_write_chars - (client->channel, buffer->data, buffer->size, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - return bytes_written; - - case G_IO_STATUS_AGAIN: - return 0; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return 0; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to flush buffer for %i: %s", - client->num, error->message); - g_error_free(error); - return 0; - } - - /* unreachable */ - return 0; -} - -static void client_write_deferred(struct client *client) -{ - size_t ret; - - while (!g_queue_is_empty(client->deferred_send)) { - struct deferred_buffer *buf = - g_queue_peek_head(client->deferred_send); - - assert(buf->size > 0); - assert(buf->size <= client->deferred_bytes); - - ret = client_write_deferred_buffer(client, buf); - if (ret == 0) - break; - - if (ret < buf->size) { - assert(client->deferred_bytes >= (size_t)ret); - client->deferred_bytes -= ret; - buf->size -= ret; - memmove(buf->data, buf->data + ret, buf->size); - break; - } else { - size_t decr = sizeof(*buf) - - sizeof(buf->data) + buf->size; - - assert(client->deferred_bytes >= decr); - client->deferred_bytes -= decr; - g_free(buf); - g_queue_pop_head(client->deferred_send); - } - - g_timer_start(client->last_activity); - } - - if (g_queue_is_empty(client->deferred_send)) { - g_debug("[%u] buffer empty %lu", client->num, - (unsigned long)client->deferred_bytes); - assert(client->deferred_bytes == 0); - } -} - -static void client_defer_output(struct client *client, - const void *data, size_t length) -{ - size_t alloc; - struct deferred_buffer *buf; - - assert(length > 0); - - alloc = sizeof(*buf) - sizeof(buf->data) + length; - client->deferred_bytes += alloc; - if (client->deferred_bytes > client_max_output_buffer_size) { - g_warning("[%u] output buffer size (%lu) is " - "larger than the max (%lu)", - client->num, - (unsigned long)client->deferred_bytes, - (unsigned long)client_max_output_buffer_size); - /* cause client to close */ - client_set_expired(client); - return; - } - - buf = g_malloc(alloc); - buf->size = length; - memcpy(buf->data, data, length); - - g_queue_push_tail(client->deferred_send, buf); -} - -static void client_write_direct(struct client *client, - const char *data, size_t length) -{ - GError *error = NULL; - GIOStatus status; - gsize bytes_written; - - assert(client != NULL); - assert(client->channel != NULL); - assert(data != NULL); - assert(length > 0); - assert(g_queue_is_empty(client->deferred_send)); - - status = g_io_channel_write_chars(client->channel, data, length, - &bytes_written, &error); - switch (status) { - case G_IO_STATUS_NORMAL: - case G_IO_STATUS_AGAIN: - break; - - case G_IO_STATUS_EOF: - /* client has disconnected */ - - client_set_expired(client); - return; - - case G_IO_STATUS_ERROR: - /* I/O error */ - - client_set_expired(client); - g_warning("failed to write to %i: %s", - client->num, error->message); - g_error_free(error); - return; - } - - if (bytes_written < length) - client_defer_output(client, data + bytes_written, - length - bytes_written); - - if (!g_queue_is_empty(client->deferred_send)) - g_debug("[%u] buffer created", client->num); -} - -static void client_write_output(struct client *client) -{ - if (client_is_expired(client) || !client->send_buf_used) - return; - - if (!g_queue_is_empty(client->deferred_send)) { - client_defer_output(client, client->send_buf, - client->send_buf_used); - - if (client_is_expired(client)) - return; - - /* try to flush the deferred buffers now; the current - server command may take too long to finish, and - meanwhile try to feed output to the client, - otherwise it will time out. One reason why - deferring is slow might be that currently each - client_write() allocates a new deferred buffer. - This should be optimized after MPD 0.14. */ - client_write_deferred(client); - } else - client_write_direct(client, client->send_buf, - client->send_buf_used); - - client->send_buf_used = 0; -} - -/** - * Write a block of data to the client. - */ -static void client_write(struct client *client, const char *buffer, size_t buflen) -{ - /* if the client is going to be closed, do nothing */ - if (client_is_expired(client)) - return; - - while (buflen > 0 && !client_is_expired(client)) { - size_t copylen; - - assert(client->send_buf_used < sizeof(client->send_buf)); - - copylen = sizeof(client->send_buf) - client->send_buf_used; - if (copylen > buflen) - copylen = buflen; - - memcpy(client->send_buf + client->send_buf_used, buffer, - copylen); - buflen -= copylen; - client->send_buf_used += copylen; - buffer += copylen; - if (client->send_buf_used >= sizeof(client->send_buf)) - client_write_output(client); - } -} - -void client_puts(struct client *client, const char *s) -{ - client_write(client, s, strlen(s)); -} - -void client_vprintf(struct client *client, const char *fmt, va_list args) -{ - va_list tmp; - int length; - char *buffer; - - va_copy(tmp, args); - length = vsnprintf(NULL, 0, fmt, tmp); - va_end(tmp); - - if (length <= 0) - /* wtf.. */ - return; - - buffer = g_malloc(length + 1); - vsnprintf(buffer, length + 1, fmt, args); - client_write(client, buffer, length); - g_free(buffer); -} - -G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) -{ - va_list args; - - va_start(args, fmt); - client_vprintf(client, fmt, args); - va_end(args); -} - -/** - * Send "idle" response to this client. - */ -static void -client_idle_notify(struct client *client) -{ - unsigned flags, i; - const char *const* idle_names; - - assert(client->idle_waiting); - assert(client->idle_flags != 0); - - flags = client->idle_flags; - client->idle_flags = 0; - client->idle_waiting = false; - - idle_names = idle_get_names(); - for (i = 0; idle_names[i]; ++i) { - if (flags & (1 << i) & client->idle_subscriptions) - client_printf(client, "changed: %s\n", - idle_names[i]); - } - - client_puts(client, "OK\n"); - g_timer_start(client->last_activity); -} - -static void -client_idle_callback(gpointer data, gpointer user_data) -{ - struct client *client = data; - unsigned flags = GPOINTER_TO_UINT(user_data); - - if (client_is_expired(client)) - return; - - client->idle_flags |= flags; - if (client->idle_waiting - && (client->idle_flags & client->idle_subscriptions)) { - client_idle_notify(client); - client_write_output(client); - } -} - -void client_manager_idle_add(unsigned flags) -{ - assert(flags != 0); - - g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags)); -} - -bool client_idle_wait(struct client *client, unsigned flags) -{ - assert(!client->idle_waiting); - - client->idle_waiting = true; - client->idle_subscriptions = flags; - - if (client->idle_flags & client->idle_subscriptions) { - client_idle_notify(client); - return true; - } else - return false; -} diff --git a/src/client_event.c b/src/client_event.c new file mode 100644 index 000000000..e67bb1d70 --- /dev/null +++ b/src/client_event.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "main.h" + +#include <assert.h> + +static gboolean +client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct client *client = data; + + assert(!client_is_expired(client)); + + if (condition != G_IO_OUT) { + client_set_expired(client); + return false; + } + + client_write_deferred(client); + + if (client_is_expired(client)) { + client_close(client); + return false; + } + + g_timer_start(client->last_activity); + + if (g_queue_is_empty(client->deferred_send)) { + /* done sending deferred buffers exist: schedule + read */ + client->source_id = g_io_add_watch(client->channel, + G_IO_IN|G_IO_ERR|G_IO_HUP, + client_in_event, client); + return false; + } + + /* write more */ + return true; +} + +gboolean +client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct client *client = data; + enum command_return ret; + + assert(!client_is_expired(client)); + + if (condition != G_IO_IN) { + client_set_expired(client); + return false; + } + + g_timer_start(client->last_activity); + + ret = client_read(client); + switch (ret) { + case COMMAND_RETURN_OK: + case COMMAND_RETURN_ERROR: + break; + + case COMMAND_RETURN_KILL: + client_close(client); + g_main_loop_quit(main_loop); + return false; + + case COMMAND_RETURN_CLOSE: + client_close(client); + return false; + } + + if (client_is_expired(client)) { + client_close(client); + return false; + } + + if (!g_queue_is_empty(client->deferred_send)) { + /* deferred buffers exist: schedule write */ + client->source_id = g_io_add_watch(client->channel, + G_IO_OUT|G_IO_ERR|G_IO_HUP, + client_out_event, client); + return false; + } + + /* read more */ + return true; +} diff --git a/src/client_expire.c b/src/client_expire.c new file mode 100644 index 000000000..f544a0ff5 --- /dev/null +++ b/src/client_expire.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +static guint expire_source_id; + +void +client_set_expired(struct client *client) +{ + if (!client_is_expired(client)) + client_schedule_expire(); + + if (client->source_id != 0) { + g_source_remove(client->source_id); + client->source_id = 0; + } + + if (client->channel != NULL) { + g_io_channel_unref(client->channel); + client->channel = NULL; + } +} + +static void +client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct client *client = data; + + if (client_is_expired(client)) { + g_debug("[%u] expired", client->num); + client_close(client); + } else if (!client->idle_waiting && /* idle clients + never expire */ + (int)g_timer_elapsed(client->last_activity, NULL) > + client_timeout) { + g_debug("[%u] timeout", client->num); + client_close(client); + } +} + +static void +client_manager_expire(void) +{ + client_list_foreach(client_check_expired_callback, NULL); +} + +/** + * An idle event which calls client_manager_expire(). + */ +static gboolean +client_manager_expire_event(G_GNUC_UNUSED gpointer data) +{ + expire_source_id = 0; + client_manager_expire(); + return false; +} + +void +client_schedule_expire(void) +{ + if (expire_source_id == 0) + /* delayed deletion */ + expire_source_id = g_idle_add(client_manager_expire_event, + NULL); +} + +void +client_deinit_expire(void) +{ + if (expire_source_id != 0) + g_source_remove(expire_source_id); +} diff --git a/src/client_global.c b/src/client_global.c new file mode 100644 index 000000000..2c5e26416 --- /dev/null +++ b/src/client_global.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "conf.h" + +#include <assert.h> + +#define CLIENT_TIMEOUT_DEFAULT (60) +#define CLIENT_MAX_CONNECTIONS_DEFAULT (10) +#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) + +/* set this to zero to indicate we have no possible clients */ +unsigned int client_max_connections; +int client_timeout; +size_t client_max_command_list_size; +size_t client_max_output_buffer_size; + +void client_manager_init(void) +{ + client_timeout = config_get_positive(CONF_CONN_TIMEOUT, + CLIENT_TIMEOUT_DEFAULT); + client_max_connections = + config_get_positive(CONF_MAX_CONN, + CLIENT_MAX_CONNECTIONS_DEFAULT); + client_max_command_list_size = + config_get_positive(CONF_MAX_COMMAND_LIST_SIZE, + CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024) + * 1024; + + client_max_output_buffer_size = + config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE, + CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024) + * 1024; +} + +static void client_close_all(void) +{ + while (!client_list_is_empty()) { + struct client *client = client_list_get_first(); + + client_close(client); + } + + assert(client_list_is_empty()); +} + +void client_manager_deinit(void) +{ + client_close_all(); + + client_max_connections = 0; + + client_deinit_expire(); +} diff --git a/src/client_idle.c b/src/client_idle.c new file mode 100644 index 000000000..23011b8c5 --- /dev/null +++ b/src/client_idle.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "idle.h" + +#include <assert.h> + +/** + * Send "idle" response to this client. + */ +static void +client_idle_notify(struct client *client) +{ + unsigned flags, i; + const char *const* idle_names; + + assert(client->idle_waiting); + assert(client->idle_flags != 0); + + flags = client->idle_flags; + client->idle_flags = 0; + client->idle_waiting = false; + + idle_names = idle_get_names(); + for (i = 0; idle_names[i]; ++i) { + if (flags & (1 << i) & client->idle_subscriptions) + client_printf(client, "changed: %s\n", + idle_names[i]); + } + + client_puts(client, "OK\n"); + g_timer_start(client->last_activity); +} + +static void +client_idle_callback(gpointer data, gpointer user_data) +{ + struct client *client = data; + unsigned flags = GPOINTER_TO_UINT(user_data); + + if (client_is_expired(client)) + return; + + client->idle_flags |= flags; + if (client->idle_waiting + && (client->idle_flags & client->idle_subscriptions)) { + client_idle_notify(client); + client_write_output(client); + } +} + +void client_manager_idle_add(unsigned flags) +{ + assert(flags != 0); + + client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags)); +} + +bool client_idle_wait(struct client *client, unsigned flags) +{ + assert(!client->idle_waiting); + + client->idle_waiting = true; + client->idle_subscriptions = flags; + + if (client->idle_flags & client->idle_subscriptions) { + client_idle_notify(client); + return true; + } else + return false; +} diff --git a/src/client_internal.h b/src/client_internal.h new file mode 100644 index 000000000..91e360fe0 --- /dev/null +++ b/src/client_internal.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_CLIENT_INTERNAL_H +#define MPD_CLIENT_INTERNAL_H + +#include "client.h" +#include "command.h" + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "client" + +struct deferred_buffer { + size_t size; + char data[sizeof(long)]; +}; + +struct client { + GIOChannel *channel; + guint source_id; + + /** the buffer for reading lines from the #channel */ + struct fifo_buffer *input; + + unsigned permission; + + /** the uid of the client process, or -1 if unknown */ + int uid; + + /** + * How long since the last activity from this client? + */ + GTimer *last_activity; + + GSList *cmd_list; /* for when in list mode */ + int cmd_list_OK; /* print OK after each command execution */ + size_t cmd_list_size; /* mem cmd_list consumes */ + GQueue *deferred_send; /* for output if client is slow */ + size_t deferred_bytes; /* mem deferred_send consumes */ + unsigned int num; /* client number */ + + char send_buf[4096]; + size_t send_buf_used; /* bytes used this instance */ + + /** is this client waiting for an "idle" response? */ + bool idle_waiting; + + /** idle flags pending on this client, to be sent as soon as + the client enters "idle" */ + unsigned idle_flags; + + /** idle flags that the client wants to receive */ + unsigned idle_subscriptions; +}; + +extern unsigned int client_max_connections; +extern int client_timeout; +extern size_t client_max_command_list_size; +extern size_t client_max_output_buffer_size; + +bool +client_list_is_empty(void); + +bool +client_list_is_full(void); + +struct client * +client_list_get_first(void); + +void +client_list_add(struct client *client); + +void +client_list_foreach(GFunc func, gpointer user_data); + +void +client_list_remove(struct client *client); + +void +client_close(struct client *client); + +static inline void +new_cmd_list_ptr(struct client *client, const char *s) +{ + client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s)); +} + +static inline void +free_cmd_list(GSList *list) +{ + for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp)) + g_free(tmp->data); + + g_slist_free(list); +} + +void +client_set_expired(struct client *client); + +/** + * Schedule an "expired" check for all clients: permanently delete + * clients which have been set "expired" with client_set_expired(). + */ +void +client_schedule_expire(void); + +/** + * Removes a scheduled "expired" check. + */ +void +client_deinit_expire(void); + +enum command_return +client_read(struct client *client); + +enum command_return +client_process_line(struct client *client, char *line); + +void +client_write_deferred(struct client *client); + +void +client_write_output(struct client *client); + +gboolean +client_in_event(GIOChannel *source, GIOCondition condition, + gpointer data); + +#endif diff --git a/src/client_list.c b/src/client_list.c new file mode 100644 index 000000000..f2134d5f2 --- /dev/null +++ b/src/client_list.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +#include <assert.h> + +static GList *clients; +static unsigned num_clients; + +bool +client_list_is_empty(void) +{ + return num_clients == 0; +} + +bool +client_list_is_full(void) +{ + return num_clients >= client_max_connections; +} + +struct client * +client_list_get_first(void) +{ + assert(clients != NULL); + + return clients->data; +} + +void +client_list_add(struct client *client) +{ + clients = g_list_prepend(clients, client); + ++num_clients; +} + +void +client_list_foreach(GFunc func, gpointer user_data) +{ + g_list_foreach(clients, func, user_data); +} + +void +client_list_remove(struct client *client) +{ + assert(num_clients > 0); + assert(clients != NULL); + + clients = g_list_remove(clients, client); + --num_clients; +} diff --git a/src/client_new.c b/src/client_new.c new file mode 100644 index 000000000..c2c3a1e30 --- /dev/null +++ b/src/client_new.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "fifo_buffer.h" +#include "socket_util.h" +#include "permission.h" + +#include <assert.h> +#include <unistd.h> + +#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO + +static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n"; + +void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid) +{ + static unsigned int next_client_num; + struct client *client; + char *remote; + + assert(fd >= 0); + + if (client_list_is_full()) { + g_warning("Max Connections Reached!"); + close(fd); + return; + } + + client = g_new0(struct client, 1); + +#ifndef G_OS_WIN32 + client->channel = g_io_channel_unix_new(fd); +#else + client->channel = g_io_channel_win32_new_socket(fd); +#endif + /* GLib is responsible for closing the file descriptor */ + g_io_channel_set_close_on_unref(client->channel, true); + /* NULL encoding means the stream is binary safe; the MPD + protocol is UTF-8 only, but we are doing this call anyway + to prevent GLib from messing around with the stream */ + g_io_channel_set_encoding(client->channel, NULL, NULL); + /* we prefer to do buffering */ + g_io_channel_set_buffered(client->channel, false); + + client->source_id = g_io_add_watch(client->channel, + G_IO_IN|G_IO_ERR|G_IO_HUP, + client_in_event, client); + + client->input = fifo_buffer_new(4096); + + client->permission = getDefaultPermissions(); + client->uid = uid; + + client->last_activity = g_timer_new(); + + client->cmd_list = NULL; + client->cmd_list_OK = -1; + client->cmd_list_size = 0; + + client->deferred_send = g_queue_new(); + client->deferred_bytes = 0; + client->num = next_client_num++; + + client->send_buf_used = 0; + + (void)write(fd, GREETING, sizeof(GREETING) - 1); + + client_list_add(client); + + remote = sockaddr_to_string(sa, sa_length, NULL); + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "[%u] opened from %s", client->num, remote); + g_free(remote); +} + +static void +deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct deferred_buffer *buffer = data; + g_free(buffer); +} + +void +client_close(struct client *client) +{ + client_list_remove(client); + + client_set_expired(client); + + g_timer_destroy(client->last_activity); + + if (client->cmd_list) { + free_cmd_list(client->cmd_list); + client->cmd_list = NULL; + } + + g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL); + g_queue_free(client->deferred_send); + + fifo_buffer_free(client->input); + + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "[%u] closed", client->num); + g_free(client); +} diff --git a/src/client_process.c b/src/client_process.c new file mode 100644 index 000000000..18976c941 --- /dev/null +++ b/src/client_process.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +#include <string.h> + +#define CLIENT_LIST_MODE_BEGIN "command_list_begin" +#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin" +#define CLIENT_LIST_MODE_END "command_list_end" + +static enum command_return +client_process_command_list(struct client *client, bool list_ok, GSList *list) +{ + enum command_return ret = COMMAND_RETURN_OK; + unsigned num = 0; + + for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { + char *cmd = cur->data; + + g_debug("command_process_list: process command \"%s\"", + cmd); + ret = command_process(client, num++, cmd); + g_debug("command_process_list: command returned %i", ret); + if (ret != COMMAND_RETURN_OK || client_is_expired(client)) + break; + else if (list_ok) + client_puts(client, "list_OK\n"); + } + + return ret; +} + +enum command_return +client_process_line(struct client *client, char *line) +{ + enum command_return ret; + + if (strcmp(line, "noidle") == 0) { + if (client->idle_waiting) { + /* send empty idle response and leave idle mode */ + client->idle_waiting = false; + command_success(client); + client_write_output(client); + } + + /* do nothing if the client wasn't idling: the client + has already received the full idle response from + client_idle_notify(), which he can now evaluate */ + + return COMMAND_RETURN_OK; + } else if (client->idle_waiting) { + /* during idle mode, clients must not send anything + except "noidle" */ + g_warning("[%u] command \"%s\" during idle", + client->num, line); + return COMMAND_RETURN_CLOSE; + } + + if (client->cmd_list_OK >= 0) { + if (strcmp(line, CLIENT_LIST_MODE_END) == 0) { + g_debug("[%u] process command list", + client->num); + + /* for scalability reasons, we have prepended + each new command; now we have to reverse it + to restore the correct order */ + client->cmd_list = g_slist_reverse(client->cmd_list); + + ret = client_process_command_list(client, + client->cmd_list_OK, + client->cmd_list); + g_debug("[%u] process command " + "list returned %i", client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client_is_expired(client)) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + + client_write_output(client); + free_cmd_list(client->cmd_list); + client->cmd_list = NULL; + client->cmd_list_OK = -1; + } else { + size_t len = strlen(line) + 1; + client->cmd_list_size += len; + if (client->cmd_list_size > + client_max_command_list_size) { + g_warning("[%u] command list size (%lu) " + "is larger than the max (%lu)", + client->num, + (unsigned long)client->cmd_list_size, + (unsigned long)client_max_command_list_size); + return COMMAND_RETURN_CLOSE; + } + + new_cmd_list_ptr(client, line); + ret = COMMAND_RETURN_OK; + } + } else { + if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) { + client->cmd_list_OK = 0; + ret = COMMAND_RETURN_OK; + } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) { + client->cmd_list_OK = 1; + ret = COMMAND_RETURN_OK; + } else { + g_debug("[%u] process command \"%s\"", + client->num, line); + ret = command_process(client, 0, line); + g_debug("[%u] command returned %i", + client->num, ret); + + if (ret == COMMAND_RETURN_CLOSE || + client_is_expired(client)) + return COMMAND_RETURN_CLOSE; + + if (ret == COMMAND_RETURN_OK) + command_success(client); + + client_write_output(client); + } + } + + return ret; +} diff --git a/src/client_read.c b/src/client_read.c new file mode 100644 index 000000000..534bf5a6f --- /dev/null +++ b/src/client_read.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" +#include "fifo_buffer.h" + +#include <assert.h> +#include <string.h> + +static char * +client_read_line(struct client *client) +{ + const char *p, *newline; + size_t length; + char *line; + + p = fifo_buffer_read(client->input, &length); + if (p == NULL) + return NULL; + + newline = memchr(p, '\n', length); + if (newline == NULL) + return NULL; + + line = g_strndup(p, newline - p); + fifo_buffer_consume(client->input, newline - p + 1); + + return g_strchomp(line); +} + +static enum command_return +client_input_received(struct client *client, size_t bytesRead) +{ + char *line; + + fifo_buffer_append(client->input, bytesRead); + + /* process all lines */ + + while ((line = client_read_line(client)) != NULL) { + enum command_return ret = client_process_line(client, line); + g_free(line); + + if (ret == COMMAND_RETURN_KILL || + ret == COMMAND_RETURN_CLOSE) + return ret; + if (client_is_expired(client)) + return COMMAND_RETURN_CLOSE; + } + + return COMMAND_RETURN_OK; +} + +enum command_return +client_read(struct client *client) +{ + char *p; + size_t max_length; + GError *error = NULL; + GIOStatus status; + gsize bytes_read; + + assert(client != NULL); + assert(client->channel != NULL); + + p = fifo_buffer_write(client->input, &max_length); + if (p == NULL) { + g_warning("[%u] buffer overflow", client->num); + return COMMAND_RETURN_CLOSE; + } + + status = g_io_channel_read_chars(client->channel, p, max_length, + &bytes_read, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + return client_input_received(client, bytes_read); + + case G_IO_STATUS_AGAIN: + /* try again later, after select() */ + return COMMAND_RETURN_OK; + + case G_IO_STATUS_EOF: + /* peer disconnected */ + return COMMAND_RETURN_CLOSE; + + case G_IO_STATUS_ERROR: + /* I/O error */ + g_warning("failed to read from client %d: %s", + client->num, error->message); + g_error_free(error); + return COMMAND_RETURN_CLOSE; + } + + /* unreachable */ + return COMMAND_RETURN_CLOSE; +} diff --git a/src/client_write.c b/src/client_write.c new file mode 100644 index 000000000..9bac20fa5 --- /dev/null +++ b/src/client_write.c @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "client_internal.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +static size_t +client_write_deferred_buffer(struct client *client, + const struct deferred_buffer *buffer) +{ + GError *error = NULL; + GIOStatus status; + gsize bytes_written; + + assert(client != NULL); + assert(client->channel != NULL); + assert(buffer != NULL); + + status = g_io_channel_write_chars + (client->channel, buffer->data, buffer->size, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + return bytes_written; + + case G_IO_STATUS_AGAIN: + return 0; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + client_set_expired(client); + return 0; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + client_set_expired(client); + g_warning("failed to flush buffer for %i: %s", + client->num, error->message); + g_error_free(error); + return 0; + } + + /* unreachable */ + return 0; +} + +void +client_write_deferred(struct client *client) +{ + size_t ret; + + while (!g_queue_is_empty(client->deferred_send)) { + struct deferred_buffer *buf = + g_queue_peek_head(client->deferred_send); + + assert(buf->size > 0); + assert(buf->size <= client->deferred_bytes); + + ret = client_write_deferred_buffer(client, buf); + if (ret == 0) + break; + + if (ret < buf->size) { + assert(client->deferred_bytes >= (size_t)ret); + client->deferred_bytes -= ret; + buf->size -= ret; + memmove(buf->data, buf->data + ret, buf->size); + break; + } else { + size_t decr = sizeof(*buf) - + sizeof(buf->data) + buf->size; + + assert(client->deferred_bytes >= decr); + client->deferred_bytes -= decr; + g_free(buf); + g_queue_pop_head(client->deferred_send); + } + + g_timer_start(client->last_activity); + } + + if (g_queue_is_empty(client->deferred_send)) { + g_debug("[%u] buffer empty %lu", client->num, + (unsigned long)client->deferred_bytes); + assert(client->deferred_bytes == 0); + } +} + +static void client_defer_output(struct client *client, + const void *data, size_t length) +{ + size_t alloc; + struct deferred_buffer *buf; + + assert(length > 0); + + alloc = sizeof(*buf) - sizeof(buf->data) + length; + client->deferred_bytes += alloc; + if (client->deferred_bytes > client_max_output_buffer_size) { + g_warning("[%u] output buffer size (%lu) is " + "larger than the max (%lu)", + client->num, + (unsigned long)client->deferred_bytes, + (unsigned long)client_max_output_buffer_size); + /* cause client to close */ + client_set_expired(client); + return; + } + + buf = g_malloc(alloc); + buf->size = length; + memcpy(buf->data, data, length); + + g_queue_push_tail(client->deferred_send, buf); +} + +static void client_write_direct(struct client *client, + const char *data, size_t length) +{ + GError *error = NULL; + GIOStatus status; + gsize bytes_written; + + assert(client != NULL); + assert(client->channel != NULL); + assert(data != NULL); + assert(length > 0); + assert(g_queue_is_empty(client->deferred_send)); + + status = g_io_channel_write_chars(client->channel, data, length, + &bytes_written, &error); + switch (status) { + case G_IO_STATUS_NORMAL: + case G_IO_STATUS_AGAIN: + break; + + case G_IO_STATUS_EOF: + /* client has disconnected */ + + client_set_expired(client); + return; + + case G_IO_STATUS_ERROR: + /* I/O error */ + + client_set_expired(client); + g_warning("failed to write to %i: %s", + client->num, error->message); + g_error_free(error); + return; + } + + if (bytes_written < length) + client_defer_output(client, data + bytes_written, + length - bytes_written); + + if (!g_queue_is_empty(client->deferred_send)) + g_debug("[%u] buffer created", client->num); +} + +void +client_write_output(struct client *client) +{ + if (client_is_expired(client) || !client->send_buf_used) + return; + + if (!g_queue_is_empty(client->deferred_send)) { + client_defer_output(client, client->send_buf, + client->send_buf_used); + + if (client_is_expired(client)) + return; + + /* try to flush the deferred buffers now; the current + server command may take too long to finish, and + meanwhile try to feed output to the client, + otherwise it will time out. One reason why + deferring is slow might be that currently each + client_write() allocates a new deferred buffer. + This should be optimized after MPD 0.14. */ + client_write_deferred(client); + } else + client_write_direct(client, client->send_buf, + client->send_buf_used); + + client->send_buf_used = 0; +} + +/** + * Write a block of data to the client. + */ +static void client_write(struct client *client, const char *buffer, size_t buflen) +{ + /* if the client is going to be closed, do nothing */ + if (client_is_expired(client)) + return; + + while (buflen > 0 && !client_is_expired(client)) { + size_t copylen; + + assert(client->send_buf_used < sizeof(client->send_buf)); + + copylen = sizeof(client->send_buf) - client->send_buf_used; + if (copylen > buflen) + copylen = buflen; + + memcpy(client->send_buf + client->send_buf_used, buffer, + copylen); + buflen -= copylen; + client->send_buf_used += copylen; + buffer += copylen; + if (client->send_buf_used >= sizeof(client->send_buf)) + client_write_output(client); + } +} + +void client_puts(struct client *client, const char *s) +{ + client_write(client, s, strlen(s)); +} + +void client_vprintf(struct client *client, const char *fmt, va_list args) +{ + va_list tmp; + int length; + char *buffer; + + va_copy(tmp, args); + length = vsnprintf(NULL, 0, fmt, tmp); + va_end(tmp); + + if (length <= 0) + /* wtf.. */ + return; + + buffer = g_malloc(length + 1); + vsnprintf(buffer, length + 1, fmt, args); + client_write(client, buffer, length); + g_free(buffer); +} + +G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + client_vprintf(client, fmt, args); + va_end(args); +} diff --git a/src/cmdline.c b/src/cmdline.c index e0274ef36..908e8f27d 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -17,15 +17,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "cmdline.h" #include "path.h" #include "log.h" #include "conf.h" #include "decoder_list.h" -#include "config.h" +#include "decoder_plugin.h" #include "output_list.h" #include "ls.h" +#ifdef ENABLE_ENCODER +#include "encoder_list.h" +#endif + #ifdef ENABLE_ARCHIVE #include "archive_list.h" #endif @@ -35,10 +40,34 @@ #include <stdio.h> #include <stdlib.h> -#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf" #define USER_CONFIG_FILE_LOCATION1 ".mpdconf" #define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf" +static GQuark +cmdline_quark(void) +{ + return g_quark_from_static_string("cmdline"); +} + +static void +print_all_decoders(FILE *fp) +{ + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { + const struct decoder_plugin *plugin = decoder_plugins[i]; + const char *const*suffixes; + + fprintf(fp, "[%s]", plugin->name); + + for (suffixes = plugin->suffixes; + suffixes != NULL && *suffixes != NULL; + ++suffixes) { + fprintf(fp, " %s", *suffixes); + } + + fprintf(fp, "\n"); + } +} + G_GNUC_NORETURN static void version(void) { @@ -51,13 +80,19 @@ static void version(void) "\n" "Supported decoders:\n"); - decoder_plugin_init_all(); - decoder_plugin_print_all_decoders(stdout); + print_all_decoders(stdout); puts("\n" "Supported outputs:\n"); audio_output_plugin_print_all_types(stdout); +#ifdef ENABLE_ENCODER + puts("\n" + "Supported encoders:\n"); + encoder_plugin_print_all_types(stdout); +#endif + + #ifdef ENABLE_ARCHIVE puts("\n" "Supported archives:\n"); @@ -72,31 +107,29 @@ static void version(void) exit(EXIT_SUCCESS); } -#if GLIB_CHECK_VERSION(2,12,0) static const char *summary = "Music Player Daemon - a daemon for playing music."; -#endif -void parseOptions(int argc, char **argv, Options *options) +bool +parse_cmdline(int argc, char **argv, struct options *options, + GError **error_r) { GError *error = NULL; GOptionContext *context; bool ret; static gboolean option_version, - option_create_db, option_no_create_db, option_no_daemon, + option_no_daemon, option_no_config; const GOptionEntry entries[] = { - { "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db, - "force (re)creation of database", NULL }, { "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill, "kill the currently running mpd session", NULL }, { "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config, "don't read from config", NULL }, - { "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db, - "don't create database, even if it doesn't exist", NULL }, { "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon, "don't detach from console", NULL }, - { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput, + { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, + NULL, NULL }, + { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr, "print messages to stderr", NULL }, { "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose, "verbose logging", NULL }, @@ -107,16 +140,13 @@ void parseOptions(int argc, char **argv, Options *options) options->kill = false; options->daemon = true; - options->stdOutput = false; + options->log_stderr = false; options->verbose = false; - options->createDB = 0; context = g_option_context_new("[path/to/mpd.conf]"); g_option_context_add_main_entries(context, entries, NULL); -#if GLIB_CHECK_VERSION(2,12,0) g_option_context_set_summary(context, summary); -#endif ret = g_option_context_parse(context, &argc, &argv, &error); g_option_context_free(context); @@ -133,18 +163,11 @@ void parseOptions(int argc, char **argv, Options *options) parser can use it already */ log_early_init(options->verbose); - if (option_create_db && option_no_create_db) - g_error("Cannot use both --create-db and --no-create-db\n"); - - if (option_no_create_db) - options->createDB = -1; - else if (option_create_db) - options->createDB = 1; - options->daemon = !option_no_daemon; if (option_no_config) { g_debug("Ignoring config, using daemon defaults\n"); + return true; } else if (argc <= 1) { /* default configuration file path */ char *path1; @@ -155,17 +178,23 @@ void parseOptions(int argc, char **argv, Options *options) path2 = g_build_filename(g_get_home_dir(), USER_CONFIG_FILE_LOCATION2, NULL); if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) - config_read_file(path1); + ret = config_read_file(path1, error_r); else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR)) - config_read_file(path2); + ret = config_read_file(path2, error_r); else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION, G_FILE_TEST_IS_REGULAR)) - config_read_file(SYSTEM_CONFIG_FILE_LOCATION); + ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION, + error_r); g_free(path1); g_free(path2); + + return ret; } else if (argc == 2) { /* specified configuration file */ - config_read_file(argv[1]); - } else - g_error("too many arguments"); + return config_read_file(argv[1], error_r); + } else { + g_set_error(error_r, cmdline_quark(), 0, + "too many arguments"); + return false; + } } diff --git a/src/cmdline.h b/src/cmdline.h index 673701055..945868b8c 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -22,14 +22,17 @@ #include <glib.h> -typedef struct _Options { +#include <stdbool.h> + +struct options { gboolean kill; gboolean daemon; - gboolean stdOutput; + gboolean log_stderr; gboolean verbose; - int createDB; -} Options; +}; -void parseOptions(int argc, char **argv, Options *options); +bool +parse_cmdline(int argc, char **argv, struct options *options, + GError **error_r); #endif diff --git a/src/command.c b/src/command.c index d30b63594..db0bafa31 100644 --- a/src/command.c +++ b/src/command.c @@ -17,14 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "command.h" #include "player_control.h" #include "playlist.h" #include "playlist_print.h" #include "playlist_save.h" +#include "playlist_queue.h" #include "queue_print.h" #include "ls.h" #include "uri.h" +#include "decoder_print.h" #include "directory.h" #include "directory_print.h" #include "database.h" @@ -32,7 +35,7 @@ #include "volume.h" #include "stats.h" #include "permission.h" -#include "buffer2array.h" +#include "tokenizer.h" #include "stored_playlist.h" #include "ack.h" #include "output_command.h" @@ -43,8 +46,8 @@ #include "client.h" #include "tag_print.h" #include "path.h" +#include "replay_gain.h" #include "idle.h" -#include "config.h" #ifdef ENABLE_SQLITE #include "sticker.h" @@ -58,7 +61,6 @@ #include <stdlib.h> #include <errno.h> -#define COMMAND_STATUS_VOLUME "volume" #define COMMAND_STATUS_STATE "state" #define COMMAND_STATUS_REPEAT "repeat" #define COMMAND_STATUS_SINGLE "single" @@ -166,8 +168,8 @@ check_int(struct client *client, int *value_r, return false; } -#if LONG_MAX > INT_MAX - if (value < INT_MIN || value > INT_MAX) { +#if G_MAXLONG > G_MAXINT + if (value < G_MININT || value > G_MAXINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -198,7 +200,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, /* compatibility with older MPD versions: specifying "-1" makes MPD display the whole list */ *value_r1 = 0; - *value_r2 = UINT_MAX; + *value_r2 = G_MAXUINT; return true; } @@ -208,8 +210,8 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, return false; } -#if LONG_MAX > UINT_MAX - if (value > UINT_MAX) { +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -220,7 +222,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, if (*test == ':') { value = strtol(++test, &test2, 10); - if (*test2 != '\0' || test == test2) { + if (*test2 != '\0') { va_list args; va_start(args, fmt); command_error_v(client, ACK_ERROR_ARG, fmt, args); @@ -228,14 +230,17 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2, return false; } + if (test == test2) + value = G_MAXUINT; + if (value < 0) { command_error(client, ACK_ERROR_ARG, "Number is negative: %s", s); return false; } -#if LONG_MAX > UINT_MAX - if (value > UINT_MAX) { +#if G_MAXLONG > G_MAXUINT + if (value > G_MAXUINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -262,7 +267,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s) return false; } - if (value > UINT_MAX) { + if (value > G_MAXUINT) { command_error(client, ACK_ERROR_ARG, "Number too large: %s", s); return false; @@ -385,6 +390,14 @@ handle_urlhandlers(struct client *client, } static enum command_return +handle_decoders(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + decoder_list_print(client); + return COMMAND_RETURN_OK; +} + +static enum command_return handle_tagtypes(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { @@ -400,7 +413,7 @@ handle_play(struct client *client, int argc, char *argv[]) if (argc == 2 && !check_int(client, &song, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = playPlaylist(&g_playlist, song); + result = playlist_play(&g_playlist, song); return print_playlist_result(client, result); } @@ -413,7 +426,7 @@ handle_playid(struct client *client, int argc, char *argv[]) if (argc == 2 && !check_int(client, &id, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = playPlaylistById(&g_playlist, id); + result = playlist_play_id(&g_playlist, id); return print_playlist_result(client, result); } @@ -421,7 +434,7 @@ static enum command_return handle_stop(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - stopPlaylist(&g_playlist); + playlist_stop(&g_playlist); return COMMAND_RETURN_OK; } @@ -441,11 +454,11 @@ handle_pause(struct client *client, bool pause_flag; if (!check_bool(client, &pause_flag, argv[1])) return COMMAND_RETURN_ERROR; - playerSetPause(pause_flag); - return COMMAND_RETURN_OK; - } - playerPause(); + pc_set_pause(pause_flag); + } else + pc_pause(); + return COMMAND_RETURN_OK; } @@ -454,10 +467,14 @@ handle_status(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { const char *state = NULL; + struct player_status player_status; int updateJobId; + char *error; int song; - switch (getPlayerState()) { + pc_get_status(&player_status); + + switch (player_status.state) { case PLAYER_STATE_STOP: state = "stop"; break; @@ -470,7 +487,7 @@ handle_status(struct client *client, } client_printf(client, - COMMAND_STATUS_VOLUME ": %i\n" + "volume: %i\n" COMMAND_STATUS_REPEAT ": %i\n" COMMAND_STATUS_RANDOM ": %i\n" COMMAND_STATUS_SINGLE ": %i\n" @@ -480,32 +497,37 @@ handle_status(struct client *client, COMMAND_STATUS_CROSSFADE ": %i\n" COMMAND_STATUS_STATE ": %s\n", volume_level_get(), - getPlaylistRepeatStatus(&g_playlist), - getPlaylistRandomStatus(&g_playlist), - getPlaylistSingleStatus(&g_playlist), - getPlaylistConsumeStatus(&g_playlist), - getPlaylistVersion(&g_playlist), - getPlaylistLength(&g_playlist), - (int)(getPlayerCrossFade() + 0.5), + playlist_get_repeat(&g_playlist), + playlist_get_random(&g_playlist), + playlist_get_single(&g_playlist), + playlist_get_consume(&g_playlist), + playlist_get_version(&g_playlist), + playlist_get_length(&g_playlist), + (int)(pc_get_cross_fade() + 0.5), state); - song = getPlaylistCurrentSong(&g_playlist); + song = playlist_get_current_song(&g_playlist); if (song >= 0) { client_printf(client, COMMAND_STATUS_SONG ": %i\n" COMMAND_STATUS_SONGID ": %u\n", - song, getPlaylistSongId(&g_playlist, song)); + song, playlist_get_song_id(&g_playlist, song)); } - if (getPlayerState() != PLAYER_STATE_STOP) { - const struct audio_format *af = player_get_audio_format(); + if (player_status.state != PLAYER_STATE_STOP) { + struct audio_format_string af_string; + client_printf(client, COMMAND_STATUS_TIME ": %i:%i\n" - COMMAND_STATUS_BITRATE ": %li\n" - COMMAND_STATUS_AUDIO ": %u:%u:%u\n", - getPlayerElapsedTime(), getPlayerTotalTime(), - getPlayerBitRate(), - af->sample_rate, af->bits, af->channels); + "elapsed: %1.3f\n" + COMMAND_STATUS_BITRATE ": %u\n" + COMMAND_STATUS_AUDIO ": %s\n", + (int)(player_status.elapsed_time + 0.5), + (int)(player_status.total_time + 0.5), + player_status.elapsed_time, + player_status.bit_rate, + audio_format_to_string(&player_status.audio_format, + &af_string)); } if ((updateJobId = isUpdatingDB())) { @@ -514,18 +536,20 @@ handle_status(struct client *client, updateJobId); } - if (getPlayerError() != PLAYER_ERROR_NOERROR) { + error = pc_get_error_message(); + if (error != NULL) { client_printf(client, COMMAND_STATUS_ERROR ": %s\n", - getPlayerErrorStr()); + error); + g_free(error); } - song = getPlaylistNextSong(&g_playlist); + song = playlist_get_next_song(&g_playlist); if (song >= 0) { client_printf(client, COMMAND_STATUS_NEXTSONG ": %i\n" COMMAND_STATUS_NEXTSONGID ": %u\n", - song, getPlaylistSongId(&g_playlist, song)); + song, playlist_get_song_id(&g_playlist, song)); } return COMMAND_RETURN_OK; @@ -569,7 +593,7 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = addToPlaylist(&g_playlist, uri, NULL); + result = playlist_append_uri(&g_playlist, uri, NULL); return print_playlist_result(client, result); } @@ -605,7 +629,7 @@ handle_addid(struct client *client, int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - result = addToPlaylist(&g_playlist, uri, &added_id); + result = playlist_append_uri(&g_playlist, uri, &added_id); } if (result != PLAYLIST_RESULT_SUCCESS) @@ -615,11 +639,11 @@ handle_addid(struct client *client, int argc, char *argv[]) int to; if (!check_int(client, &to, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = moveSongInPlaylistById(&g_playlist, added_id, to); + result = playlist_move_id(&g_playlist, added_id, to); if (result != PLAYLIST_RESULT_SUCCESS) { enum command_return ret = print_playlist_result(client, result); - deleteFromPlaylistById(&g_playlist, added_id); + playlist_delete_id(&g_playlist, added_id); return ret; } } @@ -631,13 +655,13 @@ handle_addid(struct client *client, int argc, char *argv[]) static enum command_return handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - int song; + unsigned start, end; enum playlist_result result; - if (!check_int(client, &song, argv[1], need_positive)) + if (!check_range(client, &start, &end, argv[1], need_range)) return COMMAND_RETURN_ERROR; - result = deleteFromPlaylist(&g_playlist, song); + result = playlist_delete_range(&g_playlist, start, end); return print_playlist_result(client, result); } @@ -650,7 +674,7 @@ handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &id, argv[1], need_positive)) return COMMAND_RETURN_ERROR; - result = deleteFromPlaylistById(&g_playlist, id); + result = playlist_delete_id(&g_playlist, id); return print_playlist_result(client, result); } @@ -671,7 +695,7 @@ handle_shuffle(G_GNUC_UNUSED struct client *client, argv[1], need_range)) return COMMAND_RETURN_ERROR; - shufflePlaylist(&g_playlist, start, end); + playlist_shuffle(&g_playlist, start, end); return COMMAND_RETURN_OK; } @@ -679,7 +703,7 @@ static enum command_return handle_clear(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - clearPlaylist(&g_playlist); + playlist_clear(&g_playlist); return COMMAND_RETURN_OK; } @@ -698,6 +722,10 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { enum playlist_result result; + result = playlist_open_into_queue(argv[1], &g_playlist); + if (result != PLAYLIST_RESULT_NO_SUCH_LIST) + return result; + result = playlist_load_spl(&g_playlist, argv[1]); return print_playlist_result(client, result); } @@ -808,7 +836,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[ static enum command_return handle_playlistinfo(struct client *client, int argc, char *argv[]) { - unsigned start = 0, end = UINT_MAX; + unsigned start = 0, end = G_MAXUINT; bool ret; if (argc == 2 && !check_range(client, &start, &end, @@ -837,7 +865,7 @@ handle_playlistid(struct client *client, int argc, char *argv[]) return print_playlist_result(client, PLAYLIST_RESULT_NO_SUCH_SONG); } else { - playlist_print_info(client, &g_playlist, 0, UINT_MAX); + playlist_print_info(client, &g_playlist, 0, G_MAXUINT); } return COMMAND_RETURN_OK; @@ -869,6 +897,30 @@ handle_find(struct client *client, int argc, char *argv[]) } static enum command_return +handle_findadd(struct client *client, int argc, char *argv[]) +{ + int ret; + struct locate_item_list *list = + locate_item_list_parse(argv + 1, argc - 1); + if (list == NULL || list->length == 0) { + if (list != NULL) + locate_item_list_free(list); + + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return COMMAND_RETURN_ERROR; + } + + ret = findAddIn(client, NULL, list); + if (ret == -1) + command_error(client, ACK_ERROR_NO_EXIST, + "directory or file not found"); + + locate_item_list_free(list); + + return ret; +} + +static enum command_return handle_search(struct client *client, int argc, char *argv[]) { int ret; @@ -993,14 +1045,35 @@ handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { - char *path = NULL; + const char *path = NULL; + unsigned ret; + + assert(argc <= 2); + if (argc == 2) + path = argv[1]; + + ret = update_enqueue(path, false); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return COMMAND_RETURN_OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return COMMAND_RETURN_ERROR; + } +} + +static enum command_return +handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + const char *path = NULL; unsigned ret; assert(argc <= 2); if (argc == 2) - path = g_strdup(argv[1]); + path = argv[1]; - ret = directory_update_init(path); + ret = update_enqueue(path, true); if (ret > 0) { client_printf(client, "updating_db: %i\n", ret); return COMMAND_RETURN_OK; @@ -1020,7 +1093,7 @@ handle_next(G_GNUC_UNUSED struct client *client, int single = g_playlist.queue.single; g_playlist.queue.single = false; - nextSongInPlaylist(&g_playlist); + playlist_next(&g_playlist); g_playlist.queue.single = single; return COMMAND_RETURN_OK; @@ -1030,7 +1103,7 @@ static enum command_return handle_previous(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - previousSongInPlaylist(&g_playlist); + playlist_previous(&g_playlist); return COMMAND_RETURN_OK; } @@ -1052,25 +1125,6 @@ handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) } static enum command_return -handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) -{ - int change; - bool success; - - if (!check_int(client, &change, argv[1], need_integer)) - return COMMAND_RETURN_ERROR; - - success = volume_level_change(change, true); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "problems setting volume"); - return COMMAND_RETURN_ERROR; - } - - return COMMAND_RETURN_OK; -} - -static enum command_return handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { int level; @@ -1079,7 +1133,12 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &level, argv[1], need_integer)) return COMMAND_RETURN_ERROR; - success = volume_level_change(level, 0); + if (level < 0 || level > 100) { + command_error(client, ACK_ERROR_ARG, "Invalid volume value"); + return COMMAND_RETURN_ERROR; + } + + success = volume_level_change(level); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); @@ -1103,7 +1162,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistRepeatStatus(&g_playlist, status); + playlist_set_repeat(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1121,7 +1180,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistSingleStatus(&g_playlist, status); + playlist_set_single(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1139,7 +1198,7 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistConsumeStatus(&g_playlist, status); + playlist_set_consume(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1157,7 +1216,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; } - setPlaylistRandomStatus(&g_playlist, status); + playlist_set_random(&g_playlist, status); return COMMAND_RETURN_OK; } @@ -1172,7 +1231,7 @@ static enum command_return handle_clearerror(G_GNUC_UNUSED struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { - clearPlayerError(); + pc_clear_error(); return COMMAND_RETURN_OK; } @@ -1196,17 +1255,17 @@ handle_list(struct client *client, int argc, char *argv[]) /* for compatibility with < 0.12.0 */ if (argc == 3) { - if (tagType != TAG_ITEM_ALBUM) { + if (tagType != TAG_ALBUM) { command_error(client, ACK_ERROR_ARG, "should be \"%s\" for 3 arguments", - tag_item_names[TAG_ITEM_ALBUM]); + tag_item_names[TAG_ALBUM]); return COMMAND_RETURN_ERROR; } locate_item_list_parse(argv + 1, argc - 1); conditionals = locate_item_list_new(1); - conditionals->items[0].tag = TAG_ITEM_ARTIST; + conditionals->items[0].tag = TAG_ARTIST; conditionals->items[0].needle = g_strdup(argv[2]); } else { conditionals = @@ -1241,7 +1300,7 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &to, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = moveSongRangeInPlaylist(&g_playlist, start, end, to); + result = playlist_move_range(&g_playlist, start, end, to); return print_playlist_result(client, result); } @@ -1255,7 +1314,7 @@ handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &to, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = moveSongInPlaylistById(&g_playlist, id, to); + result = playlist_move_id(&g_playlist, id, to); return print_playlist_result(client, result); } @@ -1269,7 +1328,7 @@ handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &song2, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = swapSongsInPlaylist(&g_playlist, song1, song2); + result = playlist_swap_songs(&g_playlist, song1, song2); return print_playlist_result(client, result); } @@ -1283,7 +1342,7 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_ERROR; if (!check_int(client, &id2, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = swapSongsInPlaylistById(&g_playlist, id1, id2); + result = playlist_swap_songs_id(&g_playlist, id1, id2); return print_playlist_result(client, result); } @@ -1298,7 +1357,7 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = seekSongInPlaylist(&g_playlist, song, seek_time); + result = playlist_seek_song(&g_playlist, song, seek_time); return print_playlist_result(client, result); } @@ -1313,7 +1372,7 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &seek_time, argv[2], check_integer, argv[2])) return COMMAND_RETURN_ERROR; - result = seekSongInPlaylistById(&g_playlist, id, seek_time); + result = playlist_seek_song_id(&g_playlist, id, seek_time); return print_playlist_result(client, result); } @@ -1363,7 +1422,7 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_unsigned(client, &xfade_time, argv[1])) return COMMAND_RETURN_ERROR; - setPlayerCrossFade(xfade_time); + pc_set_cross_fade(xfade_time); return COMMAND_RETURN_OK; } @@ -1477,6 +1536,28 @@ handle_listplaylists(struct client *client, } static enum command_return +handle_replay_gain_mode(struct client *client, + G_GNUC_UNUSED int argc, char *argv[]) +{ + if (!replay_gain_set_mode_string(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "Unrecognized replay gain mode"); + return COMMAND_RETURN_ERROR; + } + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_replay_gain_status(struct client *client, + G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) +{ + client_printf(client, "replay_gain_mode: %s\n", + replay_gain_get_mode_string()); + return COMMAND_RETURN_OK; +} + +static enum command_return handle_idle(struct client *client, G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]) { @@ -1519,13 +1600,14 @@ sticker_song_find_print_cb(struct song *song, const char *value, { struct sticker_song_find_data *data = user_data; - song_print_url(data->client, song); + song_print_uri(data->client, song); sticker_print_value(data->client, data->name, value); } static enum command_return handle_sticker_song(struct client *client, int argc, char *argv[]) { + /* get song song_id key */ if (argc == 5 && strcmp(argv[1], "get") == 0) { struct song *song; char *value; @@ -1548,6 +1630,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) g_free(value); return COMMAND_RETURN_OK; + /* list song song_id */ } else if (argc == 4 && strcmp(argv[1], "list") == 0) { struct song *song; struct sticker *sticker; @@ -1570,6 +1653,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) sticker_free(sticker); return COMMAND_RETURN_OK; + /* set song song_id id key */ } else if (argc == 6 && strcmp(argv[1], "set") == 0) { struct song *song; bool ret; @@ -1589,6 +1673,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) } return COMMAND_RETURN_OK; + /* delete song song_id [key] */ } else if ((argc == 4 || argc == 5) && strcmp(argv[1], "delete") == 0) { struct song *song; @@ -1611,6 +1696,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[]) } return COMMAND_RETURN_OK; + /* find song dir key */ } else if (argc == 5 && strcmp(argv[1], "find") == 0) { /* "sticker find song a/directory name" */ struct directory *directory; @@ -1679,11 +1765,13 @@ static const struct command commands[] = { { "count", PERMISSION_READ, 2, -1, handle_count }, { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, + { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, { "delete", PERMISSION_CONTROL, 1, 1, handle_delete }, { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, { "find", PERMISSION_READ, 2, -1, handle_find }, + { "findadd", PERMISSION_READ, 2, -1, handle_findadd}, { "idle", PERMISSION_READ, 0, -1, handle_idle }, { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, { "list", PERMISSION_READ, 1, -1, handle_list }, @@ -1719,6 +1807,11 @@ static const struct command commands[] = { { "random", PERMISSION_CONTROL, 1, 1, handle_random }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, + { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, + handle_replay_gain_mode }, + { "replay_gain_status", PERMISSION_READ, 0, 0, + handle_replay_gain_status }, + { "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan }, { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, { "save", PERMISSION_CONTROL, 1, 1, handle_save }, { "search", PERMISSION_READ, 2, -1, handle_search }, @@ -1738,7 +1831,6 @@ static const struct command commands[] = { { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, { "update", PERMISSION_ADMIN, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, - { "volume", PERMISSION_CONTROL, 1, 1, handle_volume }, }; static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); @@ -1892,48 +1984,71 @@ command_checked_lookup(struct client *client, unsigned permission, } enum command_return -command_process(struct client *client, char *commandString) +command_process(struct client *client, unsigned num, char *line) { + GError *error = NULL; int argc; char *argv[COMMAND_ARGV_MAX] = { NULL }; const struct command *cmd; enum command_return ret = COMMAND_RETURN_ERROR; - if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX))) - return COMMAND_RETURN_OK; + command_list_num = num; - cmd = command_checked_lookup(client, client_get_permission(client), - argc, argv); - if (cmd) - ret = cmd->handler(client, argc, argv); + /* get the command name (first word on the line) */ - current_command = NULL; + argv[0] = tokenizer_next_word(&line, &error); + if (argv[0] == NULL) { + current_command = ""; + if (*line == 0) + command_error(client, ACK_ERROR_UNKNOWN, + "No command given"); + else { + command_error(client, ACK_ERROR_UNKNOWN, + "%s", error->message); + g_error_free(error); + } + current_command = NULL; - return ret; -} + return COMMAND_RETURN_ERROR; + } -enum command_return -command_process_list(struct client *client, - bool list_ok, GSList *list) -{ - enum command_return ret = COMMAND_RETURN_OK; + argc = 1; - command_list_num = 0; + /* now parse the arguments (quoted or unquoted) */ + + while (argc < (int)G_N_ELEMENTS(argv) && + (argv[argc] = + tokenizer_next_param(&line, &error)) != NULL) + ++argc; + + /* some error checks; we have to set current_command because + command_error() expects it to be set */ + + current_command = argv[0]; - for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { - char *cmd = cur->data; - - g_debug("command_process_list: process command \"%s\"", - cmd); - ret = command_process(client, cmd); - g_debug("command_process_list: command returned %i", ret); - if (ret != COMMAND_RETURN_OK || client_is_expired(client)) - break; - else if (list_ok) - client_puts(client, "list_OK\n"); - command_list_num++; + if (argc >= (int)G_N_ELEMENTS(argv)) { + command_error(client, ACK_ERROR_ARG, "Too many arguments"); + current_command = NULL; + return COMMAND_RETURN_ERROR; } + if (*line != 0) { + command_error(client, ACK_ERROR_ARG, + "%s", error->message); + current_command = NULL; + g_error_free(error); + return COMMAND_RETURN_ERROR; + } + + /* look up and invoke the command handler */ + + cmd = command_checked_lookup(client, client_get_permission(client), + argc, argv); + if (cmd) + ret = cmd->handler(client, argc, argv); + + current_command = NULL; command_list_num = 0; + return ret; } diff --git a/src/command.h b/src/command.h index a7c408ed7..614a414b6 100644 --- a/src/command.h +++ b/src/command.h @@ -39,11 +39,7 @@ void command_init(void); void command_finish(void); enum command_return -command_process_list(struct client *client, - bool list_ok, GSList *list); - -enum command_return -command_process(struct client *client, char *commandString); +command_process(struct client *client, unsigned num, char *line); void command_success(struct client *client); diff --git a/src/compress.c b/src/compress.c deleted file mode 100644 index 3a0b4beb0..000000000 --- a/src/compress.c +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz> - */ - -#include "compress.h" - -#include <glib.h> - -#include <stdint.h> -#include <string.h> - -#ifdef USE_X -#include <X11/Xlib.h> -#include <X11/Xutil.h> - -static Display *display; -static Window window; -static Visual *visual; -static int screen; -static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC; -#endif - -static int *peaks; -static int gainCurrent, gainTarget; - -static struct { - int show_mon; - int anticlip; - int target; - int gainmax; - int gainsmooth; - unsigned buckets; -} prefs; - -#ifdef USE_X -static int mon_init; -#endif - -void CompressCfg(int show_mon, int anticlip, int target, int gainmax, - int gainsmooth, unsigned buckets) -{ - static unsigned lastsize; - - prefs.show_mon = show_mon; - prefs.anticlip = anticlip; - prefs.target = target; - prefs.gainmax = gainmax; - prefs.gainsmooth = gainsmooth; - prefs.buckets = buckets; - - /* Allocate the peak structure */ - peaks = g_realloc(peaks, sizeof(int)*prefs.buckets); - - if (prefs.buckets > lastsize) - memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets - - lastsize)); - lastsize = prefs.buckets; - -#ifdef USE_X - /* Configure the monitor window if needed */ - if (show_mon && !mon_init) - { - display = XOpenDisplay(getenv("DISPLAY")); - - /* We really shouldn't try to init X if there's no X */ - if (!display) - { - fprintf(stderr, - "X not detected; disabling monitor window\n"); - show_mon = prefs.show_mon = 0; - } - } - - if (show_mon && !mon_init) - { - XGCValues gcv; - XColor col; - - gainCurrent = gainTarget = (1 << GAINSHIFT); - - - - screen = DefaultScreen(display); - visual = DefaultVisual(display, screen); - window = XCreateSimpleWindow(display, - RootWindow(display, screen), - 0, 0, prefs.buckets, 128 + 8, 0, - BlackPixel(display, screen), - WhitePixel(display, screen)); - XStoreName(display, window, "AudioCompress monitor"); - - gcv.foreground = BlackPixel(display, screen); - blackGC = XCreateGC(display, window, GCForeground, &gcv); - gcv.foreground = WhitePixel(display, screen); - whiteGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 0; - col.green = 0; - col.blue = 65535; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - blueGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 65535; - col.green = 65535; - col.blue = 0; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - yellowGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 32767; - col.green = 32767; - col.blue = 0; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - dkyellowGC = XCreateGC(display, window, GCForeground, &gcv); - col.red = 65535; - col.green = 0; - col.blue = 0; - XAllocColor(display, DefaultColormap(display, screen), &col); - gcv.foreground = col.pixel; - redGC = XCreateGC(display, window, GCForeground, &gcv); - mon_init = 1; - } - - if (mon_init) - { - if (show_mon) - XMapWindow(display, window); - else - XUnmapWindow(display, window); - XResizeWindow(display, window, prefs.buckets, 128 + 8); - XFlush(display); - } -#endif -} - -void CompressFree(void) -{ -#ifdef USE_X - if (mon_init) - { - XFreeGC(display, blackGC); - XFreeGC(display, whiteGC); - XFreeGC(display, blueGC); - XFreeGC(display, yellowGC); - XFreeGC(display, dkyellowGC); - XFreeGC(display, redGC); - XDestroyWindow(display, window); - XCloseDisplay(display); - } -#endif - - g_free(peaks); -} - -void CompressDo(void *data, unsigned int length) -{ - int16_t *audio = (int16_t *)data, *ap; - int peak; - unsigned int i, pos; - int gr, gf, gn; - static int pn = -1; -#ifdef STATS - static int clip; -#endif - static int clipped; - - if (!peaks) - return; - - if (pn == -1) - { - for (i = 0; i < prefs.buckets; i++) - peaks[i] = 0; - } - pn = (pn + 1)%prefs.buckets; - -#ifdef DEBUG - fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data, - length); -#endif - - /* Determine peak's value and position */ - peak = 1; - pos = 0; - -#ifdef DEBUG - fprintf(stderr, "finding peak(b=%d)\n", pn); -#endif - - ap = audio; - for (i = 0; i < length/2; i++) - { - int val = *ap; - if (val > peak) - { - peak = val; - pos = i; - } else if (-val > peak) - { - peak = -val; - pos = i; - } - ap++; - } - peaks[pn] = peak; - - /* Only draw if needed, of course */ -#ifdef USE_X - if (prefs.show_mon) - { - /* current amplitude */ - XDrawLine(display, window, whiteGC, - pn, 0, - pn, - 127 - - (peaks[pn]*gainCurrent >> (GAINSHIFT + 8))); - - /* amplification */ - XDrawLine(display, window, yellowGC, - pn, - 127 - (peaks[pn]*gainCurrent - >> (GAINSHIFT + 8)), - pn, 127); - - /* peak */ - XDrawLine(display, window, blackGC, - pn, 127 - (peaks[pn] >> 8), pn, 127); - - /* clip indicator */ - if (clipped) - XDrawLine(display, window, redGC, - (pn + prefs.buckets - 1)%prefs.buckets, - 126 - clipped/(length*512), - (pn + prefs.buckets - 1)%prefs.buckets, - 127); - clipped = 0; - - /* target line */ - /* XDrawPoint(display, window, redGC, */ - /* pn, 127 - TARGET/256); */ - /* amplification edge */ - XDrawLine(display, window, dkyellowGC, - pn, - 127 - (peaks[pn]*gainCurrent - >> (GAINSHIFT + 8)), - pn - 1, - 127 - - (peaks[(pn + prefs.buckets - - 1)%prefs.buckets]*gainCurrent - >> (GAINSHIFT + 8))); - } -#endif - - for (i = 0; i < prefs.buckets; i++) - { - if (peaks[i] > peak) - { - peak = peaks[i]; - pos = 0; - } - } - - /* Determine target gain */ - gn = (1 << GAINSHIFT)*prefs.target/peak; - - if (gn <(1 << GAINSHIFT)) - gn = 1 << GAINSHIFT; - - gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn) - >> prefs.gainsmooth; - - /* Give it an extra insignifigant nudge to counteract possible - ** rounding error - */ - - if (gn < gainTarget) - gainTarget--; - else if (gn > gainTarget) - gainTarget++; - - if (gainTarget > prefs.gainmax << GAINSHIFT) - gainTarget = prefs.gainmax << GAINSHIFT; - - -#ifdef USE_X - if (prefs.show_mon) - { - int x; - - /* peak*gain */ - XDrawPoint(display, window, redGC, - pn, - 127 - (peak*gainCurrent - >> (GAINSHIFT + 8))); - - /* gain indicator */ - XFillRectangle(display, window, whiteGC, 0, 128, - prefs.buckets, 8); - x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets - / ((prefs.gainmax - 1) << GAINSHIFT); - XDrawLine(display, window, redGC, x, - 128, x, 128 + 8); - - x = (gn - (1 << GAINSHIFT))*prefs.buckets - / ((prefs.gainmax - 1) << GAINSHIFT); - - XDrawLine(display, window, blackGC, - x, 132 - 1, - x, 132 + 1); - - /* blue peak line */ - XDrawLine(display, window, blueGC, - 0, 127 - (peak >> 8), prefs.buckets, - 127 - (peak >> 8)); - XFlush(display); - XDrawLine(display, window, whiteGC, - 0, 127 - (peak >> 8), prefs.buckets, - 127 - (peak >> 8)); - } -#endif - - /* See if a peak is going to clip */ - gn = (1 << GAINSHIFT)*32768/peak; - - if (gn < gainTarget) - { - gainTarget = gn; - - if (prefs.anticlip) - pos = 0; - - } else - { - /* We're ramping up, so draw it out over the whole frame */ - pos = length; - } - - /* Determine gain rate necessary to make target */ - if (!pos) - pos = 1; - - gr = ((gainTarget - gainCurrent) << 16)/(int)pos; - - /* Do the shiznit */ - gf = gainCurrent << 16; - -#ifdef STATS - fprintf(stderr, "\rgain = %2.2f%+.2e ", - gainCurrent*1.0/(1 << GAINSHIFT), - (gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT)); -#endif - - ap = audio; - for (i = 0; i < length/2; i++) - { - int sample; - - /* Interpolate the gain */ - gainCurrent = gf >> 16; - if (i < pos) - gf += gr; - else if (i == pos) - gf = gainTarget << 16; - - /* Amplify */ - sample = (*ap)*gainCurrent >> GAINSHIFT; - if (sample < -32768) - { -#ifdef STATS - clip++; -#endif - clipped += -32768 - sample; - sample = -32768; - } else if (sample > 32767) - { -#ifdef STATS - clip++; -#endif - clipped += sample - 32767; - sample = 32767; - } - *ap++ = sample; - } -#ifdef STATS - fprintf(stderr, "clip %d b%-3d ", clip, pn); -#endif - -#ifdef DEBUG - fprintf(stderr, "\ndone\n"); -#endif -} - diff --git a/src/compress.h b/src/compress.h deleted file mode 100644 index 3e3afb565..000000000 --- a/src/compress.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -/* - * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz> - */ - -#ifndef MPD_COMPRESS_H -#define MPD_COMPRESS_H - -/* These are copied from the AudioCompress config.h, mainly because CompressDo - * needs GAINSHIFT defined. The rest are here so they can be used as defaults - * to pass to CompressCfg(). -- jat */ -#define ANTICLIP 0 /* Strict clipping protection */ -#define TARGET 25000 /* Target level */ -#define GAINMAX 32 /* The maximum amount to amplify by */ -#define GAINSHIFT 10 /* How fine-grained the gain is */ -#define GAINSMOOTH 8 /* How much inertia ramping has*/ -#define BUCKETS 400 /* How long of a history to store */ - -void CompressCfg(int monitor, - int anticlip, - int target, - int maxgain, - int smooth, - unsigned buckets); - -void CompressDo(void *data, unsigned int numSamples); - -void CompressFree(void); - -#endif diff --git a/src/conf.c b/src/conf.c index cce7dbf27..574ad9ddb 100644 --- a/src/conf.c +++ b/src/conf.c @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "conf.h" #include "utils.h" -#include "buffer2array.h" +#include "tokenizer.h" #include "path.h" +#include "glib_compat.h" #include <glib.h> @@ -36,37 +38,82 @@ #define MAX_STRING_SIZE MPD_PATH_MAX+80 #define CONF_COMMENT '#' -#define CONF_BLOCK_BEGIN "{" -#define CONF_BLOCK_END "}" - -#define CONF_REPEATABLE_MASK 0x01 -#define CONF_BLOCK_MASK 0x02 -#define CONF_LINE_TOKEN_MAX 3 struct config_entry { - const char *name; - unsigned char mask; + const char *const name; + const bool repeatable; + const bool block; GSList *params; }; -static GSList *config_entries; +static struct config_entry config_entries[] = { + { .name = CONF_MUSIC_DIR, false, false }, + { .name = CONF_PLAYLIST_DIR, false, false }, + { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false }, + { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false }, + { .name = CONF_DB_FILE, false, false }, + { .name = CONF_STICKER_FILE, false, false }, + { .name = CONF_LOG_FILE, false, false }, + { .name = CONF_PID_FILE, false, false }, + { .name = CONF_STATE_FILE, false, false }, + { .name = CONF_USER, false, false }, + { .name = CONF_GROUP, false, false }, + { .name = CONF_BIND_TO_ADDRESS, true, false }, + { .name = CONF_PORT, false, false }, + { .name = CONF_LOG_LEVEL, false, false }, + { .name = CONF_ZEROCONF_NAME, false, false }, + { .name = CONF_ZEROCONF_ENABLED, false, false }, + { .name = CONF_PASSWORD, true, false }, + { .name = CONF_DEFAULT_PERMS, false, false }, + { .name = CONF_AUDIO_OUTPUT, true, true }, + { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false }, + { .name = CONF_MIXER_TYPE, false, false }, + { .name = CONF_REPLAYGAIN, false, false }, + { .name = CONF_REPLAYGAIN_PREAMP, false, false }, + { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false }, + { .name = CONF_VOLUME_NORMALIZATION, false, false }, + { .name = CONF_SAMPLERATE_CONVERTER, false, false }, + { .name = CONF_AUDIO_BUFFER_SIZE, false, false }, + { .name = CONF_BUFFER_BEFORE_PLAY, false, false }, + { .name = CONF_HTTP_PROXY_HOST, false, false }, + { .name = CONF_HTTP_PROXY_PORT, false, false }, + { .name = CONF_HTTP_PROXY_USER, false, false }, + { .name = CONF_HTTP_PROXY_PASSWORD, false, false }, + { .name = CONF_CONN_TIMEOUT, false, false }, + { .name = CONF_MAX_CONN, false, false }, + { .name = CONF_MAX_PLAYLIST_LENGTH, false, false }, + { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false }, + { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false }, + { .name = CONF_FS_CHARSET, false, false }, + { .name = CONF_ID3V1_ENCODING, false, false }, + { .name = CONF_METADATA_TO_USE, false, false }, + { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false }, + { .name = CONF_DECODER, true, true }, + { .name = CONF_INPUT, true, true }, + { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false }, + { .name = CONF_PLAYLIST_PLUGIN, true, true }, + { .name = CONF_AUTO_UPDATE, false, false }, + { .name = "filter", true, true }, +}; -static int get_bool(const char *value) +static bool +get_bool(const char *value, bool *value_r) { - const char **x; static const char *t[] = { "yes", "true", "1", NULL }; static const char *f[] = { "no", "false", "0", NULL }; - for (x = t; *x; x++) { - if (!g_ascii_strcasecmp(*x, value)) - return 1; + if (string_array_contains(t, value)) { + *value_r = true; + return true; } - for (x = f; *x; x++) { - if (!g_ascii_strcasecmp(*x, value)) - return 0; + + if (string_array_contains(f, value)) { + *value_r = false; + return true; } - return CONF_BOOL_INVALID; + + return false; } struct config_param * @@ -83,15 +130,14 @@ config_new_param(const char *value, int line) ret->num_block_params = 0; ret->block_params = NULL; + ret->used = false; return ret; } static void -config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data) +config_param_free(struct config_param *param) { - struct config_param *param = data; - g_free(param->value); for (unsigned i = 0; i < param->num_block_params; i++) { @@ -105,42 +151,19 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data) g_free(param); } -static struct config_entry * -newConfigEntry(const char *name, int repeatable, int block) -{ - struct config_entry *ret = g_new(struct config_entry, 1); - - ret->name = name; - ret->mask = 0; - ret->params = NULL; - - if (repeatable) - ret->mask |= CONF_REPEATABLE_MASK; - if (block) - ret->mask |= CONF_BLOCK_MASK; - - return ret; -} - static void -config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data) +config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) { - struct config_entry *entry = data; - - g_slist_foreach(entry->params, config_param_free, NULL); - g_slist_free(entry->params); + struct config_param *param = data; - g_free(entry); + config_param_free(param); } static struct config_entry * config_entry_get(const char *name) { - GSList *list; - - for (list = config_entries; list != NULL; - list = g_slist_next(list)) { - struct config_entry *entry = list->data; + for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { + struct config_entry *entry = &config_entries[i]; if (strcmp(entry->name, name) == 0) return entry; } @@ -148,82 +171,65 @@ config_entry_get(const char *name) return NULL; } -static void registerConfigParam(const char *name, int repeatable, int block) +void config_global_finish(void) { - struct config_entry *entry; + for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { + struct config_entry *entry = &config_entries[i]; - entry = config_entry_get(name); - if (entry != NULL) - g_error("config parameter \"%s\" already registered\n", name); + g_slist_foreach(entry->params, + config_param_free_callback, NULL); + g_slist_free(entry->params); + } +} - entry = newConfigEntry(name, repeatable, block); - config_entries = g_slist_prepend(config_entries, entry); +void config_global_init(void) +{ } -void config_global_finish(void) +static void +config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data) { - g_slist_foreach(config_entries, config_entry_free, NULL); - g_slist_free(config_entries); + struct config_param *param = data; + + if (!param->used) + /* this whole config_param was not queried at all - + the feature might be disabled at compile time? + Silently ignore it here. */ + return; + + for (unsigned i = 0; i < param->num_block_params; i++) { + struct block_param *bp = ¶m->block_params[i]; + + if (!bp->used) + g_warning("option '%s' on line %i was not recognized", + bp->name, bp->line); + } } -void config_global_init(void) +void config_global_check(void) { - config_entries = NULL; - - /* registerConfigParam(name, repeatable, block); */ - registerConfigParam(CONF_MUSIC_DIR, 0, 0); - registerConfigParam(CONF_PLAYLIST_DIR, 0, 0); - registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0); - registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0); - registerConfigParam(CONF_DB_FILE, 0, 0); - registerConfigParam(CONF_STICKER_FILE, false, false); - registerConfigParam(CONF_LOG_FILE, 0, 0); - registerConfigParam(CONF_ERROR_FILE, 0, 0); - registerConfigParam(CONF_PID_FILE, 0, 0); - registerConfigParam(CONF_STATE_FILE, 0, 0); - registerConfigParam(CONF_USER, 0, 0); - registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0); - registerConfigParam(CONF_PORT, 0, 0); - registerConfigParam(CONF_LOG_LEVEL, 0, 0); - registerConfigParam(CONF_ZEROCONF_NAME, 0, 0); - registerConfigParam(CONF_ZEROCONF_ENABLED, 0, 0); - registerConfigParam(CONF_PASSWORD, 1, 0); - registerConfigParam(CONF_DEFAULT_PERMS, 0, 0); - registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1); - registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0); - registerConfigParam(CONF_MIXER_TYPE, 0, 0); - registerConfigParam(CONF_MIXER_DEVICE, 0, 0); - registerConfigParam(CONF_MIXER_CONTROL, 0, 0); - registerConfigParam(CONF_REPLAYGAIN, 0, 0); - registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0); - registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0); - registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0); - registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0); - registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0); - registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0); - registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0); - registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0); - registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0); - registerConfigParam(CONF_CONN_TIMEOUT, 0, 0); - registerConfigParam(CONF_MAX_CONN, 0, 0); - registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0); - registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0); - registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0); - registerConfigParam(CONF_FS_CHARSET, 0, 0); - registerConfigParam(CONF_ID3V1_ENCODING, 0, 0); - registerConfigParam(CONF_METADATA_TO_USE, 0, 0); - registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0); - registerConfigParam(CONF_DECODER, true, true); - registerConfigParam(CONF_INPUT, true, true); - registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0); + for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) { + struct config_entry *entry = &config_entries[i]; + + g_slist_foreach(entry->params, config_param_check, NULL); + } } -void +bool config_add_block_param(struct config_param * param, const char *name, - const char *value, int line) + const char *value, int line, GError **error_r) { struct block_param *bp; + bp = config_get_block_param(param, name); + if (bp != NULL) { + g_set_error(error_r, config_quark(), 0, + "\"%s\" first defined on line %i, and " + "redefined on line %i\n", name, + bp->line, line); + return false; + } + param->num_block_params++; param->block_params = g_realloc(param->block_params, @@ -235,67 +241,97 @@ config_add_block_param(struct config_param * param, const char *name, bp->name = g_strdup(name); bp->value = g_strdup(value); bp->line = line; + bp->used = false; + + return true; } static struct config_param * -config_read_block(FILE *fp, int *count, char *string) +config_read_block(FILE *fp, int *count, char *string, GError **error_r) { struct config_param *ret = config_new_param(NULL, *count); - - int i; - int numberOfArgs; - int argsMinusComment; - - while (fgets(string, MAX_STRING_SIZE, fp)) { - char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + GError *error = NULL; + bool success; + + while (true) { + char *line; + const char *name, *value; + + line = fgets(string, MAX_STRING_SIZE, fp); + if (line == NULL) { + config_param_free(ret); + g_set_error(error_r, config_quark(), 0, + "Expected '}' before end-of-file"); + return NULL; + } (*count)++; + line = g_strchug(line); + if (*line == 0 || *line == CONF_COMMENT) + continue; - numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + if (*line == '}') { + /* end of this block; return from the function + (and from this "while" loop) */ + + line = g_strchug(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + config_param_free(ret); + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after '}'", + *count); + return false; + } - for (i = 0; i < numberOfArgs; i++) { - if (array[i][0] == CONF_COMMENT) - break; + return ret; } - argsMinusComment = i; + /* parse name and value */ - if (0 == argsMinusComment) { - continue; + name = tokenizer_next_word(&line, &error); + if (name == NULL) { + assert(*line != 0); + config_param_free(ret); + g_propagate_prefixed_error(error_r, error, + "line %i: ", *count); + return NULL; } - if (1 == argsMinusComment && - 0 == strcmp(array[0], CONF_BLOCK_END)) { - break; + value = tokenizer_next_string(&line, &error); + if (value == NULL) { + config_param_free(ret); + if (*line == 0) + g_set_error(error_r, config_quark(), 0, + "line %i: Value missing", *count); + else + g_propagate_prefixed_error(error_r, error, + "line %i: ", + *count); + return NULL; } - if (2 != argsMinusComment) { - g_error("improperly formatted config file at line %i:" - " %s\n", *count, string); + if (*line != 0 && *line != CONF_COMMENT) { + config_param_free(ret); + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after value", + *count); + return NULL; } - if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) || - 0 == strcmp(array[1], CONF_BLOCK_BEGIN) || - 0 == strcmp(array[0], CONF_BLOCK_END) || - 0 == strcmp(array[1], CONF_BLOCK_END)) { - g_error("improperly formatted config file at line %i: %s " - "in block beginning at line %i\n", - *count, string, ret->line); + success = config_add_block_param(ret, name, value, *count, + error_r); + if (!success) { + config_param_free(ret); + return false; } - - config_add_block_param(ret, array[0], array[1], *count); } - - return ret; } -void config_read_file(const char *file) +bool +config_read_file(const char *file, GError **error_r) { FILE *fp; char string[MAX_STRING_SIZE + 1]; - int i; - int numberOfArgs; - int argsMinusComment; int count = 0; struct config_entry *entry; struct config_param *param; @@ -303,67 +339,110 @@ void config_read_file(const char *file) g_debug("loading file %s", file); if (!(fp = fopen(file, "r"))) { - g_error("problems opening file %s for reading: %s\n", - file, strerror(errno)); + g_set_error(error_r, config_quark(), errno, + "Failed to open %s: %s", + file, strerror(errno)); + return false; } while (fgets(string, MAX_STRING_SIZE, fp)) { - char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + char *line; + const char *name, *value; + GError *error = NULL; count++; - numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + line = g_strchug(string); + if (*line == 0 || *line == CONF_COMMENT) + continue; + + /* the first token in each line is the name, followed + by either the value or '{' */ - for (i = 0; i < numberOfArgs; i++) { - if (array[i][0] == CONF_COMMENT) - break; + name = tokenizer_next_word(&line, &error); + if (name == NULL) { + assert(*line != 0); + g_propagate_prefixed_error(error_r, error, + "line %i: ", count); + return false; } - argsMinusComment = i; + /* get the definition of that option, and check the + "repeatable" flag */ - if (0 == argsMinusComment) { - continue; + entry = config_entry_get(name); + if (entry == NULL) { + g_set_error(error_r, config_quark(), 0, + "unrecognized parameter in config file at " + "line %i: %s\n", count, name); + return false; } - if (2 != argsMinusComment) { - g_error("improperly formatted config file at line %i:" - " %s\n", count, string); + if (entry->params != NULL && !entry->repeatable) { + param = entry->params->data; + g_set_error(error_r, config_quark(), 0, + "config parameter \"%s\" is first defined " + "on line %i and redefined on line %i\n", + name, param->line, count); + return false; } - entry = config_entry_get(array[0]); - if (entry == NULL) - g_error("unrecognized parameter in config file at " - "line %i: %s\n", count, string); + /* now parse the block or the value */ - if (!(entry->mask & CONF_REPEATABLE_MASK) && - entry->params != NULL) { - param = entry->params->data; - g_error("config parameter \"%s\" is first defined on " - "line %i and redefined on line %i\n", - array[0], param->line, count); - } + if (entry->block) { + /* it's a block, call config_read_block() */ - if (entry->mask & CONF_BLOCK_MASK) { - if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) { - g_error("improperly formatted config file at " - "line %i: %s\n", count, string); + if (*line != '{') { + g_set_error(error_r, config_quark(), 0, + "line %i: '{' expected", count); + return false; } - param = config_read_block(fp, &count, string); - } else - param = config_new_param(array[1], count); + + line = g_strchug(line + 1); + if (*line != 0 && *line != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after '{'", + count); + return false; + } + + param = config_read_block(fp, &count, string, error_r); + if (param == NULL) + return false; + } else { + /* a string value */ + + value = tokenizer_next_string(&line, &error); + if (value == NULL) { + if (*line == 0) + g_set_error(error_r, config_quark(), 0, + "line %i: Value missing", + count); + else { + g_set_error(error_r, config_quark(), 0, + "line %i: %s", count, + error->message); + g_error_free(error); + } + + return false; + } + + if (*line != 0 && *line != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after value", + count); + return false; + } + + param = config_new_param(value, count); + } entry->params = g_slist_append(entry->params, param); } fclose(fp); -} - -void -config_add_param(const char *name, struct config_param *param) -{ - struct config_entry *entry = config_entry_get(name); - assert(entry != NULL); - entry->params = g_slist_append(entry->params, param); + return true; } struct config_param * @@ -391,7 +470,7 @@ config_get_next_param(const char *name, const struct config_param * last) return NULL; param = node->data; - + param->used = true; return param; } @@ -447,43 +526,35 @@ config_get_positive(const char *name, unsigned default_value) struct block_param * config_get_block_param(const struct config_param * param, const char *name) { - struct block_param *ret = NULL; - if (param == NULL) return NULL; for (unsigned i = 0; i < param->num_block_params; i++) { if (0 == strcmp(name, param->block_params[i].name)) { - if (ret) { - g_warning("\"%s\" first defined on line %i, and " - "redefined on line %i\n", name, - ret->line, param->block_params[i].line); - } - ret = param->block_params + i; + struct block_param *bp = ¶m->block_params[i]; + bp->used = true; + return bp; } } - return ret; + return NULL; } bool config_get_bool(const char *name, bool default_value) { const struct config_param *param = config_get_param(name); - int value; + bool success, value; if (param == NULL) return default_value; - value = get_bool(param->value); - if (value == CONF_BOOL_INVALID) + success = get_bool(param->value, &value); + if (!success) g_error("%s is not a boolean value (yes, true, 1) or " "(no, false, 0) on line %i\n", name, param->line); - if (value == CONF_BOOL_UNSET) - return default_value; - - return !!value; + return value; } const char * @@ -524,19 +595,16 @@ config_get_block_bool(const struct config_param *param, const char *name, bool default_value) { struct block_param *bp = config_get_block_param(param, name); - int value; + bool success, value; if (bp == NULL) return default_value; - value = get_bool(bp->value); - if (value == CONF_BOOL_INVALID) + success = get_bool(bp->value, &value); + if (!success) g_error("%s is not a boolean value (yes, true, 1) or " "(no, false, 0) on line %i\n", name, bp->line); - if (value == CONF_BOOL_UNSET) - return default_value; - - return !!value; + return value; } diff --git a/src/conf.h b/src/conf.h index c5e49960e..049c980ec 100644 --- a/src/conf.h +++ b/src/conf.h @@ -30,10 +30,10 @@ #define CONF_DB_FILE "db_file" #define CONF_STICKER_FILE "sticker_file" #define CONF_LOG_FILE "log_file" -#define CONF_ERROR_FILE "error_file" #define CONF_PID_FILE "pid_file" #define CONF_STATE_FILE "state_file" #define CONF_USER "user" +#define CONF_GROUP "group" #define CONF_BIND_TO_ADDRESS "bind_to_address" #define CONF_PORT "port" #define CONF_LOG_LEVEL "log_level" @@ -42,12 +42,12 @@ #define CONF_PASSWORD "password" #define CONF_DEFAULT_PERMS "default_permissions" #define CONF_AUDIO_OUTPUT "audio_output" +#define CONF_AUDIO_FILTER "filter" #define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format" #define CONF_MIXER_TYPE "mixer_type" -#define CONF_MIXER_DEVICE "mixer_device" -#define CONF_MIXER_CONTROL "mixer_control" #define CONF_REPLAYGAIN "replaygain" #define CONF_REPLAYGAIN_PREAMP "replaygain_preamp" +#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp" #define CONF_VOLUME_NORMALIZATION "volume_normalization" #define CONF_SAMPLERATE_CONVERTER "samplerate_converter" #define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size" @@ -68,17 +68,24 @@ #define CONF_DECODER "decoder" #define CONF_INPUT "input" #define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" - -#define CONF_BOOL_UNSET -1 -#define CONF_BOOL_INVALID -2 +#define CONF_PLAYLIST_PLUGIN "playlist_plugin" +#define CONF_AUTO_UPDATE "auto_update" #define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) #define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false +#define MAX_FILTER_CHAIN_LENGTH 255 + struct block_param { char *name; char *value; int line; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + bool used; }; struct config_param { @@ -87,31 +94,57 @@ struct config_param { struct block_param *block_params; unsigned num_block_params; + + /** + * This flag is false when nobody has queried the value of + * this option yet. + */ + bool used; }; +/** + * A GQuark for GError instances, resulting from malformed + * configuration. + */ +static inline GQuark +config_quark(void) +{ + return g_quark_from_static_string("config"); +} + void config_global_init(void); void config_global_finish(void); -void config_read_file(const char *file); - /** - * Adds a new configuration parameter. The name must be registered - * with registerConfigParam(). + * Call this function after all configuration has been evaluated. It + * checks for unused parameters, and logs warnings. */ -void -config_add_param(const char *name, struct config_param *param); +void config_global_check(void); + +bool +config_read_file(const char *file, GError **error_r); /* don't free the returned value set _last_ to NULL to get first entry */ +G_GNUC_PURE struct config_param * config_get_next_param(const char *name, const struct config_param *last); +G_GNUC_PURE static inline struct config_param * config_get_param(const char *name) { return config_get_next_param(name, NULL); } +/* Note on G_GNUC_PURE: Some of the functions declared pure are not + really pure in strict sense. They have side effect such that they + validate parameter's value and signal an error if it's invalid. + However, if the argument was already validated or we don't care + about the argument at all, this may be ignored so in the end, we + should be fine with calling those functions pure. */ + +G_GNUC_PURE const char * config_get_string(const char *name, const char *default_value); @@ -120,17 +153,27 @@ config_get_string(const char *name, const char *default_value); * absolute path. If there is a tilde prefix, it is expanded. Aborts * MPD if the path is not a valid absolute path. */ +/* We lie here really. This function is not pure as it has side + effects -- it parse the value and creates new string freeing + previous one. However, because this works the very same way each + time (ie. from the outside it appears as if function had no side + effects) we should be in the clear declaring it pure. */ +G_GNUC_PURE const char * config_get_path(const char *name); +G_GNUC_PURE unsigned config_get_positive(const char *name, unsigned default_value); +G_GNUC_PURE struct block_param * config_get_block_param(const struct config_param *param, const char *name); +G_GNUC_PURE bool config_get_bool(const char *name, bool default_value); +G_GNUC_PURE const char * config_get_block_string(const struct config_param *param, const char *name, const char *default_value); @@ -142,10 +185,12 @@ config_dup_block_string(const struct config_param *param, const char *name, return g_strdup(config_get_block_string(param, name, default_value)); } +G_GNUC_PURE unsigned config_get_block_unsigned(const struct config_param *param, const char *name, unsigned default_value); +G_GNUC_PURE bool config_get_block_bool(const struct config_param *param, const char *name, bool default_value); @@ -153,8 +198,8 @@ config_get_block_bool(const struct config_param *param, const char *name, struct config_param * config_new_param(const char *value, int line); -void -config_add_block_param(struct config_param *param, const char *name, - const char *value, int line); +bool +config_add_block_param(struct config_param * param, const char *name, + const char *value, int line, GError **error_r); #endif diff --git a/src/crossfade.c b/src/crossfade.c index 01552bf65..11dcba532 100644 --- a/src/crossfade.c +++ b/src/crossfade.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "crossfade.h" #include "pcm_mix.h" #include "chunk.h" @@ -39,9 +40,7 @@ unsigned cross_fade_calc(float duration, float total_time, return 0; assert(duration > 0); - assert(af->bits > 0); - assert(af->channels > 0); - assert(af->sample_rate > 0); + assert(audio_format_valid(af)); chunks = audio_format_time_to_size(af) / CHUNK_SIZE; chunks = (chunks * duration + 0.5); diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c index ce8202a81..46dfb1b50 100644 --- a/src/cue/cue_tag.c +++ b/src/cue/cue_tag.c @@ -1,76 +1,78 @@ +#include "config.h" #include "cue_tag.h" +#include "tag.h" -static struct tag* -cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem) +#include <libcue/libcue.h> +#include <assert.h> + +static struct tag * +cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem) { - char* tmp = NULL; - struct tag* tag = NULL; + struct tag *tag; + char *tmp; - //if (cdtext == NULL) - //return NULL; + assert(cdtext != NULL); tag = tag_new(); tag_begin_add(tag); - { /* TAG_ITEM_ALBUM_ARTIST */ + /* TAG_ALBUM_ARTIST */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp); - /* TAG_ITEM_ALBUM_ARTIST */ } + tag_add_item(tag, TAG_ALBUM_ARTIST, tmp); - { /* TAG_ITEM_ARTIST */ + /* TAG_ARTIST */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); - /* TAG_ITEM_ARTIST */ } + tag_add_item(tag, TAG_ARTIST, tmp); - /* TAG_ITEM_PERFORMER */ + /* TAG_PERFORMER */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_PERFORMER, tmp); + tag_add_item(tag, TAG_PERFORMER, tmp); - /* TAG_ITEM_COMPOSER */ + /* TAG_COMPOSER */ if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMPOSER, tmp); + tag_add_item(tag, TAG_COMPOSER, tmp); - /* TAG_ITEM_ALBUM */ + /* TAG_ALBUM */ if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ALBUM, tmp); + tag_add_item(tag, TAG_ALBUM, tmp); - /* TAG_ITEM_GENRE */ + /* TAG_GENRE */ if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_GENRE, tmp); + tag_add_item(tag, TAG_GENRE, tmp); - /* TAG_ITEM_DATE */ + /* TAG_DATE */ if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_ITEM_DATE, tmp); + tag_add_item(tag, TAG_DATE, tmp); - /* TAG_ITEM_COMMENT */ + /* TAG_COMMENT */ if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMMENT, tmp); + tag_add_item(tag, TAG_COMMENT, tmp); - /* TAG_ITEM_DISC */ + /* TAG_DISC */ if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_DISC, tmp); + tag_add_item(tag, TAG_DISC, tmp); /* stream name, usually empty - * tag_add_item(tag, TAG_ITEM_NAME,); + * tag_add_item(tag, TAG_NAME,); */ /* REM MUSICBRAINZ entry? @@ -82,195 +84,161 @@ cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem) tag_end_add(tag); - if (tag != NULL) - { - if (tag_is_empty(tag)) - { - tag_free(tag); - return NULL; - } - else - return tag; - } - else + if (tag_is_empty(tag)) { + tag_free(tag); return NULL; + } + + return tag; } -static struct tag* -cue_tag_track(struct Cdtext* cdtext, struct Rem* rem) +static struct tag * +cue_tag_track(struct Cdtext *cdtext, struct Rem *rem) { - char* tmp = NULL; - struct tag* tag = NULL; + struct tag *tag; + char *tmp; - //if (cdtext == NULL) - //return NULL; + assert(cdtext != NULL); tag = tag_new(); tag_begin_add(tag); - { /* TAG_ITEM_ARTIST */ + /* TAG_ARTIST */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); + tag_add_item(tag, TAG_ARTIST, tmp); else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, tmp); - /* TAG_ITEM_ARTIST */ } + tag_add_item(tag, TAG_ARTIST, tmp); - /* TAG_ITEM_TITLE */ + /* TAG_TITLE */ if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_TITLE, tmp); + tag_add_item(tag, TAG_TITLE, tmp); - /* TAG_ITEM_GENRE */ + /* TAG_GENRE */ if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_GENRE, tmp); + tag_add_item(tag, TAG_GENRE, tmp); - /* TAG_ITEM_DATE */ + /* TAG_DATE */ if ((tmp = rem_get(REM_DATE, rem)) != NULL) - tag_add_item(tag, TAG_ITEM_DATE, tmp); + tag_add_item(tag, TAG_DATE, tmp); - /* TAG_ITEM_COMPOSER */ + /* TAG_COMPOSER */ if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMPOSER, tmp); + tag_add_item(tag, TAG_COMPOSER, tmp); - /* TAG_ITEM_PERFORMER */ + /* TAG_PERFORMER */ if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_PERFORMER, tmp); + tag_add_item(tag, TAG_PERFORMER, tmp); - /* TAG_ITEM_COMMENT */ + /* TAG_COMMENT */ if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_COMMENT, tmp); + tag_add_item(tag, TAG_COMMENT, tmp); - /* TAG_ITEM_DISC */ + /* TAG_DISC */ if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL) - tag_add_item(tag, TAG_ITEM_DISC, tmp); + tag_add_item(tag, TAG_DISC, tmp); tag_end_add(tag); - if (tag != NULL) - { - if (tag_is_empty(tag)) - { - tag_free(tag); - return NULL; - } - else - return tag; + if (tag_is_empty(tag)) { + tag_free(tag); + return NULL; } + + return tag; +} + +static struct tag * +cue_tag_merge(struct tag *a, struct tag *b) +{ + if (a != NULL && b != NULL) { + struct tag *merge_tag = tag_merge(a, b); + tag_free(a); + tag_free(b); + return merge_tag; + } else if (a != NULL) + return a; + else if (b != NULL) + return b; else return NULL; } -struct tag* -cue_tag_file( FILE* fp, - const unsigned int tnum) +struct tag * +cue_tag(struct Cd *cd, unsigned tnum) { - struct tag* cd_tag = NULL; - struct tag* track_tag = NULL; - struct tag* merge_tag = NULL; - struct Cd* cd = NULL; + struct tag *cd_tag, *track_tag, *tag; + struct Track *track; - if (tnum > 256) - return NULL; + assert(cd != NULL); - if (fp == NULL) + track = cd_get_track(cd, tnum); + if (track == NULL) return NULL; - else - cd = cue_parse_file(fp); - if (cd == NULL) - return NULL; - else - { - /* tag from CDtext info */ - cd_tag = cue_tag_cd( cd_get_cdtext(cd), - cd_get_rem(cd)); + /* tag from CDtext info */ + cd_tag = cue_tag_cd(cd_get_cdtext(cd), cd_get_rem(cd)); - /* tag from TRACKtext info */ - track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)), - track_get_rem( cd_get_track(cd, tnum))); + /* tag from TRACKtext info */ + track_tag = cue_tag_track(track_get_cdtext(track), + track_get_rem(track)); - cd_delete(cd); - } - - if ((cd_tag != NULL) && (track_tag != NULL)) - { - merge_tag = tag_merge(cd_tag, track_tag); - tag_free(cd_tag); - tag_free(track_tag); - return merge_tag; - } - - else if (cd_tag != NULL) - { - return cd_tag; - } + tag = cue_tag_merge(cd_tag, track_tag); + if (tag == NULL) + return NULL; - else if (track_tag != NULL) - { - return track_tag; - } + /* libcue returns the track duration in frames, and there are + 75 frames per second; this formula rounds up */ + tag->time = (track_get_length(track) + 74) / 75; - else - return NULL; + return tag; } -struct tag* -cue_tag_string( char* str, - const unsigned int tnum) +struct tag * +cue_tag_file(FILE *fp, unsigned tnum) { - struct tag* cd_tag = NULL; - struct tag* track_tag = NULL; - struct tag* merge_tag = NULL; - struct Cd* cd = NULL; + struct Cd *cd; + struct tag *tag; - if (tnum > 256) - return NULL; + assert(fp != NULL); - if (str == NULL) + if (tnum > 256) return NULL; - else - cd = cue_parse_string(str); + cd = cue_parse_file(fp); if (cd == NULL) return NULL; - else - { - /* tag from CDtext info */ - cd_tag = cue_tag_cd( cd_get_cdtext(cd), - cd_get_rem(cd)); - /* tag from TRACKtext info */ - track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)), - track_get_rem( cd_get_track(cd, tnum))); + tag = cue_tag(cd, tnum); + cd_delete(cd); - cd_delete(cd); - } + return tag; +} - if ((cd_tag != NULL) && (track_tag != NULL)) - { - merge_tag = tag_merge(cd_tag, track_tag); - tag_free(cd_tag); - tag_free(track_tag); - return merge_tag; - } +struct tag * +cue_tag_string(const char *str, unsigned tnum) +{ + struct Cd *cd; + struct tag *tag; - else if (cd_tag != NULL) - { - return cd_tag; - } + assert(str != NULL); - else if (track_tag != NULL) - { - return track_tag; - } + if (tnum > 256) + return NULL; - else + cd = cue_parse_string(str); + if (cd == NULL) return NULL; + + tag = cue_tag(cd, tnum); + cd_delete(cd); + + return tag; } diff --git a/src/cue/cue_tag.h b/src/cue/cue_tag.h index adc4c466e..1ddaa59c8 100644 --- a/src/cue/cue_tag.h +++ b/src/cue/cue_tag.h @@ -1,20 +1,23 @@ #ifndef MPD_CUE_TAG_H #define MPD_CUE_TAG_H -#include "config.h" +#include "check.h" #ifdef HAVE_CUE /* libcue */ -#include <libcue/libcue.h> -#include "../tag.h" +#include <stdio.h> -struct tag* -cue_tag_file( FILE*, - const unsigned int); +struct tag; +struct Cd; -struct tag* -cue_tag_string( char*, - const unsigned int); +struct tag * +cue_tag(struct Cd *cd, unsigned tnum); + +struct tag * +cue_tag_file(FILE *file, unsigned tnum); + +struct tag * +cue_tag_string(const char *str, unsigned tnum); #endif /* libcue */ #endif diff --git a/src/daemon.c b/src/daemon.c index 33b2953a9..192fa8d37 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "daemon.h" #include <glib.h> @@ -45,20 +46,21 @@ static char *user_name; /** the Unix user id which MPD runs as */ -static uid_t user_uid; +static uid_t user_uid = (uid_t)-1; /** the Unix group id which MPD runs as */ -static gid_t user_gid; +static gid_t user_gid = (pid_t)-1; /** the absolute path of the pidfile */ static char *pidfile; -#endif +/* whether "group" conf. option was given */ +static bool had_group = false; + void daemonize_kill(void) { -#ifndef WIN32 FILE *fp; int pid, ret; @@ -82,41 +84,34 @@ daemonize_kill(void) pid, g_strerror(errno)); exit(EXIT_SUCCESS); -#else - g_error("--kill is not available on WIN32"); -#endif } void daemonize_close_stdin(void) { - int fd = open("/dev/null", O_RDONLY); - - if (fd < 0) - close(STDIN_FILENO); - else if (fd != STDIN_FILENO) { - dup2(fd, STDIN_FILENO); - close(fd); - } + close(STDIN_FILENO); + open("/dev/null", O_RDONLY); } void daemonize_set_user(void) { -#ifndef WIN32 if (user_name == NULL) return; - /* get uid */ - if (setgid(user_gid) == -1) { - g_error("cannot setgid for user \"%s\": %s", - user_name, g_strerror(errno)); + /* set gid */ + if (user_gid != (gid_t)-1 && user_gid != getgid()) { + if (setgid(user_gid) == -1) { + g_error("cannot setgid to %d: %s", + (int)user_gid, g_strerror(errno)); + } } + #ifdef _BSD_SOURCE /* init suplementary groups * (must be done before we change our uid) */ - if (initgroups(user_name, user_gid) == -1) { + if (!had_group && initgroups(user_name, user_gid) == -1) { g_warning("cannot init supplementary groups " "of user \"%s\": %s", user_name, g_strerror(errno)); @@ -124,32 +119,38 @@ daemonize_set_user(void) #endif /* set uid */ - if (setuid(user_uid) == -1) { + if (user_uid != (uid_t)-1 && user_uid != getuid() && + setuid(user_uid) == -1) { g_error("cannot change to uid of user \"%s\": %s", user_name, g_strerror(errno)); } -#endif } -#ifndef G_OS_WIN32 static void daemonize_detach(void) { - pid_t pid; - /* flush all file handles before duplicating the buffers */ fflush(NULL); +#ifdef HAVE_DAEMON + + if (daemon(0, 1)) + g_error("daemon() failed: %s", g_strerror(errno)); + +#elif defined(HAVE_FORK) + /* detach from parent process */ - pid = fork(); - if (pid < 0) + switch (fork()) { + case -1: g_error("fork() failed: %s", g_strerror(errno)); - - if (pid > 0) + case 0: + break; + default: /* exit the parent process */ _exit(EXIT_SUCCESS); + } /* release the current working directory */ @@ -160,14 +161,16 @@ daemonize_detach(void) setsid(); +#else + g_error("no support for daemonizing"); +#endif + g_debug("daemonized!"); } -#endif void daemonize(bool detach) { -#ifndef WIN32 FILE *fp = NULL; if (pidfile != NULL) { @@ -189,47 +192,45 @@ daemonize(bool detach) fprintf(fp, "%lu\n", (unsigned long)getpid()); fclose(fp); } -#else - /* no daemonization on WIN32 */ - (void)detach; -#endif } void -daemonize_init(const char *user, const char *_pidfile) +daemonize_init(const char *user, const char *group, const char *_pidfile) { -#ifndef WIN32 - if (user != NULL && strcmp(user, g_get_user_name()) != 0) { - struct passwd *pwd; - - user_name = g_strdup(user); - - pwd = getpwnam(user_name); - if (pwd == NULL) - g_error("no such user \"%s\"", user_name); + if (user) { + struct passwd *pwd = getpwnam(user); + if (!pwd) + g_error("no such user \"%s\"", user); user_uid = pwd->pw_uid; user_gid = pwd->pw_gid; + user_name = g_strdup(user); + /* this is needed by libs such as arts */ g_setenv("HOME", pwd->pw_dir, true); } + if (group) { + struct group *grp = grp = getgrnam(group); + if (!grp) + g_error("no such group \"%s\"", group); + user_gid = grp->gr_gid; + had_group = true; + } + + pidfile = g_strdup(_pidfile); -#else - (void)user; - (void)_pidfile; -#endif } void daemonize_finish(void) { -#ifndef WIN32 if (pidfile != NULL) unlink(pidfile); g_free(user_name); g_free(pidfile); -#endif } + +#endif diff --git a/src/daemon.h b/src/daemon.h index 5b3f9a7dc..a29945607 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -22,32 +22,67 @@ #include <stdbool.h> +#ifndef WIN32 void -daemonize_init(const char *user, const char *pidfile); +daemonize_init(const char *user, const char *group, const char *pidfile); +#else +static inline void +daemonize_init(const char *user, const char *group, const char *pidfile) +{ (void)user; (void)group; (void)pidfile; } +#endif +#ifndef WIN32 void daemonize_finish(void); +#else +static inline void +daemonize_finish(void) +{ /* nop */ } +#endif /** * Kill the MPD which is currently running, pid determined from the * pid file. */ +#ifndef WIN32 void daemonize_kill(void); +#else +static inline void +daemonize_kill(void) +{ g_error("--kill is not available on WIN32"); } +#endif /** * Close stdin (fd 0) and re-open it as /dev/null. */ +#ifndef WIN32 void daemonize_close_stdin(void); +#else +static inline void +daemonize_close_stdin(void) {} +#endif /** * Change to the configured Unix user. */ +#ifndef WIN32 void daemonize_set_user(void); +#else +static inline void +daemonize_set_user(void) +{ /* nop */ } +#endif +#ifndef WIN32 void daemonize(bool detach); +#else +static inline void +daemonize(bool detach) +{ (void)detach; } +#endif #endif diff --git a/src/database.c b/src/database.c index 5a06dda98..b1c0df764 100644 --- a/src/database.c +++ b/src/database.c @@ -17,13 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "database.h" #include "directory.h" #include "directory_save.h" #include "song.h" #include "path.h" #include "stats.h" -#include "config.h" +#include "text_file.h" +#include "tag.h" +#include "tag_internal.h" #include <glib.h> @@ -40,8 +43,14 @@ #define DIRECTORY_INFO_BEGIN "info_begin" #define DIRECTORY_INFO_END "info_end" +#define DB_FORMAT_PREFIX "format: " #define DIRECTORY_MPD_VERSION "mpd_version: " #define DIRECTORY_FS_CHARSET "fs_charset: " +#define DB_TAG_PREFIX "tag: " + +enum { + DB_FORMAT = 1, +}; static char *database_path; @@ -232,11 +241,19 @@ db_save(void) /* block signals when writing the db so we don't get a corrupted db */ fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT); fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset()); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (!ignore_tag_items[i]) + fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]); + fprintf(fp, "%s\n", DIRECTORY_INFO_END); - if (directory_save(fp, music_root) < 0) { + directory_save(fp, music_root); + + if (ferror(fp)) { g_warning("Failed to write to database file: %s", strerror(errno)); while (fclose(fp) && errno == EINTR); @@ -256,64 +273,64 @@ db_load(GError **error) { FILE *fp = NULL; struct stat st; - char buffer[100]; + GString *buffer = g_string_sized_new(1024); + char *line; + int format = 0; bool found_charset = false, found_version = false; bool success; + bool tags[TAG_NUM_OF_ITEM_TYPES]; assert(database_path != NULL); assert(music_root != NULL); - if (!music_root) - music_root = directory_new("", NULL); while (!(fp = fopen(database_path, "r")) && errno == EINTR) ; if (fp == NULL) { g_set_error(error, db_quark(), errno, "Failed to open database file \"%s\": %s", database_path, strerror(errno)); + g_string_free(buffer, true); return false; } /* get initial info */ - if (!fgets(buffer, sizeof(buffer), fp)) { - fclose(fp); - g_set_error(error, db_quark(), 0, "Unexpected end of file"); - return false; - } - - g_strchomp(buffer); - - if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) { + line = read_text_line(fp, buffer); + if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) { fclose(fp); g_set_error(error, db_quark(), 0, "Database corrupted"); + g_string_free(buffer, true); return false; } - while (fgets(buffer, sizeof(buffer), fp) && - !g_str_has_prefix(buffer, DIRECTORY_INFO_END)) { - g_strchomp(buffer); + memset(tags, false, sizeof(tags)); - if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) { + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, DIRECTORY_INFO_END) != 0) { + if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) { + format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1); + } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) { if (found_version) { fclose(fp); g_set_error(error, db_quark(), 0, "Duplicate version line"); + g_string_free(buffer, true); return false; } found_version = true; - } else if (g_str_has_prefix(buffer, DIRECTORY_FS_CHARSET)) { + } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) { const char *new_charset, *old_charset; if (found_charset) { fclose(fp); g_set_error(error, db_quark(), 0, "Duplicate charset line"); + g_string_free(buffer, true); return false; } found_charset = true; - new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]); + new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1; old_charset = path_get_fs_charset(); if (old_charset != NULL && strcmp(new_charset, old_charset)) { @@ -323,19 +340,50 @@ db_load(GError **error) "\"%s\" instead of \"%s\"; " "discarding database file", new_charset, old_charset); + g_string_free(buffer, true); return false; } + } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) { + const char *name = line + sizeof(DB_TAG_PREFIX) - 1; + enum tag_type tag = tag_name_parse(name); + if (tag == TAG_NUM_OF_ITEM_TYPES) { + g_set_error(error, db_quark(), 0, + "Unrecognized tag '%s', " + "discarding database file", + name); + return false; + } + + tags[tag] = true; } else { fclose(fp); g_set_error(error, db_quark(), 0, - "Malformed line: %s", buffer); + "Malformed line: %s", line); + g_string_free(buffer, true); + return false; + } + } + + if (format != DB_FORMAT) { + g_set_error(error, db_quark(), 0, + "Database format mismatch, " + "discarding database file"); + return false; + } + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + if (!ignore_tag_items[i] && !tags[i]) { + g_set_error(error, db_quark(), 0, + "Tag list mismatch, " + "discarding database file"); return false; } } g_debug("reading DB"); - success = directory_load(fp, music_root, error); + success = directory_load(fp, music_root, buffer, error); + g_string_free(buffer, true); while (fclose(fp) && errno == EINTR) ; if (!success) diff --git a/src/dbUtils.c b/src/dbUtils.c index fa2cfa27a..7a0f532b8 100644 --- a/src/dbUtils.c +++ b/src/dbUtils.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "dbUtils.h" #include "locate.h" #include "directory.h" @@ -59,7 +60,7 @@ static int printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data) { struct client *client = data; - song_print_url(client, song); + song_print_uri(client, song); return 0; } @@ -134,8 +135,7 @@ searchStatsInDirectory(struct song *song, void *data) if (locate_song_match(song, stats->criteria)) { stats->numberOfSongs++; - if (song->tag->time > 0) - stats->playTime += song->tag->time; + stats->playTime += song_get_duration(song); } return 0; @@ -168,7 +168,7 @@ int printAllIn(struct client *client, const char *name) static int directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data) { - return addSongToPlaylist(&g_playlist, song, NULL); + return playlist_append_song(&g_playlist, song, NULL); } struct add_data { @@ -200,6 +200,28 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file) } static int +findAddInDirectory(struct song *song, void *_data) +{ + struct search_data *data = _data; + + if (locate_song_match(song, data->criteria)) + return directoryAddSongToPlaylist(song, data); + + return 0; +} + +int findAddIn(struct client *client, const char *name, + const struct locate_item_list *criteria) +{ + struct search_data data; + + data.client = client; + data.criteria = criteria; + + return db_walk(name, findAddInDirectory, NULL, &data); +} + +static int directoryPrintSongInfo(struct song *song, void *data) { struct client *client = data; @@ -236,7 +258,7 @@ visitTag(struct client *client, struct strset *set, struct tag *tag = song->tag; if (tagType == LOCATE_TAG_FILE_TYPE) { - song_print_url(client, song); + song_print_uri(client, song); return; } diff --git a/src/dbUtils.h b/src/dbUtils.h index 1382c243e..914b6fa84 100644 --- a/src/dbUtils.h +++ b/src/dbUtils.h @@ -40,6 +40,10 @@ findSongsIn(struct client *client, const char *name, const struct locate_item_list *criteria); int +findAddIn(struct client *client, const char *name, + const struct locate_item_list *criteria); + +int searchStatsForSongsIn(struct client *client, const char *name, const struct locate_item_list *criteria); diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c index 7c8fe9875..4dd2b46ea 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/_flac_common.c @@ -21,7 +21,11 @@ * Common data structures and functions used by FLAC and OggFLAC */ +#include "config.h" #include "_flac_common.h" +#include "flac_metadata.h" +#include "flac_pcm.h" +#include "audio_check.h" #include <glib.h> @@ -31,186 +35,96 @@ void flac_data_init(struct flac_data *data, struct decoder * decoder, struct input_stream *input_stream) { - data->time = 0; + pcm_buffer_init(&data->buffer); + + data->have_stream_info = false; + data->first_frame = 0; + data->next_frame = 0; + data->position = 0; - data->bit_rate = 0; data->decoder = decoder; data->input_stream = input_stream; data->replay_gain_info = NULL; data->tag = NULL; } -static void -flac_find_float_comment(const FLAC__StreamMetadata *block, - const char *cmnt, float *fl, bool *found_r) -{ - int offset; - size_t pos; - int len; - unsigned char tmp, *p; - - offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, - cmnt); - if (offset < 0) - return; - - pos = strlen(cmnt) + 1; /* 1 is for '=' */ - len = block->data.vorbis_comment.comments[offset].length - pos; - if (len <= 0) - return; - - p = &block->data.vorbis_comment.comments[offset].entry[pos]; - tmp = p[len]; - p[len] = '\0'; - *fl = (float)atof((char *)p); - p[len] = tmp; - - *found_r = true; -} - -static void -flac_parse_replay_gain(const FLAC__StreamMetadata *block, - struct flac_data *data) +void +flac_data_deinit(struct flac_data *data) { - bool found = false; + pcm_buffer_deinit(&data->buffer); - if (data->replay_gain_info) + if (data->replay_gain_info != NULL) replay_gain_info_free(data->replay_gain_info); - data->replay_gain_info = replay_gain_info_new(); - - flac_find_float_comment(block, "replaygain_album_gain", - &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain, - &found); - flac_find_float_comment(block, "replaygain_album_peak", - &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak, - &found); - flac_find_float_comment(block, "replaygain_track_gain", - &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain, - &found); - flac_find_float_comment(block, "replaygain_track_peak", - &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak, - &found); - - if (!found) { - replay_gain_info_free(data->replay_gain_info); - data->replay_gain_info = NULL; - } + if (data->tag != NULL) + tag_free(data->tag); } -/** - * Checks if the specified name matches the entry's name, and if yes, - * returns the comment value (not null-temrinated). - */ -static const char * -flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, const char *char_tnum, size_t *length_r) +static enum sample_format +flac_sample_format(const FLAC__StreamMetadata_StreamInfo *si) { - size_t name_length = strlen(name); - size_t char_tnum_length = 0; - const char *comment = (const char*)entry->entry; + switch (si->bits_per_sample) { + case 8: + return SAMPLE_FORMAT_S8; - if (entry->length <= name_length || - g_ascii_strncasecmp(comment, name, name_length) != 0) - return NULL; + case 16: + return SAMPLE_FORMAT_S16; - if (char_tnum != NULL) { - char_tnum_length = strlen(char_tnum); - if (entry->length > name_length + char_tnum_length + 2 && - comment[name_length] == '[' && - g_ascii_strncasecmp(comment + name_length + 1, - char_tnum, char_tnum_length) == 0 && - comment[name_length + char_tnum_length + 1] == ']') - name_length = name_length + char_tnum_length + 2; - else if (entry->length > name_length + char_tnum_length && - g_ascii_strncasecmp(comment + name_length, - char_tnum, char_tnum_length) == 0) - name_length = name_length + char_tnum_length; - } + case 24: + return SAMPLE_FORMAT_S24_P32; - if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; - return comment + name_length + 1; - } + case 32: + return SAMPLE_FORMAT_S32; - return NULL; + default: + return SAMPLE_FORMAT_UNDEFINED; + } } -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -flac_copy_comment(struct tag *tag, - const FLAC__StreamMetadata_VorbisComment_Entry *entry, - const char *name, enum tag_type tag_type, - const char *char_tnum) +bool +flac_data_get_audio_format(struct flac_data *data, + struct audio_format *audio_format) { - const char *value; - size_t value_length; + GError *error = NULL; - value = flac_comment_value(entry, name, char_tnum, &value_length); - if (value != NULL) { - tag_add_item_n(tag, tag_type, value, value_length); - return true; + if (!data->have_stream_info) { + g_warning("no STREAMINFO packet found"); + return false; } - return false; -} - -/* tracknumber is used in VCs, MPD uses "track" ..., all the other - * tag names match */ -static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; -static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + data->sample_format = flac_sample_format(&data->stream_info); -static void -flac_parse_comment(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata_VorbisComment_Entry *entry) -{ - assert(tag != NULL); - - if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY, - TAG_ITEM_TRACK, char_tnum) || - flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY, - TAG_ITEM_DISC, char_tnum) || - flac_copy_comment(tag, entry, "album artist", - TAG_ITEM_ALBUM_ARTIST, char_tnum)) - return; + if (!audio_format_init_checked(audio_format, + data->stream_info.sample_rate, + data->sample_format, + data->stream_info.channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return false; + } - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (flac_copy_comment(tag, entry, - tag_item_names[i], i, char_tnum)) - return; -} + data->frame_size = audio_format_frame_size(audio_format); -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata *block) -{ - FLAC__StreamMetadata_VorbisComment_Entry *comments = - block->data.vorbis_comment.comments; - - for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i) - flac_parse_comment(tag, char_tnum, comments++); + return true; } void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data) { - const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info); - switch (block->type) { case FLAC__METADATA_TYPE_STREAMINFO: - data->audio_format.bits = (int8_t)si->bits_per_sample; - data->audio_format.sample_rate = si->sample_rate; - data->audio_format.channels = (int8_t)si->channels; - data->total_time = ((float)si->total_samples) / (si->sample_rate); + data->stream_info = block->data.stream_info; + data->have_stream_info = true; break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_parse_replay_gain(block, data); + if (data->replay_gain_info) + replay_gain_info_free(data->replay_gain_info); + data->replay_gain_info = flac_parse_replay_gain(block); if (data->tag != NULL) - flac_vorbis_comments_to_tag(data->tag, NULL, block); + flac_vorbis_comments_to_tag(data->tag, NULL, + &block->data.vorbis_comment); default: break; @@ -239,133 +153,43 @@ void flac_error_common_cb(const char *plugin, } } -static void flac_convert_stereo16(int16_t *dest, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - for (; position < end; ++position) { - *dest++ = buf[0][position]; - *dest++ = buf[1][position]; - } -} - -static void -flac_convert_16(int16_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -/** - * Note: this function also handles 24 bit files! - */ -static void -flac_convert_32(int32_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} - -static void -flac_convert_8(int8_t *dest, - unsigned int num_channels, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) +FLAC__StreamDecoderWriteStatus +flac_common_write(struct flac_data *data, const FLAC__Frame * frame, + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes) { - unsigned int c_chan; - - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} + enum decoder_command cmd; + size_t buffer_size = frame->header.blocksize * data->frame_size; + void *buffer; + unsigned bit_rate; -static void flac_convert(unsigned char *dest, - unsigned int num_channels, - unsigned int bytes_per_sample, - const FLAC__int32 * const buf[], - unsigned int position, unsigned int end) -{ - switch (bytes_per_sample) { - case 2: - if (num_channels == 2) - flac_convert_stereo16((int16_t*)dest, buf, - position, end); - else - flac_convert_16((int16_t*)dest, num_channels, buf, - position, end); - break; + buffer = pcm_buffer_get(&data->buffer, buffer_size); - case 4: - flac_convert_32((int32_t*)dest, num_channels, buf, - position, end); - break; + flac_convert(buffer, frame->header.channels, + data->sample_format, buf, + 0, frame->header.blocksize); - case 1: - flac_convert_8((int8_t*)dest, num_channels, buf, - position, end); + if (nbytes > 0) + bit_rate = nbytes * 8 * frame->header.sample_rate / + (1000 * frame->header.blocksize); + else + bit_rate = 0; + + cmd = decoder_data(data->decoder, data->input_stream, + buffer, buffer_size, + bit_rate, + data->replay_gain_info); + data->next_frame += frame->header.blocksize; + switch (cmd) { + case DECODE_COMMAND_NONE: + case DECODE_COMMAND_START: break; - } -} - -FLAC__StreamDecoderWriteStatus -flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[]) -{ - unsigned int c_samp; - const unsigned int num_channels = frame->header.channels; - const unsigned int bytes_per_sample = - audio_format_sample_size(&data->audio_format); - const unsigned int bytes_per_channel = - bytes_per_sample * frame->header.channels; - const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel; - unsigned int num_samples; - enum decoder_command cmd; - if (bytes_per_sample != 1 && bytes_per_sample != 2 && - bytes_per_sample != 4) - /* exotic unsupported bit rate */ + case DECODE_COMMAND_STOP: return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - for (c_samp = 0; c_samp < frame->header.blocksize; - c_samp += num_samples) { - num_samples = frame->header.blocksize - c_samp; - if (num_samples > max_samples) - num_samples = max_samples; - - flac_convert(data->chunk, - num_channels, bytes_per_sample, buf, - c_samp, c_samp + num_samples); - - cmd = decoder_data(data->decoder, data->input_stream, - data->chunk, - num_samples * bytes_per_channel, - data->time, data->bit_rate, - data->replay_gain_info); - switch (cmd) { - case DECODE_COMMAND_NONE: - case DECODE_COMMAND_START: - break; - - case DECODE_COMMAND_STOP: - return - FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - - case DECODE_COMMAND_SEEK: - return - FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; - } + case DECODE_COMMAND_SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h index 68de7e969..2f328afa6 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/_flac_common.h @@ -24,132 +24,51 @@ #ifndef MPD_FLAC_COMMON_H #define MPD_FLAC_COMMON_H -#include "../decoder_api.h" -#include "config.h" +#include "decoder_api.h" +#include "pcm_buffer.h" #include <glib.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "flac" - -#include <FLAC/export.h> -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -# include <FLAC/seekable_stream_decoder.h> -# define flac_decoder FLAC__SeekableStreamDecoder -# define flac_new() FLAC__seekable_stream_decoder_new() - -# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0) - -# define flac_get_decode_position(x,y) \ - FLAC__seekable_stream_decoder_get_decode_position(x,y) -# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x) -# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x) -# define flac_process_metadata(x) \ - FLAC__seekable_stream_decoder_process_until_end_of_metadata(x) -# define flac_seek_absolute(x,y) \ - FLAC__seekable_stream_decoder_seek_absolute(x,y) -# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x) -# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x) - -# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM - -typedef unsigned flac_read_status_size_t; -# define flac_read_status FLAC__SeekableStreamDecoderReadStatus -# define flac_read_status_continue \ - FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK -# define flac_read_status_abort \ - FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR - -# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus -# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK -# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR - -# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus -# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK -# define flac_tell_status_error \ - FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR -# define flac_tell_status_unsupported \ - FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR - -# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus -# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK -# define flac_length_status_error \ - FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR -# define flac_length_status_unsupported \ - FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR - -# ifdef HAVE_OGGFLAC -# include <OggFLAC/seekable_stream_decoder.h> -# endif -#else /* FLAC_API_VERSION_CURRENT > 7 */ - -/* - * OggFLAC support is handled by our flac_plugin already, and - * thus we *can* always have it if libFLAC was compiled with it - */ -# include "_ogg_common.h" - -# include <FLAC/stream_decoder.h> -# define flac_decoder FLAC__StreamDecoder -# define flac_new() FLAC__stream_decoder_new() - -# define flac_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) -# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \ - (FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \ - == FLAC__STREAM_DECODER_INIT_STATUS_OK) - -# define flac_get_decode_position(x,y) \ - FLAC__stream_decoder_get_decode_position(x,y) -# define flac_get_state(x) FLAC__stream_decoder_get_state(x) -# define flac_process_single(x) FLAC__stream_decoder_process_single(x) -# define flac_process_metadata(x) \ - FLAC__stream_decoder_process_until_end_of_metadata(x) -# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y) -# define flac_finish(x) FLAC__stream_decoder_finish(x) -# define flac_delete(x) FLAC__stream_decoder_delete(x) - -# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM - -typedef size_t flac_read_status_size_t; -# define flac_read_status FLAC__StreamDecoderReadStatus -# define flac_read_status_continue \ - FLAC__STREAM_DECODER_READ_STATUS_CONTINUE -# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM -# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT - -# define flac_seek_status FLAC__StreamDecoderSeekStatus -# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK -# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR -# define flac_seek_status_unsupported \ - FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED - -# define flac_tell_status FLAC__StreamDecoderTellStatus -# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK -# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR -# define flac_tell_status_unsupported \ - FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED - -# define flac_length_status FLAC__StreamDecoderLengthStatus -# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK -# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR -# define flac_length_status_unsupported \ - FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - +#include <FLAC/stream_decoder.h> #include <FLAC/metadata.h> -#define FLAC_CHUNK_SIZE 4080 +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "flac" struct flac_data { - unsigned char chunk[FLAC_CHUNK_SIZE]; - float time; - unsigned int bit_rate; - struct audio_format audio_format; - float total_time; + struct pcm_buffer buffer; + + enum sample_format sample_format; + + /** + * The size of one frame in the output buffer. + */ + unsigned frame_size; + + /** + * Is the #stream_info member valid? + */ + bool have_stream_info; + + /** + * A copy of the stream info object passed to the metadata + * callback. Once we drop support for libFLAC 1.1.2, we can + * remove this attribute, and use + * FLAC__stream_decoder_get_total_samples() etc. + */ + FLAC__StreamMetadata_StreamInfo stream_info; + + /** + * The number of the first frame in this song. This is only + * non-zero if playing sub songs from a CUE sheet. + */ + FLAC__uint64 first_frame; + + /** + * The number of the next frame which is going to be decoded. + */ + FLAC__uint64 next_frame; + FLAC__uint64 position; struct decoder *decoder; struct input_stream *input_stream; @@ -162,6 +81,20 @@ void flac_data_init(struct flac_data *data, struct decoder * decoder, struct input_stream *input_stream); +void +flac_data_deinit(struct flac_data *data); + +/** + * Obtains the audio format from the stream_info attribute, and copies + * it to the specified #audio_format object. This also updates the + * frame_size attribute. + * + * @return true on success, false the audio format is not supported + */ +bool +flac_data_get_audio_format(struct flac_data *data, + struct audio_format *audio_format); + void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data); @@ -169,13 +102,10 @@ void flac_error_common_cb(const char *plugin, FLAC__StreamDecoderErrorStatus status, struct flac_data *data); -void -flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, - const FLAC__StreamMetadata *block); - FLAC__StreamDecoderWriteStatus flac_common_write(struct flac_data *data, const FLAC__Frame * frame, - const FLAC__int32 *const buf[]); + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes); #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c index 6c6553422..d838e0ff4 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/_ogg_common.c @@ -21,8 +21,8 @@ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) */ +#include "config.h" #include "_ogg_common.h" -#include "../utils.h" ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream) { diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h index e650c366d..eca5d40e0 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/_ogg_common.h @@ -24,7 +24,7 @@ #ifndef MPD_OGG_COMMON_H #define MPD_OGG_COMMON_H -#include "../decoder_api.h" +#include "decoder_api.h" typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_plugin.c index f66d90dc1..18cfdda5d 100644 --- a/src/decoder/audiofile_plugin.c +++ b/src/decoder/audiofile_plugin.c @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #include <audiofile.h> #include <af_vfs.h> @@ -45,10 +47,20 @@ static int audiofile_get_duration(const char *file) } static ssize_t -audiofile_file_read(AFvirtualfile *vfile, void *data, size_t nbytes) +audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length) { struct input_stream *is = (struct input_stream *) vfile->closure; - return input_stream_read(is, data, nbytes); + GError *error = NULL; + size_t nbytes; + + nbytes = input_stream_read(is, data, length, &error); + if (nbytes == 0 && error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + return -1; + } + + return nbytes; } static long @@ -78,7 +90,7 @@ audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative) { struct input_stream *is = (struct input_stream *) vfile->closure; int whence = (is_relative ? SEEK_CUR : SEEK_SET); - if (input_stream_seek(is, offset, whence)) { + if (input_stream_seek(is, offset, whence, NULL)) { return is->offset; } else { return -1; @@ -99,17 +111,56 @@ setup_virtual_fops(struct input_stream *stream) return vf; } +static enum sample_format +audiofile_bits_to_sample_format(int bits) +{ + switch (bits) { + case 8: + return SAMPLE_FORMAT_S8; + + case 16: + return SAMPLE_FORMAT_S16; + + case 24: + return SAMPLE_FORMAT_S24_P32; + + case 32: + return SAMPLE_FORMAT_S32; + } + + return SAMPLE_FORMAT_UNDEFINED; +} + +static enum sample_format +audiofile_setup_sample_format(AFfilehandle af_fp) +{ + int fs, bits; + + afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) { + g_debug("input file has %d bit samples, converting to 16", + bits); + bits = 16; + } + + afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, + AF_SAMPFMT_TWOSCOMP, bits); + afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + + return audiofile_bits_to_sample_format(bits); +} + static void audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) { + GError *error = NULL; AFvirtualfile *vf; int fs, frame_count; AFfilehandle af_fp; - int bits; struct audio_format audio_format; float total_time; uint16_t bit_rate; - int ret, current = 0; + int ret; char chunk[CHUNK_SIZE]; enum decoder_command cmd; @@ -126,26 +177,13 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) return; } - afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - if (!audio_valid_sample_format(bits)) { - g_debug("input file has %d bit samples, converting to 16", - bits); - bits = 16; - } - - afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, - AF_SAMPFMT_TWOSCOMP, bits); - afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - audio_format.bits = (uint8_t)bits; - audio_format.sample_rate = - (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK); - audio_format.channels = - (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, audio_format.bits, - audio_format.channels); + if (!audio_format_init_checked(&audio_format, + afGetRate(af_fp, AF_DEFAULT_TRACK), + audiofile_setup_sample_format(af_fp), + afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK), + &error)) { + g_warning("%s", error->message); + g_error_free(error); afCloseFile(af_fp); return; } @@ -166,17 +204,14 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) if (ret <= 0) break; - current += ret; cmd = decoder_data(decoder, NULL, chunk, ret * fs, - (float)current / - (float)audio_format.sample_rate, bit_rate, NULL); if (cmd == DECODE_COMMAND_SEEK) { - current = decoder_seek_where(decoder) * + AFframecount frame = decoder_seek_where(decoder) * audio_format.sample_rate; - afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); + afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame); decoder_command_finished(decoder); cmd = DECODE_COMMAND_NONE; diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_plugin.c index 7b2806a4c..2272963b9 100644 --- a/src/decoder/faad_plugin.c +++ b/src/decoder/faad_plugin.c @@ -17,9 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" -#include "decoder_buffer.h" #include "config.h" +#include "decoder_api.h" +#include "decoder_buffer.h" +#include "audio_check.h" #define AAC_MAX_CHANNELS 6 @@ -37,6 +38,15 @@ static const unsigned adts_sample_rates[] = }; /** + * The GLib quark used for errors reported by this plugin. + */ +static inline GQuark +faad_decoder_quark(void) +{ + return g_quark_from_static_string("faad"); +} + +/** * Check whether the buffer head is an AAC frame, and return the frame * length. Returns 0 if it is not a frame. */ @@ -195,7 +205,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) /* obtain the duration from the ADTS header */ float song_length = adts_song_duration(buffer); - input_stream_seek(is, tagsize, SEEK_SET); + input_stream_seek(is, tagsize, SEEK_SET, NULL); data = decoder_buffer_read(buffer, &length); if (data != NULL) @@ -232,7 +242,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is) */ static bool faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, - struct audio_format *audio_format) + struct audio_format *audio_format, GError **error_r) { union { /* deconst hack for libfaad */ @@ -247,32 +257,33 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, /* neaacdec.h declares all arguments as "unsigned long", but internally expects uint32_t pointers. To avoid gcc warnings, use this workaround. */ - unsigned long *sample_rate_r = (unsigned long *)(void *)&sample_rate; + unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate; #else - uint32_t *sample_rate_r = &sample_rate; + uint32_t *sample_rate_p = &sample_rate; #endif u.in = decoder_buffer_read(buffer, &length); - if (u.in == NULL) + if (u.in == NULL) { + g_set_error(error_r, faad_decoder_quark(), 0, + "Empty file"); return false; + } nbytes = faacDecInit(decoder, u.out, #ifdef HAVE_FAAD_BUFLEN_FUNCS length, #endif - sample_rate_r, &channels); - if (nbytes < 0) + sample_rate_p, &channels); + if (nbytes < 0) { + g_set_error(error_r, faad_decoder_quark(), 0, + "Not an AAC stream"); return false; + } decoder_buffer_consume(buffer, nbytes); - *audio_format = (struct audio_format){ - .bits = 16, - .channels = channels, - .sample_rate = sample_rate, - }; - - return true; + return audio_format_init_checked(audio_format, sample_rate, + SAMPLE_FORMAT_S16, channels, error_r); } /** @@ -319,7 +330,7 @@ faad_get_file_time_float(const char *file) faacDecConfigurationPtr config; struct input_stream is; - if (!input_stream_open(&is, file)) + if (!input_stream_open(&is, file, NULL)) return -1; buffer = decoder_buffer_new(NULL, &is, @@ -338,8 +349,8 @@ faad_get_file_time_float(const char *file) decoder_buffer_fill(buffer); - ret = faad_decoder_init(decoder, buffer, &audio_format); - if (ret && audio_format_valid(&audio_format)) + ret = faad_decoder_init(decoder, buffer, &audio_format, NULL); + if (ret) length = 0; faacDecClose(decoder); @@ -371,7 +382,7 @@ faad_get_file_time(const char *file) static void faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) { - float file_time; + GError *error = NULL; float total_time = 0; faacDecHandle decoder; struct audio_format audio_format; @@ -408,15 +419,10 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) /* initialize it */ - ret = faad_decoder_init(decoder, buffer, &audio_format); + ret = faad_decoder_init(decoder, buffer, &audio_format, &error); if (!ret) { - g_warning("Error not a AAC stream.\n"); - faacDecClose(decoder); - return; - } - - if (!audio_format_valid(&audio_format)) { - g_warning("invalid audio format\n"); + g_warning("%s", error->message); + g_error_free(error); faacDecClose(decoder); return; } @@ -427,8 +433,6 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) /* the decoder loop */ - file_time = 0.0; - do { size_t frame_size; const void *decoded; @@ -474,15 +478,12 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) bit_rate = frame_info.bytesconsumed * 8.0 * frame_info.channels * audio_format.sample_rate / frame_info.samples / 1000 + 0.5; - file_time += - (float)(frame_info.samples) / frame_info.channels / - audio_format.sample_rate; } /* send PCM samples to MPD */ cmd = decoder_data(mpd_decoder, is, decoded, - (size_t)frame_info.samples * 2, file_time, + (size_t)frame_info.samples * 2, bit_rate, NULL); } while (cmd != DECODE_COMMAND_STOP); diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c index d2bd642fd..6d856f293 100644 --- a/src/decoder/ffmpeg_plugin.c +++ b/src/decoder/ffmpeg_plugin.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" #include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #include <glib.h> @@ -100,7 +101,7 @@ static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence) if (whence == AVSEEK_SIZE) return stream->input->size; - ret = input_stream_seek(stream->input, pos, whence); + ret = input_stream_seek(stream->input, pos, whence, NULL); if (!ret) return -1; @@ -231,7 +232,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, const AVRational *time_base) { enum decoder_command cmd = DECODE_COMMAND_NONE; - int position; uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; int16_t *aligned_buffer; size_t buffer_size; @@ -263,22 +263,42 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, if (audio_size <= 0) continue; - position = packet->pts != (int64_t)AV_NOPTS_VALUE - ? av_rescale_q(packet->pts, *time_base, - (AVRational){1, 1}) - : 0; + if (packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + av_rescale_q(packet->pts, *time_base, + (AVRational){1, 1})); cmd = decoder_data(decoder, is, aligned_buffer, audio_size, - position, codec_context->bit_rate / 1000, NULL); } return cmd; } +static enum sample_format +ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context) +{ +#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0) + int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt); + + /* XXX implement & test other sample formats */ + + switch (bits) { + case 16: + return SAMPLE_FORMAT_S16; + } + + return SAMPLE_FORMAT_UNDEFINED; +#else + /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ + return SAMPLE_FORMAT_S16; +#endif +} + static bool ffmpeg_decode_internal(struct ffmpeg_context *ctx) { + GError *error = NULL; struct decoder *decoder = ctx->decoder; AVCodecContext *codec_context = ctx->codec_context; AVFormatContext *format_context = ctx->format_context; @@ -289,19 +309,12 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx) total_time = 0; -#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0) - audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt); -#else - /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ - audio_format.bits = (uint8_t) 16; -#endif - audio_format.sample_rate = (unsigned int)codec_context->sample_rate; - audio_format.channels = codec_context->channels; - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, audio_format.bits, - audio_format.channels); + if (!audio_format_init_checked(&audio_format, + codec_context->sample_rate, + ffmpeg_sample_format(codec_context), + codec_context->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); return false; } @@ -357,8 +370,9 @@ static bool ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m, enum tag_type type, const char *name) { - AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0); - if (mt != NULL) + AVMetadataTag *mt = NULL; + + while ((mt = av_metadata_get(m, name, mt, 0)) != NULL) tag_add_item(tag, type, mt->value); return mt != NULL; } @@ -376,35 +390,35 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx) #if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) av_metadata_conv(f, NULL, f->iformat->metadata_conv); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_TITLE, "title"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "author"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM, "album"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_COMMENT, "comment"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_GENRE, "genre"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_TRACK, "track"); + ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "year"); #else if (f->author[0]) - tag_add_item(tag, TAG_ITEM_ARTIST, f->author); + tag_add_item(tag, TAG_ARTIST, f->author); if (f->title[0]) - tag_add_item(tag, TAG_ITEM_TITLE, f->title); + tag_add_item(tag, TAG_TITLE, f->title); if (f->album[0]) - tag_add_item(tag, TAG_ITEM_ALBUM, f->album); + tag_add_item(tag, TAG_ALBUM, f->album); if (f->track > 0) { char buffer[16]; snprintf(buffer, sizeof(buffer), "%d", f->track); - tag_add_item(tag, TAG_ITEM_TRACK, buffer); + tag_add_item(tag, TAG_TRACK, buffer); } if (f->comment[0]) - tag_add_item(tag, TAG_ITEM_COMMENT, f->comment); + tag_add_item(tag, TAG_COMMENT, f->comment); if (f->genre[0]) - tag_add_item(tag, TAG_ITEM_GENRE, f->genre); + tag_add_item(tag, TAG_GENRE, f->genre); if (f->year > 0) { char buffer[16]; snprintf(buffer, sizeof(buffer), "%d", f->year); - tag_add_item(tag, TAG_ITEM_DATE, buffer); + tag_add_item(tag, TAG_DATE, buffer); } #endif @@ -418,7 +432,7 @@ static struct tag *ffmpeg_tag(const char *file) struct ffmpeg_context ctx; bool ret; - if (!input_stream_open(&input, file)) { + if (!input_stream_open(&input, file, NULL)) { g_warning("failed to open %s\n", file); return NULL; } diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h new file mode 100644 index 000000000..61d2c55e8 --- /dev/null +++ b/src/decoder/flac_compat.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Common data structures and functions used by FLAC and OggFLAC + */ + +#ifndef MPD_FLAC_COMPAT_H +#define MPD_FLAC_COMPAT_H + +#include <FLAC/export.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +# include <FLAC/seekable_stream_decoder.h> + +/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been + merged into the StreamDecoder. The following macros try to emulate + the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls + to the old SeekableStreamDecoder API. */ + +#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder +#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new +#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position +#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state +#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single +#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata +#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute +#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish +#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete + +#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM + +typedef unsigned flac_read_status_size_t; + +#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus +#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR + +#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus +#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK +#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR +#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR + +#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus +#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK +#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR +#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR + +#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus +#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK +#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR +#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR + +typedef enum { + FLAC__STREAM_DECODER_INIT_STATUS_OK, + FLAC__STREAM_DECODER_INIT_STATUS_ERROR, +} FLAC__StreamDecoderInitStatus; + +static inline FLAC__StreamDecoderInitStatus +FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder, + FLAC__SeekableStreamDecoderReadCallback read_cb, + FLAC__SeekableStreamDecoderSeekCallback seek_cb, + FLAC__SeekableStreamDecoderTellCallback tell_cb, + FLAC__SeekableStreamDecoderLengthCallback length_cb, + FLAC__SeekableStreamDecoderEofCallback eof_cb, + FLAC__SeekableStreamDecoderWriteCallback write_cb, + FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, + FLAC__SeekableStreamDecoderErrorCallback error_cb, + void *data) +{ + return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) && + FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) && + FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) && + FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) && + FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) && + FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) && + FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) && + FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) && + FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) && + FLAC__seekable_stream_decoder_set_client_data(decoder, data) && + FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK + ? FLAC__STREAM_DECODER_INIT_STATUS_OK + : FLAC__STREAM_DECODER_INIT_STATUS_ERROR; +} + +#else /* FLAC_API_VERSION_CURRENT > 7 */ + +# include <FLAC/stream_decoder.h> + +# define flac_init(a,b,c,d,e,f,g,h,i,j) \ + (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ + == FLAC__STREAM_DECODER_INIT_STATUS_OK) + +typedef size_t flac_read_status_size_t; + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +#endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c new file mode 100644 index 000000000..1ff99f151 --- /dev/null +++ b/src/decoder/flac_metadata.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "flac_metadata.h" +#include "replay_gain.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> + +static bool +flac_find_float_comment(const FLAC__StreamMetadata *block, + const char *cmnt, float *fl) +{ + int offset; + size_t pos; + int len; + unsigned char tmp, *p; + + offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, + cmnt); + if (offset < 0) + return false; + + pos = strlen(cmnt) + 1; /* 1 is for '=' */ + len = block->data.vorbis_comment.comments[offset].length - pos; + if (len <= 0) + return false; + + p = &block->data.vorbis_comment.comments[offset].entry[pos]; + tmp = p[len]; + p[len] = '\0'; + *fl = (float)atof((char *)p); + p[len] = tmp; + + return true; +} + +struct replay_gain_info * +flac_parse_replay_gain(const FLAC__StreamMetadata *block) +{ + struct replay_gain_info *rgi; + bool found = false; + + rgi = replay_gain_info_new(); + + found = flac_find_float_comment(block, "replaygain_album_gain", + &rgi->tuples[REPLAY_GAIN_ALBUM].gain) || + flac_find_float_comment(block, "replaygain_album_peak", + &rgi->tuples[REPLAY_GAIN_ALBUM].peak) || + flac_find_float_comment(block, "replaygain_track_gain", + &rgi->tuples[REPLAY_GAIN_TRACK].gain) || + flac_find_float_comment(block, "replaygain_track_peak", + &rgi->tuples[REPLAY_GAIN_TRACK].peak); + if (!found) { + replay_gain_info_free(rgi); + rgi = NULL; + } + + return rgi; +} + +/** + * Checks if the specified name matches the entry's name, and if yes, + * returns the comment value (not null-temrinated). + */ +static const char * +flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, const char *char_tnum, size_t *length_r) +{ + size_t name_length = strlen(name); + size_t char_tnum_length = 0; + const char *comment = (const char*)entry->entry; + + if (entry->length <= name_length || + g_ascii_strncasecmp(comment, name, name_length) != 0) + return NULL; + + if (char_tnum != NULL) { + char_tnum_length = strlen(char_tnum); + if (entry->length > name_length + char_tnum_length + 2 && + comment[name_length] == '[' && + g_ascii_strncasecmp(comment + name_length + 1, + char_tnum, char_tnum_length) == 0 && + comment[name_length + char_tnum_length + 1] == ']') + name_length = name_length + char_tnum_length + 2; + else if (entry->length > name_length + char_tnum_length && + g_ascii_strncasecmp(comment + name_length, + char_tnum, char_tnum_length) == 0) + name_length = name_length + char_tnum_length; + } + + if (comment[name_length] == '=') { + *length_r = entry->length - name_length - 1; + return comment + name_length + 1; + } + + return NULL; +} + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +flac_copy_comment(struct tag *tag, + const FLAC__StreamMetadata_VorbisComment_Entry *entry, + const char *name, enum tag_type tag_type, + const char *char_tnum) +{ + const char *value; + size_t value_length; + + value = flac_comment_value(entry, name, char_tnum, &value_length); + if (value != NULL) { + tag_add_item_n(tag, tag_type, value, value_length); + return true; + } + + return false; +} + +/* tracknumber is used in VCs, MPD uses "track" ..., all the other + * tag names match */ +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +static void +flac_parse_comment(struct tag *tag, const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment_Entry *entry) +{ + assert(tag != NULL); + + if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY, + TAG_TRACK, char_tnum) || + flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY, + TAG_DISC, char_tnum) || + flac_copy_comment(tag, entry, "album artist", + TAG_ALBUM_ARTIST, char_tnum)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (flac_copy_comment(tag, entry, + tag_item_names[i], i, char_tnum)) + return; +} + +void +flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment *comment) +{ + for (unsigned i = 0; i < comment->num_comments; ++i) + flac_parse_comment(tag, char_tnum, &comment->comments[i]); +} + +void +flac_tag_apply_metadata(struct tag *tag, const char *track, + const FLAC__StreamMetadata *block) +{ + switch (block->type) { + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flac_vorbis_comments_to_tag(tag, track, + &block->data.vorbis_comment); + break; + + case FLAC__METADATA_TYPE_STREAMINFO: + tag->time = flac_duration(&block->data.stream_info); + break; + + default: + break; + } +} diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h new file mode 100644 index 000000000..ef97288d5 --- /dev/null +++ b/src/decoder/flac_metadata.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FLAC_METADATA_H +#define MPD_FLAC_METADATA_H + +#include <FLAC/metadata.h> + +struct tag; + +static inline unsigned +flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info) +{ + return (stream_info->total_samples + stream_info->sample_rate - 1) / + stream_info->sample_rate; +} + +struct replay_gain_info * +flac_parse_replay_gain(const FLAC__StreamMetadata *block); + +void +flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum, + const FLAC__StreamMetadata_VorbisComment *comment); + +void +flac_tag_apply_metadata(struct tag *tag, const char *track, + const FLAC__StreamMetadata *block); + +#endif diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c new file mode 100644 index 000000000..a8bf6f293 --- /dev/null +++ b/src/decoder/flac_pcm.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "flac_pcm.h" + +#include <assert.h> + +static void flac_convert_stereo16(int16_t *dest, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + for (; position < end; ++position) { + *dest++ = buf[0][position]; + *dest++ = buf[1][position]; + } +} + +static void +flac_convert_16(int16_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +/** + * Note: this function also handles 24 bit files! + */ +static void +flac_convert_32(int32_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +static void +flac_convert_8(int8_t *dest, + unsigned int num_channels, + const FLAC__int32 * const buf[], + unsigned int position, unsigned int end) +{ + unsigned int c_chan; + + for (; position < end; ++position) + for (c_chan = 0; c_chan < num_channels; c_chan++) + *dest++ = buf[c_chan][position]; +} + +void +flac_convert(void *dest, + unsigned int num_channels, enum sample_format sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end) +{ + switch (sample_format) { + case SAMPLE_FORMAT_S16: + if (num_channels == 2) + flac_convert_stereo16((int16_t*)dest, buf, + position, end); + else + flac_convert_16((int16_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + flac_convert_32((int32_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_S8: + flac_convert_8((int8_t*)dest, num_channels, buf, + position, end); + break; + + case SAMPLE_FORMAT_UNDEFINED: + /* unreachable */ + assert(false); + } +} diff --git a/src/normalize.c b/src/decoder/flac_pcm.h index 63c0d15cb..4d7a51c4d 100644 --- a/src/normalize.c +++ b/src/decoder/flac_pcm.h @@ -17,33 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "normalize.h" -#include "compress.h" -#include "conf.h" -#include "audio_format.h" - -#define DEFAULT_VOLUME_NORMALIZATION 0 - -int normalizationEnabled; +#ifndef MPD_FLAC_PCM_H +#define MPD_FLAC_PCM_H -void initNormalization(void) -{ - normalizationEnabled = config_get_bool(CONF_VOLUME_NORMALIZATION, - DEFAULT_VOLUME_NORMALIZATION); - - if (normalizationEnabled) - CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS); -} +#include "audio_format.h" -void finishNormalization(void) -{ - if (normalizationEnabled) CompressFree(); -} +#include <FLAC/ordinals.h> -void normalizeData(char *buffer, int bufferSize, - const struct audio_format *format) -{ - if ((format->bits != 16) || (format->channels != 2)) return; +void +flac_convert(void *dest, + unsigned int num_channels, enum sample_format sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end); - CompressDo(buffer, bufferSize); -} +#endif diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c index 1e568f70d..ff1d1af3f 100644 --- a/src/decoder/flac_plugin.c +++ b/src/decoder/flac_plugin.c @@ -17,7 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "_flac_common.h" +#include "flac_compat.h" +#include "flac_metadata.h" + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 +#include "_ogg_common.h" +#endif #include <glib.h> @@ -33,8 +40,8 @@ /* this code was based on flac123, from flac-tools */ -static flac_read_status -flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd, +static FLAC__StreamDecoderReadStatus +flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, FLAC__byte buf[], flac_read_status_size_t *bytes, void *fdata) { @@ -48,53 +55,59 @@ flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd, if (r == 0) { if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || input_stream_eof(data->input_stream)) - return flac_read_status_eof; + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; else - return flac_read_status_abort; + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; } - return flac_read_status_continue; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } -static flac_seek_status -flac_seek_cb(G_GNUC_UNUSED const flac_decoder *fd, +static FLAC__StreamDecoderSeekStatus +flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, FLAC__uint64 offset, void *fdata) { struct flac_data *data = (struct flac_data *) fdata; - if (!input_stream_seek(data->input_stream, offset, SEEK_SET)) - return flac_seek_status_error; + if (!data->input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; - return flac_seek_status_ok; + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) + return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; } -static flac_tell_status -flac_tell_cb(G_GNUC_UNUSED const flac_decoder *fd, +static FLAC__StreamDecoderTellStatus +flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, FLAC__uint64 * offset, void *fdata) { struct flac_data *data = (struct flac_data *) fdata; + if (!data->input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + *offset = (long)(data->input_stream->offset); - return flac_tell_status_ok; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; } -static flac_length_status -flac_length_cb(G_GNUC_UNUSED const flac_decoder *fd, +static FLAC__StreamDecoderLengthStatus +flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, FLAC__uint64 * length, void *fdata) { struct flac_data *data = (struct flac_data *) fdata; if (data->input_stream->size < 0) - return flac_length_status_unsupported; + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; *length = (size_t) (data->input_stream->size); - return flac_length_status_ok; + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; } static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata) +flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) { struct flac_data *data = (struct flac_data *) fdata; @@ -104,7 +117,7 @@ flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata) } static void -flac_error_cb(G_GNUC_UNUSED const flac_decoder *fd, +flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, FLAC__StreamDecoderErrorStatus status, void *fdata) { flac_error_common_cb("flac", status, (struct flac_data *) fdata); @@ -143,31 +156,6 @@ static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) g_warning("%s\n", str); } - -static bool -flac_init(FLAC__SeekableStreamDecoder *dec, - FLAC__SeekableStreamDecoderReadCallback read_cb, - FLAC__SeekableStreamDecoderSeekCallback seek_cb, - FLAC__SeekableStreamDecoderTellCallback tell_cb, - FLAC__SeekableStreamDecoderLengthCallback length_cb, - FLAC__SeekableStreamDecoderEofCallback eof_cb, - FLAC__SeekableStreamDecoderWriteCallback write_cb, - FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, - FLAC__SeekableStreamDecoderErrorCallback error_cb, - void *data) -{ - return FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb) && - FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb) && - FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb) && - FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb) && - FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb) && - FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb) && - FLAC__seekable_stream_decoder_set_metadata_callback(dec, metadata_cb) && - FLAC__seekable_stream_decoder_set_metadata_respond(dec, FLAC__METADATA_TYPE_VORBIS_COMMENT) && - FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb) && - FLAC__seekable_stream_decoder_set_client_data(dec, data) && - FLAC__seekable_stream_decoder_init(dec) == FLAC__SEEKABLE_STREAM_DECODER_OK; -} #else /* FLAC_API_VERSION_CURRENT >= 7 */ static void flacPrintErroredState(FLAC__StreamDecoderState state) { @@ -199,35 +187,31 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state) } #endif /* FLAC_API_VERSION_CURRENT >= 7 */ -static void flacMetadata(G_GNUC_UNUSED const flac_decoder * dec, +static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec, const FLAC__StreamMetadata * block, void *vdata) { flac_metadata_common_cb(block, (struct flac_data *) vdata); } static FLAC__StreamDecoderWriteStatus -flac_write_cb(const flac_decoder *dec, const FLAC__Frame *frame, +flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame, const FLAC__int32 *const buf[], void *vdata) { - FLAC__uint32 samples = frame->header.blocksize; struct flac_data *data = (struct flac_data *) vdata; - float timeChange; - FLAC__uint64 newPosition = 0; - - timeChange = ((float)samples) / frame->header.sample_rate; - data->time += timeChange; - - flac_get_decode_position(dec, &newPosition); - if (data->position && newPosition >= data->position) { - assert(timeChange >= 0); - - data->bit_rate = - ((newPosition - data->position) * 8.0 / timeChange) - / 1000 + 0.5; - } - data->position = newPosition; + FLAC__uint64 nbytes = 0; + + if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) { + if (data->position > 0 && nbytes > data->position) { + nbytes -= data->position; + data->position += nbytes; + } else { + data->position = nbytes; + nbytes = 0; + } + } else + nbytes = 0; - return flac_common_write(data, frame, buf); + return flac_common_write(data, frame, buf, nbytes); } static struct tag * @@ -268,12 +252,8 @@ flac_tag_load(const char *file, const char *char_tnum) block = FLAC__metadata_simple_iterator_get_block(it); if (!block) break; - if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { - flac_vorbis_comments_to_tag(tag, char_tnum, block); - } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { - tag->time = ((float)block->data.stream_info.total_samples) / - block->data.stream_info.sample_rate + 0.5; - } + + flac_tag_apply_metadata(tag, char_tnum, block); FLAC__metadata_object_delete(block); } while (FLAC__metadata_simple_iterator_next(it)); @@ -300,6 +280,8 @@ flac_cue_tag_load(const char *file) FLAC__uint64 track_time = 0; #ifdef HAVE_CUE /* libcue */ FLAC__StreamMetadata* vc; + char* cs_filename; + FILE* cs_file; #endif /* libcue */ FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO); FLAC__StreamMetadata* cs; @@ -329,16 +311,25 @@ flac_cue_tag_load(const char *file) FLAC__metadata_object_delete(vc); } + + if (tag == NULL) { + cs_filename = g_strconcat(file, ".cue", NULL); + + cs_file = fopen(cs_filename, "rt"); + g_free(cs_filename); + + if (cs_file != NULL) { + tag = cue_tag_file(cs_file, tnum); + fclose(cs_file); + } + } #endif /* libcue */ if (tag == NULL) tag = flac_tag_load(file, char_tnum); - if (char_tnum != NULL) - { - tag_add_item( tag, - TAG_ITEM_TRACK, - char_tnum); + if (char_tnum != NULL) { + tag_add_item(tag, TAG_TRACK, char_tnum); g_free(char_tnum); } @@ -382,105 +373,167 @@ flac_tag_dup(const char *file) return flac_tag_load(file, NULL); } -static void -flac_decode_internal(struct decoder * decoder, - struct input_stream *input_stream, - bool is_ogg) +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) { - flac_decoder *flac_dec; - struct flac_data data; - enum decoder_command cmd; - const char *err = NULL; - - if (!(flac_dec = flac_new())) - return; - flac_data_init(&data, decoder, input_stream); - data.tag = tag_new(); + FLAC__StreamDecoder *sd = FLAC__stream_decoder_new(); + if (sd == NULL) { + g_warning("FLAC__stream_decoder_new() failed"); + return NULL; + } #if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - { - g_debug("Failed to set metadata respond\n"); - } + if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); #endif - if (is_ogg) { - if (!flac_ogg_init(flac_dec, flac_read_cb, - flac_seek_cb, flac_tell_cb, - flac_length_cb, flac_eof_cb, - flac_write_cb, flacMetadata, - flac_error_cb, (void *)&data)) { - err = "doing Ogg init()"; - goto fail; - } - } else { - if (!flac_init(flac_dec, flac_read_cb, - flac_seek_cb, flac_tell_cb, - flac_length_cb, flac_eof_cb, - flac_write_cb, flacMetadata, - flac_error_cb, (void *)&data)) { - err = "doing init()"; - goto fail; - } - } + return sd; +} - if (!flac_process_metadata(flac_dec)) { - err = "problem reading metadata"; - goto fail; - } +static bool +flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, + bool seekable, FLAC__uint64 duration) +{ + struct audio_format audio_format; - if (!audio_format_valid(&data.audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); - goto fail; + if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { + g_warning("problem reading metadata"); + return false; } - decoder_initialized(decoder, &data.audio_format, - input_stream->seekable, data.total_time); + if (!flac_data_get_audio_format(data, &audio_format)) + return false; + + if (duration == 0) + duration = data->stream_info.total_samples; + + decoder_initialized(data->decoder, &audio_format, + seekable, + (float)duration / + (float)data->stream_info.sample_rate); + return true; +} + +static void +flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec, + FLAC__uint64 t_start, FLAC__uint64 t_end) +{ + struct decoder *decoder = data->decoder; + enum decoder_command cmd; + + data->first_frame = t_start; while (true) { - if (!tag_is_empty(data.tag)) { - cmd = decoder_tag(decoder, input_stream, data.tag); - tag_free(data.tag); - data.tag = tag_new(); + if (data->tag != NULL && !tag_is_empty(data->tag)) { + cmd = decoder_tag(data->decoder, data->input_stream, + data->tag); + tag_free(data->tag); + data->tag = tag_new(); } else cmd = decoder_get_command(decoder); if (cmd == DECODE_COMMAND_SEEK) { - FLAC__uint64 seek_sample = decoder_seek_where(decoder) * - data.audio_format.sample_rate + 0.5; - if (flac_seek_absolute(flac_dec, seek_sample)) { - data.time = ((float)seek_sample) / - data.audio_format.sample_rate; - data.position = 0; + FLAC__uint64 seek_sample = t_start + + decoder_seek_where(decoder) * + data->stream_info.sample_rate; + if (seek_sample >= t_start && + (t_end == 0 || seek_sample <= t_end) && + FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) { + data->next_frame = seek_sample; + data->position = 0; decoder_command_finished(decoder); } else decoder_seek_error(decoder); } else if (cmd == DECODE_COMMAND_STOP || - flac_get_state(flac_dec) == flac_decoder_eof) + FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) break; - if (!flac_process_single(flac_dec)) { + if (t_end != 0 && data->next_frame >= t_end) + /* end of this sub track */ + break; + + if (!FLAC__stream_decoder_process_single(flac_dec)) { cmd = decoder_get_command(decoder); if (cmd != DECODE_COMMAND_SEEK) break; } } + if (cmd != DECODE_COMMAND_STOP) { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); + flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec)); + FLAC__stream_decoder_finish(flac_dec); } +} -fail: - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); +static void +flac_decode_internal(struct decoder * decoder, + struct input_stream *input_stream, + bool is_ogg) +{ + FLAC__StreamDecoder *flac_dec; + struct flac_data data; + const char *err = NULL; + + flac_dec = flac_decoder_new(); + if (flac_dec == NULL) + return; + + flac_data_init(&data, decoder, input_stream); + data.tag = tag_new(); + + if (is_ogg) { +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + FLAC__StreamDecoderInitStatus status = + FLAC__stream_decoder_init_ogg_stream(flac_dec, + flac_read_cb, + flac_seek_cb, + flac_tell_cb, + flac_length_cb, + flac_eof_cb, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void *)&data); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing Ogg init()"; + goto fail; + } +#else + goto fail; +#endif + } else { + FLAC__StreamDecoderInitStatus status = + FLAC__stream_decoder_init_stream(flac_dec, + flac_read_cb, + flac_seek_cb, + flac_tell_cb, + flac_length_cb, + flac_eof_cb, + flac_write_cb, + flacMetadata, + flac_error_cb, + (void *)&data); + if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing init()"; + goto fail; + } + } - tag_free(data.tag); + if (!flac_decoder_initialize(&data, flac_dec, + input_stream->seekable, 0)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; + } - if (flac_dec) - flac_delete(flac_dec); + flac_decoder_loop(&data, flac_dec, 0, 0); + +fail: + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); if (err) g_warning("%s\n", err); @@ -509,7 +562,8 @@ flac_container_decode(struct decoder* decoder, FLAC__uint64 track_time = 0; FLAC__StreamMetadata* cs = NULL; - flac_decoder *flac_dec; + FLAC__StreamDecoder *flac_dec; + FLAC__StreamDecoderInitStatus init_status; struct flac_data data; const char *err = NULL; @@ -542,122 +596,43 @@ flac_container_decode(struct decoder* decoder, return; } - if (!(flac_dec = flac_new())) - { - g_free(pathname); + flac_dec = flac_decoder_new(); + if (flac_dec == NULL) return; - } flac_data_init(&data, decoder, NULL); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - { - g_debug("Failed to set metadata respond\n"); - } -#endif - - - if (is_ogg) - { - if (FLAC__stream_decoder_init_ogg_file( flac_dec, - pathname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data ) - != FLAC__STREAM_DECODER_INIT_STATUS_OK ) - { - err = "doing Ogg init()"; - goto fail; - } - } - else - { - if (FLAC__stream_decoder_init_file( flac_dec, - pathname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data ) - != FLAC__STREAM_DECODER_INIT_STATUS_OK ) - { - err = "doing init()"; - goto fail; - } - } - - if (!flac_process_metadata(flac_dec)) - { - err = "problem reading metadata"; + init_status = is_ogg + ? FLAC__stream_decoder_init_ogg_file(flac_dec, pathname, + flac_write_cb, + flacMetadata, + flac_error_cb, + &data) + : FLAC__stream_decoder_init_file(flac_dec, + pathname, flac_write_cb, + flacMetadata, flac_error_cb, + &data); + g_free(pathname); + if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + err = "doing init()"; goto fail; } - if (!audio_format_valid(&data.audio_format)) - { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); - goto fail; + if (!flac_decoder_initialize(&data, flac_dec, true, track_time)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; } - // set track time (order is important: after stream init) - data.total_time = ((float)track_time / (float)data.audio_format.sample_rate); - data.position = 0; - - decoder_initialized(decoder, &data.audio_format, - true, data.total_time); - // seek to song start (order is important: after decoder init) - flac_seek_absolute(flac_dec, (FLAC__uint64)t_start); - - while (true) - { - if (!flac_process_single(flac_dec)) - break; - - // we only need to break at the end of track if we are in "cue mode" - if (data.time >= data.total_time) - { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) - { - FLAC__uint64 seek_sample = t_start + - (decoder_seek_where(decoder) * data.audio_format.sample_rate); - - if (seek_sample >= t_start && seek_sample <= t_end && - flac_seek_absolute(flac_dec, (FLAC__uint64)seek_sample)) { - data.time = (float)(seek_sample - t_start) / - data.audio_format.sample_rate; - data.position = 0; + FLAC__stream_decoder_seek_absolute(flac_dec, (FLAC__uint64)t_start); + data.next_frame = t_start; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - else if (flac_get_state(flac_dec) == flac_decoder_eof) - break; - } - - if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) - { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } + flac_decoder_loop(&data, flac_dec, t_start, t_end); fail: - if (pathname) - g_free(pathname); - - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); - - if (flac_dec) - flac_delete(flac_dec); + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); if (err) g_warning("%s\n", err); @@ -672,24 +647,17 @@ flac_filedecode_internal(struct decoder* decoder, const char* fname, bool is_ogg) { - flac_decoder *flac_dec; + FLAC__StreamDecoder *flac_dec; struct flac_data data; const char *err = NULL; unsigned int flac_err_state = 0; - if (!(flac_dec = flac_new())) + flac_dec = flac_decoder_new(); + if (flac_dec == NULL) return; flac_data_init(&data, decoder, NULL); -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT)) - { - g_debug("Failed to set metadata respond\n"); - } -#endif - - if (is_ogg) { if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec, @@ -727,60 +695,17 @@ flac_filedecode_internal(struct decoder* decoder, } } - if (!flac_process_metadata(flac_dec)) - { - err = "problem reading metadata"; - goto fail; - } - - if (!audio_format_valid(&data.audio_format)) - { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); - goto fail; - } - - decoder_initialized(decoder, &data.audio_format, - true, data.total_time); - - while (true) - { - if (!flac_process_single(flac_dec)) - break; - - if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) - { - FLAC__uint64 seek_sample = decoder_seek_where(decoder) * - data.audio_format.sample_rate + 0.5; - if (flac_seek_absolute(flac_dec, seek_sample)) - { - data.time = ((float)seek_sample) / - data.audio_format.sample_rate; - data.position = 0; - decoder_command_finished(decoder); - } - else - decoder_seek_error(decoder); - - } - else if (flac_get_state(flac_dec) == flac_decoder_eof) - break; + if (!flac_decoder_initialize(&data, flac_dec, true, 0)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; } - if (decoder_get_command(decoder) != DECODE_COMMAND_STOP) - { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } + flac_decoder_loop(&data, flac_dec, 0, 0); fail: - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); - - if (flac_dec) - flac_delete(flac_dec); + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); if (err) g_warning("%s\n", err); @@ -836,13 +761,8 @@ oggflac_tag_dup(const char *file) do { if (!(block = FLAC__metadata_iterator_get_block(it))) break; - if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { - flac_vorbis_comments_to_tag(ret, NULL, block); - } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { - ret->time = ((float)block->data.stream_info. - total_samples) / - block->data.stream_info.sample_rate + 0.5; - } + + flac_tag_apply_metadata(ret, NULL, block); } while (FLAC__metadata_iterator_next(it)); FLAC__metadata_iterator_delete(it); @@ -864,7 +784,7 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(input_stream, 0, SEEK_SET); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); flac_decode_internal(decoder, input_stream, true); } diff --git a/src/decoder/fluidsynth_plugin.c b/src/decoder/fluidsynth_plugin.c index 99c874c09..2656b5ebd 100644 --- a/src/decoder/fluidsynth_plugin.c +++ b/src/decoder/fluidsynth_plugin.c @@ -26,9 +26,10 @@ * */ -#include "../decoder_api.h" -#include "../timer.h" -#include "../conf.h" +#include "config.h" +#include "decoder_api.h" +#include "timer.h" +#include "conf.h" #include <glib.h> @@ -87,7 +88,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) { static const struct audio_format audio_format = { .sample_rate = 48000, - .bits = 16, + .format = SAMPLE_FORMAT_S16, .channels = 2, }; char setting_sample_rate[] = "synth.sample-rate"; @@ -203,7 +204,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs) break; cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer), - 0, 0, NULL); + 0, NULL); } while (cmd == DECODE_COMMAND_NONE); /* clean up */ diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c index 1ef7183fa..f23ab3c21 100644 --- a/src/decoder/mad_plugin.c +++ b/src/decoder/mad_plugin.c @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" -#include "../conf.h" #include "config.h" +#include "decoder_api.h" +#include "conf.h" #include "tag_id3.h" +#include "audio_check.h" #include <assert.h> #include <unistd.h> @@ -164,7 +165,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder, static bool mp3_seek(struct mp3_data *data, long offset) { - if (!input_stream_seek(data->input_stream, offset, SEEK_SET)) + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) return false; mad_stream_buffer(&data->stream, data->input_buffer, 0); @@ -779,10 +780,10 @@ mp3_frame_duration(const struct mad_frame *frame) MAD_UNITS_MILLISECONDS) / 1000.0; } -static off_t +static goffset mp3_this_frame_offset(const struct mp3_data *data) { - off_t offset = data->input_stream->offset; + goffset offset = data->input_stream->offset; if (data->stream.this_frame != NULL) offset -= data->stream.bufend - data->stream.this_frame; @@ -792,7 +793,7 @@ mp3_this_frame_offset(const struct mp3_data *data) return offset; } -static off_t +static goffset mp3_rest_including_this_frame(const struct mp3_data *data) { return data->input_stream->size - mp3_this_frame_offset(data); @@ -804,7 +805,7 @@ mp3_rest_including_this_frame(const struct mp3_data *data) static void mp3_filesize_to_song_length(struct mp3_data *data) { - off_t rest = mp3_rest_including_this_frame(data); + goffset rest = mp3_rest_including_this_frame(data); if (rest > 0) { float frame_duration = mp3_frame_duration(&data->frame); @@ -919,7 +920,7 @@ static int mp3_total_file_time(const char *file) struct mp3_data data; int ret; - if (!input_stream_open(&input_stream, file)) + if (!input_stream_open(&input_stream, file, NULL)) return -1; mp3_data_init(&data, NULL, &input_stream); if (!mp3_decode_first_frame(&data, NULL, NULL)) @@ -1024,7 +1025,6 @@ mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length, cmd = decoder_data(data->decoder, data->input_stream, data->output_buffer, sizeof(data->output_buffer[0]) * num_samples, - data->elapsed_time, data->bit_rate / 1000, replay_gain_info); if (cmd != DECODE_COMMAND_NONE) @@ -1170,17 +1170,11 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) return ret != DECODE_BREAK; } -static void mp3_audio_format(struct mp3_data *data, struct audio_format *af) -{ - af->bits = 24; - af->sample_rate = (data->frame).header.samplerate; - af->channels = MAD_NCHANNELS(&(data->frame).header); -} - static void mp3_decode(struct decoder *decoder, struct input_stream *input_stream) { struct mp3_data data; + GError *error = NULL; struct tag *tag = NULL; struct replay_gain_info *replay_gain_info = NULL; struct audio_format audio_format; @@ -1192,7 +1186,21 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream) return; } - mp3_audio_format(&data, &audio_format); + if (!audio_format_init_checked(&audio_format, + data.frame.header.samplerate, + SAMPLE_FORMAT_S24_P32, + MAD_NCHANNELS(&data.frame.header), + &error)) { + g_warning("%s", error->message); + g_error_free(error); + + if (tag != NULL) + tag_free(tag); + if (replay_gain_info != NULL) + replay_gain_info_free(replay_gain_info); + mp3_data_finish(&data); + return; + } decoder_initialized(decoder, &audio_format, data.input_stream->seekable, data.total_time); diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_plugin.c index 065c34319..bbb330cb7 100644 --- a/src/decoder/mikmod_plugin.c +++ b/src/decoder/mikmod_plugin.c @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" #include <glib.h> #include <mikmod.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mikmod" @@ -29,30 +31,34 @@ #define MIKMOD_FRAME_SIZE 4096 -static BOOL mod_mpd_Init(void) +static BOOL +mikmod_mpd_init(void) { return VC_Init(); } -static void mod_mpd_Exit(void) +static void +mikmod_mpd_exit(void) { VC_Exit(); } -static void mod_mpd_Update(void) +static void +mikmod_mpd_update(void) { } -static BOOL mod_mpd_IsThere(void) +static BOOL +mikmod_mpd_is_present(void) { - return 1; + return true; } -static char drv_name[] = "MPD"; -static char drv_version[] = "MPD Output Driver v0.1"; +static char drv_name[] = PACKAGE_NAME; +static char drv_version[] = VERSION; #if (LIBMIKMOD_VERSION > 0x030106) -static char drv_alias[] = "mpd"; +static char drv_alias[] = PACKAGE; #endif static MDRIVER drv_mpd = { @@ -68,18 +74,18 @@ static MDRIVER drv_mpd = { #endif NULL, /* CommandLine */ #endif - mod_mpd_IsThere, + mikmod_mpd_is_present, VC_SampleLoad, VC_SampleUnload, VC_SampleSpace, VC_SampleLength, - mod_mpd_Init, - mod_mpd_Exit, + mikmod_mpd_init, + mikmod_mpd_exit, NULL, VC_SetNumVoices, VC_PlayStart, VC_PlayStop, - mod_mpd_Update, + mikmod_mpd_update, NULL, VC_VoiceSetVolume, VC_VoiceGetVolume, @@ -94,11 +100,19 @@ static MDRIVER drv_mpd = { VC_VoiceRealVolume }; +static unsigned mikmod_sample_rate; + static bool -mod_initMikMod(G_GNUC_UNUSED const struct config_param *param) +mikmod_decoder_init(const struct config_param *param) { static char params[] = ""; + mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate", + 44100); + if (!audio_valid_sample_rate(mikmod_sample_rate)) + g_error("Invalid sample rate in line %d: %u", + param->line, mikmod_sample_rate); + md_device = 0; md_reverb = 0; @@ -106,7 +120,7 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param) MikMod_RegisterAllLoaders(); md_pansep = 64; - md_mixfreq = 44100; + md_mixfreq = mikmod_sample_rate; md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | DMODE_16BITS); @@ -119,115 +133,83 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param) return true; } -static void mod_finishMikMod(void) +static void +mikmod_decoder_finish(void) { MikMod_Exit(); } -typedef struct _mod_Data { - MODULE *moduleHandle; - SBYTE audio_buffer[MIKMOD_FRAME_SIZE]; -} mod_Data; - -static mod_Data *mod_open(const char *path) -{ - char *path2; - MODULE *moduleHandle; - mod_Data *data; - - path2 = g_strdup(path); - moduleHandle = Player_Load(path2, 128, 0); - g_free(path2); - - if (moduleHandle == NULL) - return NULL; - - /* Prevent module from looping forever */ - moduleHandle->loop = 0; - - data = g_new(mod_Data, 1); - data->moduleHandle = moduleHandle; - - Player_Start(data->moduleHandle); - - return data; -} - -static void mod_close(mod_Data * data) -{ - Player_Stop(); - Player_Free(data->moduleHandle); - g_free(data); -} - static void -mod_decode(struct decoder *decoder, const char *path) +mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs) { - mod_Data *data; + char *path2; + MODULE *handle; struct audio_format audio_format; - float total_time = 0.0; int ret; - float secPerByte; + SBYTE buffer[MIKMOD_FRAME_SIZE]; enum decoder_command cmd = DECODE_COMMAND_NONE; - if (!(data = mod_open(path))) { - g_warning("failed to open mod: %s\n", path); + path2 = g_strdup(path_fs); + handle = Player_Load(path2, 128, 0); + g_free(path2); + + if (handle == NULL) { + g_warning("failed to open mod: %s", path_fs); return; } - audio_format.bits = 16; - audio_format.sample_rate = 44100; - audio_format.channels = 2; + /* Prevent module from looping forever */ + handle->loop = 0; - secPerByte = - 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * - (float)audio_format.sample_rate); + audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); decoder_initialized(decoder, &audio_format, false, 0); + Player_Start(handle); while (cmd == DECODE_COMMAND_NONE && Player_Active()) { - ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE); - total_time += ret * secPerByte; - cmd = decoder_data(decoder, NULL, - data->audio_buffer, ret, - total_time, 0, NULL); + ret = VC_WriteBytes(buffer, sizeof(buffer)); + cmd = decoder_data(decoder, NULL, buffer, ret, + 0, NULL); } - mod_close(data); + Player_Stop(); + Player_Free(handle); } -static struct tag *modTagDup(const char *file) +static struct tag * +mikmod_decoder_tag_dup(const char *path_fs) { char *path2; struct tag *ret = NULL; - MODULE *moduleHandle; + MODULE *handle; char *title; - path2 = g_strdup(file); - moduleHandle = Player_Load(path2, 128, 0); + path2 = g_strdup(path_fs); + handle = Player_Load(path2, 128, 0); g_free(path2); - if (moduleHandle == NULL) { - g_debug("Failed to open file: %s", file); + if (handle == NULL) { + g_debug("Failed to open file: %s", path_fs); return NULL; } - Player_Free(moduleHandle); + Player_Free(handle); ret = tag_new(); ret->time = 0; - path2 = g_strdup(file); + path2 = g_strdup(path_fs); title = g_strdup(Player_LoadTitle(path2)); g_free(path2); if (title) - tag_add_item(ret, TAG_ITEM_TITLE, title); + tag_add_item(ret, TAG_TITLE, title); return ret; } -static const char *const modSuffixes[] = { +static const char *const mikmod_decoder_suffixes[] = { "amf", "dsm", "far", @@ -248,9 +230,9 @@ static const char *const modSuffixes[] = { const struct decoder_plugin mikmod_decoder_plugin = { .name = "mikmod", - .init = mod_initMikMod, - .finish = mod_finishMikMod, - .file_decode = mod_decode, - .tag_dup = modTagDup, - .suffixes = modSuffixes, + .init = mikmod_decoder_init, + .finish = mikmod_decoder_finish, + .file_decode = mikmod_decoder_file_decode, + .tag_dup = mikmod_decoder_tag_dup, + .suffixes = mikmod_decoder_suffixes, }; diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c index f636f2fa6..05c9ef2d7 100644 --- a/src/decoder/modplug_plugin.c +++ b/src/decoder/modplug_plugin.c @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" #include <glib.h> #include <modplug.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "modplug" @@ -92,10 +94,8 @@ mod_decode(struct decoder *decoder, struct input_stream *is) ModPlug_Settings settings; GByteArray *bdatas; struct audio_format audio_format; - float total_time = 0.0; - int ret, current; + int ret; char audio_buffer[MODPLUG_FRAME_SIZE]; - float sec_perbyte; enum decoder_command cmd = DECODE_COMMAND_NONE; bdatas = mod_loadfile(decoder, is); @@ -121,37 +121,26 @@ mod_decode(struct decoder *decoder, struct input_stream *is) return; } - audio_format.bits = 16; - audio_format.sample_rate = 44100; - audio_format.channels = 2; - - sec_perbyte = - 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * - (float)audio_format.sample_rate); - - total_time = ModPlug_GetLength(f) / 1000; + audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); decoder_initialized(decoder, &audio_format, - is->seekable, total_time); - - total_time = 0; + is->seekable, ModPlug_GetLength(f) / 1000.0); do { ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE); - - if (ret == 0) { + if (ret <= 0) break; - } - total_time += ret * sec_perbyte; cmd = decoder_data(decoder, NULL, audio_buffer, ret, - total_time, 0, NULL); + 0, NULL); if (cmd == DECODE_COMMAND_SEEK) { - total_time = decoder_seek_where(decoder); - current = total_time * 1000; - ModPlug_Seek(f, current); + float where = decoder_seek_where(decoder); + + ModPlug_Seek(f, (int)(where * 1000.0)); + decoder_command_finished(decoder); } @@ -168,7 +157,7 @@ static struct tag *mod_tagdup(const char *file) char *title; struct input_stream is; - if (!input_stream_open(&is, file)) { + if (!input_stream_open(&is, file, NULL)) { g_warning("cant open file %s\n", file); return NULL; } @@ -186,11 +175,11 @@ static struct tag *mod_tagdup(const char *file) return NULL; } ret = tag_new(); - ret->time = 0; + ret->time = ModPlug_GetLength(f) / 1000; title = g_strdup(ModPlug_GetName(f)); if (title) - tag_add_item(ret, TAG_ITEM_TITLE, title); + tag_add_item(ret, TAG_TITLE, title); g_free(title); ModPlug_Unload(f); diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c index cf9382904..325a97291 100644 --- a/src/decoder/mp4ff_plugin.c +++ b/src/decoder/mp4ff_plugin.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" #include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #include <glib.h> @@ -98,7 +99,7 @@ mp4_seek(void *user_data, uint64_t position) { struct mp4_context *ctx = user_data; - return input_stream_seek(ctx->input_stream, position, SEEK_SET) + return input_stream_seek(ctx->input_stream, position, SEEK_SET, NULL) ? 0 : -1; } @@ -110,6 +111,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) int track; uint32_t sample_rate; unsigned char channels; + GError *error = NULL; decoder = faacDecOpen(); @@ -130,22 +132,17 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) return NULL; } - *track_r = track; - *audio_format = (struct audio_format){ - .bits = 16, - .channels = channels, - .sample_rate = sample_rate, - }; - - if (!audio_format_valid(audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format->sample_rate, - audio_format->bits, - audio_format->channels); + if (!audio_format_init_checked(audio_format, sample_rate, + SAMPLE_FORMAT_S16, channels, + &error)) { + g_warning("%s", error->message); + g_error_free(error); faacDecClose(decoder); return NULL; } + *track_r = track; + return decoder; } @@ -265,7 +262,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) dur -= offset; file_time += ((float)dur) / scale; - if (seeking && file_time > seek_where) + if (seeking && file_time >= seek_where) seek_position_found = true; if (seeking && seek_position_found) { @@ -331,7 +328,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) cmd = decoder_data(mpd_decoder, input_stream, sample_buffer, sample_buffer_length, - file_time, bit_rate, NULL); + bit_rate, NULL); } g_free(seek_table); @@ -359,7 +356,7 @@ mp4_tag_dup(const char *file) int32_t scale; int i; - if (!input_stream_open(&input_stream, file)) { + if (!input_stream_open(&input_stream, file, NULL)) { g_warning("Failed to open file: %s", file); return NULL; } @@ -395,22 +392,22 @@ mp4_tag_dup(const char *file) mp4ff_meta_get_by_index(mp4fh, i, &item, &value); if (0 == g_ascii_strcasecmp("artist", item)) { - tag_add_item(ret, TAG_ITEM_ARTIST, value); + tag_add_item(ret, TAG_ARTIST, value); } else if (0 == g_ascii_strcasecmp("title", item)) { - tag_add_item(ret, TAG_ITEM_TITLE, value); + tag_add_item(ret, TAG_TITLE, value); } else if (0 == g_ascii_strcasecmp("album", item)) { - tag_add_item(ret, TAG_ITEM_ALBUM, value); + tag_add_item(ret, TAG_ALBUM, value); } else if (0 == g_ascii_strcasecmp("track", item)) { - tag_add_item(ret, TAG_ITEM_TRACK, value); + tag_add_item(ret, TAG_TRACK, value); } else if (0 == g_ascii_strcasecmp("disc", item)) { /* Is that the correct id? */ - tag_add_item(ret, TAG_ITEM_DISC, value); + tag_add_item(ret, TAG_DISC, value); } else if (0 == g_ascii_strcasecmp("genre", item)) { - tag_add_item(ret, TAG_ITEM_GENRE, value); + tag_add_item(ret, TAG_GENRE, value); } else if (0 == g_ascii_strcasecmp("date", item)) { - tag_add_item(ret, TAG_ITEM_DATE, value); + tag_add_item(ret, TAG_DATE, value); } else if (0 == g_ascii_strcasecmp("writer", item)) { - tag_add_item(ret, TAG_ITEM_COMPOSER, value); + tag_add_item(ret, TAG_COMPOSER, value); } free(item); diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_plugin.c index 26349f93a..a186bc368 100644 --- a/src/decoder/mpcdec_plugin.c +++ b/src/decoder/mpcdec_plugin.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" #include "config.h" +#include "decoder_api.h" +#include "audio_check.h" #ifdef MPC_IS_OLD_API #include <mpcdec/mpcdec.h> @@ -27,6 +28,7 @@ #endif #include <glib.h> +#include <assert.h> #include <unistd.h> #undef G_LOG_DOMAIN @@ -58,7 +60,7 @@ mpc_seek_cb(cb_first_arg, mpc_int32_t offset) { struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data; - return input_stream_seek(data->is, offset, SEEK_SET); + return input_stream_seek(data->is, offset, SEEK_SET, NULL); } static mpc_int32_t @@ -140,6 +142,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) #endif mpc_reader reader; mpc_streaminfo info; + GError *error = NULL; struct audio_format audio_format; struct mpc_decoder_data data; @@ -149,10 +152,8 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) mpc_uint32_t ret; int32_t chunk[G_N_ELEMENTS(sample_buffer)]; long bit_rate = 0; - unsigned long sample_pos = 0; mpc_uint32_t vbr_update_acc; mpc_uint32_t vbr_update_bits; - float total_time; struct replay_gain_info *replay_gain_info = NULL; enum decoder_command cmd = DECODE_COMMAND_NONE; @@ -193,18 +194,14 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) mpc_demux_get_info(demux, &info); #endif - audio_format.bits = 24; - audio_format.channels = info.channels; - audio_format.sample_rate = info.sample_freq; - - if (!audio_format_valid(&audio_format)) { + if (!audio_format_init_checked(&audio_format, info.sample_freq, + SAMPLE_FORMAT_S24_P32, + info.channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); #ifndef MPC_IS_OLD_API mpc_demux_exit(demux); #endif - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, - audio_format.bits, - audio_format.channels); return; } @@ -220,16 +217,14 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) do { if (cmd == DECODE_COMMAND_SEEK) { - bool success; - - sample_pos = decoder_seek_where(mpd_decoder) * + mpc_int64_t where = decoder_seek_where(mpd_decoder) * audio_format.sample_rate; + bool success; #ifdef MPC_IS_OLD_API - success = mpc_decoder_seek_sample(&decoder, - sample_pos); + success = mpc_decoder_seek_sample(&decoder, where); #else - success = mpc_demux_seek_sample(demux, sample_pos) + success = mpc_demux_seek_sample(demux, where) == MPC_STATUS_OK; #endif if (success) @@ -260,19 +255,15 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) ret = frame.samples; #endif - sample_pos += ret; - ret *= info.channels; mpc_to_mpd_buffer(chunk, sample_buffer, ret); - total_time = ((float)sample_pos) / audio_format.sample_rate; bit_rate = vbr_update_bits * audio_format.sample_rate / 1152 / 1000; cmd = decoder_data(mpd_decoder, is, chunk, ret * sizeof(chunk[0]), - total_time, bit_rate, replay_gain_info); } while (cmd != DECODE_COMMAND_STOP); @@ -296,7 +287,7 @@ mpcdec_get_file_duration(const char *file) mpc_streaminfo info; struct mpc_decoder_data data; - if (!input_stream_open(&is, file)) + if (!input_stream_open(&is, file, NULL)) return -1; data.is = &is; diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c new file mode 100644 index 000000000..0d7cb9e0e --- /dev/null +++ b/src/decoder/mpg123_decoder_plugin.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "decoder_api.h" +#include "audio_check.h" + +#include <glib.h> + +#include <mpg123.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mpg123" + +static bool +mpd_mpg123_init(G_GNUC_UNUSED const struct config_param *param) +{ + mpg123_init(); + + return true; +} + +static void +mpd_mpg123_finish(void) +{ + mpg123_exit(); +} + +/** + * Opens a file with an existing #mpg123_handle. + * + * @param handle a handle which was created before; on error, this + * function will not free it + * @param audio_format this parameter is filled after successful + * return + * @return true on success + */ +static bool +mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, + struct audio_format *audio_format) +{ + GError *gerror = NULL; + char *path_dup; + int error; + int channels, encoding; + long rate; + + /* mpg123_open() wants a writable string :-( */ + path_dup = g_strdup(path_fs); + + error = mpg123_open(handle, path_dup); + g_free(path_dup); + if (error != MPG123_OK) { + g_warning("libmpg123 failed to open %s: %s", + path_fs, mpg123_plain_strerror(error)); + return false; + } + + /* obtain the audio format */ + + error = mpg123_getformat(handle, &rate, &channels, &encoding); + if (error != MPG123_OK) { + g_warning("mpg123_getformat() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + if (encoding != MPG123_ENC_SIGNED_16) { + /* other formats not yet implemented */ + g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding); + return false; + } + + if (!audio_format_init_checked(audio_format, rate, SAMPLE_FORMAT_S16, + channels, &gerror)) { + g_warning("%s", gerror->message); + g_error_free(gerror); + return false; + } + + return true; +} + +static void +mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) +{ + struct audio_format audio_format; + mpg123_handle *handle; + int error; + off_t num_samples; + enum decoder_command cmd; + + /* open the file */ + + handle = mpg123_new(NULL, &error); + if (handle == NULL) { + g_warning("mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return; + } + + if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { + mpg123_delete(handle); + return; + } + + num_samples = mpg123_length(handle); + + /* tell MPD core we're ready */ + + decoder_initialized(decoder, &audio_format, false, + (float)num_samples / + (float)audio_format.sample_rate); + + /* the decoder main loop */ + + do { + unsigned char buffer[8192]; + size_t nbytes; + + /* decode */ + + error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); + if (error != MPG123_OK) { + if (error != MPG123_DONE) + g_warning("mpg123_read() failed: %s", + mpg123_plain_strerror(error)); + break; + } + + /* send to MPD */ + + cmd = decoder_data(decoder, NULL, buffer, nbytes, + 0, NULL); + + /* seeking not yet implemented */ + } while (cmd == DECODE_COMMAND_NONE); + + /* cleanup */ + + mpg123_delete(handle); +} + +static struct tag * +mpd_mpg123_tag_dup(const char *path_fs) +{ + struct audio_format audio_format; + mpg123_handle *handle; + int error; + off_t num_samples; + struct tag *tag; + + handle = mpg123_new(NULL, &error); + if (handle == NULL) { + g_warning("mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return NULL; + } + + if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { + mpg123_delete(handle); + return NULL; + } + + num_samples = mpg123_length(handle); + if (num_samples <= 0) { + mpg123_delete(handle); + return NULL; + } + + tag = tag_new(); + + tag->time = num_samples / audio_format.sample_rate; + + /* ID3 tag support not yet implemented */ + + mpg123_delete(handle); + return tag; +} + +static const char *const mpg123_suffixes[] = { + "mp3", + NULL +}; + +const struct decoder_plugin mpg123_decoder_plugin = { + .name = "mpg123", + .init = mpd_mpg123_init, + .finish = mpd_mpg123_finish, + .file_decode = mpd_mpg123_file_decode, + /* streaming not yet implemented */ + .tag_dup = mpd_mpg123_tag_dup, + .suffixes = mpg123_suffixes, +}; diff --git a/src/decoder/oggflac_plugin.c b/src/decoder/oggflac_plugin.c index bdd589ccb..b8b087b47 100644 --- a/src/decoder/oggflac_plugin.c +++ b/src/decoder/oggflac_plugin.c @@ -21,19 +21,18 @@ * OggFLAC support (half-stolen from flac_plugin.c :)) */ +#include "config.h" /* must be first for large file support */ #include "_flac_common.h" #include "_ogg_common.h" +#include "flac_metadata.h" #include <glib.h> #include <OggFLAC/seekable_stream_decoder.h> #include <assert.h> #include <unistd.h> -static void oggflac_cleanup(struct flac_data *data, - OggFLAC__SeekableStreamDecoder * decoder) +static void oggflac_cleanup(OggFLAC__SeekableStreamDecoder * decoder) { - if (data->replay_gain_info) - replay_gain_info_free(data->replay_gain_info); if (decoder) OggFLAC__seekable_stream_decoder_delete(decoder); } @@ -67,7 +66,7 @@ static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const { struct flac_data *data = (struct flac_data *) fdata; - if (!input_stream_seek(data->input_stream, offset, SEEK_SET)) + if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL)) return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK; @@ -156,13 +155,8 @@ oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder, void *vdata) { struct flac_data *data = (struct flac_data *) vdata; - FLAC__uint32 samples = frame->header.blocksize; - float time_change; - time_change = ((float)samples) / frame->header.sample_rate; - data->time += time_change; - - return flac_common_write(data, frame, buf); + return flac_common_write(data, frame, buf, 0); } /* used by TagDup */ @@ -173,17 +167,7 @@ static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecode assert(data->tag != NULL); - switch (block->type) { - case FLAC__METADATA_TYPE_STREAMINFO: - data->tag->time = ((float)block->data.stream_info. - total_samples) / - block->data.stream_info.sample_rate + 0.5; - return; - case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_vorbis_comments_to_tag(data->tag, NULL, block); - default: - break; - } + flac_tag_apply_metadata(data->tag, NULL, block); } /* used by decode */ @@ -261,12 +245,21 @@ fail: static struct tag * oggflac_tag_dup(const char *file) { + GError *error = NULL; struct input_stream input_stream; OggFLAC__SeekableStreamDecoder *decoder; struct flac_data data; + struct tag *tag; + + if (!input_stream_open(&input_stream, file, &error)) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } - if (!input_stream_open(&input_stream, file)) return NULL; + } + if (ogg_stream_type_detect(&input_stream) != FLAC) { input_stream_close(&input_stream); return NULL; @@ -274,7 +267,7 @@ oggflac_tag_dup(const char *file) /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(&input_stream, 0, SEEK_SET); + input_stream_seek(&input_stream, 0, SEEK_SET, NULL); flac_data_init(&data, NULL, &input_stream); @@ -284,15 +277,18 @@ oggflac_tag_dup(const char *file) * data.tag will be set or unset, that's all we care about */ decoder = full_decoder_init_and_read_metadata(&data, 1); - oggflac_cleanup(&data, decoder); + oggflac_cleanup(decoder); input_stream_close(&input_stream); - if (!tag_is_defined(data.tag)) { - tag_free(data.tag); + if (tag_is_defined(data.tag)) { + tag = data.tag; data.tag = NULL; - } + } else + tag = NULL; - return data.tag; + flac_data_deinit(&data); + + return tag; } static void @@ -300,13 +296,14 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) { OggFLAC__SeekableStreamDecoder *decoder = NULL; struct flac_data data; + struct audio_format audio_format; if (ogg_stream_type_detect(input_stream) != FLAC) return; /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(input_stream, 0, SEEK_SET); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); flac_data_init(&data, mpd_decoder, input_stream); @@ -314,16 +311,13 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) goto fail; } - if (!audio_format_valid(&data.audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - data.audio_format.sample_rate, - data.audio_format.bits, - data.audio_format.channels); + if (!flac_data_get_audio_format(&data, &audio_format)) goto fail; - } - decoder_initialized(mpd_decoder, &data.audio_format, - input_stream->seekable, data.total_time); + decoder_initialized(mpd_decoder, &audio_format, + input_stream->seekable, + (float)data.stream_info.total_samples / + (float)data.stream_info.sample_rate); while (true) { OggFLAC__seekable_stream_decoder_process_single(decoder); @@ -333,11 +327,10 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) } if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) { FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) * - data.audio_format.sample_rate + 0.5; + data.stream_info.sample_rate; if (OggFLAC__seekable_stream_decoder_seek_absolute (decoder, seek_sample)) { - data.time = ((float)seek_sample) / - data.audio_format.sample_rate; + data.next_frame = seek_sample; data.position = 0; decoder_command_finished(mpd_decoder); } else @@ -352,7 +345,8 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream) } fail: - oggflac_cleanup(&data, decoder); + oggflac_cleanup(decoder); + flac_data_deinit(&data); } static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx index c62e6b4b6..9bfe98f3d 100644 --- a/src/decoder/sidplay_plugin.cxx +++ b/src/decoder/sidplay_plugin.cxx @@ -17,18 +17,186 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" + extern "C" { #include "../decoder_api.h" } +#include <errno.h> +#include <stdlib.h> #include <glib.h> #include <sidplay/sidplay2.h> #include <sidplay/builders/resid.h> +#include <sidplay/utils/SidTuneMod.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "sidplay" +#define SUBTUNE_PREFIX "tune_" + +static GPatternSpec *path_with_subtune; +static const char *songlength_file; +static GKeyFile *songlength_database; + +static bool all_files_are_containers; +static unsigned default_songlength; + +static bool filter_setting; + +static GKeyFile * +sidplay_load_songlength_db(const char *path) +{ + GError *error = NULL; + gchar *data; + gsize size; + + if (!g_file_get_contents(path, &data, &size, &error)) { + g_warning("unable to read songlengths file %s: %s", + path, error->message); + g_error_free(error); + return NULL; + } + + /* replace any ; comment characters with # */ + for (gsize i = 0; i < size; i++) + if (data[i] == ';') + data[i] = '#'; + + GKeyFile *db = g_key_file_new(); + bool success = g_key_file_load_from_data(db, data, size, + G_KEY_FILE_NONE, &error); + g_free(data); + if (!success) { + g_warning("unable to parse songlengths file %s: %s", + path, error->message); + g_error_free(error); + g_key_file_free(db); + return NULL; + } + + g_key_file_set_list_separator(db, ' '); + return db; +} + +static bool +sidplay_init(const struct config_param *param) +{ + /* read the songlengths database file */ + songlength_file=config_get_block_string(param, + "songlength_database", NULL); + if (songlength_file != NULL) + songlength_database = sidplay_load_songlength_db(songlength_file); + + default_songlength=config_get_block_unsigned(param, + "default_songlength", 0); + + all_files_are_containers=config_get_block_bool(param, + "all_files_are_containers", true); + + path_with_subtune=g_pattern_spec_new( + "*/" SUBTUNE_PREFIX "???.sid"); + + filter_setting=config_get_block_bool(param, "filter", true); + + return true; +} + +void +sidplay_finish() +{ + g_pattern_spec_free(path_with_subtune); + + if(songlength_database) + g_key_file_free(songlength_database); +} + +/** + * returns the file path stripped of any /tune_xxx.sid subtune + * suffix + */ +static char * +get_container_name(const char *path_fs) +{ + char *path_container=g_strdup(path_fs); + + if(!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, NULL)) + return path_container; + + char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if(ptr) *ptr='\0'; + + return path_container; +} + +/** + * returns tune number from file.sid/tune_xxx.sid style path or 1 if + * no subtune is appended + */ +static int +get_song_num(const char *path_fs) +{ + if(g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, NULL)) { + char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + if(!sub) return 1; + + sub+=strlen("/" SUBTUNE_PREFIX); + int song_num=strtol(sub, NULL, 10); + + if (errno == EINVAL) + return 1; + else + return song_num; + } else + return 1; +} + +/* get the song length in seconds */ +static int +get_song_length(const char *path_fs) +{ + if (songlength_database == NULL) + return -1; + + gchar *sid_file=get_container_name(path_fs); + SidTuneMod tune(sid_file); + g_free(sid_file); + if(!tune) { + g_warning("failed to load file for calculating md5 sum"); + return -1; + } + char md5sum[SIDTUNE_MD5_LENGTH+1]; + tune.createMD5(md5sum); + + int song_num=get_song_num(path_fs); + + gsize num_items; + gchar **values=g_key_file_get_string_list(songlength_database, + "Database", md5sum, &num_items, NULL); + if(!values || song_num>num_items) { + g_strfreev(values); + return -1; + } + + int minutes=strtol(values[song_num-1], NULL, 10); + if(errno==EINVAL) minutes=0; + + int seconds; + char *ptr=strchr(values[song_num-1], ':'); + if(ptr) { + seconds=strtol(ptr+1, NULL, 10); + if(errno==EINVAL) seconds=0; + } else + seconds=0; + + g_strfreev(values); + + return (minutes*60)+seconds; +} + static void sidplay_file_decode(struct decoder *decoder, const char *path_fs) { @@ -36,13 +204,19 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) /* load the tune */ - SidTune tune(path_fs, NULL, true); + char *path_container=get_container_name(path_fs); + SidTune tune(path_container, NULL, true); + g_free(path_container); if (!tune) { g_warning("failed to load file"); return; } - tune.selectSong(1); + int song_num=get_song_num(path_fs); + tune.selectSong(song_num); + + int song_len=get_song_length(path_fs); + if(song_len==-1) song_len=default_songlength; /* initialize the player */ @@ -67,7 +241,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) return; } - builder.filter(false); + builder.filter(filter_setting); if (!builder) { g_warning("ReSIDBuilder.filter() failed"); return; @@ -103,14 +277,16 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) /* initialize the MPD decoder */ struct audio_format audio_format; - audio_format.sample_rate = 48000; - audio_format.bits = 16; - audio_format.channels = 2; + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2); + assert(audio_format_valid(&audio_format)); - decoder_initialized(decoder, &audio_format, false, -1); + decoder_initialized(decoder, &audio_format, true, (float)song_len); /* .. and play */ + const unsigned timebase = player.timebase(); + song_len *= timebase; + enum decoder_command cmd; do { char buffer[4096]; @@ -120,30 +296,108 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) if (nbytes == 0) break; + decoder_timestamp(decoder, (double)player.time() / timebase); + cmd = decoder_data(decoder, NULL, buffer, nbytes, - 0, 0, NULL); - } while (cmd == DECODE_COMMAND_NONE); + 0, NULL); + + if(cmd==DECODE_COMMAND_SEEK) { + unsigned data_time = player.time(); + unsigned target_time = (unsigned) + (decoder_seek_where(decoder) * timebase); + + /* can't rewind so return to zero and seek forward */ + if(target_time<data_time) { + player.stop(); + data_time=0; + } + + /* ignore data until target time is reached */ + while(data_time<target_time) { + nbytes=player.play(buffer, sizeof(buffer)); + if(nbytes==0) + break; + data_time = player.time(); + } + + decoder_command_finished(decoder); + } + + if (song_len > 0 && player.time() >= song_len) + break; + + } while (cmd != DECODE_COMMAND_STOP); } static struct tag * sidplay_tag_dup(const char *path_fs) { - SidTune tune(path_fs, NULL, true); + int song_num=get_song_num(path_fs); + char *path_container=get_container_name(path_fs); + + SidTune tune(path_container, NULL, true); + g_free(path_container); if (!tune) return NULL; const SidTuneInfo &info = tune.getInfo(); struct tag *tag = tag_new(); + /* title */ + const char *title; if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL) - tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]); - + title=info.infoString[0]; + else + title=""; + + if(info.songs>1) { + char *tag_title=g_strdup_printf("%s (%d/%d)", + title, song_num, info.songs); + tag_add_item(tag, TAG_TITLE, tag_title); + g_free(tag_title); + } else + tag_add_item(tag, TAG_TITLE, title); + + /* artist */ if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]); + tag_add_item(tag, TAG_ARTIST, info.infoString[1]); + + /* track */ + char *track=g_strdup_printf("%d", song_num); + tag_add_item(tag, TAG_TRACK, track); + g_free(track); + + /* time */ + int song_len=get_song_length(path_fs); + if(song_len!=-1) tag->time=song_len; return tag; } +static char * +sidplay_container_scan(const char *path_fs, const unsigned int tnum) +{ + SidTune tune(path_fs, NULL, true); + if (!tune) + return NULL; + + const SidTuneInfo &info=tune.getInfo(); + + /* Don't treat sids containing a single tune + as containers */ + if(!all_files_are_containers && info.songs<2) + return NULL; + + /* Construct container/tune path names, eg. + Delta.sid/tune_001.sid */ + if(tnum<=info.songs) { + char *subtune= g_strdup_printf( + SUBTUNE_PREFIX "%03u.sid", tnum); + return subtune; + } else + return NULL; +} + static const char *const sidplay_suffixes[] = { "sid", NULL @@ -152,12 +406,12 @@ static const char *const sidplay_suffixes[] = { extern const struct decoder_plugin sidplay_decoder_plugin; const struct decoder_plugin sidplay_decoder_plugin = { "sidplay", - NULL, /* init() */ - NULL, /* finish() */ + sidplay_init, + sidplay_finish, NULL, /* stream_decode() */ sidplay_file_decode, sidplay_tag_dup, - NULL, /* container_scan */ + sidplay_container_scan, sidplay_suffixes, NULL, /* mime_types */ }; diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c new file mode 100644 index 000000000..fd51f4a33 --- /dev/null +++ b/src/decoder/sndfile_decoder_plugin.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" + +#include <sndfile.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "sndfile" + +static sf_count_t +sndfile_vio_get_filelen(void *user_data) +{ + const struct input_stream *is = user_data; + + return is->size; +} + +static sf_count_t +sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) +{ + struct input_stream *is = user_data; + bool success; + + success = input_stream_seek(is, offset, whence, NULL); + if (!success) + return -1; + + return is->offset; +} + +static sf_count_t +sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) +{ + struct input_stream *is = user_data; + GError *error = NULL; + size_t nbytes; + + nbytes = input_stream_read(is, ptr, count, &error); + if (nbytes == 0 && error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + return -1; + } + + return nbytes; +} + +static sf_count_t +sndfile_vio_write(G_GNUC_UNUSED const void *ptr, + G_GNUC_UNUSED sf_count_t count, + G_GNUC_UNUSED void *user_data) +{ + /* no writing! */ + return -1; +} + +static sf_count_t +sndfile_vio_tell(void *user_data) +{ + const struct input_stream *is = user_data; + + return is->offset; +} + +/** + * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a + * libsndfile stream. + */ +static SF_VIRTUAL_IO vio = { + .get_filelen = sndfile_vio_get_filelen, + .seek = sndfile_vio_seek, + .read = sndfile_vio_read, + .write = sndfile_vio_write, + .tell = sndfile_vio_tell, +}; + +/** + * Converts a frame number to a timestamp (in seconds). + */ +static float +frame_to_time(sf_count_t frame, const struct audio_format *audio_format) +{ + return (float)frame / (float)audio_format->sample_rate; +} + +/** + * Converts a timestamp (in seconds) to a frame number. + */ +static sf_count_t +time_to_frame(float t, const struct audio_format *audio_format) +{ + return (sf_count_t)(t * audio_format->sample_rate); +} + +static void +sndfile_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + GError *error = NULL; + SNDFILE *sf; + SF_INFO info; + struct audio_format audio_format; + size_t frame_size; + sf_count_t read_frames, num_frames; + int buffer[4096]; + enum decoder_command cmd; + + info.format = 0; + + sf = sf_open_virtual(&vio, SFM_READ, &info, is); + if (sf == NULL) { + g_warning("sf_open_virtual() failed"); + return; + } + + /* for now, always read 32 bit samples. Later, we could lower + MPD's CPU usage by reading 16 bit samples with + sf_readf_short() on low-quality source files. */ + if (!audio_format_init_checked(&audio_format, info.samplerate, + SAMPLE_FORMAT_S32, + info.channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + decoder_initialized(decoder, &audio_format, info.seekable, + frame_to_time(info.frames, &audio_format)); + + frame_size = audio_format_frame_size(&audio_format); + read_frames = sizeof(buffer) / frame_size; + + do { + num_frames = sf_readf_int(sf, buffer, read_frames); + if (num_frames <= 0) + break; + + cmd = decoder_data(decoder, is, + buffer, num_frames * frame_size, + 0, NULL); + if (cmd == DECODE_COMMAND_SEEK) { + sf_count_t c = + time_to_frame(decoder_seek_where(decoder), + &audio_format); + c = sf_seek(sf, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else + decoder_command_finished(decoder); + cmd = DECODE_COMMAND_NONE; + } + } while (cmd == DECODE_COMMAND_NONE); + + sf_close(sf); +} + +static struct tag * +sndfile_tag_dup(const char *path_fs) +{ + SNDFILE *sf; + SF_INFO info; + struct tag *tag; + const char *p; + + info.format = 0; + + sf = sf_open(path_fs, SFM_READ, &info); + if (sf == NULL) + return NULL; + + if (!audio_valid_sample_rate(info.samplerate)) { + sf_close(sf); + g_warning("Invalid sample rate in %s\n", path_fs); + return NULL; + } + + tag = tag_new(); + tag->time = info.frames / info.samplerate; + + p = sf_get_string(sf, SF_STR_TITLE); + if (p != NULL) + tag_add_item(tag, TAG_TITLE, p); + + p = sf_get_string(sf, SF_STR_ARTIST); + if (p != NULL) + tag_add_item(tag, TAG_ARTIST, p); + + p = sf_get_string(sf, SF_STR_DATE); + if (p != NULL) + tag_add_item(tag, TAG_DATE, p); + + sf_close(sf); + + return tag; +} + +static const char *const sndfile_suffixes[] = { + "wav", "aiff", "aif", /* Microsoft / SGI / Apple */ + "au", "snd", /* Sun / DEC / NeXT */ + "paf", /* Paris Audio File */ + "iff", "svx", /* Commodore Amiga IFF / SVX */ + "sf", /* IRCAM */ + "voc", /* Creative */ + "w64", /* Soundforge */ + "pvf", /* Portable Voice Format */ + "xi", /* Fasttracker */ + "htk", /* HMM Tool Kit */ + "caf", /* Apple */ + "sd2", /* Sound Designer II */ + + /* libsndfile also supports FLAC and Ogg Vorbis, but only by + linking with libFLAC and libvorbis - we can do better, we + have native plugins for these libraries */ + + NULL +}; + +static const char *const sndfile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + + /* what are the MIME types of the other supported formats? */ + + NULL +}; + +const struct decoder_plugin sndfile_decoder_plugin = { + .name = "sndfile", + .stream_decode = sndfile_stream_decode, + .tag_dup = sndfile_tag_dup, + .suffixes = sndfile_suffixes, + .mime_types = sndfile_mime_types, +}; diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c index 0ff898647..b323cf775 100644..100755 --- a/src/decoder/vorbis_plugin.c +++ b/src/decoder/vorbis_plugin.c @@ -17,13 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */ - -#include "_ogg_common.h" #include "config.h" +#include "_ogg_common.h" +#include "audio_check.h" #include "uri.h" #ifndef HAVE_TREMOR +#define OV_EXCLUDE_STATIC_CALLBACKS #include <vorbis/vorbisfile.h> #else #include <tremor/ivorbisfile.h> @@ -55,17 +55,17 @@ #define OGG_DECODE_USE_BIGENDIAN 0 #endif -typedef struct _OggCallbackData { +struct vorbis_decoder_data { struct decoder *decoder; struct input_stream *input_stream; bool seekable; -} OggCallbackData; +}; static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) { + struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata; size_t ret; - OggCallbackData *data = (OggCallbackData *) vdata; ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb); @@ -76,11 +76,11 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) { - const OggCallbackData *data = (const OggCallbackData *) vdata; + struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata; return data->seekable && decoder_get_command(data->decoder) != DECODE_COMMAND_STOP && - input_stream_seek(data->input_stream, offset, whence) + input_stream_seek(data->input_stream, offset, whence, NULL) ? 0 : -1; } @@ -92,12 +92,37 @@ static int ogg_close_cb(G_GNUC_UNUSED void *vdata) static long ogg_tell_cb(void *vdata) { - const OggCallbackData *data = (const OggCallbackData *) vdata; + const struct vorbis_decoder_data *data = + (const struct vorbis_decoder_data *)vdata; return (long)data->input_stream->offset; } static const char * +vorbis_strerror(int code) +{ + switch (code) { + case OV_EREAD: + return "read error"; + + case OV_ENOTVORBIS: + return "not vorbis stream"; + + case OV_EVERSION: + return "vorbis version mismatch"; + + case OV_EBADHEADER: + return "invalid vorbis header"; + + case OV_EFAULT: + return "internal logic error"; + + default: + return "unknown error"; + } +} + +static const char * vorbis_comment_value(const char *comment, const char *needle) { size_t len = strlen(needle); @@ -176,11 +201,11 @@ vorbis_parse_comment(struct tag *tag, const char *comment) assert(tag != NULL); if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, - TAG_ITEM_TRACK) || + TAG_TRACK) || vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, - TAG_ITEM_DISC) || + TAG_DISC) || vorbis_copy_comment(tag, comment, "album artist", - TAG_ITEM_ALBUM_ARTIST)) + TAG_ALBUM_ARTIST)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) @@ -228,7 +253,7 @@ oggvorbis_seekable(struct decoder *decoder) uri = decoder_get_uri(decoder); /* disable seeking on remote streams, because libvorbis seeks around like crazy, and due to being very expensive, this - delays song playback my 10 or 20 seconds */ + delays song playback by 10 or 20 seconds */ seekable = !uri_has_scheme(uri); g_free(uri); @@ -240,10 +265,12 @@ static void vorbis_stream_decode(struct decoder *decoder, struct input_stream *input_stream) { + GError *error = NULL; OggVorbis_File vf; ov_callbacks callbacks; - OggCallbackData data; + struct vorbis_decoder_data data; struct audio_format audio_format; + float total_time; int current_section; int prev_section = -1; long ret; @@ -251,8 +278,7 @@ vorbis_stream_decode(struct decoder *decoder, long bitRate = 0; long test; struct replay_gain_info *replay_gain_info = NULL; - char **comments; - bool initialized = false; + const vorbis_info *vi; enum decoder_command cmd = DECODE_COMMAND_NONE; if (ogg_stream_type_detect(input_stream) != VORBIS) @@ -260,7 +286,7 @@ vorbis_stream_decode(struct decoder *decoder, /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(input_stream, 0, SEEK_SET); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); data.decoder = decoder; data.input_stream = input_stream; @@ -271,36 +297,33 @@ vorbis_stream_decode(struct decoder *decoder, callbacks.close_func = ogg_close_cb; callbacks.tell_func = ogg_tell_cb; if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { - const char *error; - if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) return; - switch (ret) { - case OV_EREAD: - error = "read error"; - break; - case OV_ENOTVORBIS: - error = "not vorbis stream"; - break; - case OV_EVERSION: - error = "vorbis version mismatch"; - break; - case OV_EBADHEADER: - error = "invalid vorbis header"; - break; - case OV_EFAULT: - error = "internal logic error"; - break; - default: - error = "unknown error"; - break; - } + g_warning("Error decoding Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return; + } - g_warning("Error decoding Ogg Vorbis stream: %s", error); + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); return; } - audio_format.bits = 16; + + if (!audio_format_init_checked(&audio_format, vi->rate, + SAMPLE_FORMAT_S16, + vi->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + + decoder_initialized(decoder, &audio_format, data.seekable, total_time); do { if (cmd == DECODE_COMMAND_SEEK) { @@ -320,30 +343,23 @@ vorbis_stream_decode(struct decoder *decoder, break; if (current_section != prev_section) { - /*printf("new song!\n"); */ - vorbis_info *vi = ov_info(&vf, -1); + char **comments; struct replay_gain_info *new_rgi; - audio_format.channels = vi->channels; - audio_format.sample_rate = vi->rate; - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, - audio_format.bits, - audio_format.channels); + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); break; } - if (!initialized) { - float total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - decoder_initialized(decoder, &audio_format, - data.seekable, - total_time); - initialized = true; + if (vi->rate != (long)audio_format.sample_rate || + vi->channels != (int)audio_format.channels) { + /* we don't support audio format + change yet */ + g_warning("audio format change, stopping here"); + break; } + comments = ov_comment(&vf, -1)->user_comments; vorbis_send_comments(decoder, input_stream, comments); new_rgi = vorbis_comments_to_replay_gain(comments); @@ -352,16 +368,15 @@ vorbis_stream_decode(struct decoder *decoder, replay_gain_info_free(replay_gain_info); replay_gain_info = new_rgi; } - } - prev_section = current_section; + prev_section = current_section; + } if ((test = ov_bitrate_instant(&vf)) > 0) bitRate = test / 1000; cmd = decoder_data(decoder, input_stream, chunk, ret, - ov_pcm_tell(&vf) / audio_format.sample_rate, bitRate, replay_gain_info); } while (cmd != DECODE_COMMAND_STOP); @@ -378,7 +393,7 @@ vorbis_tag_dup(const char *file) FILE *fp; OggVorbis_File vf; - fp = fopen(file, "r"); + fp = fopen(file, "rb"); if (!fp) { return NULL; } diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c index 7ad3a62b0..fbcebb803 100644 --- a/src/decoder/wavpack_plugin.c +++ b/src/decoder/wavpack_plugin.c @@ -17,9 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" -#include "../path.h" -#include "../utils.h" +#include "config.h" +#include "decoder_api.h" +#include "audio_check.h" +#include "path.h" +#include "utils.h" #include <wavpack/wavpack.h> #include <glib.h> @@ -41,17 +43,17 @@ static struct { const char *name; enum tag_type type; } tagtypes[] = { - { "artist", TAG_ITEM_ARTIST }, - { "album", TAG_ITEM_ALBUM }, - { "title", TAG_ITEM_TITLE }, - { "track", TAG_ITEM_TRACK }, - { "name", TAG_ITEM_NAME }, - { "genre", TAG_ITEM_GENRE }, - { "date", TAG_ITEM_DATE }, - { "composer", TAG_ITEM_COMPOSER }, - { "performer", TAG_ITEM_PERFORMER }, - { "comment", TAG_ITEM_COMMENT }, - { "disc", TAG_ITEM_DISC }, + { "artist", TAG_ARTIST }, + { "album", TAG_ALBUM }, + { "title", TAG_TITLE }, + { "track", TAG_TRACK }, + { "name", TAG_NAME }, + { "genre", TAG_GENRE }, + { "date", TAG_DATE }, + { "composer", TAG_COMPOSER }, + { "performer", TAG_PERFORMER }, + { "comment", TAG_COMMENT }, + { "disc", TAG_DISC }, }; /** A pointer type for format converter function. */ @@ -97,19 +99,11 @@ format_samples_int(int bytes_per_sample, void *buffer, uint32_t count) } break; } + case 3: + case 4: /* do nothing */ break; - case 4: { - uint32_t *dst = buffer; - assert_static(sizeof(*dst) <= sizeof(*src)); - - /* downsample to 24-bit */ - while (count--) { - *dst++ = *src++ >> 8; - } - break; - } } } @@ -129,6 +123,33 @@ format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer, } } +/** + * Choose a MPD sample format from libwavpacks' number of bits. + */ +static enum sample_format +wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample) +{ + if (is_float) + return SAMPLE_FORMAT_S24_P32; + + switch (bytes_per_sample) { + case 1: + return SAMPLE_FORMAT_S8; + + case 2: + return SAMPLE_FORMAT_S16; + + case 3: + return SAMPLE_FORMAT_S24_P32; + + case 4: + return SAMPLE_FORMAT_S32; + + default: + return SAMPLE_FORMAT_UNDEFINED; + } +} + /* * This does the main decoding thing. * Requires an already opened WavpackContext. @@ -137,30 +158,27 @@ static void wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, struct replay_gain_info *replay_gain_info) { + GError *error = NULL; + bool is_float; + enum sample_format sample_format; struct audio_format audio_format; format_samples_t format_samples; char chunk[CHUNK_SIZE]; int samples_requested, samples_got; - float total_time, current_time; + float total_time; int bytes_per_sample, output_sample_size; - int position; - - audio_format.sample_rate = WavpackGetSampleRate(wpc); - audio_format.channels = WavpackGetReducedChannels(wpc); - audio_format.bits = WavpackGetBitsPerSample(wpc); - /* round bitwidth to 8-bit units */ - audio_format.bits = (audio_format.bits + 7) & (~7); - /* mpd handles max 24-bit samples */ - if (audio_format.bits > 24) { - audio_format.bits = 24; - } - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, - audio_format.bits, - audio_format.channels); + is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0; + sample_format = + wavpack_bits_to_sample_format(is_float, + WavpackGetBytesPerSample(wpc)); + + if (!audio_format_init_checked(&audio_format, + WavpackGetSampleRate(wpc), + sample_format, + WavpackGetNumChannels(wpc), &error)) { + g_warning("%s", error->message); + g_error_free(error); return; } @@ -180,8 +198,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, decoder_initialized(decoder, &audio_format, can_seek, total_time); - position = 0; - do { if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) { if (can_seek) { @@ -189,7 +205,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, audio_format.sample_rate; if (WavpackSeekSample(wpc, where)) { - position = where; decoder_command_finished(decoder); } else { decoder_seek_error(decoder); @@ -209,9 +224,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, if (samples_got > 0) { int bitrate = (int)(WavpackGetInstantBitrate(wpc) / 1000 + 0.5); - position += samples_got; - current_time = position; - current_time /= audio_format.sample_rate; format_samples( bytes_per_sample, chunk, @@ -221,7 +233,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, decoder_data( decoder, NULL, chunk, samples_got * output_sample_size, - current_time, bitrate, + bitrate, replay_gain_info ); } @@ -397,13 +409,13 @@ wavpack_input_get_pos(void *id) static int wavpack_input_set_pos_abs(void *id, uint32_t pos) { - return input_stream_seek(wpin(id)->is, pos, SEEK_SET) ? 0 : -1; + return input_stream_seek(wpin(id)->is, pos, SEEK_SET, NULL) ? 0 : -1; } static int wavpack_input_set_pos_rel(void *id, int32_t delta, int mode) { - return input_stream_seek(wpin(id)->is, delta, mode) ? 0 : -1; + return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1; } static int @@ -474,7 +486,7 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc, wvc_url = g_strconcat(utf8url, "c", NULL); g_free(utf8url); - ret = input_stream_open(is_wvc, wvc_url); + ret = input_stream_open(is_wvc, wvc_url, NULL); g_free(wvc_url); if (!ret) { @@ -508,7 +520,7 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) char error[ERRORLEN]; WavpackContext *wpc; struct input_stream is_wvc; - int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE; + int open_flags = OPEN_NORMALIZE; struct wavpack_input isp, isp_wvc; bool can_seek = is->seekable; @@ -553,7 +565,7 @@ wavpack_filedecode(struct decoder *decoder, const char *fname) wpc = WavpackOpenFileInput( fname, error, - OPEN_TAGS | OPEN_WVC | OPEN_2CH_MAX | OPEN_NORMALIZE, 23 + OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23 ); if (wpc == NULL) { g_warning( diff --git a/src/decoder/wildmidi_plugin.c b/src/decoder/wildmidi_plugin.c index 8bad6943a..0392c06b9 100644 --- a/src/decoder/wildmidi_plugin.c +++ b/src/decoder/wildmidi_plugin.c @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" #include <glib.h> @@ -58,7 +59,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) { static const struct audio_format audio_format = { .sample_rate = WILDMIDI_SAMPLE_RATE, - .bits = 16, + .format = SAMPLE_FORMAT_S16, .channels = 2, }; midi *wm; @@ -91,8 +92,6 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) break; cmd = decoder_data(decoder, NULL, buffer, len, - (float)info->current_sample / - (float)WILDMIDI_SAMPLE_RATE, 0, NULL); if (cmd == DECODE_COMMAND_SEEK) { diff --git a/src/decoder_api.c b/src/decoder_api.c index c696ba101..e902c454a 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_api.h" #include "decoder_internal.h" #include "decoder_control.h" @@ -24,8 +25,6 @@ #include "audio.h" #include "song.h" #include "buffer.h" - -#include "normalize.h" #include "pipe.h" #include "chunk.h" @@ -37,12 +36,16 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "decoder" -void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder, - const struct audio_format *audio_format, - bool seekable, float total_time) +void +decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + bool seekable, float total_time) { - assert(dc.state == DECODE_STATE_START); - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; + struct audio_format_string af_string; + + assert(dc->state == DECODE_STATE_START); + assert(dc->pipe != NULL); assert(decoder != NULL); assert(decoder->stream_tag == NULL); assert(decoder->decoder_tag == NULL); @@ -51,47 +54,58 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder, assert(audio_format_defined(audio_format)); assert(audio_format_valid(audio_format)); - dc.in_audio_format = *audio_format; - getOutputAudioFormat(audio_format, &dc.out_audio_format); + dc->in_audio_format = *audio_format; + getOutputAudioFormat(audio_format, &dc->out_audio_format); - dc.seekable = seekable; - dc.total_time = total_time; + dc->seekable = seekable; + dc->total_time = total_time; - dc.state = DECODE_STATE_DECODE; - notify_signal(&pc.notify); + decoder_lock(dc); + dc->state = DECODE_STATE_DECODE; + decoder_unlock(dc); - g_debug("audio_format=%u:%u:%u, seekable=%s", - dc.in_audio_format.sample_rate, dc.in_audio_format.bits, - dc.in_audio_format.channels, + player_lock_signal(); + + g_debug("audio_format=%s, seekable=%s", + audio_format_to_string(&dc->in_audio_format, &af_string), seekable ? "true" : "false"); - if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) - g_debug("converting to %u:%u:%u", - dc.out_audio_format.sample_rate, - dc.out_audio_format.bits, - dc.out_audio_format.channels); + if (!audio_format_equals(&dc->in_audio_format, + &dc->out_audio_format)) + g_debug("converting to %s", + audio_format_to_string(&dc->out_audio_format, + &af_string)); } char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder) { - assert(dc.pipe != NULL); + const struct decoder_control *dc = decoder->dc; + + assert(dc->pipe != NULL); - return song_get_uri(dc.current_song); + return song_get_uri(dc->song); } enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder) { - assert(dc.pipe != NULL); + const struct decoder_control *dc = decoder->dc; - return dc.command; + assert(dc->pipe != NULL); + + return dc->command; } -void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder) +void +decoder_command_finished(struct decoder *decoder) { - assert(dc.command != DECODE_COMMAND_NONE); - assert(dc.command != DECODE_COMMAND_SEEK || - dc.seek_error || decoder->seeking); - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; + + decoder_lock(dc); + + assert(dc->command != DECODE_COMMAND_NONE); + assert(dc->command != DECODE_COMMAND_SEEK || + dc->seek_error || decoder->seeking); + assert(dc->pipe != NULL); if (decoder->seeking) { decoder->seeking = false; @@ -99,33 +113,41 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder) /* delete frames from the old song position */ if (decoder->chunk != NULL) { - music_buffer_return(dc.buffer, decoder->chunk); + music_buffer_return(dc->buffer, decoder->chunk); decoder->chunk = NULL; } - music_pipe_clear(dc.pipe, dc.buffer); + music_pipe_clear(dc->pipe, dc->buffer); + + decoder->timestamp = dc->seek_where; } - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + dc->command = DECODE_COMMAND_NONE; + decoder_unlock(dc); + + player_lock_signal(); } double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) { - assert(dc.command == DECODE_COMMAND_SEEK); - assert(dc.pipe != NULL); + const struct decoder_control *dc = decoder->dc; + + assert(dc->command == DECODE_COMMAND_SEEK); + assert(dc->pipe != NULL); decoder->seeking = true; - return dc.seek_where; + return dc->seek_where; } void decoder_seek_error(struct decoder * decoder) { - assert(dc.command == DECODE_COMMAND_SEEK); - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; - dc.seek_error = true; + assert(dc->command == DECODE_COMMAND_SEEK); + assert(dc->pipe != NULL); + + dc->seek_error = true; decoder->seeking = false; decoder_command_finished(decoder); @@ -135,11 +157,14 @@ size_t decoder_read(struct decoder *decoder, struct input_stream *is, void *buffer, size_t length) { + const struct decoder_control *dc = + decoder != NULL ? decoder->dc : NULL; + GError *error = NULL; size_t nbytes; assert(decoder == NULL || - dc.state == DECODE_STATE_START || - dc.state == DECODE_STATE_DECODE); + dc->state == DECODE_STATE_START || + dc->state == DECODE_STATE_DECODE); assert(is != NULL); assert(buffer != NULL); @@ -152,12 +177,19 @@ size_t decoder_read(struct decoder *decoder, /* ignore the SEEK command during initialization, the plugin should handle that after it has initialized successfully */ - (dc.command != DECODE_COMMAND_SEEK || - (dc.state != DECODE_STATE_START && !decoder->seeking)) && - dc.command != DECODE_COMMAND_NONE) + (dc->command != DECODE_COMMAND_SEEK || + (dc->state != DECODE_STATE_START && !decoder->seeking)) && + dc->command != DECODE_COMMAND_NONE) + return 0; + + nbytes = input_stream_read(is, buffer, length, &error); + + if (G_UNLIKELY(nbytes == 0 && error != NULL)) { + g_warning("%s", error->message); + g_error_free(error); return 0; + } - nbytes = input_stream_read(is, buffer, length); if (nbytes > 0 || input_stream_eof(is)) return nbytes; @@ -167,6 +199,15 @@ size_t decoder_read(struct decoder *decoder, } } +void +decoder_timestamp(struct decoder *decoder, double t) +{ + assert(decoder != NULL); + assert(t >= 0); + + decoder->timestamp = t; +} + /** * Sends a #tag as-is to the music pipe. Flushes the current chunk * (decoder.chunk) if there is one. @@ -181,15 +222,15 @@ do_send_tag(struct decoder *decoder, struct input_stream *is, /* there is a partial chunk - flush it, we want the tag in a new chunk */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); } assert(decoder->chunk == NULL); chunk = decoder_get_chunk(decoder, is); if (chunk == NULL) { - assert(dc.command != DECODE_COMMAND_NONE); - return dc.command; + assert(decoder->dc->command != DECODE_COMMAND_NONE); + return decoder->dc->command; } chunk->tag = tag_dup(tag); @@ -225,31 +266,35 @@ enum decoder_command decoder_data(struct decoder *decoder, struct input_stream *is, const void *_data, size_t length, - float data_time, uint16_t bitRate, + uint16_t kbit_rate, struct replay_gain_info *replay_gain_info) { + struct decoder_control *dc = decoder->dc; const char *data = _data; + GError *error = NULL; + enum decoder_command cmd; + + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); + assert(length % audio_format_frame_size(&dc->in_audio_format) == 0); - assert(dc.state == DECODE_STATE_DECODE); - assert(dc.pipe != NULL); - assert(length % audio_format_frame_size(&dc.in_audio_format) == 0); + decoder_lock(dc); + cmd = dc->command; + decoder_unlock(dc); - if (dc.command == DECODE_COMMAND_STOP || - dc.command == DECODE_COMMAND_SEEK || + if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK || length == 0) - return dc.command; + return cmd; /* send stream tags */ if (update_stream_tag(decoder, is)) { - enum decoder_command cmd; - if (decoder->decoder_tag != NULL) { /* merge with tag from decoder plugin */ struct tag *tag; - tag = tag_merge(decoder->stream_tag, - decoder->decoder_tag); + tag = tag_merge(decoder->decoder_tag, + decoder->stream_tag); cmd = do_send_tag(decoder, is, tag); tag_free(tag); } else @@ -260,17 +305,18 @@ decoder_data(struct decoder *decoder, return cmd; } - if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) { + if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) { data = pcm_convert(&decoder->conv_state, - &dc.in_audio_format, data, length, - &dc.out_audio_format, &length); - - /* under certain circumstances, pcm_convert() may - return an empty buffer - this condition should be - investigated further, but for now, do this check as - a workaround: */ - if (data == NULL) - return DECODE_COMMAND_NONE; + &dc->in_audio_format, data, length, + &dc->out_audio_format, &length, + &error); + if (data == NULL) { + /* the PCM conversion has failed - stop + playback, since we have no better way to + bail out */ + g_warning("%s", error->message); + return DECODE_COMMAND_STOP; + } } while (length > 0) { @@ -281,16 +327,18 @@ decoder_data(struct decoder *decoder, chunk = decoder_get_chunk(decoder, is); if (chunk == NULL) { - assert(dc.command != DECODE_COMMAND_NONE); - return dc.command; + assert(dc->command != DECODE_COMMAND_NONE); + return dc->command; } - dest = music_chunk_write(chunk, &dc.out_audio_format, - data_time, bitRate, &nbytes); + dest = music_chunk_write(chunk, &dc->out_audio_format, + decoder->timestamp - + dc->song->start_ms / 1000.0, + kbit_rate, &nbytes); if (dest == NULL) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); continue; } @@ -305,24 +353,30 @@ decoder_data(struct decoder *decoder, /* apply replay gain or normalization */ - if (replay_gain_info != NULL && - replay_gain_mode != REPLAY_GAIN_OFF) + if (replay_gain_mode != REPLAY_GAIN_OFF) replay_gain_apply(replay_gain_info, dest, nbytes, - &dc.out_audio_format); - else if (normalizationEnabled) - normalizeData(dest, nbytes, &dc.out_audio_format); + &dc->out_audio_format); /* expand the music pipe chunk */ - full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes); + full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); if (full) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); } data += nbytes; length -= nbytes; + + decoder->timestamp += (double)nbytes / + audio_format_time_to_size(&dc->in_audio_format); + + if (dc->song->end_ms > 0 && + decoder->timestamp >= dc->song->end_ms / 1000.0) + /* the end of this range has been reached: + stop decoding */ + return DECODE_COMMAND_STOP; } return DECODE_COMMAND_NONE; @@ -332,10 +386,11 @@ enum decoder_command decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, const struct tag *tag) { + G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc; enum decoder_command cmd; - assert(dc.state == DECODE_STATE_DECODE); - assert(dc.pipe != NULL); + assert(dc->state == DECODE_STATE_DECODE); + assert(dc->pipe != NULL); assert(tag != NULL); /* save the tag */ diff --git a/src/decoder_api.h b/src/decoder_api.h index 37090d8d0..9df6bed19 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -17,15 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_API_H -#define MPD_DECODER_API_H - -/* +/*! \file + * \brief The MPD Decoder API + * * This is the public API which is used by decoder plugins to * communicate with the mpd core. - * */ +#ifndef MPD_DECODER_API_H +#define MPD_DECODER_API_H + +#include "check.h" #include "decoder_command.h" #include "decoder_plugin.h" #include "input_stream.h" @@ -36,65 +38,119 @@ #include <stdbool.h> - /** * Notify the player thread that it has finished initialization and * that it has read the song's meta data. + * + * @param decoder the decoder object + * @param audio_format the audio format which is going to be sent to + * decoder_data() + * @param seekable true if the song is seekable + * @param total_time the total number of seconds in this song; -1 if unknown */ -void decoder_initialized(struct decoder * decoder, - const struct audio_format *audio_format, - bool seekable, float total_time); +void +decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + bool seekable, float total_time); /** * Returns the URI of the current song in UTF-8 encoding. * - * The return value is allocated on the heap, and must be freed by the - * caller. + * @param decoder the decoder object + * @return an allocated string which must be freed with g_free() */ -char *decoder_get_uri(struct decoder *decoder); +char * +decoder_get_uri(struct decoder *decoder); -enum decoder_command decoder_get_command(struct decoder * decoder); +/** + * Determines the pending decoder command. + * + * @param decoder the decoder object + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending + */ +enum decoder_command +decoder_get_command(struct decoder *decoder); /** * Called by the decoder when it has performed the requested command * (dc->command). This function resets dc->command and wakes up the * player thread. + * + * @param decoder the decoder object */ -void decoder_command_finished(struct decoder * decoder); +void +decoder_command_finished(struct decoder *decoder); -double decoder_seek_where(struct decoder * decoder); +/** + * Call this when you have received the DECODE_COMMAND_SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the week + */ +double +decoder_seek_where(struct decoder *decoder); + +/** + * Call this right before decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +void +decoder_seek_error(struct decoder *decoder); -void decoder_seek_error(struct decoder * decoder); +/** + * Blocking read from the input stream. + * + * @param decoder the decoder object + * @param is the input stream to read from + * @param buffer the destination buffer + * @param length the maximum number of bytes to read + * @return the number of bytes read, or 0 if one of the following + * occurs: end of file; error; command (like SEEK or STOP). + */ +size_t +decoder_read(struct decoder *decoder, struct input_stream *is, + void *buffer, size_t length); /** - * Blocking read from the input stream. Returns the number of bytes - * read, or 0 if one of the following occurs: end of file; error; - * command (like SEEK or STOP). + * Sets the time stamp for the next data chunk [seconds]. The MPD + * core automatically counts it up, and a decoder plugin only needs to + * use this function if it thinks that adding to the time stamp based + * on the buffer size won't work. */ -size_t decoder_read(struct decoder *decoder, - struct input_stream *inStream, - void *buffer, size_t length); +void +decoder_timestamp(struct decoder *decoder, double t); /** * This function is called by the decoder plugin when it has * successfully decoded block of input data. * - * We send inStream for buffering the inputStream while waiting to - * send the next chunk + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param data the source buffer + * @param length the number of bytes in the buffer + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ enum decoder_command -decoder_data(struct decoder *decoder, - struct input_stream *inStream, - const void *data, size_t datalen, - float data_time, uint16_t bitRate, +decoder_data(struct decoder *decoder, struct input_stream *is, + const void *data, size_t length, + uint16_t kbit_rate, struct replay_gain_info *replay_gain_info); /** * This function is called by the decoder plugin when it has * successfully decoded a tag. * + * @param decoder the decoder object * @param is an input stream which is buffering while we are waiting * for the player + * @param tag the tag to send + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ enum decoder_command decoder_tag(struct decoder *decoder, struct input_stream *is, diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c index b6fa90004..a313eacc5 100644 --- a/src/decoder_buffer.c +++ b/src/decoder_buffer.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_buffer.h" #include "decoder_api.h" diff --git a/src/decoder_control.c b/src/decoder_control.c index 44bb63e15..a26edd150 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -17,108 +17,133 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_control.h" +#include "player_control.h" #include <assert.h> -struct decoder_control dc; +void +dc_init(struct decoder_control *dc) +{ + dc->thread = NULL; -void dc_init(void) + dc->mutex = g_mutex_new(); + dc->cond = g_cond_new(); + + dc->state = DECODE_STATE_STOP; + dc->command = DECODE_COMMAND_NONE; +} + +void +dc_deinit(struct decoder_control *dc) { - notify_init(&dc.notify); - dc.state = DECODE_STATE_STOP; - dc.command = DECODE_COMMAND_NONE; + g_cond_free(dc->cond); + g_mutex_free(dc->mutex); } -void dc_deinit(void) +static void +dc_command_wait_locked(struct decoder_control *dc) { - notify_deinit(&dc.notify); + while (dc->command != DECODE_COMMAND_NONE) + player_wait_decoder(dc); } void -dc_command_wait(struct notify *notify) +dc_command_wait(struct decoder_control *dc) { - while (dc.command != DECODE_COMMAND_NONE) { - notify_signal(&dc.notify); - notify_wait(notify); - } + decoder_lock(dc); + dc_command_wait_locked(dc); + decoder_unlock(dc); } static void -dc_command(struct notify *notify, enum decoder_command cmd) +dc_command_locked(struct decoder_control *dc, enum decoder_command cmd) { - dc.command = cmd; - dc_command_wait(notify); + dc->command = cmd; + decoder_signal(dc); + dc_command_wait_locked(dc); } -static void dc_command_async(enum decoder_command cmd) +static void +dc_command(struct decoder_control *dc, enum decoder_command cmd) { - dc.command = cmd; - notify_signal(&dc.notify); + decoder_lock(dc); + dc_command_locked(dc, cmd); + decoder_unlock(dc); } -void -dc_start(struct notify *notify, struct song *song) +static void +dc_command_async(struct decoder_control *dc, enum decoder_command cmd) { - assert(dc.pipe != NULL); - assert(song != NULL); + decoder_lock(dc); - dc.next_song = song; - dc_command(notify, DECODE_COMMAND_START); + dc->command = cmd; + decoder_signal(dc); + + decoder_unlock(dc); } void -dc_start_async(struct song *song) +dc_start(struct decoder_control *dc, struct song *song, + struct music_buffer *buffer, struct music_pipe *pipe) { - assert(dc.pipe != NULL); assert(song != NULL); + assert(buffer != NULL); + assert(pipe != NULL); - dc.next_song = song; - dc_command_async(DECODE_COMMAND_START); + dc->song = song; + dc->buffer = buffer; + dc->pipe = pipe; + dc_command(dc, DECODE_COMMAND_START); } void -dc_stop(struct notify *notify) +dc_stop(struct decoder_control *dc) { - if (dc.command != DECODE_COMMAND_NONE) + decoder_lock(dc); + + if (dc->command != DECODE_COMMAND_NONE) /* Attempt to cancel the current command. If it's too late and the decoder thread is already executing the old command, we'll call STOP again in this function (see below). */ - dc_command(notify, DECODE_COMMAND_STOP); + dc_command_locked(dc, DECODE_COMMAND_STOP); + + if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR) + dc_command_locked(dc, DECODE_COMMAND_STOP); - if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR) - dc_command(notify, DECODE_COMMAND_STOP); + decoder_unlock(dc); } bool -dc_seek(struct notify *notify, double where) +dc_seek(struct decoder_control *dc, double where) { - assert(dc.state != DECODE_STATE_START); + assert(dc->state != DECODE_STATE_START); assert(where >= 0.0); - if (dc.state == DECODE_STATE_STOP || - dc.state == DECODE_STATE_ERROR || !dc.seekable) + if (dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR || !dc->seekable) return false; - dc.seek_where = where; - dc.seek_error = false; - dc_command(notify, DECODE_COMMAND_SEEK); + dc->seek_where = where; + dc->seek_error = false; + dc_command(dc, DECODE_COMMAND_SEEK); - if (dc.seek_error) + if (dc->seek_error) return false; return true; } void -dc_quit(void) +dc_quit(struct decoder_control *dc) { - assert(dc.thread != NULL); + assert(dc->thread != NULL); - dc.quit = true; - dc_command_async(DECODE_COMMAND_STOP); + dc->quit = true; + dc_command_async(dc, DECODE_COMMAND_STOP); - g_thread_join(dc.thread); - dc.thread = NULL; + g_thread_join(dc->thread); + dc->thread = NULL; } diff --git a/src/decoder_control.h b/src/decoder_control.h index 6a04a1617..38c4f0d83 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -22,7 +22,8 @@ #include "decoder_command.h" #include "audio_format.h" -#include "notify.h" + +#include <glib.h> #include <assert.h> @@ -45,14 +46,25 @@ struct decoder_control { thread isn't running */ GThread *thread; - struct notify notify; + /** + * This lock protects #state and #command. + */ + GMutex *mutex; + + /** + * Trigger this object after you have modified #command. This + * is also used by the decoder thread to notify the caller + * when it has finished a command. + */ + GCond *cond; + + enum decoder_state state; + enum decoder_command command; - volatile enum decoder_state state; - volatile enum decoder_command command; bool quit; bool seek_error; bool seekable; - volatile double seek_where; + double seek_where; /** the format of the song file */ struct audio_format in_audio_format; @@ -60,54 +72,139 @@ struct decoder_control { /** the format being sent to the music pipe */ struct audio_format out_audio_format; - struct song *current_song; - struct song *next_song; + /** + * The song currently being decoded. This attribute is set by + * the player thread, when it sends the #DECODE_COMMAND_START + * command. + */ + const struct song *song; + float total_time; /** the #music_chunk allocator */ struct music_buffer *buffer; - /** the destination pipe for decoded chunks */ + /** + * The destination pipe for decoded chunks. The caller thread + * owns this object, and is responsible for freeing it. + */ struct music_pipe *pipe; }; -extern struct decoder_control dc; +void +dc_init(struct decoder_control *dc); + +void +dc_deinit(struct decoder_control *dc); + +/** + * Locks the #decoder_control object. + */ +static inline void +decoder_lock(struct decoder_control *dc) +{ + g_mutex_lock(dc->mutex); +} + +/** + * Unlocks the #decoder_control object. + */ +static inline void +decoder_unlock(struct decoder_control *dc) +{ + g_mutex_unlock(dc->mutex); +} + +/** + * Waits for a signal on the #decoder_control object. This function + * is only valid in the decoder thread. The object must be locked + * prior to calling this function. + */ +static inline void +decoder_wait(struct decoder_control *dc) +{ + g_cond_wait(dc->cond, dc->mutex); +} + +/** + * Signals the #decoder_control object. This function is only valid + * in the player thread. The object should be locked prior to calling + * this function. + */ +static inline void +decoder_signal(struct decoder_control *dc) +{ + g_cond_signal(dc->cond); +} + +static inline bool +decoder_is_idle(const struct decoder_control *dc) +{ + return dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR; +} + +static inline bool +decoder_is_starting(const struct decoder_control *dc) +{ + return dc->state == DECODE_STATE_START; +} -void dc_init(void); +static inline bool +decoder_has_failed(const struct decoder_control *dc) +{ + assert(dc->command == DECODE_COMMAND_NONE); -void dc_deinit(void); + return dc->state == DECODE_STATE_ERROR; +} -static inline bool decoder_is_idle(void) +static inline bool +decoder_lock_is_idle(struct decoder_control *dc) { - return (dc.state == DECODE_STATE_STOP || - dc.state == DECODE_STATE_ERROR) && - dc.command != DECODE_COMMAND_START; + bool ret; + + decoder_lock(dc); + ret = decoder_is_idle(dc); + decoder_unlock(dc); + + return ret; } -static inline bool decoder_is_starting(void) +static inline bool +decoder_lock_is_starting(struct decoder_control *dc) { - return dc.command == DECODE_COMMAND_START || - dc.state == DECODE_STATE_START; + bool ret; + + decoder_lock(dc); + ret = decoder_is_starting(dc); + decoder_unlock(dc); + + return ret; } -static inline bool decoder_has_failed(void) +static inline bool +decoder_lock_has_failed(struct decoder_control *dc) { - assert(dc.command == DECODE_COMMAND_NONE); + bool ret; + + decoder_lock(dc); + ret = decoder_has_failed(dc); + decoder_unlock(dc); - return dc.state == DECODE_STATE_ERROR; + return ret; } -static inline struct song * -decoder_current_song(void) +static inline const struct song * +decoder_current_song(const struct decoder_control *dc) { - switch (dc.state) { + switch (dc->state) { case DECODE_STATE_STOP: case DECODE_STATE_ERROR: return NULL; case DECODE_STATE_START: case DECODE_STATE_DECODE: - return dc.current_song; + return dc->song; } assert(false); @@ -115,21 +212,27 @@ decoder_current_song(void) } void -dc_command_wait(struct notify *notify); - -void -dc_start(struct notify *notify, struct song *song); +dc_command_wait(struct decoder_control *dc); +/** + * Start the decoder. + * + * @param the decoder + * @param song the song to be decoded + * @param pipe the pipe which receives the decoded chunks (owned by + * the caller) + */ void -dc_start_async(struct song *song); +dc_start(struct decoder_control *dc, struct song *song, + struct music_buffer *buffer, struct music_pipe *pipe); void -dc_stop(struct notify *notify); +dc_stop(struct decoder_control *dc); bool -dc_seek(struct notify *notify, double where); +dc_seek(struct decoder_control *dc, double where); void -dc_quit(void); +dc_quit(struct decoder_control *dc); #endif diff --git a/src/decoder_internal.c b/src/decoder_internal.c index 4a56fa5f3..b376a5755 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_internal.h" #include "decoder_control.h" #include "player_control.h" @@ -28,21 +29,45 @@ #include <assert.h> /** + * This is a wrapper for input_stream_buffer(). It assumes that the + * decoder is currently locked, and temporarily unlocks it while + * calling input_stream_buffer(). We shouldn't hold the lock during a + * potentially blocking operation. + */ +static bool +decoder_input_buffer(struct decoder_control *dc, struct input_stream *is) +{ + GError *error = NULL; + int ret; + + decoder_unlock(dc); + ret = input_stream_buffer(is, &error); + if (ret < 0) { + g_warning("%s", error->message); + g_error_free(error); + } + + decoder_lock(dc); + + return ret > 0; +} + +/** * All chunks are full of decoded data; wait for the player to free * one. */ static enum decoder_command -need_chunks(struct input_stream *is, bool do_wait) +need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait) { - if (dc.command == DECODE_COMMAND_STOP || - dc.command == DECODE_COMMAND_SEEK) - return dc.command; + if (dc->command == DECODE_COMMAND_STOP || + dc->command == DECODE_COMMAND_SEEK) + return dc->command; - if ((is == NULL || input_stream_buffer(is) <= 0) && do_wait) { - notify_wait(&dc.notify); - notify_signal(&pc.notify); + if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) { + decoder_wait(dc); + player_signal(); - return dc.command; + return dc->command; } return DECODE_COMMAND_NONE; @@ -51,6 +76,7 @@ need_chunks(struct input_stream *is, bool do_wait) struct music_chunk * decoder_get_chunk(struct decoder *decoder, struct input_stream *is) { + struct decoder_control *dc = decoder->dc; enum decoder_command cmd; assert(decoder != NULL); @@ -59,11 +85,13 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is) return decoder->chunk; do { - decoder->chunk = music_buffer_allocate(dc.buffer); + decoder->chunk = music_buffer_allocate(dc->buffer); if (decoder->chunk != NULL) return decoder->chunk; - cmd = need_chunks(is, true); + decoder_lock(dc); + cmd = need_chunks(dc, is, true); + decoder_unlock(dc); } while (cmd == DECODE_COMMAND_NONE); return NULL; @@ -72,13 +100,15 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is) void decoder_flush_chunk(struct decoder *decoder) { + struct decoder_control *dc = decoder->dc; + assert(decoder != NULL); assert(decoder->chunk != NULL); if (music_chunk_is_empty(decoder->chunk)) - music_buffer_return(dc.buffer, decoder->chunk); + music_buffer_return(dc->buffer, decoder->chunk); else - music_pipe_push(dc.pipe, decoder->chunk); + music_pipe_push(dc->pipe, decoder->chunk); decoder->chunk = NULL; } diff --git a/src/decoder_internal.h b/src/decoder_internal.h index cf54dbf6d..df558f4dd 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -26,8 +26,15 @@ struct input_stream; struct decoder { + struct decoder_control *dc; + struct pcm_convert_state conv_state; + /** + * The time stamp of the next data chunk, in seconds. + */ + double timestamp; + bool seeking; /** diff --git a/src/decoder_list.c b/src/decoder_list.c index a42585e34..c322bc433 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_list.h" #include "decoder_plugin.h" #include "utils.h" -#include "config.h" #include "conf.h" #include <glib.h> @@ -28,9 +28,11 @@ #include <string.h> extern const struct decoder_plugin mad_decoder_plugin; +extern const struct decoder_plugin mpg123_decoder_plugin; extern const struct decoder_plugin vorbis_decoder_plugin; extern const struct decoder_plugin flac_decoder_plugin; extern const struct decoder_plugin oggflac_decoder_plugin; +extern const struct decoder_plugin sndfile_decoder_plugin; extern const struct decoder_plugin audiofile_decoder_plugin; extern const struct decoder_plugin mp4ff_decoder_plugin; extern const struct decoder_plugin faad_decoder_plugin; @@ -43,10 +45,13 @@ extern const struct decoder_plugin wildmidi_decoder_plugin; extern const struct decoder_plugin fluidsynth_decoder_plugin; extern const struct decoder_plugin ffmpeg_decoder_plugin; -static const struct decoder_plugin *const decoder_plugins[] = { +const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_MAD &mad_decoder_plugin, #endif +#ifdef HAVE_MPG123 + &mpg123_decoder_plugin, +#endif #ifdef ENABLE_VORBIS_DECODER &vorbis_decoder_plugin, #endif @@ -56,6 +61,9 @@ static const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FLAC &flac_decoder_plugin, #endif +#ifdef ENABLE_SNDFILE + &sndfile_decoder_plugin, +#endif #ifdef HAVE_AUDIOFILE &audiofile_decoder_plugin, #endif @@ -89,32 +97,48 @@ static const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FFMPEG &ffmpeg_decoder_plugin, #endif + NULL }; enum { - num_decoder_plugins = G_N_ELEMENTS(decoder_plugins), + num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1, }; /** which plugins have been initialized successfully? */ -static bool decoder_plugins_enabled[num_decoder_plugins]; +bool decoder_plugins_enabled[num_decoder_plugins]; -const struct decoder_plugin * -decoder_plugin_from_suffix(const char *suffix, unsigned int next) +static unsigned +decoder_plugin_index(const struct decoder_plugin *plugin) { - static unsigned i = num_decoder_plugins; + unsigned i = 0; + while (decoder_plugins[i] != plugin) + ++i; + + return i; +} + +static unsigned +decoder_plugin_next_index(const struct decoder_plugin *plugin) +{ + return plugin == 0 + ? 0 /* start with first plugin */ + : decoder_plugin_index(plugin) + 1; +} + +const struct decoder_plugin * +decoder_plugin_from_suffix(const char *suffix, + const struct decoder_plugin *plugin) +{ if (suffix == NULL) return NULL; - if (!next) - i = 0; - for (; i < num_decoder_plugins; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; + for (unsigned i = decoder_plugin_next_index(plugin); + decoder_plugins[i] != NULL; ++i) { + plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i] && - stringFoundInStringArray(plugin->suffixes, suffix)) { - ++i; + decoder_plugin_supports_suffix(plugin, suffix)) return plugin; - } } return NULL; @@ -130,10 +154,10 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) if (!next) i = 0; - for (; i < num_decoder_plugins; ++i) { + for (; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i] && - stringFoundInStringArray(plugin->mime_types, mimeType)) { + decoder_plugin_supports_mime_type(plugin, mimeType)) { ++i; return plugin; } @@ -145,7 +169,7 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next) const struct decoder_plugin * decoder_plugin_from_name(const char *name) { - for (unsigned i = 0; i < num_decoder_plugins; ++i) { + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i] && strcmp(plugin->name, name) == 0) @@ -155,27 +179,6 @@ decoder_plugin_from_name(const char *name) return NULL; } -void decoder_plugin_print_all_decoders(FILE * fp) -{ - for (unsigned i = 0; i < num_decoder_plugins; ++i) { - const struct decoder_plugin *plugin = decoder_plugins[i]; - const char *const*suffixes; - - if (!decoder_plugins_enabled[i]) - continue; - - fprintf(fp, "[%s]", plugin->name); - - for (suffixes = plugin->suffixes; - suffixes != NULL && *suffixes != NULL; - ++suffixes) { - fprintf(fp, " %s", *suffixes); - } - - fprintf(fp, "\n"); - } -} - /** * Find the "decoder" configuration block for the specified plugin. * @@ -203,7 +206,7 @@ decoder_plugin_config(const char *plugin_name) void decoder_plugin_init_all(void) { - for (unsigned i = 0; i < num_decoder_plugins; ++i) { + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; const struct config_param *param = decoder_plugin_config(plugin->name); @@ -219,7 +222,7 @@ void decoder_plugin_init_all(void) void decoder_plugin_deinit_all(void) { - for (unsigned i = 0; i < num_decoder_plugins; ++i) { + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) { const struct decoder_plugin *plugin = decoder_plugins[i]; if (decoder_plugins_enabled[i]) diff --git a/src/decoder_list.h b/src/decoder_list.h index 23788189c..a5fe6b99f 100644 --- a/src/decoder_list.h +++ b/src/decoder_list.h @@ -20,14 +20,25 @@ #ifndef MPD_DECODER_LIST_H #define MPD_DECODER_LIST_H -#include <stdio.h> +#include <stdbool.h> struct decoder_plugin; +extern const struct decoder_plugin *const decoder_plugins[]; +extern bool decoder_plugins_enabled[]; + /* interface for using plugins */ +/** + * Find the next enabled decoder plugin which supports the specified suffix. + * + * @param suffix the file name suffix + * @param plugin the previous plugin, or NULL to find the first plugin + * @return a plugin, or NULL if none matches + */ const struct decoder_plugin * -decoder_plugin_from_suffix(const char *suffix, unsigned int next); +decoder_plugin_from_suffix(const char *suffix, + const struct decoder_plugin *plugin); const struct decoder_plugin * decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); @@ -35,8 +46,6 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next); const struct decoder_plugin * decoder_plugin_from_name(const char *name); -void decoder_plugin_print_all_decoders(FILE * fp); - /* this is where we "load" all the "plugins" ;-) */ void decoder_plugin_init_all(void); diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c new file mode 100644 index 000000000..b5966ff8d --- /dev/null +++ b/src/decoder_plugin.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_plugin.h" +#include "utils.h" + +#include <assert.h> + +bool +decoder_plugin_supports_suffix(const struct decoder_plugin *plugin, + const char *suffix) +{ + assert(plugin != NULL); + assert(suffix != NULL); + + return plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix); + +} + +bool +decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, + const char *mime_type) +{ + assert(plugin != NULL); + assert(mime_type != NULL); + + return plugin->mime_types != NULL && + string_array_contains(plugin->mime_types, mime_type); +} diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h index 66501a0a1..a078540a6 100644 --- a/src/decoder_plugin.h +++ b/src/decoder_plugin.h @@ -161,4 +161,18 @@ decoder_plugin_container_scan( const struct decoder_plugin *plugin, return plugin->container_scan(pathname, tnum); } +/** + * Does the plugin announce the specified file name suffix? + */ +bool +decoder_plugin_supports_suffix(const struct decoder_plugin *plugin, + const char *suffix); + +/** + * Does the plugin announce the specified MIME type? + */ +bool +decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin, + const char *mime_type); + #endif diff --git a/src/decoder_print.c b/src/decoder_print.c new file mode 100644 index 000000000..5dbb32803 --- /dev/null +++ b/src/decoder_print.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "decoder_print.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "client.h" + +#include <assert.h> + +static void +decoder_plugin_print(struct client *client, + const struct decoder_plugin *plugin) +{ + const char *const*p; + + assert(plugin != NULL); + assert(plugin->name != NULL); + + client_printf(client, "plugin: %s\n", plugin->name); + + if (plugin->suffixes != NULL) + for (p = plugin->suffixes; *p != NULL; ++p) + client_printf(client, "suffix: %s\n", *p); + + if (plugin->mime_types != NULL) + for (p = plugin->mime_types; *p != NULL; ++p) + client_printf(client, "mime_type: %s\n", *p); +} + +void +decoder_list_print(struct client *client) +{ + for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) + if (decoder_plugins_enabled[i]) + decoder_plugin_print(client, decoder_plugins[i]); +} diff --git a/src/decoder_print.h b/src/decoder_print.h new file mode 100644 index 000000000..6ba5dd081 --- /dev/null +++ b/src/decoder_print.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DECODER_PRINT_H +#define MPD_DECODER_PRINT_H + +struct client; + +void +decoder_list_print(struct client *client); + +#endif diff --git a/src/decoder_thread.c b/src/decoder_thread.c index d6ff058ec..9c6f3aab8 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_thread.h" #include "decoder_control.h" #include "decoder_internal.h" @@ -35,6 +36,63 @@ #include <unistd.h> +static enum decoder_command +decoder_lock_get_command(struct decoder_control *dc) +{ + enum decoder_command command; + + decoder_lock(dc); + command = dc->command; + decoder_unlock(dc); + + return command; +} + +/** + * Opens the input stream with input_stream_open(), and waits until + * the stream gets ready. If a decoder STOP command is received + * during that, it cancels the operation (but does not close the + * stream). + * + * Unlock the decoder before calling this function. + * + * @return true on success of if #DECODE_COMMAND_STOP is received, + * false on error + */ +static bool +decoder_input_stream_open(struct decoder_control *dc, + struct input_stream *is, const char *uri) +{ + GError *error = NULL; + + if (!input_stream_open(is, uri, &error)) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return false; + } + + /* wait for the input stream to become ready; its metadata + will be available then */ + + while (!is->ready && + decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) { + int ret; + + ret = input_stream_buffer(is, &error); + if (ret < 0) { + input_stream_close(is); + g_warning("%s", error->message); + g_error_free(error); + return false; + } + } + + return true; +} + static bool decoder_stream_decode(const struct decoder_plugin *plugin, struct decoder *decoder, @@ -47,17 +105,24 @@ decoder_stream_decode(const struct decoder_plugin *plugin, assert(decoder->decoder_tag == NULL); assert(input_stream != NULL); assert(input_stream->ready); - assert(dc.state == DECODE_STATE_START); + assert(decoder->dc->state == DECODE_STATE_START); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + decoder_unlock(decoder->dc); /* rewind the stream, so each plugin gets a fresh start */ - input_stream_seek(input_stream, 0, SEEK_SET); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); decoder_plugin_stream_decode(plugin, decoder, input_stream); - assert(dc.state == DECODE_STATE_START || - dc.state == DECODE_STATE_DECODE); + decoder_lock(decoder->dc); + + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); - return dc.state != DECODE_STATE_START; + return decoder->dc->state != DECODE_STATE_START; } static bool @@ -70,150 +135,201 @@ decoder_file_decode(const struct decoder_plugin *plugin, assert(decoder->stream_tag == NULL); assert(decoder->decoder_tag == NULL); assert(path != NULL); - assert(path[0] == '/'); - assert(dc.state == DECODE_STATE_START); + assert(g_path_is_absolute(path)); + assert(decoder->dc->state == DECODE_STATE_START); + + if (decoder->dc->command == DECODE_COMMAND_STOP) + return true; + + decoder_unlock(decoder->dc); decoder_plugin_file_decode(plugin, decoder, path); - assert(dc.state == DECODE_STATE_START || - dc.state == DECODE_STATE_DECODE); + decoder_lock(decoder->dc); + + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); - return dc.state != DECODE_STATE_START; + return decoder->dc->state != DECODE_STATE_START; } -static void decoder_run_song(const struct song *song, const char *uri) +/** + * Try decoding a stream, using plugins matching the stream's MIME type. + */ +static bool +decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is) { - struct decoder decoder; - int ret; - bool close_instream = true; - struct input_stream input_stream; const struct decoder_plugin *plugin; + unsigned int next = 0; - if (!input_stream_open(&input_stream, uri)) { - dc.state = DECODE_STATE_ERROR; - return; - } + if (is->mime == NULL) + return false; - decoder.seeking = false; - decoder.song_tag = song->tag != NULL && song_is_file(song) - ? tag_dup(song->tag) : NULL; - decoder.stream_tag = NULL; - decoder.decoder_tag = NULL; - decoder.chunk = NULL; + while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) + if (plugin->stream_decode != NULL && + decoder_stream_decode(plugin, decoder, is)) + return true; - dc.state = DECODE_STATE_START; - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + return false; +} - /* wait for the input stream to become ready; its metadata - will be available then */ +/** + * Try decoding a stream, using plugins matching the stream's URI + * suffix. + */ +static bool +decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is, + const char *uri) +{ + const char *suffix = uri_get_suffix(uri); + const struct decoder_plugin *plugin = NULL; - while (!input_stream.ready) { - if (dc.command == DECODE_COMMAND_STOP) { - input_stream_close(&input_stream); - dc.state = DECODE_STATE_STOP; - return; - } + if (suffix == NULL) + return false; - ret = input_stream_buffer(&input_stream); - if (ret < 0) { - input_stream_close(&input_stream); - dc.state = DECODE_STATE_ERROR; - return; - } - } + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) + if (plugin->stream_decode != NULL && + decoder_stream_decode(plugin, decoder, is)) + return true; - if (dc.command == DECODE_COMMAND_STOP) { - input_stream_close(&input_stream); - dc.state = DECODE_STATE_STOP; - return; - } + return false; +} - pcm_convert_init(&decoder.conv_state); +/** + * Try decoding a stream, using the fallback plugin. + */ +static bool +decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is) +{ + const struct decoder_plugin *plugin; + + plugin = decoder_plugin_from_name("mad"); + return plugin != NULL && plugin->stream_decode != NULL && + decoder_stream_decode(plugin, decoder, is); +} + +/** + * Try decoding a stream. + */ +static bool +decoder_run_stream(struct decoder *decoder, const char *uri) +{ + struct decoder_control *dc = decoder->dc; + struct input_stream input_stream; + bool success; - ret = false; - if (!song_is_file(song)) { - unsigned int next = 0; + decoder_unlock(dc); + if (!decoder_input_stream_open(dc, &input_stream, uri)) { + decoder_lock(dc); + return false; + } + + decoder_lock(dc); + + success = dc->command == DECODE_COMMAND_STOP || /* first we try mime types: */ - while ((plugin = decoder_plugin_from_mime_type(input_stream.mime, next++))) { - if (plugin->stream_decode == NULL) + decoder_run_stream_mime_type(decoder, &input_stream) || + /* if that fails, try suffix matching the URL: */ + decoder_run_stream_suffix(decoder, &input_stream, uri) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + decoder_run_stream_fallback(decoder, &input_stream); + + decoder_unlock(dc); + input_stream_close(&input_stream); + decoder_lock(dc); + + return success; +} + +/** + * Try decoding a file. + */ +static bool +decoder_run_file(struct decoder *decoder, const char *path_fs) +{ + struct decoder_control *dc = decoder->dc; + const char *suffix = uri_get_suffix(path_fs); + const struct decoder_plugin *plugin = NULL; + + if (suffix == NULL) + return false; + + decoder_unlock(dc); + + while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) { + if (plugin->file_decode != NULL) { + decoder_lock(dc); + + if (decoder_file_decode(plugin, decoder, path_fs)) + return true; + + decoder_unlock(dc); + } else if (plugin->stream_decode != NULL) { + struct input_stream input_stream; + bool success; + + if (!decoder_input_stream_open(dc, &input_stream, + path_fs)) continue; - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - if (ret) - break; - plugin = NULL; - } + decoder_lock(dc); - /* if that fails, try suffix matching the URL: */ - if (plugin == NULL) { - const char *s = uri_get_suffix(uri); - next = 0; - while ((plugin = decoder_plugin_from_suffix(s, next++))) { - if (plugin->stream_decode == NULL) - continue; - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - if (ret) - break; - - assert(dc.state == DECODE_STATE_START); - plugin = NULL; - } - } - /* fallback to mp3: */ - /* this is needed for bastard streams that don't have a suffix - or set the mimeType */ - if (plugin == NULL) { - /* we already know our mp3Plugin supports streams, no - * need to check for stream{Types,DecodeFunc} */ - if ((plugin = decoder_plugin_from_name("mad"))) { - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - } - } - } else { - unsigned int next = 0; - const char *s = uri_get_suffix(uri); - while ((plugin = decoder_plugin_from_suffix(s, next++))) { - if (plugin->file_decode != NULL) { - input_stream_close(&input_stream); - close_instream = false; - ret = decoder_file_decode(plugin, - &decoder, uri); - if (ret) - break; - } else if (plugin->stream_decode != NULL) { - if (!close_instream) { - /* the input_stream object has - been closed before - decoder_file_decode() - - reopen it */ - if (input_stream_open(&input_stream, uri)) - close_instream = true; - else - continue; - } - - ret = decoder_stream_decode(plugin, &decoder, - &input_stream); - if (ret) - break; + success = decoder_stream_decode(plugin, decoder, + &input_stream); + + decoder_unlock(dc); + + input_stream_close(&input_stream); + + if (success) { + decoder_lock(dc); + return true; } } } + decoder_lock(dc); + return false; +} + +static void +decoder_run_song(struct decoder_control *dc, + const struct song *song, const char *uri) +{ + struct decoder decoder = { + .dc = dc, + }; + int ret; + + decoder.timestamp = 0.0; + decoder.seeking = false; + decoder.song_tag = song->tag != NULL && song_is_file(song) + ? tag_dup(song->tag) : NULL; + decoder.stream_tag = NULL; + decoder.decoder_tag = NULL; + decoder.chunk = NULL; + + dc->state = DECODE_STATE_START; + dc->command = DECODE_COMMAND_NONE; + + player_signal(); + + pcm_convert_init(&decoder.conv_state); + + ret = song_is_file(song) + ? decoder_run_file(&decoder, uri) + : decoder_run_stream(&decoder, uri); + + decoder_unlock(dc); + pcm_convert_deinit(&decoder.conv_state); /* flush the last chunk */ if (decoder.chunk != NULL) decoder_flush_chunk(&decoder); - if (close_instream) - input_stream_close(&input_stream); - if (decoder.song_tag != NULL) tag_free(decoder.song_tag); @@ -223,66 +339,82 @@ static void decoder_run_song(const struct song *song, const char *uri) if (decoder.decoder_tag != NULL) tag_free(decoder.decoder_tag); - dc.state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; + decoder_lock(dc); + + dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR; } -static void decoder_run(void) +static void +decoder_run(struct decoder_control *dc) { - struct song *song = dc.next_song; + const struct song *song = dc->song; char *uri; + assert(song != NULL); + if (song_is_file(song)) uri = map_song_fs(song); else uri = song_get_uri(song); if (uri == NULL) { - dc.state = DECODE_STATE_ERROR; + dc->state = DECODE_STATE_ERROR; return; } - dc.current_song = dc.next_song; /* NEED LOCK */ - decoder_run_song(song, uri); + decoder_run_song(dc, song, uri); g_free(uri); } -static gpointer decoder_task(G_GNUC_UNUSED gpointer arg) +static gpointer +decoder_task(gpointer arg) { + struct decoder_control *dc = arg; + + decoder_lock(dc); + do { - assert(dc.state == DECODE_STATE_STOP || - dc.state == DECODE_STATE_ERROR); + assert(dc->state == DECODE_STATE_STOP || + dc->state == DECODE_STATE_ERROR); - switch (dc.command) { + switch (dc->command) { case DECODE_COMMAND_START: case DECODE_COMMAND_SEEK: - decoder_run(); + decoder_run(dc); + + dc->command = DECODE_COMMAND_NONE; - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + player_signal(); break; case DECODE_COMMAND_STOP: - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + dc->command = DECODE_COMMAND_NONE; + + player_signal(); break; case DECODE_COMMAND_NONE: - notify_wait(&dc.notify); + decoder_wait(dc); break; } - } while (dc.command != DECODE_COMMAND_NONE || !dc.quit); + } while (dc->command != DECODE_COMMAND_NONE || !dc->quit); + + decoder_unlock(dc); return NULL; } -void decoder_thread_start(void) +void +decoder_thread_start(struct decoder_control *dc) { GError *e = NULL; - assert(dc.thread == NULL); + assert(dc->thread == NULL); + + dc->quit = false; - dc.thread = g_thread_create(decoder_task, NULL, true, &e); - if (dc.thread == NULL) + dc->thread = g_thread_create(decoder_task, dc, true, &e); + if (dc->thread == NULL) g_error("Failed to spawn decoder task: %s", e->message); } diff --git a/src/decoder_thread.h b/src/decoder_thread.h index 50ed7116e..a25564b87 100644 --- a/src/decoder_thread.h +++ b/src/decoder_thread.h @@ -20,6 +20,9 @@ #ifndef MPD_DECODER_THREAD_H #define MPD_DECODER_THREAD_H -void decoder_thread_start(void); +struct decoder_control; + +void +decoder_thread_start(struct decoder_control *dc); #endif diff --git a/src/directory.c b/src/directory.c index ef8c038a3..62a297e14 100644 --- a/src/directory.c +++ b/src/directory.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "directory.h" #include "song.h" #include "path.h" diff --git a/src/directory.h b/src/directory.h index 8207bd3a2..c8789fbe3 100644 --- a/src/directory.h +++ b/src/directory.h @@ -20,6 +20,7 @@ #ifndef MPD_DIRECTORY_H #define MPD_DIRECTORY_H +#include "check.h" #include "dirvec.h" #include "songvec.h" diff --git a/src/directory_print.c b/src/directory_print.c index e0575e80f..8e86abf41 100644 --- a/src/directory_print.c +++ b/src/directory_print.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "directory_print.h" #include "directory.h" #include "client.h" diff --git a/src/directory_save.c b/src/directory_save.c index 132508447..0204e71e1 100644 --- a/src/directory_save.c +++ b/src/directory_save.c @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "directory_save.h" #include "directory.h" #include "song.h" -#include "path.h" +#include "text_file.h" #include "song_save.h" #include <assert.h> @@ -39,109 +40,137 @@ directory_quark(void) return g_quark_from_static_string("directory"); } -/* TODO error checking */ -int +void directory_save(FILE *fp, struct directory *directory) { struct dirvec *children = &directory->children; size_t i; - int retv; if (!directory_is_root(directory)) { fprintf(fp, DIRECTORY_MTIME "%lu\n", (unsigned long)directory->mtime); - retv = fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, - directory_get_path(directory)); - if (retv < 0) - return -1; + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, + directory_get_path(directory)); } for (i = 0; i < children->nr; ++i) { struct directory *cur = children->base[i]; char *base = g_path_get_basename(cur->path); - retv = fprintf(fp, DIRECTORY_DIR "%s\n", base); + fprintf(fp, DIRECTORY_DIR "%s\n", base); g_free(base); - if (retv < 0) - return -1; - if (directory_save(fp, cur) < 0) - return -1; + + directory_save(fp, cur); + + if (ferror(fp)) + return; } songvec_save(fp, &directory->songs); - if (!directory_is_root(directory) && - fprintf(fp, DIRECTORY_END "%s\n", - directory_get_path(directory)) < 0) - return -1; - return 0; + if (!directory_is_root(directory)) + fprintf(fp, DIRECTORY_END "%s\n", + directory_get_path(directory)); } -bool -directory_load(FILE *fp, struct directory *directory, GError **error) +static struct directory * +directory_load_subdir(FILE *fp, struct directory *parent, const char *name, + GString *buffer, GError **error_r) { - char buffer[MPD_PATH_MAX * 2]; - char key[MPD_PATH_MAX * 2]; - char *name; + struct directory *directory; + const char *line; bool success; - while (fgets(buffer, sizeof(buffer), fp) - && !g_str_has_prefix(buffer, DIRECTORY_END)) { - if (g_str_has_prefix(buffer, DIRECTORY_DIR)) { - struct directory *subdir; + if (directory_get_child(parent, name) != NULL) { + g_set_error(error_r, directory_quark(), 0, + "Duplicate subdirectory '%s'", name); + return NULL; + } - g_strchomp(buffer); - strcpy(key, &(buffer[strlen(DIRECTORY_DIR)])); - if (!fgets(buffer, sizeof(buffer), fp)) { - g_set_error(error, directory_quark(), 0, - "Unexpected end of file"); - return false; - } + if (directory_is_root(parent)) { + directory = directory_new(name, parent); + } else { + char *path = g_strconcat(directory_get_path(parent), "/", + name, NULL); + directory = directory_new(path, parent); + g_free(path); + } - if (g_str_has_prefix(buffer, DIRECTORY_MTIME)) { - directory->mtime = - g_ascii_strtoull(buffer + sizeof(DIRECTORY_MTIME) - 1, - NULL, 10); + line = read_text_line(fp, buffer); + if (line == NULL) { + g_set_error(error_r, directory_quark(), 0, + "Unexpected end of file"); + directory_free(directory); + return NULL; + } - if (!fgets(buffer, sizeof(buffer), fp)) { - g_set_error(error, directory_quark(), 0, - "Unexpected end of file"); - return false; - } - } + if (g_str_has_prefix(line, DIRECTORY_MTIME)) { + directory->mtime = + g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1, + NULL, 10); + + line = read_text_line(fp, buffer); + if (line == NULL) { + g_set_error(error_r, directory_quark(), 0, + "Unexpected end of file"); + directory_free(directory); + return NULL; + } + } - if (!g_str_has_prefix(buffer, DIRECTORY_BEGIN)) { - g_set_error(error, directory_quark(), 0, - "Malformed line: %s", buffer); - return false; - } + if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) { + g_set_error(error_r, directory_quark(), 0, + "Malformed line: %s", line); + directory_free(directory); + return NULL; + } - g_strchomp(buffer); - name = &(buffer[strlen(DIRECTORY_BEGIN)]); - if (!g_str_has_prefix(name, directory->path) != 0) { - g_set_error(error, directory_quark(), 0, - "Wrong path in database: '%s' in '%s'", - name, directory->path); + success = directory_load(fp, directory, buffer, error_r); + if (!success) { + directory_free(directory); + return NULL; + } + + return directory; +} + +bool +directory_load(FILE *fp, struct directory *directory, + GString *buffer, GError **error) +{ + const char *line; + + while ((line = read_text_line(fp, buffer)) != NULL && + !g_str_has_prefix(line, DIRECTORY_END)) { + if (g_str_has_prefix(line, DIRECTORY_DIR)) { + struct directory *subdir = + directory_load_subdir(fp, directory, + line + sizeof(DIRECTORY_DIR) - 1, + buffer, error); + if (subdir == NULL) return false; - } - subdir = directory_get_child(directory, name); - if (subdir != NULL) { - assert(subdir->parent == directory); - } else { - subdir = directory_new(name, directory); - dirvec_add(&directory->children, subdir); + dirvec_add(&directory->children, subdir); + } else if (g_str_has_prefix(line, SONG_BEGIN)) { + const char *name = line + sizeof(SONG_BEGIN) - 1; + struct song *song; + + if (songvec_find(&directory->songs, name) != NULL) { + g_set_error(error, directory_quark(), 0, + "Duplicate song '%s'", name); + return NULL; } - success = directory_load(fp, subdir, error); - if (!success) + song = song_load(fp, directory, name, + buffer, error); + if (song == NULL) return false; - } else if (g_str_has_prefix(buffer, SONG_BEGIN)) { - readSongInfoIntoList(fp, &directory->songs, directory); + + songvec_add(&directory->songs, song); } else { g_set_error(error, directory_quark(), 0, - "Malformed line: %s", buffer); + "Malformed line: %s", line); return false; } } diff --git a/src/directory_save.h b/src/directory_save.h index 28ec094ad..fa2775624 100644 --- a/src/directory_save.h +++ b/src/directory_save.h @@ -27,10 +27,11 @@ struct directory; -int +void directory_save(FILE *fp, struct directory *directory); bool -directory_load(FILE *fp, struct directory *directory, GError **error); +directory_load(FILE *fp, struct directory *directory, + GString *buffer, GError **error); #endif diff --git a/src/dirvec.c b/src/dirvec.c index 3ccb5d413..8061835d5 100644 --- a/src/dirvec.c +++ b/src/dirvec.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "dirvec.h" #include "directory.h" diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c new file mode 100644 index 000000000..395af5788 --- /dev/null +++ b/src/encoder/flac_encoder.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "audio_format.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> + +#include <FLAC/stream_encoder.h> + +struct flac_encoder { + struct encoder encoder; + + struct audio_format audio_format; + unsigned compression; + + FLAC__StreamEncoder *fse; + + struct pcm_buffer expand_buffer; + + struct pcm_buffer buffer; + size_t buffer_length; +}; + +extern const struct encoder_plugin flac_encoder_plugin; + + +static inline GQuark +flac_encoder_quark(void) +{ + return g_quark_from_static_string("flac_encoder"); +} + +static bool +flac_encoder_configure(struct flac_encoder *encoder, + const struct config_param *param, G_GNUC_UNUSED GError **error) +{ + encoder->compression = config_get_block_unsigned(param, + "compression", 5); + + return true; +} + +static struct encoder * +flac_encoder_init(const struct config_param *param, GError **error) +{ + struct flac_encoder *encoder; + + encoder = g_new(struct flac_encoder, 1); + encoder_struct_init(&encoder->encoder, &flac_encoder_plugin); + + /* load configuration from "param" */ + if (!flac_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + g_free(encoder); + return NULL; + } + + return &encoder->encoder; +} + +static void +flac_encoder_finish(struct encoder *_encoder) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + /* the real libFLAC cleanup was already performed by + flac_encoder_close(), so no real work here */ + g_free(encoder); +} + +static bool +flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample, + GError **error) +{ +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +#else + if ( !FLAC__stream_encoder_set_compression_level(encoder->fse, + encoder->compression)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac compression to %d", + encoder->compression); + return false; + } +#endif + if ( !FLAC__stream_encoder_set_channels(encoder->fse, + encoder->audio_format.channels)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac channels num to %d", + encoder->audio_format.channels); + return false; + } + if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse, + bits_per_sample)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac bit format to %d", + bits_per_sample); + return false; + } + if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse, + encoder->audio_format.sample_rate)) { + g_set_error(error, flac_encoder_quark(), 0, + "error setting flac sample rate to %d", + encoder->audio_format.sample_rate); + return false; + } + return true; +} + +static FLAC__StreamEncoderWriteStatus +flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse, + const FLAC__byte data[], +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 + unsigned bytes, +#else + size_t bytes, +#endif + G_GNUC_UNUSED unsigned samples, + G_GNUC_UNUSED unsigned current_frame, void *client_data) +{ + struct flac_encoder *encoder = (struct flac_encoder *) client_data; + + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + bytes); + + //transfer data to buffer + memcpy( buffer + encoder->buffer_length, data, bytes); + encoder->buffer_length += bytes; + + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + +static void +flac_encoder_close(struct encoder *_encoder) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + FLAC__stream_encoder_delete(encoder->fse); + + pcm_buffer_deinit(&encoder->buffer); + pcm_buffer_deinit(&encoder->expand_buffer); +} + +static bool +flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, + GError **error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + unsigned bits_per_sample; + + encoder->audio_format = *audio_format; + + /* FIXME: flac should support 32bit as well */ + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + bits_per_sample = 8; + break; + + case SAMPLE_FORMAT_S16: + bits_per_sample = 16; + break; + + case SAMPLE_FORMAT_S24_P32: + bits_per_sample = 24; + break; + + default: + bits_per_sample = 24; + audio_format->format = SAMPLE_FORMAT_S24_P32; + } + + /* allocate the encoder */ + encoder->fse = FLAC__stream_encoder_new(); + if (encoder->fse == NULL) { + g_set_error(error, flac_encoder_quark(), 0, + "flac_new() failed"); + return false; + } + + if (!flac_encoder_setup(encoder, bits_per_sample, error)) { + FLAC__stream_encoder_delete(encoder->fse); + return false; + } + + encoder->buffer_length = 0; + pcm_buffer_init(&encoder->buffer); + pcm_buffer_init(&encoder->expand_buffer); + + /* this immediatelly outputs data throught callback */ + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 + { + FLAC__StreamEncoderState init_status; + + FLAC__stream_encoder_set_write_callback(encoder->fse, + flac_write_callback); + + init_status = FLAC__stream_encoder_init(encoder->fse); + + if (init_status != FLAC__STREAM_ENCODER_OK) { + g_set_error(error, flac_encoder_quark(), 0, + "failed to initialize encoder: %s\n", + FLAC__StreamEncoderStateString[init_status]); + flac_encoder_close(_encoder); + return false; + } + } +#else + { + FLAC__StreamEncoderInitStatus init_status; + + init_status = FLAC__stream_encoder_init_stream(encoder->fse, + flac_write_callback, + NULL, NULL, NULL, encoder); + + if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + g_set_error(error, flac_encoder_quark(), 0, + "failed to initialize encoder: %s\n", + FLAC__StreamEncoderInitStatusString[init_status]); + flac_encoder_close(_encoder); + return false; + } + } +#endif + + return true; +} + + +static bool +flac_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + + (void) FLAC__stream_encoder_finish(encoder->fse); + return true; +} + +static inline void +pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples) +{ + while (num_samples > 0) { + *out++ = *in++; + --num_samples; + } +} + +static inline void +pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples) +{ + while (num_samples > 0) { + *out++ = *in++; + --num_samples; + } +} + +static bool +flac_encoder_write(struct encoder *_encoder, + const void *data, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + unsigned num_frames, num_samples; + void *exbuffer; + const void *buffer = NULL; + + /* format conversion */ + + num_frames = length / audio_format_frame_size(&encoder->audio_format); + num_samples = num_frames * encoder->audio_format.channels; + + switch (encoder->audio_format.format) { + case SAMPLE_FORMAT_S8: + exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*4); + pcm8_to_flac(exbuffer, data, num_samples); + buffer = exbuffer; + break; + + case SAMPLE_FORMAT_S16: + exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*2); + pcm16_to_flac(exbuffer, data, num_samples); + buffer = exbuffer; + break; + + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + /* nothing need to be done; format is the same for + both mpd and libFLAC */ + buffer = data; + break; + } + + /* feed samples to encoder */ + + if (!FLAC__stream_encoder_process_interleaved(encoder->fse, buffer, + num_frames)) { + g_set_error(error, flac_encoder_quark(), 0, + "flac encoder process failed"); + return false; + } + + return true; +} + +static size_t +flac_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct flac_encoder *encoder = (struct flac_encoder *)_encoder; + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length); + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, buffer, length); + + encoder->buffer_length -= length; + memmove(buffer, buffer + length, encoder->buffer_length); + + return length; +} + +static const char * +flac_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/flac"; +} + +const struct encoder_plugin flac_encoder_plugin = { + .name = "flac", + .init = flac_encoder_init, + .finish = flac_encoder_finish, + .open = flac_encoder_open, + .close = flac_encoder_close, + .flush = flac_encoder_flush, + .write = flac_encoder_write, + .read = flac_encoder_read, + .get_mime_type = flac_encoder_get_mime_type, +}; + diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c index acaf4470f..279a676d9 100644 --- a/src/encoder/lame_encoder.c +++ b/src/encoder/lame_encoder.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" #include "audio_format.h" @@ -184,7 +185,7 @@ lame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, { struct lame_encoder *encoder = (struct lame_encoder *)_encoder; - audio_format->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; audio_format->channels = 2; encoder->audio_format = *audio_format; @@ -274,6 +275,12 @@ lame_encoder_read(struct encoder *_encoder, void *dest, size_t length) return length; } +static const char * +lame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/mpeg"; +} + const struct encoder_plugin lame_encoder_plugin = { .name = "lame", .init = lame_encoder_init, @@ -282,4 +289,5 @@ const struct encoder_plugin lame_encoder_plugin = { .close = lame_encoder_close, .write = lame_encoder_write, .read = lame_encoder_read, + .get_mime_type = lame_encoder_get_mime_type, }; diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c new file mode 100644 index 000000000..3a73e3987 --- /dev/null +++ b/src/encoder/null_encoder.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> + +struct null_encoder { + struct encoder encoder; + + struct pcm_buffer buffer; + size_t buffer_length; +}; + +extern const struct encoder_plugin null_encoder_plugin; + +static inline GQuark +null_encoder_quark(void) +{ + return g_quark_from_static_string("null_encoder"); +} + +static struct encoder * +null_encoder_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + struct null_encoder *encoder; + + encoder = g_new(struct null_encoder, 1); + encoder_struct_init(&encoder->encoder, &null_encoder_plugin); + + return &encoder->encoder; +} + +static void +null_encoder_finish(struct encoder *_encoder) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + + g_free(encoder); +} + +static void +null_encoder_close(struct encoder *_encoder) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + + pcm_buffer_deinit(&encoder->buffer); +} + + +static bool +null_encoder_open(struct encoder *_encoder, + G_GNUC_UNUSED struct audio_format *audio_format, + G_GNUC_UNUSED GError **error) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + + encoder->buffer_length = 0; + pcm_buffer_init(&encoder->buffer); + + return true; +} + +static bool +null_encoder_write(struct encoder *_encoder, + const void *data, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length); + + memcpy(buffer+encoder->buffer_length, data, length); + + encoder->buffer_length += length; + return true; +} + +static size_t +null_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct null_encoder *encoder = (struct null_encoder *)_encoder; + char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length); + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, buffer, length); + + encoder->buffer_length -= length; + memmove(buffer, buffer + length, encoder->buffer_length); + + return length; +} + +const struct encoder_plugin null_encoder_plugin = { + .name = "null", + .init = null_encoder_init, + .finish = null_encoder_finish, + .open = null_encoder_open, + .close = null_encoder_close, + .write = null_encoder_write, + .read = null_encoder_read, +}; diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c new file mode 100644 index 000000000..5081db9fb --- /dev/null +++ b/src/encoder/twolame_encoder.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "audio_format.h" + +#include <twolame.h> +#include <assert.h> +#include <string.h> + +struct twolame_encoder { + struct encoder encoder; + + struct audio_format audio_format; + float quality; + int bitrate; + + twolame_options *options; + + unsigned char buffer[32768]; + size_t buffer_length; + + /** + * Call libtwolame's flush function when the buffer is empty? + */ + bool flush; +}; + +extern const struct encoder_plugin twolame_encoder_plugin; + +static inline GQuark +twolame_encoder_quark(void) +{ + return g_quark_from_static_string("twolame_encoder"); +} + +static bool +twolame_encoder_configure(struct twolame_encoder *encoder, + const struct config_param *param, GError **error) +{ + const char *value; + char *endptr; + + value = config_get_block_string(param, "quality", NULL); + if (value != NULL) { + /* a quality was configured (VBR) */ + + encoder->quality = g_ascii_strtod(value, &endptr); + + if (*endptr != '\0' || encoder->quality < -1.0 || + encoder->quality > 10.0) { + g_set_error(error, twolame_encoder_quark(), 0, + "quality \"%s\" is not a number in the " + "range -1 to 10, line %i", + value, param->line); + return false; + } + + if (config_get_block_string(param, "bitrate", NULL) != NULL) { + g_set_error(error, twolame_encoder_quark(), 0, + "quality and bitrate are " + "both defined (line %i)", + param->line); + return false; + } + } else { + /* a bit rate was configured */ + + value = config_get_block_string(param, "bitrate", NULL); + if (value == NULL) { + g_set_error(error, twolame_encoder_quark(), 0, + "neither bitrate nor quality defined " + "at line %i", + param->line); + return false; + } + + encoder->quality = -2.0; + encoder->bitrate = g_ascii_strtoll(value, &endptr, 10); + + if (*endptr != '\0' || encoder->bitrate <= 0) { + g_set_error(error, twolame_encoder_quark(), 0, + "bitrate at line %i should be a positive integer", + param->line); + return false; + } + } + + return true; +} + +static struct encoder * +twolame_encoder_init(const struct config_param *param, GError **error) +{ + struct twolame_encoder *encoder; + + g_debug("libtwolame version %s", get_twolame_version()); + + encoder = g_new(struct twolame_encoder, 1); + encoder_struct_init(&encoder->encoder, &twolame_encoder_plugin); + + /* load configuration from "param" */ + if (!twolame_encoder_configure(encoder, param, error)) { + /* configuration has failed, roll back and return error */ + g_free(encoder); + return NULL; + } + + return &encoder->encoder; +} + +static void +twolame_encoder_finish(struct encoder *_encoder) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + /* the real libtwolame cleanup was already performed by + twolame_encoder_close(), so no real work here */ + g_free(encoder); +} + +static bool +twolame_encoder_setup(struct twolame_encoder *encoder, GError **error) +{ + if (encoder->quality >= -1.0) { + /* a quality was configured (VBR) */ + + if (0 != twolame_set_VBR(encoder->options, true)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame VBR mode"); + return false; + } + if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame VBR quality"); + return false; + } + } else { + /* a bit rate was configured */ + + if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame bitrate"); + return false; + } + } + + if (0 != twolame_set_num_channels(encoder->options, + encoder->audio_format.channels)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame num channels"); + return false; + } + + if (0 != twolame_set_in_samplerate(encoder->options, + encoder->audio_format.sample_rate)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error setting twolame sample rate"); + return false; + } + + if (0 > twolame_init_params(encoder->options)) { + g_set_error(error, twolame_encoder_quark(), 0, + "error initializing twolame params"); + return false; + } + + return true; +} + +static bool +twolame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format, + GError **error) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + audio_format->format = SAMPLE_FORMAT_S16; + audio_format->channels = 2; + + encoder->audio_format = *audio_format; + + encoder->options = twolame_init(); + if (encoder->options == NULL) { + g_set_error(error, twolame_encoder_quark(), 0, + "twolame_init() failed"); + return false; + } + + if (!twolame_encoder_setup(encoder, error)) { + twolame_close(&encoder->options); + return false; + } + + encoder->buffer_length = 0; + encoder->flush = false; + + return true; +} + +static void +twolame_encoder_close(struct encoder *_encoder) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + twolame_close(&encoder->options); +} + +static bool +twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + encoder->flush = true; + return true; +} + +static bool +twolame_encoder_write(struct encoder *_encoder, + const void *data, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + unsigned num_frames; + const int16_t *src = (const int16_t*)data; + int bytes_out; + + assert(encoder->buffer_length == 0); + + num_frames = + length / audio_format_frame_size(&encoder->audio_format); + + bytes_out = twolame_encode_buffer_interleaved(encoder->options, + src, num_frames, + encoder->buffer, + sizeof(encoder->buffer)); + if (bytes_out < 0) { + g_set_error(error, twolame_encoder_quark(), 0, + "twolame encoder failed"); + return false; + } + + encoder->buffer_length = (size_t)bytes_out; + return true; +} + +static size_t +twolame_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder; + + if (encoder->buffer_length == 0 && encoder->flush) { + int ret = twolame_encode_flush(encoder->options, + encoder->buffer, + sizeof(encoder->buffer)); + if (ret > 0) + encoder->buffer_length = (size_t)ret; + + encoder->flush = false; + } + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, encoder->buffer, length); + + encoder->buffer_length -= length; + memmove(encoder->buffer, encoder->buffer + length, + encoder->buffer_length); + + return length; +} + +static const char * +twolame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/mpeg"; +} + +const struct encoder_plugin twolame_encoder_plugin = { + .name = "twolame", + .init = twolame_encoder_init, + .finish = twolame_encoder_finish, + .open = twolame_encoder_open, + .close = twolame_encoder_close, + .flush = twolame_encoder_flush, + .write = twolame_encoder_write, + .read = twolame_encoder_read, + .get_mime_type = twolame_encoder_get_mime_type, +}; diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c index a5f6387f6..8e118e90a 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/vorbis_encoder.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" #include "tag.h" @@ -211,7 +212,7 @@ vorbis_encoder_open(struct encoder *_encoder, struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; bool ret; - audio_format->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; encoder->audio_format = *audio_format; @@ -381,6 +382,12 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) return nbytes; } +static const char * +vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "application/x-ogg"; +} + const struct encoder_plugin vorbis_encoder_plugin = { .name = "vorbis", .init = vorbis_encoder_init, @@ -391,4 +398,5 @@ const struct encoder_plugin vorbis_encoder_plugin = { .tag = vorbis_encoder_tag, .write = vorbis_encoder_write, .read = vorbis_encoder_read, + .get_mime_type = vorbis_encoder_get_mime_type, }; diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c new file mode 100644 index 000000000..3f6e21845 --- /dev/null +++ b/src/encoder/wave_encoder.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "encoder_api.h" +#include "encoder_plugin.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> + +struct wave_encoder { + struct encoder encoder; + unsigned bits; + + struct pcm_buffer buffer; + size_t buffer_length; +}; + +struct wave_header { + uint32_t id_riff; + uint32_t riff_size; + uint32_t id_wave; + uint32_t id_fmt; + uint32_t fmt_size; + uint16_t format; + uint16_t channels; + uint32_t freq; + uint32_t byterate; + uint16_t blocksize; + uint16_t bits; + uint32_t id_data; + uint32_t data_size; +}; + +extern const struct encoder_plugin wave_encoder_plugin; + +static inline GQuark +wave_encoder_quark(void) +{ + return g_quark_from_static_string("wave_encoder"); +} + +static void +fill_wave_header(struct wave_header *header, int channels, int bits, + int freq, int block_size) +{ + int data_size = 0x0FFFFFFF; + + /* constants */ + header->id_riff = GUINT32_TO_LE(0x46464952); + header->id_wave = GUINT32_TO_LE(0x45564157); + header->id_fmt = GUINT32_TO_LE(0x20746d66); + header->id_data = GUINT32_TO_LE(0x61746164); + + /* wave format */ + header->format = GUINT16_TO_LE(1); // PCM_FORMAT + header->channels = GUINT16_TO_LE(channels); + header->bits = GUINT16_TO_LE(bits); + header->freq = GUINT32_TO_LE(freq); + header->blocksize = GUINT16_TO_LE(block_size); + header->byterate = GUINT32_TO_LE(freq * block_size); + + /* chunk sizes (fake data length) */ + header->fmt_size = GUINT32_TO_LE(16); + header->data_size = GUINT32_TO_LE(data_size); + header->riff_size = GUINT32_TO_LE(4 + (8 + 16) + + (8 + data_size)); +} + +static struct encoder * +wave_encoder_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + struct wave_encoder *encoder; + + encoder = g_new(struct wave_encoder, 1); + encoder_struct_init(&encoder->encoder, &wave_encoder_plugin); + pcm_buffer_init(&encoder->buffer); + + return &encoder->encoder; +} + +static void +wave_encoder_finish(struct encoder *_encoder) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + + pcm_buffer_deinit(&encoder->buffer); + g_free(encoder); +} + +static bool +wave_encoder_open(struct encoder *_encoder, + G_GNUC_UNUSED struct audio_format *audio_format, + G_GNUC_UNUSED GError **error) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + void *buffer; + + assert(audio_format_valid(audio_format)); + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + encoder->bits = 8; + break; + + case SAMPLE_FORMAT_S16: + encoder->bits = 16; + break; + + case SAMPLE_FORMAT_S24_P32: + encoder->bits = 24; + break; + + case SAMPLE_FORMAT_S32: + encoder->bits = 32; + break; + + default: + audio_format->format = SAMPLE_FORMAT_S16; + encoder->bits = 16; + break; + } + + buffer = pcm_buffer_get(&encoder->buffer, sizeof(struct wave_header) ); + + /* create PCM wave header in initial buffer */ + fill_wave_header((struct wave_header *) buffer, + audio_format->channels, + encoder->bits, + audio_format->sample_rate, + (encoder->bits / 8) * audio_format->channels ); + + encoder->buffer_length = sizeof(struct wave_header); + return true; +} + +static inline size_t +pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length) +{ + size_t cnt = length >> 1; + while (cnt > 0) { + *dst16++ = GUINT16_TO_LE(*src16++); + cnt--; + } + return length; +} + +static inline size_t +pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length) +{ + size_t cnt = length >> 2; + while (cnt > 0){ + *dst32++ = GUINT32_TO_LE(*src32++); + cnt--; + } + return length; +} + +static inline size_t +pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) +{ + uint32_t value; + uint8_t *dst_old = dst8; + + length = length >> 2; + while (length > 0){ + value = *src32++; + *dst8++ = (value) & 0xFF; + *dst8++ = (value >> 8) & 0xFF; + *dst8++ = (value >> 16) & 0xFF; + length--; + } + //correct buffer length + return (dst8 - dst_old); +} + +static bool +wave_encoder_write(struct encoder *_encoder, + const void *src, size_t length, + G_GNUC_UNUSED GError **error) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + void *dst; + + dst = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length); + +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) + switch (encoder->bits) { + case 8: + case 16: + case 32:// optimized cases + memcpy(dst, src, length); + break; + case 24: + length = pcm24_to_wave(dst, src, length); + break; + } +#elif (G_BYTE_ORDER == G_BIG_ENDIAN) + switch (encoder->bits) { + case 8: + memcpy(dst, src, length); + break; + case 16: + length = pcm16_to_wave(dst, src, length); + break; + case 24: + length = pcm24_to_wave(dst, src, length); + break; + case 32: + length = pcm32_to_wave(dst, src, length); + break; + } +#else +#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder +#endif + + encoder->buffer_length += length; + return true; +} + +static size_t +wave_encoder_read(struct encoder *_encoder, void *dest, size_t length) +{ + struct wave_encoder *encoder = (struct wave_encoder *)_encoder; + uint8_t *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length ); + + if (length > encoder->buffer_length) + length = encoder->buffer_length; + + memcpy(dest, buffer, length); + + encoder->buffer_length -= length; + memmove(buffer, buffer + length, encoder->buffer_length); + + return length; +} + +static const char * +wave_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder) +{ + return "audio/wav"; +} + +const struct encoder_plugin wave_encoder_plugin = { + .name = "wave", + .init = wave_encoder_init, + .finish = wave_encoder_finish, + .open = wave_encoder_open, + .write = wave_encoder_write, + .read = wave_encoder_read, + .get_mime_type = wave_encoder_get_mime_type, +}; diff --git a/src/encoder_list.c b/src/encoder_list.c index d563b6bc8..d86753d50 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -17,22 +17,36 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_list.h" #include "encoder_plugin.h" -#include "config.h" #include <string.h> +extern const struct encoder_plugin null_encoder_plugin; extern const struct encoder_plugin vorbis_encoder_plugin; extern const struct encoder_plugin lame_encoder_plugin; +extern const struct encoder_plugin twolame_encoder_plugin; +extern const struct encoder_plugin wave_encoder_plugin; +extern const struct encoder_plugin flac_encoder_plugin; static const struct encoder_plugin *encoder_plugins[] = { + &null_encoder_plugin, #ifdef ENABLE_VORBIS_ENCODER &vorbis_encoder_plugin, #endif #ifdef ENABLE_LAME_ENCODER &lame_encoder_plugin, #endif +#ifdef ENABLE_TWOLAME_ENCODER + &twolame_encoder_plugin, +#endif +#ifdef ENABLE_WAVE_ENCODER + &wave_encoder_plugin, +#endif +#ifdef ENABLE_FLAC_ENCODER + &flac_encoder_plugin, +#endif NULL }; @@ -45,3 +59,13 @@ encoder_plugin_get(const char *name) return NULL; } + +void +encoder_plugin_print_all_types(FILE * fp) +{ + for (unsigned i = 0; encoder_plugins[i] != NULL; ++i) + fprintf(fp, "%s ", encoder_plugins[i]->name); + + fprintf(fp, "\n"); + fflush(fp); +} diff --git a/src/encoder_list.h b/src/encoder_list.h index bc20ad8c5..26caab242 100644 --- a/src/encoder_list.h +++ b/src/encoder_list.h @@ -20,6 +20,8 @@ #ifndef MPD_ENCODER_LIST_H #define MPD_ENCODER_LIST_H +#include <stdio.h> + struct encoder_plugin; /** @@ -32,4 +34,7 @@ struct encoder_plugin; const struct encoder_plugin * encoder_plugin_get(const char *name); +void +encoder_plugin_print_all_types(FILE * fp); + #endif diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h index 958fe97cf..e8f2e4527 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -58,6 +58,8 @@ struct encoder_plugin { GError **error); size_t (*read)(struct encoder *encoder, void *dest, size_t length); + + const char *(*get_mime_type)(struct encoder *encoder); }; /** @@ -192,4 +194,19 @@ encoder_read(struct encoder *encoder, void *dest, size_t length) return encoder->plugin->read(encoder, dest, length); } +/** + * Get mime type of encoded content. + * + * @param plugin the encoder plugin + * @return an constant string, NULL on failure + */ +static inline const char * +encoder_get_mime_type(struct encoder *encoder) +{ + /* this method is optional */ + return encoder->plugin->get_mime_type != NULL + ? encoder->plugin->get_mime_type(encoder) + : NULL; +} + #endif diff --git a/src/event_pipe.c b/src/event_pipe.c index 3e5009150..dbec23aa8 100644 --- a/src/event_pipe.c +++ b/src/event_pipe.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "event_pipe.h" -#include "utils.h" +#include "fd_util.h" #include <stdbool.h> #include <assert.h> @@ -84,17 +85,9 @@ void event_pipe_init(void) GIOChannel *channel; int ret; -#ifdef WIN32 - ret = _pipe(event_pipe, 512, _O_BINARY); -#else - ret = pipe(event_pipe); -#endif + ret = pipe_cloexec_nonblock(event_pipe); if (ret < 0) g_error("Couldn't open pipe: %s", strerror(errno)); -#ifndef WIN32 - if (set_nonblocking(event_pipe[1]) < 0) - g_error("Couldn't set non-blocking I/O: %s", strerror(errno)); -#endif channel = g_io_channel_unix_new(event_pipe[0]); event_pipe_source_id = g_io_add_watch(channel, G_IO_IN, diff --git a/src/event_pipe.h b/src/event_pipe.h index ecb7ec9e8..4614ef25c 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -32,7 +32,7 @@ enum pipe_event { /** an idle event was emitted */ PIPE_EVENT_IDLE, - /** must call syncPlayerAndPlaylist() */ + /** must call playlist_sync() */ PIPE_EVENT_PLAYLIST, /** the current song's tag has changed */ @@ -41,6 +41,9 @@ enum pipe_event { /** SIGHUP received: reload configuration, roll log file */ PIPE_EVENT_RELOAD, + /** a hardware mixer plugin has detected a change */ + PIPE_EVENT_MIXER, + PIPE_EVENT_MAX }; diff --git a/src/exclude.c b/src/exclude.c new file mode 100644 index 000000000..5bf7ccbbd --- /dev/null +++ b/src/exclude.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#include "config.h" +#include "exclude.h" +#include "path.h" + +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +GSList * +exclude_list_load(const char *path_fs) +{ + FILE *file; + char line[1024]; + GSList *list = NULL; + + assert(path_fs != NULL); + + file = fopen(path_fs, "r"); + if (file == NULL) { + if (errno != ENOENT) { + char *path_utf8 = fs_charset_to_utf8(path_fs); + g_debug("Failed to open %s: %s", + path_utf8, g_strerror(errno)); + g_free(path_utf8); + } + + return NULL; + } + + while (fgets(line, sizeof(line), file) != NULL) { + char *p = strchr(line, '#'); + if (p != NULL) + *p = 0; + + p = g_strstrip(line); + if (*p != 0) + list = g_slist_prepend(list, g_pattern_spec_new(p)); + } + + fclose(file); + + return list; +} + +void +exclude_list_free(GSList *list) +{ + while (list != NULL) { + GPatternSpec *pattern = list->data; + g_pattern_spec_free(pattern); + list = g_slist_remove(list, list->data); + } +} + +bool +exclude_list_check(GSList *list, const char *name_fs) +{ + assert(name_fs != NULL); + + /* XXX include full path name in check */ + + for (; list != NULL; list = list->next) { + GPatternSpec *pattern = list->data; + + if (g_pattern_match_string(pattern, name_fs)) + return true; + } + + return false; +} diff --git a/src/exclude.h b/src/exclude.h new file mode 100644 index 000000000..637feb846 --- /dev/null +++ b/src/exclude.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * The .mpdignore backend code. + * + */ + +#ifndef MPD_EXCLUDE_H +#define MPD_EXCLUDE_H + +#include <glib.h> + +#include <stdbool.h> + +/** + * Loads and parses a .mpdignore file. + */ +GSList * +exclude_list_load(const char *path_fs); + +/** + * Frees a list returned by exclude_list_load(). + */ +void +exclude_list_free(GSList *list); + +/** + * Checks whether one of the patterns in the .mpdignore file matches + * the specified file name. + */ +bool +exclude_list_check(GSList *list, const char *name_fs); + +#endif diff --git a/src/fd_util.c b/src/fd_util.c new file mode 100644 index 000000000..9c60d00ae --- /dev/null +++ b/src/fd_util.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" /* must be first for large file support */ +#include "fd_util.h" + +#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4)) +#define _GNU_SOURCE +#endif + +#include <assert.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif + +#ifdef HAVE_INOTIFY_INIT +#include <sys/inotify.h> +#endif + +#ifndef WIN32 + +static int +fd_mask_flags(int fd, int and_mask, int xor_mask) +{ + int ret; + + assert(fd >= 0); + + ret = fcntl(fd, F_GETFD, 0); + if (ret < 0) + return ret; + + return fcntl(fd, F_SETFD, (ret & and_mask) ^ xor_mask); +} + +#endif /* !WIN32 */ + +static int +fd_set_cloexec(int fd, bool enable) +{ +#ifndef WIN32 + return fd_mask_flags(fd, ~FD_CLOEXEC, enable ? FD_CLOEXEC : 0); +#else + (void)fd; + (void)enable; +#endif +} + +/** + * Enables non-blocking mode for the specified file descriptor. On + * WIN32, this function only works for sockets. + */ +static int +fd_set_nonblock(int fd) +{ +#ifdef WIN32 + u_long val = 1; + return ioctlsocket(fd, FIONBIO, &val); +#else + int flags; + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return flags; + + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#endif +} + +int +open_cloexec(const char *path_fs, int flags, int mode) +{ + int fd; + +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif + +#ifdef O_NOCTTY + flags |= O_NOCTTY; +#endif + + fd = open(path_fs, flags, mode); + if (fd >= 0) + fd_set_cloexec(fd, true); + + return fd; +} + +int +pipe_cloexec(int fd[2]) +{ +#ifdef WIN32 + return _pipe(event_pipe, 512, _O_BINARY); +#else + int ret; + +#ifdef HAVE_PIPE2 + ret = pipe2(fd, O_CLOEXEC); + if (ret >= 0 || errno != ENOSYS) + return ret; +#endif + + ret = pipe(fd); + if (ret >= 0) { + fd_set_cloexec(fd[0], true); + fd_set_cloexec(fd[1], true); + } + + return ret; +#endif +} + +int +pipe_cloexec_nonblock(int fd[2]) +{ +#ifdef WIN32 + return _pipe(event_pipe, 512, _O_BINARY); +#else + int ret; + +#ifdef HAVE_PIPE2 + ret = pipe2(fd, O_CLOEXEC|O_NONBLOCK); + if (ret >= 0 || errno != ENOSYS) + return ret; +#endif + + ret = pipe(fd); + if (ret >= 0) { + fd_set_cloexec(fd[0], true); + fd_set_cloexec(fd[1], true); + + fd_set_nonblock(fd[0]); + fd_set_nonblock(fd[1]); + } + + return ret; +#endif +} + +int +socket_cloexec_nonblock(int domain, int type, int protocol) +{ + int fd; + +#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) + fd = socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol); + if (fd >= 0 || errno != EINVAL) + return fd; +#endif + + fd = socket(domain, type, protocol); + if (fd >= 0) { + fd_set_cloexec(fd, true); + fd_set_nonblock(fd); + } + + return fd; +} + +int +accept_cloexec_nonblock(int fd, struct sockaddr *address, + size_t *address_length_r) +{ + int ret; + socklen_t address_length = *address_length_r; + +#ifdef HAVE_ACCEPT4 + ret = accept4(fd, address, &address_length, + SOCK_CLOEXEC|SOCK_NONBLOCK); + if (ret >= 0 || errno != ENOSYS) { + if (ret >= 0) + *address_length_r = address_length; + + return ret; + } +#endif + + ret = accept(fd, address, &address_length); + if (ret >= 0) { + fd_set_cloexec(ret, true); + fd_set_nonblock(ret); + *address_length_r = address_length; + } + + return ret; +} + +#ifdef HAVE_INOTIFY_INIT + +int +inotify_init_cloexec(void) +{ + int fd; + +#ifdef HAVE_INOTIFY_INIT1 + fd = inotify_init1(IN_CLOEXEC); + if (fd >= 0 || errno != ENOSYS) + return fd; +#endif + + fd = inotify_init(); + if (fd >= 0) + fd_set_cloexec(fd, true); + + return fd; +} + +#endif diff --git a/src/fd_util.h b/src/fd_util.h new file mode 100644 index 000000000..57ad63288 --- /dev/null +++ b/src/fd_util.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This library provides easy helper functions for working with file + * descriptors. It has wrappers for taking advantage of Linux 2.6 + * specific features like O_CLOEXEC. + * + */ + +#ifndef FD_UTIL_H +#define FD_UTIL_H + +#include <stdbool.h> +#include <stddef.h> + +struct sockaddr; + +/** + * Wrapper for open(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + */ +int +open_cloexec(const char *path_fs, int flags, int mode); + +/** + * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + */ +int +pipe_cloexec(int fd[2]); + +/** + * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + * + * On systems that supports it (everybody except for Windows), it also + * sets the NONBLOCK flag. + */ +int +pipe_cloexec_nonblock(int fd[2]); + +/** + * Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag + * (atomically if supported by the OS). + */ +int +socket_cloexec_nonblock(int domain, int type, int protocol); + +/** + * Wrapper for accept(), which sets the CLOEXEC and the NONBLOCK flags + * (atomically if supported by the OS). + */ +int +accept_cloexec_nonblock(int fd, struct sockaddr *address, + size_t *address_length_r); + +/** + * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically + * if supported by the OS). + */ +int +inotify_init_cloexec(void); + +#endif diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c index adee438c0..ceff6e605 100644 --- a/src/fifo_buffer.c +++ b/src/fifo_buffer.c @@ -28,6 +28,7 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "config.h" #include "fifo_buffer.h" #include <glib.h> diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c new file mode 100644 index 000000000..c862ffe23 --- /dev/null +++ b/src/filter/autoconvert_filter_plugin.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/autoconvert_filter_plugin.h" +#include "filter/convert_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_convert.h" +#include "audio_format.h" +#include "poison.h" + +#include <assert.h> +#include <string.h> + +struct autoconvert_filter { + struct filter base; + + /** + * The audio format being fed to the underlying filter. This + * plugin actually doesn't need this variable, we have it here + * just so our open() method doesn't return a stack pointer. + */ + struct audio_format in_audio_format; + + /** + * The underlying filter. + */ + struct filter *filter; + + /** + * A convert_filter, just in case conversion is needed. NULL + * if unused. + */ + struct filter *convert; +}; + +static void +autoconvert_filter_finish(struct filter *_filter) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + filter_free(filter->filter); + g_free(filter); +} + +static const struct audio_format * +autoconvert_filter_open(struct filter *_filter, + struct audio_format *in_audio_format, + GError **error_r) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + const struct audio_format *out_audio_format; + + assert(audio_format_valid(in_audio_format)); + + /* open the "real" filter */ + + filter->in_audio_format = *in_audio_format; + + out_audio_format = filter_open(filter->filter, + &filter->in_audio_format, error_r); + if (out_audio_format == NULL) + return NULL; + + /* need to convert? */ + + if (!audio_format_equals(&filter->in_audio_format, in_audio_format)) { + /* yes - create a convert_filter */ + struct audio_format audio_format2 = *in_audio_format; + const struct audio_format *audio_format3; + + filter->convert = filter_new(&convert_filter_plugin, NULL, + error_r); + if (filter->convert == NULL) { + filter_close(filter->filter); + return NULL; + } + + audio_format3 = filter_open(filter->convert, &audio_format2, + error_r); + if (audio_format3 == NULL) { + filter_free(filter->convert); + filter_close(filter->filter); + return NULL; + } + + assert(audio_format_equals(&audio_format2, in_audio_format)); + + convert_filter_set(filter->convert, &filter->in_audio_format); + } else + /* no */ + filter->convert = NULL; + + return out_audio_format; +} + +static void +autoconvert_filter_close(struct filter *_filter) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + if (filter->convert != NULL) { + filter_close(filter->convert); + filter_free(filter->convert); + } + + filter_close(filter->filter); +} + +static const void * +autoconvert_filter_filter(struct filter *_filter, const void *src, + size_t src_size, size_t *dest_size_r, + GError **error_r) +{ + struct autoconvert_filter *filter = + (struct autoconvert_filter *)_filter; + + if (filter->convert != NULL) { + src = filter_filter(filter->convert, src, src_size, &src_size, + error_r); + if (src == NULL) + return NULL; + } + + return filter_filter(filter->filter, src, src_size, dest_size_r, + error_r); +} + +static const struct filter_plugin autoconvert_filter_plugin = { + .name = "convert", + .finish = autoconvert_filter_finish, + .open = autoconvert_filter_open, + .close = autoconvert_filter_close, + .filter = autoconvert_filter_filter, +}; + +struct filter * +autoconvert_filter_new(struct filter *_filter) +{ + struct autoconvert_filter *filter = + g_new(struct autoconvert_filter, 1); + + filter_init(&filter->base, &autoconvert_filter_plugin); + filter->filter = _filter; + + return &filter->base; +} diff --git a/src/buffer2array.h b/src/filter/autoconvert_filter_plugin.h index bed23a29f..cb98d6af2 100644 --- a/src/buffer2array.h +++ b/src/filter/autoconvert_filter_plugin.h @@ -17,15 +17,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_BUFFER_2_ARRAY_H -#define MPD_BUFFER_2_ARRAY_H +#ifndef AUTOCONVERT_FILTER_PLUGIN_H +#define AUTOCONVERT_FILTER_PLUGIN_H -/* tokenizes up to max elements in buffer (a null-terminated string) and - * stores the result in array (which must be capable of holding up to - * max elements). Tokenization is based on C string quoting rules. - * The arguments buffer and array are modified. - * Returns the number of elements tokenized. +struct filter; + +/** + * Creates a new "autoconvert" filter. When opened, it ensures that + * the input audio format isn't changed. If the underlying filter + * requests a different format, it automatically creates a + * convert_filter. */ -int buffer2array(char *buffer, char *array[], const int max); +struct filter * +autoconvert_filter_new(struct filter *filter); #endif diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c new file mode 100644 index 000000000..56fa3a5e1 --- /dev/null +++ b/src/filter/chain_filter_plugin.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "audio_format.h" + +#include <assert.h> + +struct filter_chain { + /** the base class */ + struct filter base; + + GSList *children; +}; + +static inline GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +static struct filter * +chain_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct filter_chain *chain = g_new(struct filter_chain, 1); + + filter_init(&chain->base, &chain_filter_plugin); + chain->children = NULL; + + return &chain->base; +} + +static void +chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct filter *filter = data; + + filter_free(filter); +} + +static void +chain_filter_finish(struct filter *_filter) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + + g_slist_foreach(chain->children, chain_free_child, NULL); + g_slist_free(chain->children); + + g_free(chain); +} + +/** + * Close all filters in the chain until #until is reached. #until + * itself is not closed. + */ +static void +chain_close_until(struct filter_chain *chain, const struct filter *until) +{ + GSList *i = chain->children; + struct filter *filter; + + while (true) { + /* this assertion fails if #until does not exist + (anymore) */ + assert(i != NULL); + + if (i->data == until) + /* don't close this filter */ + break; + + /* close this filter */ + filter = i->data; + filter_close(filter); + + i = g_slist_next(i); + } +} + +static const struct audio_format * +chain_open_child(struct filter *filter, + const struct audio_format *prev_audio_format, + GError **error_r) +{ + struct audio_format conv_audio_format = *prev_audio_format; + const struct audio_format *next_audio_format; + + next_audio_format = filter_open(filter, &conv_audio_format, error_r); + if (next_audio_format == NULL) + return NULL; + + if (!audio_format_equals(&conv_audio_format, prev_audio_format)) { + struct audio_format_string s; + + filter_close(filter); + g_set_error(error_r, filter_quark(), 0, + "Audio format not supported by filter '%s': %s", + filter->plugin->name, + audio_format_to_string(prev_audio_format, &s)); + return NULL; + } + + return next_audio_format; +} + +static const struct audio_format * +chain_filter_open(struct filter *_filter, struct audio_format *in_audio_format, + GError **error_r) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + const struct audio_format *audio_format = in_audio_format; + + for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { + struct filter *filter = i->data; + + audio_format = chain_open_child(filter, audio_format, error_r); + if (audio_format == NULL) { + /* rollback, close all children */ + chain_close_until(chain, filter); + return NULL; + } + } + + /* return the output format of the last filter */ + return audio_format; +} + +static void +chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct filter *filter = data; + + filter_close(filter); +} + +static void +chain_filter_close(struct filter *_filter) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + + g_slist_foreach(chain->children, chain_close_child, NULL); +} + +static const void * +chain_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct filter_chain *chain = (struct filter_chain *)_filter; + + for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) { + struct filter *filter = i->data; + + /* feed the output of the previous filter as input + into the current one */ + src = filter_filter(filter, src, src_size, &src_size, error_r); + if (src == NULL) + return NULL; + } + + /* return the output of the last filter */ + *dest_size_r = src_size; + return src; +} + +const struct filter_plugin chain_filter_plugin = { + .name = "chain", + .init = chain_filter_init, + .finish = chain_filter_finish, + .open = chain_filter_open, + .close = chain_filter_close, + .filter = chain_filter_filter, +}; + +struct filter * +filter_chain_new(void) +{ + struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL); + /* chain_filter_init() never fails */ + assert(filter != NULL); + + return filter; +} + +void +filter_chain_append(struct filter *_chain, struct filter *filter) +{ + struct filter_chain *chain = (struct filter_chain *)_chain; + + chain->children = g_slist_append(chain->children, filter); +} + diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h new file mode 100644 index 000000000..f8462b22d --- /dev/null +++ b/src/filter/chain_filter_plugin.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * A filter chain is a container for several filters. They are + * chained together, i.e. called in a row, one filter passing its + * output to the next one. + */ + +#ifndef MPD_FILTER_CHAIN_H +#define MPD_FILTER_CHAIN_H + +struct filter; + +/** + * Creates a new filter chain. + */ +struct filter * +filter_chain_new(void); + +/** + * Appends a new filter at the end of the filter chain. You must call + * this function before the first filter_open() call. + * + * @param chain the filter chain created with filter_chain_new() + * @param filter the filter to be appended to #chain + */ +void +filter_chain_append(struct filter *chain, struct filter *filter); + +#endif diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c new file mode 100644 index 000000000..87070fa3a --- /dev/null +++ b/src/filter/convert_filter_plugin.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/convert_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_convert.h" +#include "audio_format.h" +#include "poison.h" + +#include <assert.h> +#include <string.h> + +struct convert_filter { + struct filter base; + + /** + * The current convert, from 0 to #PCM_CONVERT_1. + */ + unsigned convert; + + /** + * The input audio format; PCM data is passed to the filter() + * method in this format. + */ + struct audio_format in_audio_format; + + /** + * The output audio format; the consumer of this plugin + * expects PCM data in this format. This defaults to + * #in_audio_format, and can be set with convert_filter_set(). + */ + struct audio_format out_audio_format; + + struct pcm_convert_state state; +}; + +static struct filter * +convert_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct convert_filter *filter = g_new(struct convert_filter, 1); + + filter_init(&filter->base, &convert_filter_plugin); + return &filter->base; +} + +static void +convert_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +convert_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + assert(audio_format_valid(audio_format)); + + filter->in_audio_format = filter->out_audio_format = *audio_format; + pcm_convert_init(&filter->state); + + return &filter->in_audio_format; +} + +static void +convert_filter_close(struct filter *_filter) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + pcm_convert_deinit(&filter->state); + + poison_undefined(&filter->in_audio_format, + sizeof(filter->in_audio_format)); + poison_undefined(&filter->out_audio_format, + sizeof(filter->out_audio_format)); +} + +static const void * +convert_filter_filter(struct filter *_filter, const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + const void *dest; + + if (audio_format_equals(&filter->in_audio_format, + &filter->out_audio_format)) { + /* optimized special case: no-op */ + *dest_size_r = src_size; + return src; + } + + dest = pcm_convert(&filter->state, &filter->in_audio_format, + src, src_size, + &filter->out_audio_format, dest_size_r, + error_r); + if (dest == NULL) + return NULL; + + return dest; +} + +const struct filter_plugin convert_filter_plugin = { + .name = "convert", + .init = convert_filter_init, + .finish = convert_filter_finish, + .open = convert_filter_open, + .close = convert_filter_close, + .filter = convert_filter_filter, +}; + +void +convert_filter_set(struct filter *_filter, + const struct audio_format *out_audio_format) +{ + struct convert_filter *filter = (struct convert_filter *)_filter; + + assert(filter != NULL); + assert(audio_format_valid(&filter->in_audio_format)); + assert(audio_format_valid(&filter->out_audio_format)); + assert(out_audio_format != NULL); + assert(audio_format_valid(out_audio_format)); + assert(filter->in_audio_format.reverse_endian == 0); + + filter->out_audio_format = *out_audio_format; +} diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h new file mode 100644 index 000000000..8d370b0cb --- /dev/null +++ b/src/filter/convert_filter_plugin.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef CONVERT_FILTER_PLUGIN_H +#define CONVERT_FILTER_PLUGIN_H + +struct filter; +struct audio_format; + +/** + * Sets the output audio format for the specified filter. You must + * call this after the filter has been opened. Since this audio + * format switch is a violation of the filter API, this filter must be + * the last in a chain. + */ +void +convert_filter_set(struct filter *filter, + const struct audio_format *out_audio_format); + +#endif diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c new file mode 100644 index 000000000..4a8bde51d --- /dev/null +++ b/src/filter/normalize_filter_plugin.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "pcm_buffer.h" +#include "audio_format.h" +#include "AudioCompress/compress.h" + +#include <assert.h> +#include <string.h> + +struct normalize_filter { + struct filter filter; + + struct Compressor *compressor; + + struct pcm_buffer buffer; +}; + +static inline GQuark +normalize_quark(void) +{ + return g_quark_from_static_string("normalize"); +} + +static struct filter * +normalize_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = g_new(struct normalize_filter, 1); + + filter_init(&filter->filter, &normalize_filter_plugin); + + return &filter->filter; +} + +static void +normalize_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +normalize_filter_open(struct filter *_filter, + struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + + audio_format->format = SAMPLE_FORMAT_S16; + audio_format->reverse_endian = false; + + filter->compressor = Compressor_new(0); + + pcm_buffer_init(&filter->buffer); + + return audio_format; +} + +static void +normalize_filter_close(struct filter *_filter) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); + Compressor_delete(filter->compressor); +} + +static const void * +normalize_filter_filter(struct filter *_filter, + const void *src, size_t src_size, size_t *dest_size_r, + G_GNUC_UNUSED GError **error_r) +{ + struct normalize_filter *filter = (struct normalize_filter *)_filter; + void *dest; + + dest = pcm_buffer_get(&filter->buffer, src_size); + + memcpy(dest, src, src_size); + + Compressor_Process_int16(filter->compressor, dest, src_size / 2); + + *dest_size_r = src_size; + return dest; +} + +const struct filter_plugin normalize_filter_plugin = { + .name = "normalize", + .init = normalize_filter_init, + .finish = normalize_filter_finish, + .open = normalize_filter_open, + .close = normalize_filter_close, + .filter = normalize_filter_filter, +}; diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c new file mode 100644 index 000000000..d677780dc --- /dev/null +++ b/src/filter/null_filter_plugin.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This filter plugin does nothing. That is not quite useful, except + * for testing the filter core, or as a template for new filter + * plugins. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + +#include <assert.h> + +struct null_filter { + struct filter filter; +}; + +static struct filter * +null_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct null_filter *filter = g_new(struct null_filter, 1); + + filter_init(&filter->filter, &null_filter_plugin); + return &filter->filter; +} + +static void +null_filter_finish(struct filter *_filter) +{ + struct null_filter *filter = (struct null_filter *)_filter; + + g_free(filter); +} + +static const struct audio_format * +null_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct null_filter *filter = (struct null_filter *)_filter; + (void)filter; + + return audio_format; +} + +static void +null_filter_close(struct filter *_filter) +{ + struct null_filter *filter = (struct null_filter *)_filter; + (void)filter; +} + +static const void * +null_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) +{ + struct null_filter *filter = (struct null_filter *)_filter; + (void)filter; + + /* return the unmodified source buffer */ + *dest_size_r = src_size; + return src; +} + +const struct filter_plugin null_filter_plugin = { + .name = "null", + .init = null_filter_init, + .finish = null_filter_finish, + .open = null_filter_open, + .close = null_filter_close, + .filter = null_filter_filter, +}; diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c new file mode 100644 index 000000000..d9ca149c8 --- /dev/null +++ b/src/filter/route_filter_plugin.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This filter copies audio data between channels. Useful for + * upmixing mono/stereo audio to surround speaker configurations. + * + * Its configuration consists of a "filter" section with a single + * "routes" entry, formatted as: \\ + * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\ + * where each pair of numbers signifies a set of channels. + * Each source>dest pair leads to the data from channel #source + * being copied to channel #dest in the output. + * + * Example: \\ + * routes "0>0, 1>1, 0>2, 1>3"\\ + * upmixes stereo audio to a 4-speaker system, copying the front-left + * (0) to front left (0) and rear left (2), copying front-right (1) to + * front-right (1) and rear-right (3). + * + * If multiple sources are copied to the same destination channel, only + * one of them takes effect. + */ + +#include "config.h" +#include "conf.h" +#include "audio_format.h" +#include "audio_check.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "pcm_buffer.h" + +#include <assert.h> +#include <string.h> +#include <stdlib.h> + + +struct route_filter { + + /** + * Inherit (and support cast to/from) filter + */ + struct filter base; + + /** + * The minimum number of channels we need for output + * to be able to perform all the copies the user has specified + */ + unsigned char min_output_channels; + + /** + * The minimum number of input channels we need to + * copy all the data the user has requested. If fewer + * than this many are supplied by the input, undefined + * copy operations are given zeroed sources in stead. + */ + unsigned char min_input_channels; + + /** + * The set of copy operations to perform on each sample + * The index is an output channel to use, the value is + * a corresponding input channel from which to take the + * data. A -1 means "no source" + */ + signed char* sources; + + /** + * The actual input format of our signal, once opened + */ + struct audio_format input_format; + + /** + * The decided upon output format, once opened + */ + struct audio_format output_format; + + /** + * The size, in bytes, of each multichannel frame in the + * input buffer + */ + size_t input_frame_size; + + /** + * The size, in bytes, of each multichannel frame in the + * output buffer + */ + size_t output_frame_size; + + /** + * The output buffer used last time around, can be reused if the size doesn't differ. + */ + struct pcm_buffer output_buffer; + +}; + +/** + * Parse the "routes" section, a string on the form + * a>b, c>d, e>f, ... + * where a... are non-unique, non-negative integers + * and input channel a gets copied to output channel b, etc. + * @param param the configuration block to read + * @param filter a route_filter whose min_channels and sources[] to set + * @return true on success, false on error + */ +static bool +route_filter_parse(const struct config_param *param, + struct route_filter *filter, + GError **error_r) { + + /* TODO: + * With a more clever way of marking "don't copy to output N", + * This could easily be merged into a single loop with some + * dynamic g_realloc() instead of one count run and one g_malloc(). + */ + + gchar **tokens; + int number_of_copies; + + // A cowardly default, just passthrough stereo + const char *routes = + config_get_block_string(param, "routes", "0>0, 1>1"); + + filter->min_input_channels = 0; + filter->min_output_channels = 0; + + tokens = g_strsplit(routes, ",", 255); + number_of_copies = g_strv_length(tokens); + + // Start by figuring out a few basic things about the routing set + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Squeeze whitespace + g_strstrip(tokens[c]); + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + // Keep track of the highest channel numbers seen + // as either in- or outputs + if (source >= filter->min_input_channels) + filter->min_input_channels = source + 1; + if (dest >= filter->min_output_channels) + filter->min_output_channels = dest + 1; + + g_strfreev(sd); + } + + if (!audio_valid_channel_count(filter->min_output_channels)) { + g_strfreev(tokens); + g_set_error(error_r, audio_format_quark(), 0, + "Invalid number of output channels requested: %d", + filter->min_output_channels); + return false; + } + + // Allocate a map of "copy nothing to me" + filter->sources = + g_malloc(filter->min_output_channels * sizeof(signed char)); + + for (int i=0; i<filter->min_output_channels; ++i) + filter->sources[i] = -1; + + // Run through the spec again, and save the + // actual mapping output <- input + for (int c=0; c<number_of_copies; ++c) { + + // String and int representations of the source/destination + gchar **sd; + int source, dest; + + // Split the a>b string into source and destination + sd = g_strsplit(tokens[c], ">", 2); + if (g_strv_length(sd) != 2) { + g_set_error(error_r, config_quark(), 1, + "Invalid copy around %d in routes spec: %s", + param->line, tokens[c]); + g_strfreev(sd); + g_strfreev(tokens); + return false; + } + + source = strtol(sd[0], NULL, 10); + dest = strtol(sd[1], NULL, 10); + + filter->sources[dest] = source; + + g_strfreev(sd); + } + + g_strfreev(tokens); + + return true; +} + +static struct filter * +route_filter_init(const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = g_new(struct route_filter, 1); + filter_init(&filter->base, &route_filter_plugin); + + // Allocate and set the filter->sources[] array + route_filter_parse(param, filter, error_r); + + return &filter->base; +} + +static void +route_filter_finish(struct filter *_filter) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + g_free(filter->sources); + g_free(filter); +} + +static const struct audio_format * +route_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + // Copy the input format for later reference + filter->input_format = *audio_format; + filter->input_frame_size = + audio_format_frame_size(&filter->input_format); + + // Decide on an output format which has enough channels, + // and is otherwise identical + filter->output_format = *audio_format; + filter->output_format.channels = filter->min_output_channels; + + // Precalculate this simple value, to speed up allocation later + filter->output_frame_size = + audio_format_frame_size(&filter->output_format); + + // This buffer grows as needed + pcm_buffer_init(&filter->output_buffer); + + return &filter->output_format; +} + +static void +route_filter_close(struct filter *_filter) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + pcm_buffer_deinit(&filter->output_buffer); +} + +static const void * +route_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, G_GNUC_UNUSED GError **error_r) +{ + struct route_filter *filter = (struct route_filter *)_filter; + + size_t number_of_frames = src_size / filter->input_frame_size; + + size_t bytes_per_frame_per_channel = + audio_format_sample_size(&filter->input_format); + + // A moving pointer that always refers to channel 0 in the input, at the currently handled frame + const uint8_t *base_source = src; + + // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output + uint8_t *chan_destination; + + // Grow our reusable buffer, if needed, and set the moving pointer + *dest_size_r = number_of_frames * filter->output_frame_size; + chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r); + + + // Perform our copy operations, with N input channels and M output channels + for (unsigned int s=0; s<number_of_frames; ++s) { + + // Need to perform one copy per output channel + for (unsigned int c=0; c<filter->min_output_channels; ++c) { + if (filter->sources[c] == -1 || + filter->sources[c] >= filter->input_format.channels) { + // No source for this destination output, + // give it zeroes as input + memset(chan_destination, + 0x00, + bytes_per_frame_per_channel); + } else { + // Get the data from channel sources[c] + // and copy it to the output + const uint8_t *data = base_source + + (filter->sources[c] * bytes_per_frame_per_channel); + memcpy(chan_destination, + data, + bytes_per_frame_per_channel); + } + // Move on to the next output channel + chan_destination += bytes_per_frame_per_channel; + } + + + // Go on to the next N input samples + base_source += filter->input_frame_size; + } + + // Here it is, ladies and gentlemen! Rerouted data! + return (void *) filter->output_buffer.buffer; +} + +const struct filter_plugin route_filter_plugin = { + .name = "route", + .init = route_filter_init, + .finish = route_filter_finish, + .open = route_filter_open, + .close = route_filter_close, + .filter = route_filter_filter, +}; diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c new file mode 100644 index 000000000..82eed3acc --- /dev/null +++ b/src/filter/volume_filter_plugin.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter/volume_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" +#include "pcm_buffer.h" +#include "pcm_volume.h" +#include "audio_format.h" +#include "player_control.h" + +#include <assert.h> +#include <string.h> + +struct volume_filter { + struct filter filter; + + /** + * The current volume, from 0 to #PCM_VOLUME_1. + */ + unsigned volume; + + struct audio_format audio_format; + + struct pcm_buffer buffer; +}; + +static inline GQuark +volume_quark(void) +{ + return g_quark_from_static_string("pcm_volume"); +} + +static struct filter * +volume_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct volume_filter *filter = g_new(struct volume_filter, 1); + + filter_init(&filter->filter, &volume_filter_plugin); + filter->volume = PCM_VOLUME_1; + + return &filter->filter; +} + +static void +volume_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +volume_filter_open(struct filter *_filter, struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + + audio_format->reverse_endian = false; + + filter->audio_format = *audio_format; + pcm_buffer_init(&filter->buffer); + + return &filter->audio_format; +} + +static void +volume_filter_close(struct filter *_filter) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); +} + +static const void * +volume_filter_filter(struct filter *_filter, const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + bool success; + void *dest; + + if (filter->volume >= PCM_VOLUME_1) { + /* optimized special case: 100% volume = no-op */ + *dest_size_r = src_size; + return src; + } + + dest = pcm_buffer_get(&filter->buffer, src_size); + *dest_size_r = src_size; + + if (filter->volume <= 0) { + /* optimized special case: 0% volume = memset(0) */ + /* XXX is this valid for all sample formats? What + about floating point? */ + memset(dest, 0, src_size); + return dest; + } + + memcpy(dest, src, src_size); + + success = pcm_volume(dest, src_size, &filter->audio_format, + filter->volume); + if (!success) { + g_set_error(error_r, volume_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin volume_filter_plugin = { + .name = "volume", + .init = volume_filter_init, + .finish = volume_filter_finish, + .open = volume_filter_open, + .close = volume_filter_close, + .filter = volume_filter_filter, +}; + +unsigned +volume_filter_get(const struct filter *_filter) +{ + const struct volume_filter *filter = + (const struct volume_filter *)_filter; + + assert(filter->filter.plugin == &volume_filter_plugin); + assert(filter->volume <= PCM_VOLUME_1); + + return filter->volume; +} + +void +volume_filter_set(struct filter *_filter, unsigned volume) +{ + struct volume_filter *filter = (struct volume_filter *)_filter; + + assert(filter->filter.plugin == &volume_filter_plugin); + assert(volume <= PCM_VOLUME_1); + + filter->volume = volume; +} + diff --git a/src/normalize.h b/src/filter/volume_filter_plugin.h index a8144951d..c064741a2 100644 --- a/src/normalize.h +++ b/src/filter/volume_filter_plugin.h @@ -17,18 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_NORMALIZE_H -#define MPD_NORMALIZE_H +#ifndef VOLUME_FILTER_PLUGIN_H +#define VOLUME_FILTER_PLUGIN_H -struct audio_format; +struct filter; -extern int normalizationEnabled; +unsigned +volume_filter_get(const struct filter *filter); -void initNormalization(void); +void +volume_filter_set(struct filter *filter, unsigned volume); -void finishNormalization(void); - -void normalizeData(char *buffer, int bufferSize, - const struct audio_format *format); - -#endif /* !NORMALIZE_H */ +#endif diff --git a/src/filter_config.c b/src/filter_config.c new file mode 100644 index 000000000..1a92916ea --- /dev/null +++ b/src/filter_config.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "filter_config.h" +#include "config.h" +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + +#include <string.h> + + +static GQuark +filter_quark(void) +{ + return g_quark_from_static_string("filter"); +} + +/** + * Find the "filter" configuration block for the specified name. + * + * @param filter_template_name the name of the filter template + * @param error_r space to return an error description + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +filter_plugin_config(const char *filter_template_name, GError **error_r) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) { + g_set_error(error_r, filter_quark(), 1, + "filter configuration without 'name' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, filter_template_name) == 0) + return param; + } + + g_set_error(error_r, filter_quark(), 1, + "filter template not found: %s", + filter_template_name); + + return NULL; +} + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(struct filter *chain, const char *spec, GError **error_r) +{ + + // Split on comma + gchar** tokens = g_strsplit_set(spec, ",", 255); + + int added_filters = 0; + + // Add each name to the filter chain by instantiating an actual filter + char **template_names = tokens; + while (*template_names != NULL) { + struct filter *f; + const struct config_param *cfg; + + // Squeeze whitespace + g_strstrip(*template_names); + + cfg = filter_plugin_config(*template_names, error_r); + if (cfg == NULL) { + // The error has already been set, just stop. + break; + } + + // Instantiate one of those filter plugins with the template name as a hint + f = filter_configured_new(cfg, error_r); + if (f == NULL) { + // The error has already been set, just stop. + break; + } + + filter_chain_append(chain, f); + ++added_filters; + + ++template_names; + } + + g_strfreev(tokens); + + return added_filters; +} diff --git a/src/filter_config.h b/src/filter_config.h new file mode 100644 index 000000000..8e20320ff --- /dev/null +++ b/src/filter_config.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Utility functions for filter configuration + */ + +#ifndef MPD_FILTER_CONFIG_H +#define MPD_FILTER_CONFIG_H + +#include "conf.h" +#include "filter/chain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" + + +/** + * Builds a filter chain from a configuration string on the form + * "name1, name2, name3, ..." by looking up each name among the + * configured filter sections. + * @param chain the chain to append filters on + * @param spec the filter chain specification + * @param error_r space to return an error description + * @return the number of filters which were successfully added + */ +unsigned int +filter_chain_parse(struct filter *chain, const char *spec, GError **error_r); + +#endif diff --git a/src/filter_internal.h b/src/filter_internal.h new file mode 100644 index 000000000..b086e31b1 --- /dev/null +++ b/src/filter_internal.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * Internal stuff for the filter core and filter plugins. + */ + +#ifndef MPD_FILTER_INTERNAL_H +#define MPD_FILTER_INTERNAL_H + +struct filter { + const struct filter_plugin *plugin; +}; + +static inline void +filter_init(struct filter *filter, const struct filter_plugin *plugin) +{ + filter->plugin = plugin; +} + +#endif diff --git a/src/filter_plugin.c b/src/filter_plugin.c new file mode 100644 index 000000000..21cb2f277 --- /dev/null +++ b/src/filter_plugin.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "conf.h" + +#ifndef NDEBUG +#include "audio_format.h" +#endif + +#include <assert.h> + +struct filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r) +{ + assert(plugin != NULL); + assert(error_r == NULL || *error_r == NULL); + + return plugin->init(param, error_r); +} + +struct filter * +filter_configured_new(const struct config_param *param, GError **error_r) +{ + const char *plugin_name; + const struct filter_plugin *plugin; + + assert(param != NULL); + assert(error_r == NULL || *error_r == NULL); + + plugin_name = config_get_block_string(param, "plugin", NULL); + if (plugin_name == NULL) { + g_set_error(error_r, config_quark(), 0, + "No filter plugin specified"); + return NULL; + } + + plugin = filter_plugin_by_name(plugin_name); + if (plugin == NULL) { + g_set_error(error_r, config_quark(), 0, + "No such filter plugin: %s", plugin_name); + return NULL; + } + + return filter_new(plugin, param, error_r); +} + +void +filter_free(struct filter *filter) +{ + assert(filter != NULL); + + filter->plugin->finish(filter); +} + +const struct audio_format * +filter_open(struct filter *filter, struct audio_format *audio_format, + GError **error_r) +{ + const struct audio_format *out_audio_format; + + assert(filter != NULL); + assert(audio_format != NULL); + assert(audio_format_valid(audio_format)); + assert(error_r == NULL || *error_r == NULL); + + out_audio_format = filter->plugin->open(filter, audio_format, error_r); + + assert(out_audio_format == NULL || audio_format_valid(audio_format)); + assert(out_audio_format == NULL || + audio_format_valid(out_audio_format)); + + return out_audio_format; +} + +void +filter_close(struct filter *filter) +{ + assert(filter != NULL); + + filter->plugin->close(filter); +} + +const void * +filter_filter(struct filter *filter, const void *src, size_t src_size, + size_t *dest_size_r, + GError **error_r) +{ + assert(filter != NULL); + assert(src != NULL); + assert(src_size > 0); + assert(dest_size_r != NULL); + assert(error_r == NULL || *error_r == NULL); + + return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r); +} diff --git a/src/filter_plugin.h b/src/filter_plugin.h new file mode 100644 index 000000000..cec15794f --- /dev/null +++ b/src/filter_plugin.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This header declares the filter_plugin class. It describes a + * plugin API for objects which filter raw PCM data. + */ + +#ifndef MPD_FILTER_PLUGIN_H +#define MPD_FILTER_PLUGIN_H + +#include <glib.h> + +#include <stdbool.h> +#include <stddef.h> + +struct config_param; +struct filter; + +struct filter_plugin { + const char *name; + + /** + * Allocates and configures a filter. + */ + struct filter *(*init)(const struct config_param *param, + GError **error_r); + + /** + * Free instance data. + */ + void (*finish)(struct filter *filter); + + /** + * Opens a filter. + * + * @param audio_format the audio format of incoming data; the + * plugin may modify the object to enforce another input + * format + */ + const struct audio_format * + (*open)(struct filter *filter, + struct audio_format *audio_format, + GError **error_r); + + /** + * Closes a filter. + */ + void (*close)(struct filter *filter); + + /** + * Filters a block of PCM data. + */ + const void *(*filter)(struct filter *filter, + const void *src, size_t src_size, + size_t *dest_buffer_r, + GError **error_r); +}; + +/** + * Creates a new instance of the specified filter plugin. + * + * @param plugin the filter plugin + * @param param optional configuration section + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +struct filter * +filter_new(const struct filter_plugin *plugin, + const struct config_param *param, GError **error_r); + +/** + * Creates a new filter, loads configuration and the plugin name from + * the specified configuration section. + * + * @param param the configuration section + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return a new filter object, or NULL on error + */ +struct filter * +filter_configured_new(const struct config_param *param, GError **error_r); + +/** + * Deletes a filter. It must be closed prior to calling this + * function, see filter_close(). + * + * @param filter the filter object + */ +void +filter_free(struct filter *filter); + +/** + * Opens the filter, preparing it for filter_filter(). + * + * @param filter the filter object + * @param audio_format the audio format of incoming data; the plugin + * may modify the object to enforce another input format + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return the format of outgoing data + */ +const struct audio_format * +filter_open(struct filter *filter, struct audio_format *audio_format, + GError **error_r); + +/** + * Closes the filter. After that, you may call filter_open() again. + * + * @param filter the filter object + */ +void +filter_close(struct filter *filter); + +/** + * Filters a block of PCM data. + * + * @param filter the filter object + * @param src the input buffer + * @param src_size the size of #src_buffer in bytes + * @param dest_size_r the size of the returned buffer + * @param error location to store the error occuring, or NULL to + * ignore errors. + * @return the destination buffer on success (will be invalidated by + * filter_close() or filter_filter()), NULL on error + */ +const void * +filter_filter(struct filter *filter, const void *src, size_t src_size, + size_t *dest_size_r, + GError **error_r); + +#endif diff --git a/src/filter_registry.c b/src/filter_registry.c new file mode 100644 index 000000000..5ad8053b7 --- /dev/null +++ b/src/filter_registry.c @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "filter_registry.h" +#include "filter_plugin.h" + +#include <stddef.h> +#include <string.h> + +const struct filter_plugin *const filter_plugins[] = { + &null_filter_plugin, + &route_filter_plugin, + &normalize_filter_plugin, + &volume_filter_plugin, + NULL, +}; + +const struct filter_plugin * +filter_plugin_by_name(const char *name) +{ + for (unsigned i = 0; filter_plugins[i] != NULL; ++i) + if (strcmp(filter_plugins[i]->name, name) == 0) + return filter_plugins[i]; + + return NULL; +} diff --git a/src/filter_registry.h b/src/filter_registry.h new file mode 100644 index 000000000..8651f7938 --- /dev/null +++ b/src/filter_registry.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** \file + * + * This library manages all filter plugins which are enabled at + * compile time. + */ + +#ifndef MPD_FILTER_REGISTRY_H +#define MPD_FILTER_REGISTRY_H + +extern const struct filter_plugin null_filter_plugin; +extern const struct filter_plugin chain_filter_plugin; +extern const struct filter_plugin convert_filter_plugin; +extern const struct filter_plugin route_filter_plugin; +extern const struct filter_plugin normalize_filter_plugin; +extern const struct filter_plugin volume_filter_plugin; + +const struct filter_plugin * +filter_plugin_by_name(const char *name); + +#endif diff --git a/src/glib_compat.h b/src/glib_compat.h new file mode 100644 index 000000000..641fef99a --- /dev/null +++ b/src/glib_compat.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Compatibility with older GLib versions. Some of this isn't + * implemented properly, just "good enough" to allow users with older + * operating systems to run MPD. + */ + +#ifndef MPD_GLIB_COMPAT_H +#define MPD_GLIB_COMPAT_H + +#include <glib.h> + +#if !GLIB_CHECK_VERSION(2,14,0) + +#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) + +static inline guint +g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data) +{ + return g_timeout_add(interval * 1000, function, data); +} + +#endif /* !2.14 */ + +#if !GLIB_CHECK_VERSION(2,16,0) + +static inline void +g_propagate_prefixed_error(GError **dest_r, GError *src, + G_GNUC_UNUSED const gchar *format, ...) +{ + g_propagate_error(dest_r, src); +} + +static inline char * +g_uri_escape_string(const char *unescaped, + G_GNUC_UNUSED const char *reserved_chars_allowed, + G_GNUC_UNUSED gboolean allow_utf8) +{ + return g_strdup(unescaped); +} + +#endif /* !2.16 */ + +#endif diff --git a/src/icy_metadata.c b/src/icy_metadata.c index 69aa89092..009104b74 100644 --- a/src/icy_metadata.c +++ b/src/icy_metadata.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "icy_metadata.h" #include "tag.h" @@ -95,7 +96,7 @@ icy_parse_tag_item(struct tag *tag, const char *item) if (p[0] != NULL && p[1] != NULL) { if (strcmp(p[0], "StreamTitle") == 0) - icy_add_item(tag, TAG_ITEM_TITLE, p[1]); + icy_add_item(tag, TAG_TITLE, p[1]); else g_debug("unknown icy-tag: '%s'", p[0]); } diff --git a/src/icy_server.c b/src/icy_server.c index 486c62c36..a9e6bc496 100644 --- a/src/icy_server.c +++ b/src/icy_server.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "icy_server.h" #include <glib.h> diff --git a/src/idle.c b/src/idle.c index 11b57376d..ea08e6a9b 100644 --- a/src/idle.c +++ b/src/idle.c @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "idle.h" #include "event_pipe.h" @@ -40,6 +41,7 @@ static const char *const idle_names[] = { "output", "options", "sticker", + "update", NULL }; diff --git a/src/idle.h b/src/idle.h index a69acabb0..c8ed57f74 100644 --- a/src/idle.h +++ b/src/idle.h @@ -50,6 +50,9 @@ enum { /** a sticker has been modified. */ IDLE_STICKER = 0x80, + + /** a database update has started or finished. */ + IDLE_UPDATE = 0x100, }; /** diff --git a/src/inotify_queue.c b/src/inotify_queue.c new file mode 100644 index 000000000..d486e21db --- /dev/null +++ b/src/inotify_queue.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "inotify_queue.h" +#include "update.h" + +#include <glib.h> + +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +enum { + /** + * Wait this long after the last change before calling + * update_enqueue(). This increases the probability that + * updates can be bundled. + */ + INOTIFY_UPDATE_DELAY_S = 5, +}; + +static GSList *inotify_queue; +static guint queue_source_id; + +void +mpd_inotify_queue_init(void) +{ +} + +static void +free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + g_free(data); +} + +void +mpd_inotify_queue_finish(void) +{ + if (queue_source_id != 0) + g_source_remove(queue_source_id); + + g_slist_foreach(inotify_queue, free_callback, NULL); + g_slist_free(inotify_queue); +} + +static gboolean +mpd_inotify_run_update(G_GNUC_UNUSED gpointer data) +{ + unsigned id; + + while (inotify_queue != NULL) { + char *uri_utf8 = inotify_queue->data; + + id = update_enqueue(uri_utf8, false); + if (id == 0) + /* retry later */ + return true; + + g_debug("updating '%s' job=%u", uri_utf8, id); + + g_free(uri_utf8); + inotify_queue = g_slist_delete_link(inotify_queue, + inotify_queue); + } + + /* done, remove the timer event by returning false */ + queue_source_id = 0; + return false; +} + +static bool +path_in(const char *path, const char *possible_parent) +{ + size_t length = strlen(possible_parent); + + return path[0] == 0 || + (memcmp(possible_parent, path, length) == 0 && + (path[length] == 0 || path[length] == '/')); +} + +void +mpd_inotify_enqueue(char *uri_utf8) +{ + GSList *old_queue = inotify_queue; + + if (queue_source_id != 0) + g_source_remove(queue_source_id); + queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S, + mpd_inotify_run_update, NULL); + + inotify_queue = NULL; + while (old_queue != NULL) { + char *current_uri = old_queue->data; + + if (path_in(uri_utf8, current_uri)) { + /* already enqueued */ + g_free(uri_utf8); + inotify_queue = g_slist_concat(inotify_queue, + old_queue); + return; + } + + old_queue = g_slist_delete_link(old_queue, old_queue); + + if (path_in(current_uri, uri_utf8)) + /* existing path is a sub-path of the new + path; we can dequeue the existing path and + update the new path instead */ + g_free(current_uri); + else + /* move the existing path to the new queue */ + inotify_queue = g_slist_prepend(inotify_queue, + current_uri); + } + + inotify_queue = g_slist_prepend(inotify_queue, uri_utf8); +} diff --git a/src/inotify_queue.h b/src/inotify_queue.h new file mode 100644 index 000000000..6e12e9bda --- /dev/null +++ b/src/inotify_queue.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_QUEUE_H +#define MPD_INOTIFY_QUEUE_H + +void +mpd_inotify_queue_init(void); + +void +mpd_inotify_queue_finish(void); + +void +mpd_inotify_enqueue(char *uri_utf8); + +#endif diff --git a/src/inotify_source.c b/src/inotify_source.c new file mode 100644 index 000000000..31dc1e7dc --- /dev/null +++ b/src/inotify_source.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "inotify_source.h" +#include "fifo_buffer.h" +#include "fd_util.h" + +#include <sys/inotify.h> +#include <unistd.h> +#include <errno.h> +#include <stdbool.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +struct mpd_inotify_source { + int fd; + + GIOChannel *channel; + + /** + * The channel's source id in the GLib main loop. + */ + guint id; + + struct fifo_buffer *buffer; + + mpd_inotify_callback_t callback; + void *callback_ctx; +}; + +/** + * A GQuark for GError instances. + */ +static inline GQuark +mpd_inotify_quark(void) +{ + return g_quark_from_static_string("inotify"); +} + +static gboolean +mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct mpd_inotify_source *source = data; + void *dest; + size_t length; + ssize_t nbytes; + const struct inotify_event *event; + + dest = fifo_buffer_write(source->buffer, &length); + if (dest == NULL) + g_error("buffer full"); + + nbytes = read(source->fd, dest, length); + if (nbytes < 0) + g_error("failed to read from inotify: %s", g_strerror(errno)); + if (nbytes == 0) + g_error("end of file from inotify"); + + fifo_buffer_append(source->buffer, nbytes); + + while (true) { + const char *name; + + event = fifo_buffer_read(source->buffer, &length); + if (event == NULL || length < sizeof(*event) || + length < sizeof(*event) + event->len) + break; + + if (event->len > 0 && event->name[event->len - 1] == 0) + name = event->name; + else + name = NULL; + + source->callback(event->wd, event->mask, name, + source->callback_ctx); + fifo_buffer_consume(source->buffer, + sizeof(*event) + event->len); + } + + return true; +} + +struct mpd_inotify_source * +mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, + GError **error_r) +{ + struct mpd_inotify_source *source = + g_new(struct mpd_inotify_source, 1); + + source->fd = inotify_init_cloexec(); + if (source->fd < 0) { + g_set_error(error_r, mpd_inotify_quark(), errno, + "inotify_init() has failed: %s", + g_strerror(errno)); + g_free(source); + return NULL; + } + + source->buffer = fifo_buffer_new(4096); + + source->channel = g_io_channel_unix_new(source->fd); + source->id = g_io_add_watch(source->channel, G_IO_IN, + mpd_inotify_in_event, source); + + source->callback = callback; + source->callback_ctx = callback_ctx; + + return source; +} + +void +mpd_inotify_source_free(struct mpd_inotify_source *source) +{ + g_source_remove(source->id); + g_io_channel_unref(source->channel); + fifo_buffer_free(source->buffer); + close(source->fd); + g_free(source); +} + +int +mpd_inotify_source_add(struct mpd_inotify_source *source, + const char *path_fs, unsigned mask, + GError **error_r) +{ + int wd = inotify_add_watch(source->fd, path_fs, mask); + if (wd < 0) + g_set_error(error_r, mpd_inotify_quark(), errno, + "inotify_add_watch() has failed: %s", + g_strerror(errno)); + + return wd; +} + +void +mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd) +{ + int ret = inotify_rm_watch(source->fd, wd); + if (ret < 0 && errno != EINVAL) + g_warning("inotify_rm_watch() has failed: %s", + g_strerror(errno)); + + /* EINVAL may happen here when the file has been deleted; the + kernel seems to auto-unregister deleted files */ +} diff --git a/src/inotify_source.h b/src/inotify_source.h new file mode 100644 index 000000000..eb609ccfc --- /dev/null +++ b/src/inotify_source.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_SOURCE_H +#define MPD_INOTIFY_SOURCE_H + +#include <glib.h> + +typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask, + const char *name, void *ctx); + +struct mpd_inotify_source; + +/** + * Creates a new inotify source and registers it in the GLib main + * loop. + * + * @param a callback invoked for events received from the kernel + */ +struct mpd_inotify_source * +mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx, + GError **error_r); + +void +mpd_inotify_source_free(struct mpd_inotify_source *source); + +/** + * Adds a path to the notify list. + * + * @return a watch descriptor or -1 on error + */ +int +mpd_inotify_source_add(struct mpd_inotify_source *source, + const char *path_fs, unsigned mask, + GError **error_r); + +/** + * Removes a path from the notify list. + * + * @param wd the watch descriptor returned by mpd_inotify_source_add() + */ +void +mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd); + +#endif diff --git a/src/inotify_update.c b/src/inotify_update.c new file mode 100644 index 000000000..53a7ff73c --- /dev/null +++ b/src/inotify_update.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "inotify_update.h" +#include "inotify_source.h" +#include "inotify_queue.h" +#include "database.h" +#include "mapper.h" +#include "path.h" + +#include <assert.h> +#include <sys/inotify.h> +#include <sys/stat.h> +#include <stdbool.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "inotify" + +enum { + IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF + |IN_MOVE|IN_MOVE_SELF +#ifdef IN_ONLYDIR + |IN_ONLYDIR +#endif +}; + +struct watch_directory { + struct watch_directory *parent; + + char *name; + + int descriptor; + + GList *children; +}; + +static struct mpd_inotify_source *inotify_source; + +static struct watch_directory inotify_root; +static GTree *inotify_directories; + +static gint +compare(gconstpointer a, gconstpointer b) +{ + if (a < b) + return -1; + else if (a > b) + return 1; + else + return 0; +} + +static void +tree_add_watch_directory(struct watch_directory *directory) +{ + g_tree_insert(inotify_directories, + GINT_TO_POINTER(directory->descriptor), directory); +} + +static void +tree_remove_watch_directory(struct watch_directory *directory) +{ + G_GNUC_UNUSED + bool found = g_tree_remove(inotify_directories, + GINT_TO_POINTER(directory->descriptor)); + assert(found); +} + +static struct watch_directory * +tree_find_watch_directory(int wd) +{ + return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd)); +} + +static void +remove_watch_directory(struct watch_directory *directory) +{ + assert(directory != NULL); + assert(directory->parent != NULL); + assert(directory->parent->children != NULL); + + tree_remove_watch_directory(directory); + + while (directory->children != NULL) + remove_watch_directory(directory->children->data); + + directory->parent->children = + g_list_remove(directory->parent->children, directory); + + mpd_inotify_source_rm(inotify_source, directory->descriptor); + g_free(directory->name); + g_slice_free(struct watch_directory, directory); +} + +static char * +watch_directory_get_uri_fs(const struct watch_directory *directory) +{ + char *parent_uri, *uri; + + if (directory->parent == NULL) + return NULL; + + parent_uri = watch_directory_get_uri_fs(directory->parent); + if (parent_uri == NULL) + return g_strdup(directory->name); + + uri = g_strconcat(parent_uri, "/", directory->name, NULL); + g_free(parent_uri); + + return uri; +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != NULL; +} + +static void +recursive_watch_subdirectories(struct watch_directory *directory, + const char *path_fs) +{ + GError *error = NULL; + DIR *dir; + struct dirent *ent; + + assert(directory != NULL); + assert(path_fs != NULL); + + dir = opendir(path_fs); + if (dir == NULL) { + g_warning("Failed to open directory %s: %s", + path_fs, g_strerror(errno)); + return; + } + + while ((ent = readdir(dir))) { + char *child_path_fs; + struct stat st; + int ret; + struct watch_directory *child; + + if (skip_path(ent->d_name)) + continue; + + child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL); + /* XXX what about symlinks? */ + ret = lstat(child_path_fs, &st); + if (ret < 0) { + g_warning("Failed to stat %s: %s", + child_path_fs, g_strerror(errno)); + g_free(child_path_fs); + continue; + } + + if (!S_ISDIR(st.st_mode)) { + g_free(child_path_fs); + continue; + } + + ret = mpd_inotify_source_add(inotify_source, child_path_fs, + IN_MASK, &error); + if (ret < 0) { + g_warning("Failed to register %s: %s", + child_path_fs, error->message); + g_error_free(error); + error = NULL; + g_free(child_path_fs); + continue; + } + + child = tree_find_watch_directory(ret); + if (child != NULL) { + /* already being watched */ + g_free(child_path_fs); + continue; + } + + child = g_slice_new(struct watch_directory); + child->parent = directory; + child->name = g_strdup(ent->d_name); + child->descriptor = ret; + child->children = NULL; + + directory->children = g_list_prepend(directory->children, + child); + + tree_add_watch_directory(child); + + recursive_watch_subdirectories(child, child_path_fs); + g_free(child_path_fs); + } + + closedir(dir); +} + +static void +mpd_inotify_callback(int wd, unsigned mask, + G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx) +{ + struct watch_directory *directory; + char *uri_fs; + + /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/ + + directory = tree_find_watch_directory(wd); + if (directory == NULL) + return; + + uri_fs = watch_directory_get_uri_fs(directory); + + if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) { + g_free(uri_fs); + remove_watch_directory(directory); + return; + } + + if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 && + (mask & IN_ISDIR) != 0) { + /* a sub directory was changed: register those in + inotify */ + char *root = map_directory_fs(db_get_root()); + char *path_fs; + + if (uri_fs != NULL) { + path_fs = g_strconcat(root, "/", uri_fs, NULL); + g_free(root); + } else + path_fs = root; + + recursive_watch_subdirectories(directory, path_fs); + g_free(path_fs); + } + + if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0) { + /* a file was changed, or a direectory was + moved/deleted: queue a database update */ + char *uri_utf8 = uri_fs != NULL + ? fs_charset_to_utf8(uri_fs) + : g_strdup(""); + + if (uri_utf8 != NULL) + /* this function will take care of freeing + uri_utf8 */ + mpd_inotify_enqueue(uri_utf8); + } + + g_free(uri_fs); +} + +void +mpd_inotify_init(void) +{ + struct directory *root; + char *path; + GError *error = NULL; + + g_debug("initializing inotify"); + + root = db_get_root(); + if (root == NULL) { + g_debug("no music directory configured"); + return; + } + + path = map_directory_fs(root); + if (path == NULL) { + g_warning("mapper has failed"); + return; + } + + inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL, + &error); + if (inotify_source == NULL) { + g_warning("%s", error->message); + g_error_free(error); + g_free(path); + return; + } + + inotify_root.name = path; + inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path, + IN_MASK, &error); + if (inotify_root.descriptor < 0) { + g_warning("%s", error->message); + g_error_free(error); + mpd_inotify_source_free(inotify_source); + inotify_source = NULL; + g_free(path); + return; + } + + inotify_directories = g_tree_new(compare); + tree_add_watch_directory(&inotify_root); + + recursive_watch_subdirectories(&inotify_root, path); + + mpd_inotify_queue_init(); + + g_debug("watching music directory"); +} + +static gboolean +free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value, + G_GNUC_UNUSED gpointer data) +{ + struct watch_directory *directory = value; + + g_free(directory->name); + g_list_free(directory->children); + + if (directory != &inotify_root) + g_slice_free(struct watch_directory, directory); + + return false; +} + +void +mpd_inotify_finish(void) +{ + if (inotify_source == NULL) + return; + + mpd_inotify_queue_finish(); + mpd_inotify_source_free(inotify_source); + + g_tree_foreach(inotify_directories, free_watch_directory, NULL); + g_tree_destroy(inotify_directories); +} diff --git a/src/inotify_update.h b/src/inotify_update.h new file mode 100644 index 000000000..f77e183a6 --- /dev/null +++ b/src/inotify_update.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INOTIFY_UPDATE_H +#define MPD_INOTIFY_UPDATE_H + +#include "check.h" + +#ifdef HAVE_INOTIFY_INIT + +void +mpd_inotify_init(void); + +void +mpd_inotify_finish(void); + +#else /* !HAVE_INOTIFY_INIT */ + +static inline void +mpd_inotify_init(void) +{ +} + +static inline void +mpd_inotify_finish(void) +{ +} + +#endif /* !HAVE_INOTIFY_INIT */ + +#endif diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c index 8e897f0c2..53d472a5a 100644 --- a/src/input/archive_input_plugin.c +++ b/src/input/archive_input_plugin.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "input/archive_input_plugin.h" #include "archive_api.h" #include "archive_list.h" @@ -33,14 +34,15 @@ * plugin and gzip fetches file from disk */ static bool -input_archive_open(struct input_stream *is, const char *pathname) +input_archive_open(struct input_stream *is, const char *pathname, + GError **error_r) { const struct archive_plugin *arplug; struct archive_file *file; char *archive, *filename, *suffix, *pname; bool opened; - if (pathname[0] != '/') + if (!g_path_is_absolute(pathname)) return false; pname = g_strdup(pathname); @@ -59,18 +61,20 @@ input_archive_open(struct input_stream *is, const char *pathname) return false; } - file = arplug->open(archive); + file = archive_file_open(arplug, archive, error_r); + if (file == NULL) + return false; //setup fileops - opened = arplug->open_stream(file, is, filename); + opened = archive_file_open_stream(file, is, filename, error_r); + g_free(pname); if (!opened) { - g_warning("open inarchive file %s failed\n\n",filename); - arplug->close(file); + archive_file_close(file); } else { is->ready = true; } - g_free(pname); + return opened; } diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c index 43010c8bb..5cfc28fc8 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/curl_input_plugin.c @@ -17,12 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "input/curl_input_plugin.h" #include "input_plugin.h" #include "conf.h" -#include "config.h" #include "tag.h" #include "icy_metadata.h" +#include "glib_compat.h" #include <assert.h> @@ -96,8 +97,15 @@ static struct curl_slist *http_200_aliases; static const char *proxy, *proxy_user, *proxy_password; static unsigned proxy_port; +static inline GQuark +curl_quark(void) +{ + return g_quark_from_static_string("curl"); +} + static bool -input_curl_init(const struct config_param *param) +input_curl_init(const struct config_param *param, + G_GNUC_UNUSED GError **error_r) { CURLcode code = curl_global_init(CURL_GLOBAL_ALL); if (code != CURLE_OK) { @@ -144,11 +152,6 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) g_free(data); } -/* g_queue_clear() was introduced in GLib 2.14 */ -#if !GLIB_CHECK_VERSION(2,14,0) -#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) -#endif - /** * Frees the current "libcurl easy" handle, and everything associated * with it. @@ -206,7 +209,7 @@ input_curl_tag(struct input_stream *is) } static bool -input_curl_multi_info_read(struct input_stream *is) +input_curl_multi_info_read(struct input_stream *is, GError **error_r) { struct input_curl *c = is->data; CURLMsg *msg; @@ -219,8 +222,9 @@ input_curl_multi_info_read(struct input_stream *is) is->ready = true; if (msg->data.result != CURLE_OK) { - g_warning("curl failed: %s\n", c->error); - is->error = -1; + g_set_error(error_r, curl_quark(), + msg->data.result, + "curl failed: %s", c->error); return false; } } @@ -236,7 +240,7 @@ input_curl_multi_info_read(struct input_stream *is) * available */ static int -input_curl_select(struct input_curl *c) +input_curl_select(struct input_curl *c, GError **error_r) { fd_set rfds, wfds, efds; int max_fd, ret; @@ -255,8 +259,9 @@ input_curl_select(struct input_curl *c) mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd); if (mcode != CURLM_OK) { - g_warning("curl_multi_fdset() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_fdset() failed: %s", + curl_multi_strerror(mcode)); return -1; } @@ -264,13 +269,15 @@ input_curl_select(struct input_curl *c) ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout); if (ret < 0) - g_warning("select() failed: %s\n", strerror(errno)); + g_set_error(error_r, g_quark_from_static_string("errno"), + errno, + "select() failed: %s\n", g_strerror(errno)); return ret; } static bool -fill_buffer(struct input_stream *is) +fill_buffer(struct input_stream *is, GError **error_r) { struct input_curl *c = is->data; CURLMcode mcode = CURLM_CALL_MULTI_PERFORM; @@ -282,7 +289,7 @@ fill_buffer(struct input_stream *is) if (mcode != CURLM_CALL_MULTI_PERFORM) { /* if we're still here, there is no input yet - wait for input */ - int ret = input_curl_select(c); + int ret = input_curl_select(c, error_r); if (ret <= 0) /* no data yet or error */ return false; @@ -290,14 +297,15 @@ fill_buffer(struct input_stream *is) mcode = curl_multi_perform(c->multi, &running_handles); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - g_warning("curl_multi_perform() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); c->eof = true; is->ready = true; return false; } - bret = input_curl_multi_info_read(is); + bret = input_curl_multi_info_read(is, error_r); if (!bret) return false; } @@ -389,14 +397,15 @@ copy_icy_tag(struct input_curl *c) if (c->tag != NULL) tag_free(c->tag); - if (c->meta_name != NULL && !tag_has_type(tag, TAG_ITEM_NAME)) - tag_add_item(tag, TAG_ITEM_NAME, c->meta_name); + if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME)) + tag_add_item(tag, TAG_NAME, c->meta_name); c->tag = tag; } static size_t -input_curl_read(struct input_stream *is, void *ptr, size_t size) +input_curl_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { struct input_curl *c = is->data; bool success; @@ -406,7 +415,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) do { /* fill the buffer */ - success = fill_buffer(is); + success = fill_buffer(is, error_r); if (!success) return 0; @@ -424,7 +433,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) if (icy_defined(&c->icy_metadata)) copy_icy_tag(c); - is->offset += (off_t)nbytes; + is->offset += (goffset)nbytes; return nbytes; } @@ -444,7 +453,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is) } static int -input_curl_buffer(struct input_stream *is) +input_curl_buffer(struct input_stream *is, GError **error_r) { struct input_curl *c = is->data; CURLMcode mcode; @@ -457,7 +466,8 @@ input_curl_buffer(struct input_stream *is) /* not ready yet means the caller is waiting in a busy loop; relax that by calling select() on the socket */ - input_curl_select(c); + if (input_curl_select(c, error_r) < 0) + return -1; do { mcode = curl_multi_perform(c->multi, &running_handles); @@ -465,14 +475,15 @@ input_curl_buffer(struct input_stream *is) g_queue_is_empty(c->buffers)); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { - g_warning("curl_multi_perform() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); c->eof = true; is->ready = true; return -1; } - ret = input_curl_multi_info_read(is); + ret = input_curl_multi_info_read(is, error_r); if (!ret) return -1; @@ -537,7 +548,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) tag_free(c->tag); c->tag = tag_new(); - tag_add_item(c->tag, TAG_ITEM_NAME, c->meta_name); + tag_add_item(c->tag, TAG_NAME, c->meta_name); } else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) { char buffer[64]; size_t icy_metaint; @@ -589,7 +600,7 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) } static bool -input_curl_easy_init(struct input_stream *is) +input_curl_easy_init(struct input_stream *is, GError **error_r) { struct input_curl *c = is->data; CURLcode code; @@ -599,13 +610,18 @@ input_curl_easy_init(struct input_stream *is) c->easy = curl_easy_init(); if (c->easy == NULL) { - g_warning("curl_easy_init() failed\n"); + g_set_error(error_r, curl_quark(), 0, + "curl_easy_init() failed"); return false; } mcode = curl_multi_add_handle(c->multi, c->easy); - if (mcode != CURLM_OK) + if (mcode != CURLM_OK) { + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_add_handle() failed: %s", + curl_multi_strerror(mcode)); return false; + } curl_easy_setopt(c->easy, CURLOPT_USERAGENT, "Music Player Daemon " VERSION); @@ -635,8 +651,12 @@ input_curl_easy_init(struct input_stream *is) } code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); - if (code != CURLE_OK) + if (code != CURLE_OK) { + g_set_error(error_r, curl_quark(), code, + "curl_easy_setopt() failed: %s", + curl_easy_strerror(code)); return false; + } c->request_headers = NULL; c->request_headers = curl_slist_append(c->request_headers, @@ -659,7 +679,7 @@ input_curl_reinit(struct input_stream *is) } static bool -input_curl_send_request(struct input_curl *c) +input_curl_send_request(struct input_curl *c, GError **error_r) { CURLMcode mcode; int running_handles; @@ -669,8 +689,9 @@ input_curl_send_request(struct input_curl *c) } while (mcode == CURLM_CALL_MULTI_PERFORM); if (mcode != CURLM_OK) { - g_warning("curl_multi_perform() failed: %s\n", - curl_multi_strerror(mcode)); + g_set_error(error_r, curl_quark(), mcode, + "curl_multi_perform() failed: %s", + curl_multi_strerror(mcode)); return false; } @@ -678,7 +699,8 @@ input_curl_send_request(struct input_curl *c) } static bool -input_curl_seek(struct input_stream *is, off_t offset, int whence) +input_curl_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { struct input_curl *c = is->data; bool ret; @@ -726,7 +748,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence) buffer = (struct buffer *)g_queue_pop_head(c->buffers); length = buffer->size - buffer->consumed; - if (offset - is->offset < (off_t)length) + if (offset - is->offset < (goffset)length) length = offset - is->offset; buffer = consume_buffer(buffer, length); @@ -752,7 +774,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence) return true; } - ret = input_curl_easy_init(is); + ret = input_curl_easy_init(is, error_r); if (!ret) return false; @@ -763,15 +785,15 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence) curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range); } - ret = input_curl_send_request(c); + ret = input_curl_send_request(c, error_r); if (!ret) return false; - return input_curl_multi_info_read(is); + return input_curl_multi_info_read(is, error_r); } static bool -input_curl_open(struct input_stream *is, const char *url) +input_curl_open(struct input_stream *is, const char *url, GError **error_r) { struct input_curl *c; bool ret; @@ -788,8 +810,8 @@ input_curl_open(struct input_stream *is, const char *url) c->multi = curl_multi_init(); if (c->multi == NULL) { - g_warning("curl_multi_init() failed\n"); - + g_set_error(error_r, curl_quark(), 0, + "curl_multi_init() failed"); input_curl_free(is); return false; } @@ -797,19 +819,19 @@ input_curl_open(struct input_stream *is, const char *url) icy_clear(&c->icy_metadata); c->tag = NULL; - ret = input_curl_easy_init(is); + ret = input_curl_easy_init(is, error_r); if (!ret) { input_curl_free(is); return false; } - ret = input_curl_send_request(c); + ret = input_curl_send_request(c, error_r); if (!ret) { input_curl_free(is); return false; } - ret = input_curl_multi_info_read(is); + ret = input_curl_multi_info_read(is, error_r); if (!ret) { input_curl_free(is); return false; diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c index bda1777ac..e8ad87f45 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/file_input_plugin.c @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "input/file_input_plugin.h" #include "input_plugin.h" +#include "fd_util.h" #include <sys/stat.h> #include <fcntl.h> @@ -30,20 +32,28 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_file" +static inline GQuark +file_quark(void) +{ + return g_quark_from_static_string("file"); +} + static bool -input_file_open(struct input_stream *is, const char *filename) +input_file_open(struct input_stream *is, const char *filename, + GError **error_r) { int fd, ret; struct stat st; - if (filename[0] != '/') + if (!g_path_is_absolute(filename)) return false; - fd = open(filename, O_RDONLY); + fd = open_cloexec(filename, O_RDONLY, 0); if (fd < 0) { - is->error = errno; - g_debug("Failed to open \"%s\": %s", - filename, g_strerror(errno)); + if (errno != ENOENT && errno != ENOTDIR) + g_set_error(error_r, file_quark(), errno, + "Failed to open \"%s\": %s", + filename, g_strerror(errno)); return false; } @@ -51,14 +61,16 @@ input_file_open(struct input_stream *is, const char *filename) ret = fstat(fd, &st); if (ret < 0) { - is->error = errno; + g_set_error(error_r, file_quark(), errno, + "Failed to stat \"%s\": %s", + filename, g_strerror(errno)); close(fd); return false; } if (!S_ISREG(st.st_mode)) { - g_debug("Not a regular file: %s", filename); - is->error = EINVAL; + g_set_error(error_r, file_quark(), 0, + "Not a regular file: %s", filename); close(fd); return false; } @@ -77,13 +89,15 @@ input_file_open(struct input_stream *is, const char *filename) } static bool -input_file_seek(struct input_stream *is, off_t offset, int whence) +input_file_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { int fd = GPOINTER_TO_INT(is->data); - offset = lseek(fd, offset, whence); + offset = (goffset)lseek(fd, (off_t)offset, whence); if (offset < 0) { - is->error = errno; + g_set_error(error_r, file_quark(), errno, + "Failed to seek: %s", g_strerror(errno)); return false; } @@ -92,16 +106,16 @@ input_file_seek(struct input_stream *is, off_t offset, int whence) } static size_t -input_file_read(struct input_stream *is, void *ptr, size_t size) +input_file_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { int fd = GPOINTER_TO_INT(is->data); ssize_t nbytes; nbytes = read(fd, ptr, size); if (nbytes < 0) { - is->error = errno; - g_debug("input_file_read: error reading: %s\n", - strerror(is->error)); + g_set_error(error_r, file_quark(), errno, + "Failed to read: %s", g_strerror(errno)); return 0; } diff --git a/src/input/lastfm_input_plugin.c b/src/input/lastfm_input_plugin.c deleted file mode 100644 index 8e13a60a9..000000000 --- a/src/input/lastfm_input_plugin.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "input/lastfm_input_plugin.h" -#include "input/curl_input_plugin.h" -#include "input_plugin.h" -#include "conf.h" - -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "input_lastfm" - -static const char *lastfm_user, *lastfm_password; - -static bool -lastfm_input_init(const struct config_param *param) -{ - lastfm_user = config_get_block_string(param, "user", NULL); - lastfm_password = config_get_block_string(param, "password", NULL); - - return lastfm_user != NULL && lastfm_password != NULL; -} - -static char * -lastfm_get(const char *url) -{ - struct input_stream input_stream; - bool success; - int ret; - char buffer[4096]; - size_t length = 0, nbytes; - - success = input_stream_open(&input_stream, url); - if (!success) - return NULL; - - while (!input_stream.ready) { - ret = input_stream_buffer(&input_stream); - if (ret < 0) { - input_stream_close(&input_stream); - return NULL; - } - } - - do { - nbytes = input_stream_read(&input_stream, buffer + length, - sizeof(buffer) - length); - if (nbytes == 0) { - if (input_stream_eof(&input_stream)) - break; - - /* I/O error */ - input_stream_close(&input_stream); - return NULL; - } - - length += nbytes; - } while (length < sizeof(buffer)); - - input_stream_close(&input_stream); - return g_strndup(buffer, length); -} - -static char * -lastfm_find(const char *response, const char *name) -{ - size_t name_length = strlen(name); - - while (true) { - const char *eol = strchr(response, '\n'); - if (eol == NULL) - return NULL; - - if (strncmp(response, name, name_length) == 0 && - response[name_length] == '=') { - response += name_length + 1; - return g_strndup(response, eol - response); - } - - response = eol + 1; - } -} - -static bool -lastfm_input_open(struct input_stream *is, const char *url) -{ - char *md5, *p, *q, *response, *session, *stream_url; - bool success; - - if (strncmp(url, "lastfm://", 9) != 0) - return false; - - /* handshake */ - -#if GLIB_CHECK_VERSION(2,16,0) - q = g_uri_escape_string(lastfm_user, NULL, false); -#else - q = g_strdup(lastfm_user); -#endif - -#if GLIB_CHECK_VERSION(2,16,0) - if (strlen(lastfm_password) != 32) - md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, - lastfm_password, - strlen(lastfm_password)); - else -#endif - md5 = g_strdup(lastfm_password); - - p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" - "version=1.1.1&platform=linux&" - "username=", q, "&" - "passwordmd5=", md5, "&debug=0&partner=", NULL); - g_free(q); - g_free(md5); - - response = lastfm_get(p); - g_free(p); - if (response == NULL) - return false; - - /* extract session id from response */ - - session = lastfm_find(response, "session"); - stream_url = lastfm_find(response, "stream_url"); - g_free(response); - if (session == NULL || stream_url == NULL) { - g_free(session); - g_free(stream_url); - return false; - } - -#if GLIB_CHECK_VERSION(2,16,0) - q = g_uri_escape_string(session, NULL, false); - g_free(session); - session = q; -#endif - - /* "adjust" last.fm radio */ - - if (strlen(url) > 9) { - char *escaped_url; - -#if GLIB_CHECK_VERSION(2,16,0) - escaped_url = g_uri_escape_string(url, NULL, false); -#else - escaped_url = g_strdup(url); -#endif - - p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" - "session=", session, "&url=", escaped_url, "&debug=0", - NULL); - g_free(escaped_url); - - response = lastfm_get(p); - g_free(response); - g_free(p); - - if (response == NULL) { - g_free(session); - g_free(stream_url); - return false; - } - } - - /* load the last.fm playlist */ - - p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" - "sk=", session, "&discovery=0&desktop=1.5.1.31879", - NULL); - g_free(session); - - response = lastfm_get(p); - g_free(p); - - if (response == NULL) { - g_free(stream_url); - return false; - } - - p = strstr(response, "<location>"); - if (p == NULL) { - g_free(response); - g_free(stream_url); - return false; - } - - p += 10; - q = strchr(p, '<'); - - if (q == NULL) { - g_free(response); - g_free(stream_url); - return false; - } - - g_free(stream_url); - stream_url = g_strndup(p, q - p); - g_free(response); - - /* now really open the last.fm radio stream */ - - success = input_stream_open(is, stream_url); - g_free(stream_url); - return success; -} - -const struct input_plugin lastfm_input_plugin = { - .name = "lastfm", - .init = lastfm_input_init, - .open = lastfm_input_open, -}; diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c index 2a3c53776..b38fb43df 100644 --- a/src/input/mms_input_plugin.c +++ b/src/input/mms_input_plugin.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "input/mms_input_plugin.h" #include "input_plugin.h" @@ -35,8 +36,14 @@ struct input_mms { bool eof; }; +static inline GQuark +mms_quark(void) +{ + return g_quark_from_static_string("mms"); +} + static bool -input_mms_open(struct input_stream *is, const char *url) +input_mms_open(struct input_stream *is, const char *url, GError **error_r) { struct input_mms *m; @@ -49,7 +56,7 @@ input_mms_open(struct input_stream *is, const char *url) m = g_new(struct input_mms, 1); m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); if (m->mms == NULL) { - g_warning("mmsx_connect() failed"); + g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); return false; } @@ -64,7 +71,8 @@ input_mms_open(struct input_stream *is, const char *url) } static size_t -input_mms_read(struct input_stream *is, void *ptr, size_t size) +input_mms_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { struct input_mms *m = is->data; int ret; @@ -72,8 +80,9 @@ input_mms_read(struct input_stream *is, void *ptr, size_t size) ret = mmsx_read(NULL, m->mms, ptr, size); if (ret <= 0) { if (ret < 0) { - is->error = errno; - g_warning("mmsx_read() failed: %s", g_strerror(errno)); + g_set_error(error_r, mms_quark(), errno, + "mmsx_read() failed: %s", + g_strerror(errno)); } m->eof = true; @@ -103,14 +112,16 @@ input_mms_eof(struct input_stream *is) } static int -input_mms_buffer(G_GNUC_UNUSED struct input_stream *is) +input_mms_buffer(G_GNUC_UNUSED struct input_stream *is, + G_GNUC_UNUSED GError **error_r) { return 0; } static bool input_mms_seek(G_GNUC_UNUSED struct input_stream *is, - G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence) + G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence, + G_GNUC_UNUSED GError **error_r) { return false; } diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c index 0a874a29c..6daf94895 100644 --- a/src/input/rewind_input_plugin.c +++ b/src/input/rewind_input_plugin.c @@ -82,7 +82,6 @@ copy_attributes(struct input_stream *dest) dest->ready = src->ready; dest->seekable = src->seekable; - dest->error = src->error; dest->size = src->size; dest->offset = src->offset; @@ -111,11 +110,11 @@ input_rewind_tag(struct input_stream *is) } static int -input_rewind_buffer(struct input_stream *is) +input_rewind_buffer(struct input_stream *is, GError **error_r) { struct input_rewind *r = is->data; - int ret = input_stream_buffer(&r->input); + int ret = input_stream_buffer(&r->input, error_r); if (ret < 0 || !reading_from_buffer(is)) copy_attributes(is); @@ -123,7 +122,8 @@ input_rewind_buffer(struct input_stream *is) } static size_t -input_rewind_read(struct input_stream *is, void *ptr, size_t size) +input_rewind_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { struct input_rewind *r = is->data; @@ -144,9 +144,9 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size) } else { /* pass method call to underlying stream */ - size_t nbytes = input_stream_read(&r->input, ptr, size); + size_t nbytes = input_stream_read(&r->input, ptr, size, error_r); - if (r->input.offset > (off_t)sizeof(r->buffer)) + if (r->input.offset > (goffset)sizeof(r->buffer)) /* disable buffering */ r->tail = 0; else if (r->tail == (size_t)is->offset) { @@ -173,13 +173,14 @@ input_rewind_eof(G_GNUC_UNUSED struct input_stream *is) } static bool -input_rewind_seek(struct input_stream *is, off_t offset, int whence) +input_rewind_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { struct input_rewind *r = is->data; assert(is->ready); - if (whence == SEEK_SET && r->tail > 0 && offset <= (off_t)r->tail) { + if (whence == SEEK_SET && r->tail > 0 && offset <= (goffset)r->tail) { /* buffered seek */ assert(!reading_from_buffer(is) || @@ -191,7 +192,8 @@ input_rewind_seek(struct input_stream *is, off_t offset, int whence) return true; } else { - bool success = input_stream_seek(&r->input, offset, whence); + bool success = input_stream_seek(&r->input, offset, whence, + error_r); copy_attributes(is); /* disable the buffer, because r->input has left the diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h index 33fedf4e1..2d532c96a 100644 --- a/src/input/rewind_input_plugin.h +++ b/src/input/rewind_input_plugin.h @@ -27,11 +27,11 @@ #ifndef MPD_INPUT_REWIND_H #define MPD_INPUT_REWIND_H -#include "config.h" +#include "check.h" struct input_stream; -#ifdef HAVE_CURL +#ifdef ENABLE_CURL void input_rewind_open(struct input_stream *is); diff --git a/src/input_init.c b/src/input_init.c new file mode 100644 index 000000000..c4d015594 --- /dev/null +++ b/src/input_init.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input_init.h" +#include "input_plugin.h" +#include "input_registry.h" +#include "conf.h" +#include "glib_compat.h" + +#include <string.h> + +static inline GQuark +input_quark(void) +{ + return g_quark_from_static_string("input"); +} + +/** + * Find the "input" configuration block for the specified plugin. + * + * @param plugin_name the name of the input plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +input_plugin_config(const char *plugin_name, GError **error_r) +{ + const struct config_param *param = NULL; + + while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { + const char *name = + config_get_block_string(param, "plugin", NULL); + if (name == NULL) { + g_set_error(error_r, input_quark(), 0, + "input configuration without 'plugin' name in line %d", + param->line); + return NULL; + } + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +bool +input_stream_global_init(GError **error_r) +{ + GError *error = NULL; + + for (unsigned i = 0; input_plugins[i] != NULL; ++i) { + const struct input_plugin *plugin = input_plugins[i]; + const struct config_param *param = + input_plugin_config(plugin->name, &error); + if (param == NULL && error != NULL) { + g_propagate_error(error_r, error); + return false; + } + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + if (plugin->init == NULL || plugin->init(param, &error)) + input_plugins_enabled[i] = true; + else { + g_propagate_prefixed_error(error_r, error, + "Failed to initialize input plugin '%s': ", + plugin->name); + return false; + } + } + + return true; +} + +void input_stream_global_finish(void) +{ + for (unsigned i = 0; input_plugins[i] != NULL; ++i) + if (input_plugins_enabled[i] && + input_plugins[i]->finish != NULL) + input_plugins[i]->finish(); +} diff --git a/src/input_init.h b/src/input_init.h new file mode 100644 index 000000000..8416de59e --- /dev/null +++ b/src/input_init.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_INIT_H +#define MPD_INPUT_INIT_H + +#include "check.h" + +#include <glib.h> +#include <stdbool.h> + +/** + * Initializes this library and all input_stream implementations. + * + * @param error_r location to store the error occuring, or NULL to + * ignore errors + */ +bool +input_stream_global_init(GError **error_r); + +/** + * Deinitializes this library and all input_stream implementations. + */ +void input_stream_global_finish(void); + +#endif diff --git a/src/input_plugin.h b/src/input_plugin.h index 8fe852bc6..d7dbfa853 100644 --- a/src/input_plugin.h +++ b/src/input_plugin.h @@ -35,10 +35,12 @@ struct input_plugin { /** * Global initialization. This method is called when MPD starts. * + * @param error_r location to store the error occuring, or + * NULL to ignore errors * @return true on success, false if the plugin should be * disabled */ - bool (*init)(const struct config_param *param); + bool (*init)(const struct config_param *param, GError **error_r); /** * Global deinitialization. Called once before MPD shuts @@ -46,14 +48,17 @@ struct input_plugin { */ void (*finish)(void); - bool (*open)(struct input_stream *is, const char *url); + bool (*open)(struct input_stream *is, const char *url, + GError **error_r); void (*close)(struct input_stream *is); struct tag *(*tag)(struct input_stream *is); - int (*buffer)(struct input_stream *is); - size_t (*read)(struct input_stream *is, void *ptr, size_t size); + int (*buffer)(struct input_stream *is, GError **error_r); + size_t (*read)(struct input_stream *is, void *ptr, size_t size, + GError **error_r); bool (*eof)(struct input_stream *is); - bool (*seek)(struct input_stream *is, off_t offset, int whence); + bool (*seek)(struct input_stream *is, goffset offset, int whence, + GError **error_r); }; #endif diff --git a/src/input_registry.c b/src/input_registry.c new file mode 100644 index 000000000..07266a856 --- /dev/null +++ b/src/input_registry.c @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "input_registry.h" +#include "input/file_input_plugin.h" + +#ifdef ENABLE_ARCHIVE +#include "input/archive_input_plugin.h" +#endif + +#ifdef ENABLE_CURL +#include "input/curl_input_plugin.h" +#endif + +#ifdef ENABLE_MMS +#include "input/mms_input_plugin.h" +#endif + +#include <glib.h> + +const struct input_plugin *const input_plugins[] = { + &input_plugin_file, +#ifdef ENABLE_ARCHIVE + &input_plugin_archive, +#endif +#ifdef ENABLE_CURL + &input_plugin_curl, +#endif +#ifdef ENABLE_MMS + &input_plugin_mms, +#endif + NULL +}; + +bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1]; diff --git a/src/input_registry.h b/src/input_registry.h new file mode 100644 index 000000000..0a9cd570f --- /dev/null +++ b/src/input_registry.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_INPUT_REGISTRY_H +#define MPD_INPUT_REGISTRY_H + +#include "check.h" + +#include <stdbool.h> + +/** + * NULL terminated list of all input plugins which were enabled at + * compile time. + */ +extern const struct input_plugin *const input_plugins[]; + +extern bool input_plugins_enabled[]; + +#endif diff --git a/src/input_stream.c b/src/input_stream.c index 6a1b5841b..fda217f0f 100644 --- a/src/input_stream.c +++ b/src/input_stream.c @@ -17,115 +17,41 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "input_plugin.h" #include "config.h" -#include "conf.h" - -#include "input/file_input_plugin.h" +#include "input_stream.h" +#include "input_registry.h" +#include "input_plugin.h" #include "input/rewind_input_plugin.h" -#ifdef ENABLE_ARCHIVE -#include "input/archive_input_plugin.h" -#endif - -#ifdef HAVE_CURL -#include "input/curl_input_plugin.h" -#endif - -#include "input/lastfm_input_plugin.h" - -#ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" -#endif - #include <glib.h> #include <assert.h> -#include <string.h> - -static const struct input_plugin *const input_plugins[] = { - &input_plugin_file, -#ifdef ENABLE_ARCHIVE - &input_plugin_archive, -#endif -#ifdef HAVE_CURL - &input_plugin_curl, -#endif -#ifdef ENABLE_LASTFM - &lastfm_input_plugin, -#endif -#ifdef ENABLE_MMS - &input_plugin_mms, -#endif -}; - -static bool input_plugins_enabled[G_N_ELEMENTS(input_plugins)]; - -static const unsigned num_input_plugins = - sizeof(input_plugins) / sizeof(input_plugins[0]); - -/** - * Find the "input" configuration block for the specified plugin. - * - * @param plugin_name the name of the input plugin - * @return the configuration block, or NULL if none was configured - */ -static const struct config_param * -input_plugin_config(const char *plugin_name) -{ - const struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) { - const char *name = - config_get_block_string(param, "plugin", NULL); - if (name == NULL) - g_error("input configuration without 'plugin' name in line %d", - param->line); - if (strcmp(name, plugin_name) == 0) - return param; - } - - return NULL; -} - -void input_stream_global_init(void) +static inline GQuark +input_quark(void) { - for (unsigned i = 0; i < num_input_plugins; ++i) { - const struct input_plugin *plugin = input_plugins[i]; - const struct config_param *param = - input_plugin_config(plugin->name); - - if (!config_get_block_bool(param, "enabled", true)) - /* the plugin is disabled in mpd.conf */ - continue; - - if (plugin->init == NULL || plugin->init(param)) - input_plugins_enabled[i] = true; - } -} - -void input_stream_global_finish(void) -{ - for (unsigned i = 0; i < num_input_plugins; ++i) - if (input_plugins_enabled[i] && - input_plugins[i]->finish != NULL) - input_plugins[i]->finish(); + return g_quark_from_static_string("input"); } bool -input_stream_open(struct input_stream *is, const char *url) +input_stream_open(struct input_stream *is, const char *url, GError **error_r) { + GError *error = NULL; + + assert(error_r == NULL || *error_r == NULL); + is->seekable = false; is->ready = false; is->offset = 0; is->size = -1; - is->error = 0; is->mime = NULL; - for (unsigned i = 0; i < num_input_plugins; ++i) { + for (unsigned i = 0; input_plugins[i] != NULL; ++i) { const struct input_plugin *plugin = input_plugins[i]; - if (input_plugins_enabled[i] && plugin->open(is, url)) { + if (!input_plugins_enabled[i]) + continue; + + if (plugin->open(is, url, &error)) { assert(is->plugin != NULL); assert(is->plugin->close != NULL); assert(is->plugin->read != NULL); @@ -135,19 +61,24 @@ input_stream_open(struct input_stream *is, const char *url) input_rewind_open(is); return true; + } else if (error != NULL) { + g_propagate_error(error_r, error); + return false; } } + g_set_error(error_r, input_quark(), 0, "Unrecognized URI"); return false; } bool -input_stream_seek(struct input_stream *is, off_t offset, int whence) +input_stream_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r) { if (is->plugin->seek == NULL) return false; - return is->plugin->seek(is, offset, whence); + return is->plugin->seek(is, offset, whence, error_r); } struct tag * @@ -161,12 +92,13 @@ input_stream_tag(struct input_stream *is) } size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size) +input_stream_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) { assert(ptr != NULL); assert(size > 0); - return is->plugin->read(is, ptr, size); + return is->plugin->read(is, ptr, size, error_r); } void input_stream_close(struct input_stream *is) @@ -181,10 +113,11 @@ bool input_stream_eof(struct input_stream *is) return is->plugin->eof(is); } -int input_stream_buffer(struct input_stream *is) +int +input_stream_buffer(struct input_stream *is, GError **error_r) { if (is->plugin->buffer == NULL) return 0; - return is->plugin->buffer(is); + return is->plugin->buffer(is, error_r); } diff --git a/src/input_stream.h b/src/input_stream.h index 35b0d44fd..7d61b9d86 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -20,11 +20,17 @@ #ifndef MPD_INPUT_STREAM_H #define MPD_INPUT_STREAM_H +#include "check.h" + +#include <glib.h> + #include <stddef.h> #include <stdbool.h> #include <sys/types.h> -struct input_stream; +#if !GLIB_CHECK_VERSION(2,14,0) +typedef gint64 goffset; +#endif struct input_stream { /** @@ -49,19 +55,14 @@ struct input_stream { bool seekable; /** - * an optional errno error code, set to non-zero after an error occured - */ - int error; - - /** * the size of the resource, or -1 if unknown */ - off_t size; + goffset size; /** * the current offset within the stream */ - off_t offset; + goffset offset; /** * the MIME content type of the resource, or NULL if unknown @@ -70,16 +71,6 @@ struct input_stream { }; /** - * Initializes this library and all input_stream implementations. - */ -void input_stream_global_init(void); - -/** - * Deinitializes this library and all input_stream implementations. - */ -void input_stream_global_finish(void); - -/** * Opens a new input stream. You may not access it until the "ready" * flag is set. * @@ -87,7 +78,7 @@ void input_stream_global_finish(void); * @return true on success */ bool -input_stream_open(struct input_stream *is, const char *url); +input_stream_open(struct input_stream *is, const char *url, GError **error_r); /** * Closes the input stream and free resources. This does not free the @@ -106,7 +97,8 @@ input_stream_close(struct input_stream *is); * @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END */ bool -input_stream_seek(struct input_stream *is, off_t offset, int whence); +input_stream_seek(struct input_stream *is, goffset offset, int whence, + GError **error_r); /** * Returns true if the stream has reached end-of-file. @@ -130,7 +122,7 @@ input_stream_tag(struct input_stream *is); * The semantics of this function are not well-defined, and it will * eventually be removed. */ -int input_stream_buffer(struct input_stream *is); +int input_stream_buffer(struct input_stream *is, GError **error_r); /** * Reads data from the stream into the caller-supplied buffer. @@ -142,6 +134,7 @@ int input_stream_buffer(struct input_stream *is); * @return the number of bytes read */ size_t -input_stream_read(struct input_stream *is, void *ptr, size_t size); +input_stream_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r); #endif diff --git a/src/listen.c b/src/listen.c index 98108d9da..2b1ac7f5d 100644 --- a/src/listen.c +++ b/src/listen.c @@ -17,12 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "listen.h" #include "socket_util.h" #include "client.h" #include "conf.h" -#include "utils.h" -#include "config.h" +#include "fd_util.h" +#include "glib_compat.h" #include <sys/types.h> #include <sys/stat.h> @@ -347,7 +348,8 @@ listen_add_config_param(unsigned int port, } } -void listen_global_init(void) +bool +listen_global_init(GError **error_r) { int port = config_get_positive(CONF_PORT, DEFAULT_PORT); const struct config_param *param = @@ -361,10 +363,12 @@ void listen_global_init(void) do { success = listen_add_config_param(port, param, &error); - if (!success) - g_error("Failed to listen on %s (line %i): %s", - param->value, param->line, - error->message); + if (!success) { + g_propagate_prefixed_error(error_r, error, + "Failed to listen on %s (line %i): ", + param->value, param->line); + return false; + } param = config_get_next_param(CONF_BIND_TO_ADDRESS, param); @@ -374,12 +378,16 @@ void listen_global_init(void) configured port on all interfaces */ success = listen_add_port(port, &error); - if (!success) - g_error("Failed to listen on *:%d: %s", - port, error->message); + if (!success) { + g_propagate_prefixed_error(error_r, error, + "Failed to listen on *:%d: ", + port); + return false; + } } listen_port = port; + return true; } void listen_global_finish(void) @@ -419,12 +427,11 @@ listen_in_event(G_GNUC_UNUSED GIOChannel *source, { int listen_fd = GPOINTER_TO_INT(data), fd; struct sockaddr_storage sa; - socklen_t sa_length = sizeof(sa); + size_t sa_length = sizeof(sa); - fd = accept(listen_fd, (struct sockaddr*)&sa, &sa_length); + fd = accept_cloexec_nonblock(listen_fd, (struct sockaddr*)&sa, + &sa_length); if (fd >= 0) { - set_nonblocking(fd); - client_new(fd, (struct sockaddr*)&sa, sa_length, get_remote_uid(fd)); } else if (fd < 0 && errno != EINTR) { diff --git a/src/listen.h b/src/listen.h index 63253fc53..9f55edb88 100644 --- a/src/listen.h +++ b/src/listen.h @@ -20,9 +20,14 @@ #ifndef MPD_LISTEN_H #define MPD_LISTEN_H +#include <glib.h> + +#include <stdbool.h> + extern int listen_port; -void listen_global_init(void); +bool +listen_global_init(GError **error_r); void listen_global_finish(void); diff --git a/src/locate.c b/src/locate.c index 175bca35a..7bc23db16 100644 --- a/src/locate.c +++ b/src/locate.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "locate.h" #include "path.h" #include "tag.h" @@ -42,9 +43,9 @@ locate_parse_type(const char *str) if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY)) return LOCATE_TAG_ANY_TYPE; - for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) - if (0 == g_ascii_strcasecmp(str, tag_item_names[i])) - return i; + i = tag_name_parse_i(str); + if (i != TAG_NUM_OF_ITEM_TYPES) + return i; return -1; } @@ -17,10 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "log.h" #include "conf.h" #include "utils.h" -#include "config.h" +#include "fd_util.h" #include <assert.h> #include <sys/types.h> @@ -128,7 +129,7 @@ open_log_file(void) { assert(out_filename != NULL); - return open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); } static void @@ -271,7 +272,10 @@ void setup_log_output(bool use_stdout) { fflush(NULL); if (!use_stdout) { - if (out_filename != NULL) { + if (out_filename == NULL) + out_fd = open("/dev/null", O_WRONLY); + + if (out_fd >= 0) { redirect_logs(out_fd); close(out_fd); } @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "ls.h" #include "uri.h" #include "client.h" -#include "config.h" #include <assert.h> #include <string.h> @@ -32,12 +32,9 @@ * connected by IPC socket. */ static const char *remoteUrlPrefixes[] = { -#ifdef HAVE_CURL +#ifdef ENABLE_CURL "http://", #endif -#ifdef ENABLE_LASTFM - "lastfm://", -#endif #ifdef ENABLE_MMS "mms://", "mmsh://", diff --git a/src/main.c b/src/main.c index 5035a4836..a40dae369 100644 --- a/src/main.c +++ b/src/main.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "main.h" #include "daemon.h" #include "client.h" @@ -33,7 +34,6 @@ #include "path.h" #include "mapper.h" #include "chunk.h" -#include "decoder_control.h" #include "player_control.h" #include "stats.h" #include "sig_handlers.h" @@ -44,18 +44,21 @@ #include "permission.h" #include "replay_gain.h" #include "decoder_list.h" -#include "input_stream.h" +#include "input_init.h" +#include "playlist_list.h" #include "state_file.h" #include "tag.h" #include "dbUtils.h" -#include "config.h" -#include "normalize.h" #include "zeroconf.h" #include "event_pipe.h" #include "dirvec.h" #include "songvec.h" #include "tag_pool.h" +#ifdef ENABLE_INOTIFY +#include "inotify_update.h" +#endif + #ifdef ENABLE_SQLITE #include "sticker.h" #endif @@ -88,7 +91,34 @@ enum { GThread *main_task; GMainLoop *main_loop; -struct notify main_notify; +GCond *main_cond; + +static void +glue_daemonize_init(const struct options *options) +{ + daemonize_init(config_get_string(CONF_USER, NULL), + config_get_string(CONF_GROUP, NULL), + config_get_path(CONF_PID_FILE)); + + if (options->kill) + daemonize_kill(); +} + +static void +glue_mapper_init(void) +{ + const char *music_dir, *playlist_dir; + + music_dir = config_get_path(CONF_MUSIC_DIR); +#if GLIB_CHECK_VERSION(2,14,0) + if (music_dir == NULL) + music_dir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); +#endif + + playlist_dir = config_get_path(CONF_PLAYLIST_DIR); + + mapper_init(music_dir, playlist_dir); +} /** * Returns the database. If this function returns false, this has not @@ -96,7 +126,7 @@ struct notify main_notify; * process has been daemonized. */ static bool -openDB(const Options *options) +glue_db_init_and_load(void) { const char *path = config_get_path(CONF_DB_FILE); bool ret; @@ -115,19 +145,11 @@ openDB(const Options *options) db_init(path); - if (options->createDB > 0) - /* don't attempt to load the old database */ - return false; - ret = db_load(&error); if (!ret) { g_warning("Failed to load database: %s", error->message); g_error_free(error); - if (options->createDB < 0) - g_error("can't open db file and using " - "\"--no-create-db\" command line option"); - if (!db_check()) exit(EXIT_FAILURE); @@ -141,11 +163,34 @@ openDB(const Options *options) } /** + * Configure and initialize the sticker subsystem. + */ +static void +glue_sticker_init(void) +{ +#ifdef ENABLE_SQLITE + bool success; + GError *error = NULL; + + success = sticker_global_init(config_get_path(CONF_STICKER_FILE), + &error); + if (!success) + g_error("%s", error->message); +#endif +} + +static void +glue_state_file_init(void) +{ + state_file_init(config_get_path(CONF_STATE_FILE)); +} + +/** * Windows-only initialization of the Winsock2 library. */ -#ifdef WIN32 static void winsock_init(void) { +#ifdef WIN32 WSADATA sockinfo; int retval; @@ -161,9 +206,8 @@ static void winsock_init(void) g_error("We use Winsock2 but your version is either too new or " "old; please install Winsock 2.x\n"); } - -} #endif +} /** * Initialize the decoder and player core, including the music pipe. @@ -210,7 +254,6 @@ initialize_decoder_and_player(void) buffered_before_play = buffered_chunks; pc_init(buffered_chunks, buffered_before_play); - dc_init(); } /** @@ -228,9 +271,11 @@ idle_event_emitted(void) int main(int argc, char *argv[]) { - Options options; + struct options options; clock_t start; bool create_db; + GError *error = NULL; + bool success; daemonize_close_stdin(); @@ -239,45 +284,51 @@ int main(int argc, char *argv[]) setlocale(LC_CTYPE,""); #endif + g_set_application_name("Music Player Daemon"); + /* enable GLib's thread safety code */ g_thread_init(NULL); -#ifdef WIN32 winsock_init(); -#endif idle_init(); dirvec_init(); songvec_init(); tag_pool_init(); config_global_init(); - parseOptions(argc, argv, &options); - - daemonize_init(config_get_string(CONF_USER, NULL), - config_get_path(CONF_PID_FILE)); + success = parse_cmdline(argc, argv, &options, &error); + if (!success) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } - if (options.kill) - daemonize_kill(); + glue_daemonize_init(&options); stats_global_init(); tag_lib_init(); - log_init(options.verbose, options.stdOutput); + log_init(options.verbose, options.log_stderr); - listen_global_init(); + success = listen_global_init(&error); + if (!success) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } daemonize_set_user(); main_task = g_thread_self(); main_loop = g_main_loop_new(NULL, FALSE); - notify_init(&main_notify); + main_cond = g_cond_new(); event_pipe_init(); event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); path_global_init(); - mapper_init(); + glue_mapper_init(); initPermissions(); - initPlaylist(); + playlist_global_init(); spl_global_init(); #ifdef ENABLE_ARCHIVE archive_plugin_init_all(); @@ -285,11 +336,9 @@ int main(int argc, char *argv[]) decoder_plugin_init_all(); update_global_init(); - create_db = !openDB(&options); + create_db = !glue_db_init_and_load(); -#ifdef ENABLE_SQLITE - sticker_global_init(config_get_path(CONF_STICKER_FILE)); -#endif + glue_sticker_init(); command_init(); initialize_decoder_and_player(); @@ -298,12 +347,18 @@ int main(int argc, char *argv[]) audio_output_all_init(); client_manager_init(); replay_gain_global_init(); - initNormalization(); - input_stream_global_init(); + + if (!input_stream_global_init(&error)) { + g_warning("%s", error->message); + g_error_free(error); + return EXIT_FAILURE; + } + + playlist_list_global_init(); daemonize(options.daemon); - setup_log_output(options.stdOutput); + setup_log_output(options.log_stderr); initSigHandlers(); @@ -312,15 +367,29 @@ int main(int argc, char *argv[]) player_create(); if (create_db) { - /* the database failed to load, or MPD was started - with --create-db: recreate a new database */ - unsigned job = directory_update_init(NULL); + /* the database failed to load: recreate the + database */ + unsigned job = update_enqueue(NULL, true); if (job == 0) g_error("directory update failed"); } + glue_state_file_init(); - state_file_init(config_get_path(CONF_STATE_FILE)); + success = config_get_bool(CONF_AUTO_UPDATE, false); +#ifdef ENABLE_INOTIFY + if (success && mapper_has_music_directory()) + mpd_inotify_init(); +#else + if (success) + g_warning("inotify: auto_update was disabled. enable during compilation phase"); +#endif + + config_global_check(); + + /* enable all audio outputs (if not already done by + playlist_state_restore() */ + pc_update_audio(); /* run the main loop */ @@ -330,12 +399,16 @@ int main(int argc, char *argv[]) g_main_loop_unref(main_loop); +#ifdef ENABLE_INOTIFY + mpd_inotify_finish(); +#endif + state_file_finish(); - playerKill(); + pc_kill(); finishZeroconf(); client_manager_deinit(); listen_global_finish(); - finishPlaylist(); + playlist_global_finish(); start = clock(); db_finish(); @@ -346,18 +419,16 @@ int main(int argc, char *argv[]) sticker_global_finish(); #endif - notify_deinit(&main_notify); + g_cond_free(main_cond); event_pipe_deinit(); + playlist_list_global_finish(); input_stream_global_finish(); - finishNormalization(); audio_output_all_finish(); - finishAudioConfig(); volume_finish(); mapper_finish(); path_global_finish(); finishPermissions(); - dc_deinit(); pc_deinit(); command_finish(); update_global_finish(); diff --git a/src/main.h b/src/main.h index 8ed02bf5d..de673829d 100644 --- a/src/main.h +++ b/src/main.h @@ -26,6 +26,6 @@ extern GThread *main_task; extern GMainLoop *main_loop; -extern struct notify main_notify; +extern GCond *main_cond; #endif diff --git a/src/mapper.c b/src/mapper.c index 5518cb79e..0c3348aa1 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -21,20 +21,16 @@ * Maps directory and song objects to file system paths. */ +#include "config.h" #include "mapper.h" #include "directory.h" #include "song.h" #include "path.h" -#include "conf.h" #include <glib.h> #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> #include <string.h> -#include <errno.h> static char *music_dir; static size_t music_dir_length; @@ -58,17 +54,10 @@ strdup_chop_slash(const char *path_fs) static void mapper_set_music_dir(const char *path) { - int ret; - struct stat st; - music_dir = strdup_chop_slash(path); music_dir_length = strlen(music_dir); - ret = stat(music_dir, &st); - if (ret < 0) - g_warning("failed to stat music directory \"%s\": %s", - music_dir, g_strerror(errno)); - else if (!S_ISDIR(st.st_mode)) + if (!g_file_test(music_dir, G_FILE_TEST_IS_DIR)) g_warning("music directory is not a directory: \"%s\"", music_dir); } @@ -76,38 +65,20 @@ mapper_set_music_dir(const char *path) static void mapper_set_playlist_dir(const char *path) { - int ret; - struct stat st; - playlist_dir = g_strdup(path); - ret = stat(playlist_dir, &st); - if (ret < 0) - g_warning("failed to stat playlist directory \"%s\": %s", - playlist_dir, g_strerror(errno)); - else if (!S_ISDIR(st.st_mode)) + if (!g_file_test(playlist_dir, G_FILE_TEST_IS_DIR)) g_warning("playlist directory is not a directory: \"%s\"", playlist_dir); } -void mapper_init(void) +void mapper_init(const char *_music_dir, const char *_playlist_dir) { - const char *path; - - path = config_get_path(CONF_MUSIC_DIR); - if (path != NULL) - mapper_set_music_dir(path); -#if GLIB_CHECK_VERSION(2,14,0) - else { - path = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC); - if (path != NULL) - mapper_set_music_dir(path); - } -#endif + if (_music_dir != NULL) + mapper_set_music_dir(_music_dir); - path = config_get_path(CONF_PLAYLIST_DIR); - if (path != NULL) - mapper_set_playlist_dir(path); + if (_playlist_dir != NULL) + mapper_set_playlist_dir(_playlist_dir); } void mapper_finish(void) @@ -189,9 +160,9 @@ map_song_fs(const struct song *song) assert(song_is_file(song)); if (song_in_database(song)) - return map_directory_child_fs(song->parent, song->url); + return map_directory_child_fs(song->parent, song->uri); else - return utf8_to_fs_charset(song->url); + return utf8_to_fs_charset(song->uri); } char * @@ -199,10 +170,10 @@ map_fs_to_utf8(const char *path_fs) { if (music_dir != NULL && strncmp(path_fs, music_dir, music_dir_length) == 0 && - path_fs[music_dir_length] == '/') + G_IS_DIR_SEPARATOR(path_fs[music_dir_length])) /* remove musicDir prefix */ path_fs += music_dir_length + 1; - else if (path_fs[0] == '/') + else if (G_IS_DIR_SEPARATOR(path_fs[0])) /* not within musicDir */ return NULL; diff --git a/src/mapper.h b/src/mapper.h index f109de0bd..12108cbeb 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -31,7 +31,7 @@ struct directory; struct song; -void mapper_init(void); +void mapper_init(const char *_music_dir, const char *_playlist_dir); void mapper_finish(void); diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer_plugin.c index 52e553cc5..6726f785a 100644 --- a/src/mixer/alsa_mixer.c +++ b/src/mixer/alsa_mixer_plugin.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../mixer_api.h" +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" #include <glib.h> #include <alsa/asoundlib.h> @@ -42,12 +43,22 @@ struct alsa_mixer { int volume_set; }; +/** + * The quark used for GError.domain. + */ +static inline GQuark +alsa_mixer_quark(void) +{ + return g_quark_from_static_string("alsa_mixer"); +} + static struct mixer * -alsa_mixer_init(const struct config_param *param) +alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, + G_GNUC_UNUSED GError **error_r) { struct alsa_mixer *am = g_new(struct alsa_mixer, 1); - mixer_init(&am->base, &alsa_mixer); + mixer_init(&am->base, &alsa_mixer_plugin); am->device = config_get_block_string(param, "mixer_device", VOLUME_MIXER_ALSA_DEFAULT); @@ -81,7 +92,7 @@ alsa_mixer_close(struct mixer *data) } static bool -alsa_mixer_open(struct mixer *data) +alsa_mixer_open(struct mixer *data, GError **error_r) { struct alsa_mixer *am = (struct alsa_mixer *)data; int err; @@ -91,29 +102,33 @@ alsa_mixer_open(struct mixer *data) err = snd_mixer_open(&am->handle, 0); if (err < 0) { - g_warning("problems opening alsa mixer: %s\n", snd_strerror(err)); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_open() failed: %s", snd_strerror(err)); return false; } if ((err = snd_mixer_attach(am->handle, am->device)) < 0) { - g_warning("problems attaching alsa mixer: %s\n", - snd_strerror(err)); alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to attach to %s: %s", + am->device, snd_strerror(err)); return false; } if ((err = snd_mixer_selem_register(am->handle, NULL, NULL)) < 0) { - g_warning("problems snd_mixer_selem_register'ing: %s\n", - snd_strerror(err)); alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_selem_register() failed: %s", + snd_strerror(err)); return false; } if ((err = snd_mixer_load(am->handle)) < 0) { - g_warning("problems snd_mixer_selem_register'ing: %s\n", - snd_strerror(err)); alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_load() failed: %s\n", + snd_strerror(err)); return false; } @@ -138,14 +153,14 @@ alsa_mixer_open(struct mixer *data) return true; } - g_warning("can't find alsa mixer control \"%s\"\n", am->control); - alsa_mixer_close(data); + g_set_error(error_r, alsa_mixer_quark(), 0, + "no such mixer control: %s", am->control); return false; } static int -alsa_mixer_get_volume(struct mixer *mixer) +alsa_mixer_get_volume(struct mixer *mixer, GError **error_r) { struct alsa_mixer *am = (struct alsa_mixer *)mixer; int err; @@ -156,8 +171,9 @@ alsa_mixer_get_volume(struct mixer *mixer) err = snd_mixer_handle_events(am->handle); if (err < 0) { - g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", - snd_strerror(err), "handle_events"); + g_set_error(error_r, alsa_mixer_quark(), err, + "snd_mixer_handle_events() failed: %s", + snd_strerror(err)); return false; } @@ -165,8 +181,9 @@ alsa_mixer_get_volume(struct mixer *mixer) SND_MIXER_SCHN_FRONT_LEFT, &level); if (err < 0) { - g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", - snd_strerror(err), "selem_get_playback_volume"); + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to read ALSA volume: %s", + snd_strerror(err)); return false; } @@ -183,7 +200,7 @@ alsa_mixer_get_volume(struct mixer *mixer) } static bool -alsa_mixer_set_volume(struct mixer *mixer, unsigned volume) +alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) { struct alsa_mixer *am = (struct alsa_mixer *)mixer; float vol; @@ -203,15 +220,16 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume) err = snd_mixer_selem_set_playback_volume_all(am->elem, level); if (err < 0) { - g_warning("problems setting alsa volume: %s\n", - snd_strerror(err)); + g_set_error(error_r, alsa_mixer_quark(), err, + "failed to set ALSA volume: %s", + snd_strerror(err)); return false; } return true; } -const struct mixer_plugin alsa_mixer = { +const struct mixer_plugin alsa_mixer_plugin = { .init = alsa_mixer_init, .finish = alsa_mixer_finish, .open = alsa_mixer_open, diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer_plugin.c index f2db01ff4..6e75edd9b 100644 --- a/src/mixer/oss_mixer.c +++ b/src/mixer/oss_mixer_plugin.c @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../mixer_api.h" +#include "config.h" +#include "mixer_api.h" +#include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -49,6 +51,15 @@ struct oss_mixer { int volume_control; }; +/** + * The quark used for GError.domain. + */ +static inline GQuark +oss_mixer_quark(void) +{ + return g_quark_from_static_string("oss_mixer"); +} + static int oss_find_mixer(const char *name) { @@ -65,11 +76,12 @@ oss_find_mixer(const char *name) } static struct mixer * -oss_mixer_init(const struct config_param *param) +oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param, + GError **error_r) { struct oss_mixer *om = g_new(struct oss_mixer, 1); - mixer_init(&om->base, &oss_mixer); + mixer_init(&om->base, &oss_mixer_plugin); om->device = config_get_block_string(param, "mixer_device", VOLUME_MIXER_OSS_DEFAULT); @@ -78,9 +90,9 @@ oss_mixer_init(const struct config_param *param) if (om->control != NULL) { om->volume_control = oss_find_mixer(om->control); if (om->volume_control < 0) { - g_warning("mixer control \"%s\" not found", - om->control); g_free(om); + g_set_error(error_r, oss_mixer_quark(), 0, + "no such mixer control: %s", om->control); return NULL; } } else @@ -108,13 +120,15 @@ oss_mixer_close(struct mixer *data) } static bool -oss_mixer_open(struct mixer *data) +oss_mixer_open(struct mixer *data, GError **error_r) { struct oss_mixer *om = (struct oss_mixer *) data; - om->device_fd = open(om->device, O_RDONLY); + om->device_fd = open_cloexec(om->device, O_RDONLY, 0); if (om->device_fd < 0) { - g_warning("Unable to open oss mixer \"%s\"\n", om->device); + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to open %s: %s", + om->device, g_strerror(errno)); return false; } @@ -122,14 +136,17 @@ oss_mixer_open(struct mixer *data) int devmask = 0; if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - g_warning("errors getting read_devmask for oss mixer\n"); + g_set_error(error_r, oss_mixer_quark(), errno, + "READ_DEVMASK failed: %s", + g_strerror(errno)); oss_mixer_close(data); return false; } if (((1 << om->volume_control) & devmask) == 0) { - g_warning("mixer control \"%s\" not usable\n", - om->control); + g_set_error(error_r, oss_mixer_quark(), 0, + "mixer control \"%s\" not usable", + om->control); oss_mixer_close(data); return false; } @@ -138,7 +155,7 @@ oss_mixer_open(struct mixer *data) } static int -oss_mixer_get_volume(struct mixer *mixer) +oss_mixer_get_volume(struct mixer *mixer, GError **error_r) { struct oss_mixer *om = (struct oss_mixer *)mixer; int left, right, level; @@ -148,7 +165,9 @@ oss_mixer_get_volume(struct mixer *mixer) ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level); if (ret < 0) { - g_warning("unable to read oss volume\n"); + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to read OSS volume: %s", + g_strerror(errno)); return false; } @@ -164,7 +183,7 @@ oss_mixer_get_volume(struct mixer *mixer) } static bool -oss_mixer_set_volume(struct mixer *mixer, unsigned volume) +oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) { struct oss_mixer *om = (struct oss_mixer *)mixer; int level; @@ -177,14 +196,16 @@ oss_mixer_set_volume(struct mixer *mixer, unsigned volume) ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level); if (ret < 0) { - g_warning("unable to set oss volume\n"); + g_set_error(error_r, oss_mixer_quark(), errno, + "failed to set OSS volume: %s", + g_strerror(errno)); return false; } return true; } -const struct mixer_plugin oss_mixer = { +const struct mixer_plugin oss_mixer_plugin = { .init = oss_mixer_init, .finish = oss_mixer_finish, .open = oss_mixer_open, diff --git a/src/mixer/pulse_mixer.c b/src/mixer/pulse_mixer.c deleted file mode 100644 index 5d9ce0475..000000000 --- a/src/mixer/pulse_mixer.c +++ /dev/null @@ -1,382 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "mixer_api.h" -#include "conf.h" - -#include <glib.h> -#include <pulse/volume.h> -#include <pulse/pulseaudio.h> - -#include <string.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pulse_mixer" - -struct pulse_mixer { - struct mixer base; - - const char *server; - const char *sink; - const char *output_name; - - uint32_t index; - bool online; - - struct pa_context *context; - struct pa_threaded_mainloop *mainloop; - struct pa_cvolume volume; - -}; - -/** - * \brief waits for a pulseaudio operation to finish, frees it and - * unlocks the mainloop - * \param operation the operation to wait for - * \return true if operation has finished normally (DONE state), - * false otherwise - */ -static bool -pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, - struct pa_operation *operation) -{ - pa_operation_state_t state; - - assert(mainloop != NULL); - assert(operation != NULL); - - state = pa_operation_get_state(operation); - while (state == PA_OPERATION_RUNNING) { - pa_threaded_mainloop_wait(mainloop); - state = pa_operation_get_state(operation); - } - - pa_operation_unref(operation); - - return state == PA_OPERATION_DONE; -} - -static void -sink_input_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) -{ - - struct pulse_mixer *pm = userdata; - - if (eol) { - g_debug("eol error sink_input_cb"); - return; - } - - if (i == NULL) { - g_debug("Sink input callback failure"); - return; - } - - g_debug("sink input cb %s, index %d ",i->name,i->index); - - if (strcmp(i->name,pm->output_name) == 0) { - pm->index = i->index; - pm->online = true; - pm->volume = i->volume; - } else - g_debug("bad name"); -} - -static void -sink_input_vol(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, - int eol, void *userdata) -{ - - struct pulse_mixer *pm = userdata; - - if (eol) { - g_debug("eol error sink_input_vol"); - return; - } - - if (i == NULL) { - g_debug("Sink input callback failure"); - return; - } - - g_debug("sink input vol %s, index %d ", i->name, i->index); - - pm->volume = i->volume; - - pa_threaded_mainloop_signal(pm->mainloop, 0); -} - -static void -subscribe_cb(pa_context *c, pa_subscription_event_type_t t, - uint32_t idx, void *userdata) -{ - - struct pulse_mixer *pm = userdata; - - g_debug("subscribe call back"); - - switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { - case PA_SUBSCRIPTION_EVENT_SINK_INPUT: - if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == - PA_SUBSCRIPTION_EVENT_REMOVE && - pm->index == idx) - pm->online = false; - else { - pa_operation *o; - - o = pa_context_get_sink_input_info(c, idx, - sink_input_cb, pm); - if (o == NULL) { - g_debug("pa_context_get_sink_input_info() failed"); - return; - } - - pa_operation_unref(o); - } - - break; - } -} - -static void -context_state_cb(pa_context *context, void *userdata) -{ - struct pulse_mixer *pm = userdata; - - switch (pa_context_get_state(context)) { - case PA_CONTEXT_READY: { - pa_operation *o; - - pa_context_set_subscribe_callback(context, subscribe_cb, pm); - - o = pa_context_subscribe(context, - (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, - NULL, NULL); - if (o == NULL) { - g_debug("pa_context_subscribe() failed"); - return; - } - - pa_operation_unref(o); - - o = pa_context_get_sink_input_info_list(context, - sink_input_cb, pm); - if (o == NULL) { - g_debug("pa_context_get_sink_input_info_list() failed"); - return; - } - - pa_operation_unref(o); - - pa_threaded_mainloop_signal(pm->mainloop, 0); - break; - } - - case PA_CONTEXT_UNCONNECTED: - case PA_CONTEXT_CONNECTING: - case PA_CONTEXT_AUTHORIZING: - case PA_CONTEXT_SETTING_NAME: - break; - case PA_CONTEXT_TERMINATED: - case PA_CONTEXT_FAILED: - pa_threaded_mainloop_signal(pm->mainloop, 0); - break; - } -} - - -static struct mixer * -pulse_mixer_init(const struct config_param *param) -{ - struct pulse_mixer *pm = g_new(struct pulse_mixer,1); - mixer_init(&pm->base, &pulse_mixer); - - pm->online = false; - - pm->server = config_get_block_string(param, "server", NULL); - pm->sink = config_get_block_string(param, "sink", NULL); - pm->output_name = config_get_block_string(param, "name", NULL); - - return &pm->base; -} - -static void -pulse_mixer_finish(struct mixer *data) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) data; - - g_free(pm); -} - -static bool -pulse_mixer_setup(struct pulse_mixer *pm) -{ - pa_context_set_state_callback(pm->context, context_state_cb, pm); - - if (pa_context_connect(pm->context, pm->server, - (pa_context_flags_t)0, NULL) < 0) { - g_debug("context server fail"); - return false; - } - - pa_threaded_mainloop_lock(pm->mainloop); - - if (pa_threaded_mainloop_start(pm->mainloop) < 0) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_debug("error start mainloop"); - return false; - } - - pa_threaded_mainloop_wait(pm->mainloop); - - if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_debug("error context not ready"); - return false; - } - - pa_threaded_mainloop_unlock(pm->mainloop); - - return true; -} - -static bool -pulse_mixer_open(struct mixer *data) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) data; - - g_debug("pulse mixer open"); - - pm->index = 0; - pm->online = false; - - pm->mainloop = pa_threaded_mainloop_new(); - if (pm->mainloop == NULL) { - g_debug("failed mainloop"); - return false; - } - - pm->context = pa_context_new(pa_threaded_mainloop_get_api(pm->mainloop), - "Mixer mpd"); - if (pm->context == NULL) { - pa_threaded_mainloop_stop(pm->mainloop); - pa_threaded_mainloop_free(pm->mainloop); - g_debug("failed context"); - return false; - } - - if (!pulse_mixer_setup(pm)) { - pa_threaded_mainloop_stop(pm->mainloop); - pa_context_disconnect(pm->context); - pa_context_unref(pm->context); - pa_threaded_mainloop_free(pm->mainloop); - return false; - } - - return true; -} - -static void -pulse_mixer_close(struct mixer *data) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) data; - - pa_threaded_mainloop_stop(pm->mainloop); - pa_context_disconnect(pm->context); - pa_context_unref(pm->context); - pa_threaded_mainloop_free(pm->mainloop); - - pm->online = false; -} - -static int -pulse_mixer_get_volume(struct mixer *mixer) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) mixer; - int ret; - pa_operation *o; - - pa_threaded_mainloop_lock(pm->mainloop); - - if (!pm->online) { - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } - - o = pa_context_get_sink_input_info(pm->context, pm->index, - sink_input_vol, pm); - if (o == NULL) { - pa_threaded_mainloop_unlock(pm->mainloop); - g_debug("pa_context_get_sink_input_info() failed"); - return false; - } - - if (!pulse_wait_for_operation(pm->mainloop, o)) { - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } - - ret = pm->online - ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) - : -1; - - pa_threaded_mainloop_unlock(pm->mainloop); - - return ret; -} - -static bool -pulse_mixer_set_volume(struct mixer *mixer, unsigned volume) -{ - struct pulse_mixer *pm = (struct pulse_mixer *) mixer; - struct pa_cvolume cvolume; - pa_operation *o; - - pa_threaded_mainloop_lock(pm->mainloop); - - if (!pm->online) { - pa_threaded_mainloop_unlock(pm->mainloop); - return false; - } - - pa_cvolume_set(&cvolume, pm->volume.channels, - (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); - - o = pa_context_set_sink_input_volume(pm->context, pm->index, - &cvolume, NULL, NULL); - pa_threaded_mainloop_unlock(pm->mainloop); - if (o == NULL) { - g_debug("pa_context_set_sink_input_volume() failed"); - return false; - } - - pa_operation_unref(o); - - return true; -} - -const struct mixer_plugin pulse_mixer = { - .init = pulse_mixer_init, - .finish = pulse_mixer_finish, - .open = pulse_mixer_open, - .close = pulse_mixer_close, - .get_volume = pulse_mixer_get_volume, - .set_volume = pulse_mixer_set_volume, -}; diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c new file mode 100644 index 000000000..5669e05c4 --- /dev/null +++ b/src/mixer/pulse_mixer_plugin.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "pulse_mixer_plugin.h" +#include "mixer_api.h" +#include "output/pulse_output_plugin.h" +#include "conf.h" +#include "event_pipe.h" + +#include <glib.h> + +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/introspect.h> +#include <pulse/stream.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pulse_mixer" + +struct pulse_mixer { + struct mixer base; + + struct pulse_output *output; + + bool online; + struct pa_cvolume volume; + +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +pulse_mixer_quark(void) +{ + return g_quark_from_static_string("pulse_mixer"); +} + +static void +pulse_mixer_offline(struct pulse_mixer *pm) +{ + if (!pm->online) + return; + + pm->online = false; + + event_pipe_emit(PIPE_EVENT_MIXER); +} + +/** + * Callback invoked by pulse_mixer_update(). Receives the new mixer + * value. + */ +static void +pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i, + int eol, void *userdata) +{ + struct pulse_mixer *pm = userdata; + + if (eol) + return; + + if (i == NULL) { + pulse_mixer_offline(pm); + return; + } + + pm->online = true; + pm->volume = i->volume; + + event_pipe_emit(PIPE_EVENT_MIXER); +} + +static void +pulse_mixer_update(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream) +{ + pa_operation *o; + + assert(context != NULL); + assert(stream != NULL); + assert(pa_stream_get_state(stream) == PA_STREAM_READY); + + o = pa_context_get_sink_input_info(context, + pa_stream_get_index(stream), + pulse_mixer_volume_cb, pm); + if (o == NULL) { + g_warning("pa_context_get_sink_input_info() failed: %s", + pa_strerror(pa_context_errno(context))); + pulse_mixer_offline(pm); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm, + struct pa_context *context) +{ + pa_operation *o; + + assert(context != NULL); + + o = pa_context_subscribe(context, + (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT, + NULL, NULL); + if (o == NULL) { + g_warning("pa_context_subscribe() failed: %s", + pa_strerror(pa_context_errno(context))); + return; + } + + pa_operation_unref(o); +} + +void +pulse_mixer_on_disconnect(struct pulse_mixer *pm) +{ + pulse_mixer_offline(pm); +} + +void +pulse_mixer_on_change(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream) +{ + pulse_mixer_update(pm, context, stream); +} + +static struct mixer * +pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + GError **error_r) +{ + struct pulse_mixer *pm; + struct pulse_output *po = ao; + + if (ao == NULL) { + g_set_error(error_r, pulse_mixer_quark(), 0, + "The pulse mixer cannot work without the audio output"); + return false; + } + + pm = g_new(struct pulse_mixer,1); + mixer_init(&pm->base, &pulse_mixer_plugin); + + pm->online = false; + pm->output = po; + + pulse_output_set_mixer(po, pm); + + return &pm->base; +} + +static void +pulse_mixer_finish(struct mixer *data) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) data; + + pulse_output_clear_mixer(pm->output, pm); + + /* free resources */ + + g_free(pm); +} + +static int +pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) mixer; + int ret; + + pa_threaded_mainloop_lock(pm->output->mainloop); + + ret = pm->online + ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM) + : -1; + + pa_threaded_mainloop_unlock(pm->output->mainloop); + + return ret; +} + +static bool +pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct pulse_mixer *pm = (struct pulse_mixer *) mixer; + struct pa_cvolume cvolume; + bool success; + + pa_threaded_mainloop_lock(pm->output->mainloop); + if (!pm->online) { + pa_threaded_mainloop_unlock(pm->output->mainloop); + g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected"); + return false; + } + + pa_cvolume_set(&cvolume, pm->volume.channels, + (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5); + success = pulse_output_set_volume(pm->output, &cvolume, error_r); + if (success) + pm->volume = cvolume; + pa_threaded_mainloop_unlock(pm->output->mainloop); + + return success; +} + +const struct mixer_plugin pulse_mixer_plugin = { + .init = pulse_mixer_init, + .finish = pulse_mixer_finish, + .get_volume = pulse_mixer_get_volume, + .set_volume = pulse_mixer_set_volume, +}; diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h new file mode 100644 index 000000000..2318b94be --- /dev/null +++ b/src/mixer/pulse_mixer_plugin.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PULSE_MIXER_PLUGIN_H +#define MPD_PULSE_MIXER_PLUGIN_H + +#include <pulse/def.h> + +struct pulse_mixer; +struct pa_context; +struct pa_stream; + +void +pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context); + +void +pulse_mixer_on_disconnect(struct pulse_mixer *pm); + +void +pulse_mixer_on_change(struct pulse_mixer *pm, + struct pa_context *context, struct pa_stream *stream); + +#endif diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c new file mode 100644 index 000000000..30ae13013 --- /dev/null +++ b/src/mixer/software_mixer_plugin.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "software_mixer_plugin.h" +#include "mixer_api.h" +#include "filter_plugin.h" +#include "filter_registry.h" +#include "filter/volume_filter_plugin.h" +#include "pcm_volume.h" + +#include <assert.h> +#include <math.h> + +struct software_mixer { + /** the base mixer class */ + struct mixer base; + + struct filter *filter; + + unsigned volume; +}; + +static struct mixer * +software_mixer_init(G_GNUC_UNUSED void *ao, + G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = g_new(struct software_mixer, 1); + + mixer_init(&sm->base, &software_mixer_plugin); + + sm->filter = filter_new(&volume_filter_plugin, NULL, NULL); + assert(sm->filter != NULL); + + sm->volume = 100; + + return &sm->base; +} + +static void +software_mixer_finish(struct mixer *data) +{ + struct software_mixer *sm = (struct software_mixer *)data; + + g_free(sm); +} + +static int +software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + return sm->volume; +} + +static bool +software_mixer_set_volume(struct mixer *mixer, unsigned volume, + G_GNUC_UNUSED GError **error_r) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + assert(volume <= 100); + + sm->volume = volume; + + if (volume >= 100) + volume = PCM_VOLUME_1; + else if (volume > 0) + volume = pcm_float_to_volume((exp(volume / 25.0) - 1) / + (54.5981500331F - 1)); + + volume_filter_set(sm->filter, volume); + return true; +} + +const struct mixer_plugin software_mixer_plugin = { + .init = software_mixer_init, + .finish = software_mixer_finish, + .get_volume = software_mixer_get_volume, + .set_volume = software_mixer_set_volume, + .global = true, +}; + +struct filter * +software_mixer_get_filter(struct mixer *mixer) +{ + struct software_mixer *sm = (struct software_mixer *)mixer; + + assert(sm->base.plugin == &software_mixer_plugin); + + return sm->filter; +} diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h new file mode 100644 index 000000000..a59f7edeb --- /dev/null +++ b/src/mixer/software_mixer_plugin.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef SOFTWARE_MIXER_PLUGIN_H +#define SOFTWARE_MIXER_PLUGIN_H + +struct mixer; +struct filter; + +/** + * Returns the (volume) filter associated with this mixer. All users + * of this mixer plugin should install this filter. + */ +struct filter * +software_mixer_get_filter(struct mixer *mixer); + +#endif diff --git a/src/mixer_all.c b/src/mixer_all.c index 252cb61ab..71f5c3c95 100644 --- a/src/mixer_all.c +++ b/src/mixer_all.c @@ -17,11 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "mixer_all.h" #include "mixer_control.h" #include "output_all.h" #include "output_plugin.h" #include "output_internal.h" +#include "pcm_volume.h" +#include "mixer_api.h" +#include "mixer_list.h" #include <glib.h> @@ -35,6 +39,8 @@ output_mixer_get_volume(unsigned i) { struct audio_output *output; struct mixer *mixer; + int volume; + GError *error = NULL; assert(i < audio_output_count()); @@ -46,7 +52,14 @@ output_mixer_get_volume(unsigned i) if (mixer == NULL) return -1; - return mixer_get_volume(mixer); + volume = mixer_get_volume(mixer, &error); + if (volume < 0 && error != NULL) { + g_warning("Failed to read mixer for '%s': %s", + output->name, error->message); + g_error_free(error); + } + + return volume; } int @@ -70,12 +83,15 @@ mixer_all_get_volume(void) } static bool -output_mixer_set_volume(unsigned i, int volume, bool relative) +output_mixer_set_volume(unsigned i, unsigned volume) { struct audio_output *output; struct mixer *mixer; + bool success; + GError *error = NULL; assert(i < audio_output_count()); + assert(volume <= 100); output = audio_output_get(i); if (!output->enabled) @@ -85,31 +101,81 @@ output_mixer_set_volume(unsigned i, int volume, bool relative) if (mixer == NULL) return false; - if (relative) { - int prev = mixer_get_volume(mixer); - if (prev < 0) - return false; - - volume += prev; + success = mixer_set_volume(mixer, volume, &error); + if (!success && error != NULL) { + g_warning("Failed to set mixer for '%s': %s", + output->name, error->message); + g_error_free(error); } - if (volume > 100) - volume = 100; - else if (volume < 0) - volume = 0; - - return mixer_set_volume(mixer, volume); + return success; } bool -mixer_all_set_volume(int volume, bool relative) +mixer_all_set_volume(unsigned volume) { bool success = false; unsigned count = audio_output_count(); + assert(volume <= 100); + for (unsigned i = 0; i < count; i++) - success = output_mixer_set_volume(i, volume, relative) + success = output_mixer_set_volume(i, volume) || success; return success; } + +static int +output_mixer_get_software_volume(unsigned i) +{ + struct audio_output *output; + struct mixer *mixer; + + assert(i < audio_output_count()); + + output = audio_output_get(i); + if (!output->enabled) + return -1; + + mixer = output->mixer; + if (mixer == NULL || mixer->plugin != &software_mixer_plugin) + return -1; + + return mixer_get_volume(mixer, NULL); +} + +int +mixer_all_get_software_volume(void) +{ + unsigned count = audio_output_count(), ok = 0; + int volume, total = 0; + + for (unsigned i = 0; i < count; i++) { + volume = output_mixer_get_software_volume(i); + if (volume >= 0) { + total += volume; + ++ok; + } + } + + if (ok == 0) + return -1; + + return total / ok; +} + +void +mixer_all_set_software_volume(unsigned volume) +{ + unsigned count = audio_output_count(); + + assert(volume <= PCM_VOLUME_1); + + for (unsigned i = 0; i < count; i++) { + struct audio_output *output = audio_output_get(i); + if (output->mixer != NULL && + output->mixer->plugin == &software_mixer_plugin) + mixer_set_volume(output->mixer, volume, NULL); + } +} diff --git a/src/mixer_all.h b/src/mixer_all.h index 66c4988de..ebe8fed68 100644 --- a/src/mixer_all.h +++ b/src/mixer_all.h @@ -37,11 +37,26 @@ mixer_all_get_volume(void); /** * Sets the volume on all available mixers. * - * @param volume the volume (range 0..100 or -100..100 if #relative) - * @param relative if true, then the #volume is added to the current value + * @param volume the volume (range 0..100) * @return true on success, false on failure */ bool -mixer_all_set_volume(int volume, bool relative); +mixer_all_set_volume(unsigned volume); + +/** + * Similar to mixer_all_get_volume(), but gets the volume only for + * software mixers. See #software_mixer_plugin. This function fails + * if no software mixer is configured. + */ +int +mixer_all_get_software_volume(void); + +/** + * Similar to mixer_all_set_volume(), but sets the volume only for + * software mixers. See #software_mixer_plugin. This function cannot + * fail, because the underlying software mixers cannot fail either. + */ +void +mixer_all_set_software_volume(unsigned volume); #endif diff --git a/src/mixer_api.c b/src/mixer_api.c index cff23a397..67b7037ef 100644 --- a/src/mixer_api.c +++ b/src/mixer_api.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "mixer_api.h" #undef G_LOG_DOMAIN diff --git a/src/mixer_control.c b/src/mixer_control.c index e19b82d65..af01600a1 100644 --- a/src/mixer_control.c +++ b/src/mixer_control.c @@ -17,38 +17,26 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "mixer_control.h" #include "mixer_api.h" -#include <glib.h> - #include <assert.h> #include <stddef.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mixer" -static bool mixers_enabled = true; - -void -mixer_disable_all(void) -{ - g_debug("mixer api is disabled"); - mixers_enabled = false; -} - struct mixer * -mixer_new(const struct mixer_plugin *plugin, const struct config_param *param) +mixer_new(const struct mixer_plugin *plugin, void *ao, + const struct config_param *param, + GError **error_r) { struct mixer *mixer; - //mixers are disabled (by using software volume) - if (!mixers_enabled) { - return NULL; - } assert(plugin != NULL); - mixer = plugin->init(param); + mixer = plugin->init(ao, param, error_r); assert(mixer == NULL || mixer->plugin == plugin); @@ -72,7 +60,7 @@ mixer_free(struct mixer *mixer) } bool -mixer_open(struct mixer *mixer) +mixer_open(struct mixer *mixer, GError **error_r) { bool success; @@ -83,8 +71,10 @@ mixer_open(struct mixer *mixer) if (mixer->open) success = true; + else if (mixer->plugin->open == NULL) + success = mixer->open = true; else - success = mixer->open = mixer->plugin->open(mixer); + success = mixer->open = mixer->plugin->open(mixer, error_r); mixer->failed = !success; @@ -100,7 +90,9 @@ mixer_close_internal(struct mixer *mixer) assert(mixer->plugin != NULL); assert(mixer->open); - mixer->plugin->close(mixer); + if (mixer->plugin->close != NULL) + mixer->plugin->close(mixer); + mixer->open = false; } @@ -140,21 +132,26 @@ mixer_failed(struct mixer *mixer) } int -mixer_get_volume(struct mixer *mixer) +mixer_get_volume(struct mixer *mixer, GError **error_r) { int volume; assert(mixer != NULL); - if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer)) + if (mixer->plugin->global && !mixer->failed && + !mixer_open(mixer, error_r)) return -1; g_mutex_lock(mixer->mutex); if (mixer->open) { - volume = mixer->plugin->get_volume(mixer); - if (volume < 0) + GError *error = NULL; + + volume = mixer->plugin->get_volume(mixer, &error); + if (volume < 0 && error != NULL) { + g_propagate_error(error_r, error); mixer_failed(mixer); + } } else volume = -1; @@ -164,22 +161,21 @@ mixer_get_volume(struct mixer *mixer) } bool -mixer_set_volume(struct mixer *mixer, unsigned volume) +mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) { bool success; assert(mixer != NULL); assert(volume <= 100); - if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer)) + if (mixer->plugin->global && !mixer->failed && + !mixer_open(mixer, error_r)) return false; g_mutex_lock(mixer->mutex); if (mixer->open) { - success = mixer->plugin->set_volume(mixer, volume); - if (!success) - mixer_failed(mixer); + success = mixer->plugin->set_volume(mixer, volume, error_r); } else success = false; diff --git a/src/mixer_control.h b/src/mixer_control.h index 0f73e8f75..a550e0864 100644 --- a/src/mixer_control.h +++ b/src/mixer_control.h @@ -25,23 +25,24 @@ #ifndef MPD_MIXER_CONTROL_H #define MPD_MIXER_CONTROL_H +#include <glib.h> + #include <stdbool.h> struct mixer; struct mixer_plugin; struct config_param; -void -mixer_disable_all(void); - struct mixer * -mixer_new(const struct mixer_plugin *plugin, const struct config_param *param); +mixer_new(const struct mixer_plugin *plugin, void *ao, + const struct config_param *param, + GError **error_r); void mixer_free(struct mixer *mixer); bool -mixer_open(struct mixer *mixer); +mixer_open(struct mixer *mixer, GError **error_r); void mixer_close(struct mixer *mixer); @@ -54,9 +55,9 @@ void mixer_auto_close(struct mixer *mixer); int -mixer_get_volume(struct mixer *mixer); +mixer_get_volume(struct mixer *mixer, GError **error_r); bool -mixer_set_volume(struct mixer *mixer, unsigned volume); +mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r); #endif diff --git a/src/mixer_list.h b/src/mixer_list.h index 7db4a00d8..2d9c773f6 100644 --- a/src/mixer_list.h +++ b/src/mixer_list.h @@ -25,8 +25,9 @@ #ifndef MPD_MIXER_LIST_H #define MPD_MIXER_LIST_H -extern const struct mixer_plugin alsa_mixer; -extern const struct mixer_plugin oss_mixer; -extern const struct mixer_plugin pulse_mixer; +extern const struct mixer_plugin software_mixer_plugin; +extern const struct mixer_plugin alsa_mixer_plugin; +extern const struct mixer_plugin oss_mixer_plugin; +extern const struct mixer_plugin pulse_mixer_plugin; #endif diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h index 2b9b440e5..b6e67e8ff 100644 --- a/src/mixer_plugin.h +++ b/src/mixer_plugin.h @@ -27,6 +27,8 @@ #ifndef MPD_MIXER_PLUGIN_H #define MPD_MIXER_PLUGIN_H +#include <glib.h> + #include <stdbool.h> struct config_param; @@ -35,8 +37,16 @@ struct mixer; struct mixer_plugin { /** * Alocates and configures a mixer device. + * + * @param ao the pointer returned by audio_output_plugin.init + * @param param the configuration section, or NULL if there is + * no configuration + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return a mixer object, or NULL on error */ - struct mixer *(*init)(const struct config_param *param); + struct mixer *(*init)(void *ao, const struct config_param *param, + GError **error_r); /** * Finish and free mixer data @@ -45,8 +55,12 @@ struct mixer_plugin { /** * Open mixer device + * + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return true on success, false on error */ - bool (*open)(struct mixer *data); + bool (*open)(struct mixer *data, GError **error_r); /** * Close mixer device @@ -56,18 +70,23 @@ struct mixer_plugin { /** * Reads the current volume. * - * @return the current volume (0..100 including) or -1 on - * error + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return the current volume (0..100 including) or -1 if + * unavailable or on error (error_r set, mixer will be closed) */ - int (*get_volume)(struct mixer *mixer); + int (*get_volume)(struct mixer *mixer, GError **error_r); /** * Sets the volume. * + * @param error_r location to store the error occuring, or + * NULL to ignore errors * @param volume the new volume (0..100 including) - * @return true on success + * @return true on success, false on error */ - bool (*set_volume)(struct mixer *mixer, unsigned volume); + bool (*set_volume)(struct mixer *mixer, unsigned volume, + GError **error_r); /** * If true, then the mixer is automatically opened, even if diff --git a/src/mixer_type.c b/src/mixer_type.c new file mode 100644 index 000000000..804ecafef --- /dev/null +++ b/src/mixer_type.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "mixer_type.h" + +#include <assert.h> +#include <string.h> + +enum mixer_type +mixer_type_parse(const char *input) +{ + assert(input != NULL); + + if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0) + return MIXER_TYPE_NONE; + else if (strcmp(input, "hardware") == 0) + return MIXER_TYPE_HARDWARE; + else if (strcmp(input, "software") == 0) + return MIXER_TYPE_SOFTWARE; + else + return MIXER_TYPE_UNKNOWN; +} diff --git a/src/mixer_type.h b/src/mixer_type.h new file mode 100644 index 000000000..ec3cbf9cc --- /dev/null +++ b/src/mixer_type.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_MIXER_TYPE_H +#define MPD_MIXER_TYPE_H + +enum mixer_type { + /** parser error */ + MIXER_TYPE_UNKNOWN, + + /** mixer disabled */ + MIXER_TYPE_NONE, + + /** software mixer with pcm_volume() */ + MIXER_TYPE_SOFTWARE, + + /** hardware mixer (output's plugin) */ + MIXER_TYPE_HARDWARE, +}; + +/** + * Parses a "mixer_type" setting from the configuration file. + * + * @param input the configured string value; must not be NULL + * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could + * not be parsed + */ +enum mixer_type +mixer_type_parse(const char *input); + +#endif diff --git a/src/notify.c b/src/notify.c index 9168867d6..8954a8e61 100644 --- a/src/notify.c +++ b/src/notify.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "notify.h" void notify_init(struct notify *notify) diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 818c83ca2..b7325de07 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include "mixer_list.h" #include <glib.h> @@ -69,6 +70,16 @@ struct alsa_data { /** the size of one audio frame */ size_t frame_size; + + /** + * The size of one period, in number of frames. + */ + snd_pcm_uframes_t period_frames; + + /** + * The number of frames written in the current period. + */ + snd_pcm_uframes_t period_position; }; /** @@ -174,15 +185,37 @@ alsa_test_default_device(void) static snd_pcm_format_t get_bitformat(const struct audio_format *af) { - switch (af->bits) { - case 8: return SND_PCM_FORMAT_S8; - case 16: return SND_PCM_FORMAT_S16; - case 24: return SND_PCM_FORMAT_S24; - case 32: return SND_PCM_FORMAT_S32; + switch (af->format) { + case SAMPLE_FORMAT_S8: + return SND_PCM_FORMAT_S8; + + case SAMPLE_FORMAT_S16: + return SND_PCM_FORMAT_S16; + + case SAMPLE_FORMAT_S24_P32: + return SND_PCM_FORMAT_S24; + + case SAMPLE_FORMAT_S32: + return SND_PCM_FORMAT_S32; + + default: + return SND_PCM_FORMAT_UNKNOWN; } - return SND_PCM_FORMAT_UNKNOWN; } +static snd_pcm_format_t +byteswap_bitformat(snd_pcm_format_t fmt) +{ + switch(fmt) { + case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE; + case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE; + case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE; + case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE; + case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE; + case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} /** * Set up the snd_pcm_t object which was opened by the caller. Set up * the configured settings and the audio format. @@ -208,7 +241,6 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, configure_hw: /* configure HW params */ snd_pcm_hw_params_alloca(&hwparams); - cmd = "snd_pcm_hw_params_any"; err = snd_pcm_hw_params_any(ad->pcm, hwparams); if (err < 0) @@ -236,30 +268,72 @@ configure_hw: } err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat); - if (err == -EINVAL && (audio_format->bits == 24 || - audio_format->bits == 16)) { + if (err == -EINVAL && + byteswap_bitformat(bitformat) != SND_PCM_FORMAT_UNKNOWN) { + err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, + byteswap_bitformat(bitformat)); + if (err == 0) { + g_debug("ALSA device \"%s\": converting format %s to reverse-endian", + alsa_device(ad), + sample_format_to_string(audio_format->format)); + audio_format->reverse_endian = 1; + } + } + if (err == -EINVAL && (audio_format->format == SAMPLE_FORMAT_S24_P32 || + audio_format->format == SAMPLE_FORMAT_S16)) { /* fall back to 32 bit, let pcm_convert.c do the conversion */ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, SND_PCM_FORMAT_S32); - if (err == 0) - audio_format->bits = 32; + if (err == 0) { + g_debug("ALSA device \"%s\": converting format %s to 32 bit\n", + alsa_device(ad), + sample_format_to_string(audio_format->format)); + audio_format->format = SAMPLE_FORMAT_S32; + } + } + if (err == -EINVAL && (audio_format->format == SAMPLE_FORMAT_S24_P32 || + audio_format->format == SAMPLE_FORMAT_S16)) { + /* fall back to 32 bit, let pcm_convert.c do the conversion */ + err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, + byteswap_bitformat(SND_PCM_FORMAT_S32)); + if (err == 0) { + g_debug("ALSA device \"%s\": converting format %s to 32 bit backward-endian\n", + alsa_device(ad), + sample_format_to_string(audio_format->format)); + audio_format->format = SAMPLE_FORMAT_S32; + audio_format->reverse_endian = 1; + } } - if (err == -EINVAL && audio_format->bits != 16) { + if (err == -EINVAL && audio_format->format != SAMPLE_FORMAT_S16) { /* fall back to 16 bit, let pcm_convert.c do the conversion */ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, SND_PCM_FORMAT_S16); if (err == 0) { - g_debug("ALSA device \"%s\": converting %u bit to 16 bit\n", - alsa_device(ad), audio_format->bits); - audio_format->bits = 16; + g_debug("ALSA device \"%s\": converting format %s to 16 bit\n", + alsa_device(ad), + sample_format_to_string(audio_format->format)); + audio_format->format = SAMPLE_FORMAT_S16; + } + } + if (err == -EINVAL && audio_format->format != SAMPLE_FORMAT_S16) { + /* fall back to 16 bit, let pcm_convert.c do the conversion */ + err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, + byteswap_bitformat(SND_PCM_FORMAT_S16)); + if (err == 0) { + g_debug("ALSA device \"%s\": converting format %s to 16 bit backward-endian\n", + alsa_device(ad), + sample_format_to_string(audio_format->format)); + audio_format->format = SAMPLE_FORMAT_S16; + audio_format->reverse_endian = 1; } } if (err < 0) { g_set_error(error, alsa_output_quark(), err, - "ALSA device \"%s\" does not support %u bit audio: %s", - alsa_device(ad), audio_format->bits, + "ALSA device \"%s\" does not support format %s: %s", + alsa_device(ad), + sample_format_to_string(audio_format->format), snd_strerror(-err)); return false; } @@ -365,6 +439,9 @@ configure_hw: g_debug("buffer_size=%u period_size=%u", (unsigned)alsa_buffer_size, (unsigned)alsa_period_size); + ad->period_frames = alsa_period_size; + ad->period_position = 0; + return true; error: @@ -387,7 +464,7 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error) /* sample format is not supported by this plugin - fall back to 16 bit samples */ - audio_format->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; bitformat = SND_PCM_FORMAT_S16; } @@ -431,6 +508,7 @@ alsa_recover(struct alsa_data *ad, int err) /* fall-through to snd_pcm_prepare: */ case SND_PCM_STATE_SETUP: case SND_PCM_STATE_XRUN: + ad->period_position = 0; err = snd_pcm_prepare(ad->pcm); break; case SND_PCM_STATE_DISCONNECTED: @@ -448,11 +526,47 @@ alsa_recover(struct alsa_data *ad, int err) } static void +alsa_drain(void *data) +{ + struct alsa_data *ad = data; + + if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING) + return; + + if (ad->period_position > 0) { + /* generate some silence to finish the partial + period */ + snd_pcm_uframes_t nframes = + ad->period_frames - ad->period_position; + size_t nbytes = nframes * ad->frame_size; + void *buffer = g_malloc(nbytes); + snd_pcm_hw_params_t *params; + snd_pcm_format_t format; + unsigned channels; + + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_current(ad->pcm, params); + snd_pcm_hw_params_get_format(params, &format); + snd_pcm_hw_params_get_channels(params, &channels); + + snd_pcm_format_set_silence(format, buffer, nframes * channels); + ad->writei(ad->pcm, buffer, nframes); + g_free(buffer); + } + + snd_pcm_drain(ad->pcm); + + ad->period_position = 0; +} + +static void alsa_cancel(void *data) { struct alsa_data *ad = data; - alsa_recover(ad, snd_pcm_drop(ad->pcm)); + ad->period_position = 0; + + snd_pcm_drop(ad->pcm); } static void @@ -460,9 +574,6 @@ alsa_close(void *data) { struct alsa_data *ad = data; - if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING) - snd_pcm_drain(ad->pcm); - snd_pcm_close(ad->pcm); } @@ -475,8 +586,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error) while (true) { snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size); - if (ret > 0) + if (ret > 0) { + ad->period_position = (ad->period_position + ret) + % ad->period_frames; return ret * ad->frame_size; + } if (ret < 0 && ret != -EAGAIN && ret != -EINTR && alsa_recover(ad, ret) < 0) { @@ -494,7 +608,9 @@ const struct audio_output_plugin alsaPlugin = { .finish = alsa_finish, .open = alsa_open, .play = alsa_play, + .drain = alsa_drain, .cancel = alsa_cancel, .close = alsa_close, - .mixer_plugin = &alsa_mixer, + + .mixer_plugin = &alsa_mixer_plugin, }; diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c index 12d2b7552..7afca0db2 100644 --- a/src/output/ao_plugin.c +++ b/src/output/ao_plugin.c @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include <ao/ao.h> #include <glib.h> @@ -169,13 +170,24 @@ ao_output_open(void *data, struct audio_format *audio_format, ao_sample_format format; struct ao_data *ad = (struct ao_data *)data; - /* support for 24 bit samples in libao is currently dubious, - and until we have sorted that out, resample everything to - 16 bit */ - if (audio_format->bits > 16) - audio_format->bits = 16; + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + format.bits = 8; + break; + + case SAMPLE_FORMAT_S16: + format.bits = 16; + break; + + default: + /* support for 24 bit samples in libao is currently + dubious, and until we have sorted that out, + convert everything to 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + format.bits = 16; + break; + } - format.bits = audio_format->bits; format.rate = audio_format->sample_rate; format.byte_format = AO_FMT_NATIVE; format.channels = audio_format->channels; diff --git a/src/output/fifo_plugin.c b/src/output/fifo_output_plugin.c index 76bbe8cfa..658c77340 100644 --- a/src/output/fifo_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -17,9 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../utils.h" -#include "../timer.h" +#include "config.h" +#include "output_api.h" +#include "utils.h" +#include "timer.h" +#include "fd_util.h" #include <glib.h> @@ -152,7 +154,7 @@ fifo_open(struct fifo_data *fd, GError **error) if (!fifo_check(fd, error)) return false; - fd->input = open(fd->path, O_RDONLY|O_NONBLOCK); + fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK, 0); if (fd->input < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for reading: %s", @@ -161,7 +163,7 @@ fifo_open(struct fifo_data *fd, GError **error) return false; } - fd->output = open(fd->path, O_WRONLY|O_NONBLOCK); + fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK, 0); if (fd->output < 0) { g_set_error(error, fifo_output_quark(), errno, "Could not open FIFO \"%s\" for writing: %s", diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c index 52a398e3b..83f08372e 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "httpd_client.h" #include "httpd_internal.h" #include "fifo_buffer.h" #include "page.h" #include "icy_server.h" +#include "glib_compat.h" #include <stdbool.h> #include <assert.h> @@ -482,11 +484,6 @@ httpd_client_queue_size(const struct httpd_client *client) return size; } -/* g_queue_clear() was introduced in GLib 2.14 */ -#if !GLIB_CHECK_VERSION(2,14,0) -#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0) -#endif - void httpd_client_cancel(struct httpd_client *client) { diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index 2257e27a2..22155b7ba 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -30,11 +30,18 @@ #include <glib.h> #include <sys/socket.h> +#include <stdbool.h> struct httpd_client; struct httpd_output { /** + * True if the audio output is open and accepts client + * connections. + */ + bool open; + + /** * The configured encoder plugin. */ struct encoder *encoder; @@ -97,6 +104,12 @@ struct httpd_output { * function. */ char buffer[32768]; + + /** + * The maximum and current number of clients connected + * at the same time. + */ + guint clients_max, clients_cnt; }; /** diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c index 9fdf46456..a54253351 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "httpd_internal.h" #include "httpd_client.h" #include "output_api.h" @@ -25,6 +26,7 @@ #include "socket_util.h" #include "page.h" #include "icy_server.h" +#include "fd_util.h" #include <assert.h> @@ -46,6 +48,52 @@ httpd_output_quark(void) return g_quark_from_static_string("httpd_output"); } +static gboolean +httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data); + +static bool +httpd_output_bind(struct httpd_output *httpd, GError **error_r) +{ + GIOChannel *channel; + + httpd->open = false; + + /* create and set up listener socket */ + + httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0, + (struct sockaddr *)&httpd->address, + httpd->address_size, + 16, error_r); + if (httpd->fd < 0) + return false; + + g_mutex_lock(httpd->mutex); + + channel = g_io_channel_unix_new(httpd->fd); + httpd->source_id = g_io_add_watch(channel, G_IO_IN, + httpd_listen_in_event, httpd); + g_io_channel_unref(channel); + + g_mutex_unlock(httpd->mutex); + + return true; +} + +static void +httpd_output_unbind(struct httpd_output *httpd) +{ + assert(!httpd->open); + + g_mutex_lock(httpd->mutex); + + g_source_remove(httpd->source_id); + close(httpd->fd); + + g_mutex_unlock(httpd->mutex); +} + static void * httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, const struct config_param *param, @@ -69,12 +117,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, return NULL; } - if (strcmp(encoder_name, "vorbis") == 0) - httpd->content_type = "application/x-ogg"; - else if (strcmp(encoder_name, "lame") == 0) - httpd->content_type = "audio/mpeg"; - else - httpd->content_type = "application/octet-stream"; + httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0); /* initialize listen address */ @@ -94,6 +137,12 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, if (httpd->encoder == NULL) return NULL; + /* determine content type */ + httpd->content_type = encoder_get_mime_type(httpd->encoder); + if (httpd->content_type == NULL) { + httpd->content_type = "application/octet-stream"; + } + httpd->mutex = g_mutex_new(); return httpd; @@ -124,6 +173,7 @@ httpd_client_add(struct httpd_output *httpd, int fd) httpd->encoder->plugin->tag == NULL); httpd->clients = g_list_prepend(httpd->clients, client); + httpd->clients_cnt++; /* pass metadata to client */ if (httpd->metadata) @@ -138,18 +188,26 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source, struct httpd_output *httpd = data; int fd; struct sockaddr_storage sa; - socklen_t sa_length = sizeof(sa); + size_t sa_length = sizeof(sa); g_mutex_lock(httpd->mutex); /* the listener socket has become readable - a client has connected */ - fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length); - if (fd >= 0) - httpd_client_add(httpd, fd); - else if (fd < 0 && errno != EINTR) + fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa, + &sa_length); + if (fd >= 0) { + /* can we allow additional client */ + if (httpd->open && + (httpd->clients_max == 0 || + httpd->clients_cnt < httpd->clients_max)) + httpd_client_add(httpd, fd); + else + close(fd); + } else if (fd < 0 && errno != EINTR) { g_warning("accept() failed: %s", g_strerror(errno)); + } g_mutex_unlock(httpd->mutex); @@ -199,31 +257,30 @@ httpd_output_encoder_open(struct httpd_output *httpd, } static bool +httpd_output_enable(void *data, GError **error_r) +{ + struct httpd_output *httpd = data; + + return httpd_output_bind(httpd, error_r); +} + +static void +httpd_output_disable(void *data) +{ + struct httpd_output *httpd = data; + + httpd_output_unbind(httpd); +} + +static bool httpd_output_open(void *data, struct audio_format *audio_format, GError **error) { struct httpd_output *httpd = data; bool success; - GIOChannel *channel; g_mutex_lock(httpd->mutex); - /* create and set up listener socket */ - - httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0, - (struct sockaddr *)&httpd->address, - httpd->address_size, - 16, error); - if (httpd->fd < 0) { - g_mutex_unlock(httpd->mutex); - return false; - } - - channel = g_io_channel_unix_new(httpd->fd); - httpd->source_id = g_io_add_watch(channel, G_IO_IN, - httpd_listen_in_event, httpd); - g_io_channel_unref(channel); - /* open the encoder */ success = httpd_output_encoder_open(httpd, audio_format, error); @@ -237,8 +294,11 @@ httpd_output_open(void *data, struct audio_format *audio_format, /* initialize other attributes */ httpd->clients = NULL; + httpd->clients_cnt = 0; httpd->timer = timer_new(audio_format); + httpd->open = true; + g_mutex_unlock(httpd->mutex); return true; } @@ -257,6 +317,8 @@ static void httpd_output_close(void *data) g_mutex_lock(httpd->mutex); + httpd->open = false; + timer_free(httpd->timer); g_list_foreach(httpd->clients, httpd_client_delete, NULL); @@ -267,9 +329,6 @@ static void httpd_output_close(void *data) encoder_close(httpd->encoder); - g_source_remove(httpd->source_id); - close(httpd->fd); - g_mutex_unlock(httpd->mutex); } @@ -281,6 +340,7 @@ httpd_output_remove_client(struct httpd_output *httpd, assert(client != NULL); httpd->clients = g_list_remove(httpd->clients, client); + httpd->clients_cnt--; } void @@ -433,9 +493,8 @@ httpd_output_tag(void *data, const struct tag *tag) page_unref (httpd->metadata); httpd->metadata = - icy_server_metadata_page(tag, TAG_ITEM_ALBUM, - TAG_ITEM_ARTIST, - TAG_ITEM_TITLE, + icy_server_metadata_page(tag, TAG_ALBUM, + TAG_ARTIST, TAG_TITLE, TAG_NUM_OF_ITEM_TYPES); if (httpd->metadata != NULL) { g_mutex_lock(httpd->mutex); @@ -468,6 +527,8 @@ const struct audio_output_plugin httpd_output_plugin = { .name = "httpd", .init = httpd_output_init, .finish = httpd_output_finish, + .enable = httpd_output_enable, + .disable = httpd_output_disable, .open = httpd_output_open, .close = httpd_output_close, .send_tag = httpd_output_tag, diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c new file mode 100644 index 000000000..f50bc37d0 --- /dev/null +++ b/src/output/jack_output_plugin.c @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" + +#include <assert.h> + +#include <glib.h> +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/ringbuffer.h> + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "jack" + +enum { + MAX_PORTS = 16, +}; + +static const size_t sample_size = sizeof(jack_default_audio_sample_t); + +struct jack_data { + /** + * libjack options passed to jack_client_open(). + */ + jack_options_t options; + + const char *name; + + const char *server_name; + + /* configuration */ + + char *source_ports[MAX_PORTS]; + unsigned num_source_ports; + + char *destination_ports[MAX_PORTS]; + unsigned num_destination_ports; + + size_t ringbuffer_size; + + /* the current audio format */ + struct audio_format audio_format; + + /* jack library stuff */ + jack_port_t *ports[MAX_PORTS]; + jack_client_t *client; + jack_ringbuffer_t *ringbuffer[MAX_PORTS]; + + bool shutdown; + + /** + * While this flag is set, the "process" callback generates + * silence. + */ + bool pause; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +jack_output_quark(void) +{ + return g_quark_from_static_string("jack_output"); +} + +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + struct jack_data *jd = (struct jack_data *) arg; + jack_default_audio_sample_t *out; + size_t available; + + if (nframes <= 0) + return 0; + + if (jd->pause) { + /* generate silence while MPD is paused */ + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; + } + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + available = jack_ringbuffer_read_space(jd->ringbuffer[i]); + assert(available % sample_size == 0); + available /= sample_size; + if (available > nframes) + available = nframes; + + out = jack_port_get_buffer(jd->ports[i], nframes); + jack_ringbuffer_read(jd->ringbuffer[i], + (char *)out, available * sample_size); + + while (available < nframes) + /* ringbuffer underrun, fill with silence */ + out[available++] = 0.0; + } + + /* generate silence for the unused source ports */ + + for (unsigned i = jd->audio_format.channels; + i < jd->num_source_ports; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; +} + +static void +mpd_jack_shutdown(void *arg) +{ + struct jack_data *jd = (struct jack_data *) arg; + jd->shutdown = true; +} + +static void +set_audioformat(struct jack_data *jd, struct audio_format *audio_format) +{ + audio_format->sample_rate = jack_get_sample_rate(jd->client); + + if (jd->num_source_ports == 1) + audio_format->channels = 1; + else if (audio_format->channels > jd->num_source_ports) + audio_format->channels = 2; + + if (audio_format->format != SAMPLE_FORMAT_S16 && + audio_format->format != SAMPLE_FORMAT_S24_P32) + audio_format->format = SAMPLE_FORMAT_S24_P32; +} + +static void +mpd_jack_error(const char *msg) +{ + g_warning("%s", msg); +} + +#ifdef HAVE_JACK_SET_INFO_FUNCTION +static void +mpd_jack_info(const char *msg) +{ + g_message("%s", msg); +} +#endif + +/** + * Disconnect the JACK client. + */ +static void +mpd_jack_disconnect(struct jack_data *jd) +{ + assert(jd != NULL); + assert(jd->client != NULL); + + jack_deactivate(jd->client); + jack_client_close(jd->client); + jd->client = NULL; +} + +/** + * Connect the JACK client and performs some basic setup + * (e.g. register callbacks). + */ +static bool +mpd_jack_connect(struct jack_data *jd, GError **error_r) +{ + jack_status_t status; + + assert(jd != NULL); + + jd->shutdown = false; + + jd->client = jack_client_open(jd->name, jd->options, &status, + jd->server_name); + if (jd->client == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "Failed to connect to JACK server, status=%d", + status); + return false; + } + + jack_set_process_callback(jd->client, mpd_jack_process, jd); + jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + jd->ports[i] = jack_port_register(jd->client, + jd->source_ports[i], + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (jd->ports[i] == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "Cannot register output port \"%s\"", + jd->source_ports[i]); + mpd_jack_disconnect(jd); + return false; + } + } + + return true; +} + +static bool +mpd_jack_test_default_device(void) +{ + return true; +} + +static unsigned +parse_port_list(int line, const char *source, char **dest, GError **error_r) +{ + char **list = g_strsplit(source, ",", 0); + unsigned n = 0; + + for (n = 0; list[n] != NULL; ++n) { + if (n >= MAX_PORTS) { + g_set_error(error_r, jack_output_quark(), 0, + "too many port names in line %d", + line); + return 0; + } + + dest[n] = list[n]; + } + + g_free(list); + + if (n == 0) { + g_set_error(error_r, jack_output_quark(), 0, + "at least one port name expected in line %d", + line); + return 0; + } + + return n; +} + +static void * +mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, GError **error_r) +{ + struct jack_data *jd; + const char *value; + + jd = g_new(struct jack_data, 1); + jd->options = JackNullOption; + + jd->name = config_get_block_string(param, "client_name", NULL); + if (jd->name != NULL) + jd->options |= JackUseExactName; + else + /* if there's a no configured client name, we don't + care about the JackUseExactName option */ + jd->name = "Music Player Daemon"; + + jd->server_name = config_get_block_string(param, "server_name", NULL); + if (jd->server_name != NULL) + jd->options |= JackServerName; + + if (!config_get_block_bool(param, "autostart", false)) + jd->options |= JackNoStartServer; + + /* configure the source ports */ + + value = config_get_block_string(param, "source_ports", "left,right"); + jd->num_source_ports = parse_port_list(param->line, value, + jd->source_ports, error_r); + if (jd->num_source_ports == 0) + return NULL; + + /* configure the destination ports */ + + value = config_get_block_string(param, "destination_ports", NULL); + if (value == NULL) { + /* compatibility with MPD < 0.16 */ + value = config_get_block_string(param, "ports", NULL); + if (value != NULL) + g_warning("deprecated option 'ports' in line %d", + param->line); + } + + if (value != NULL) { + jd->num_destination_ports = + parse_port_list(param->line, value, + jd->destination_ports, error_r); + if (jd->num_destination_ports == 0) + return NULL; + } else { + jd->num_destination_ports = 0; + } + + if (jd->num_destination_ports > 0 && + jd->num_destination_ports != jd->num_source_ports) + g_warning("number of source ports (%u) mismatches the " + "number of destination ports (%u) in line %d", + jd->num_source_ports, jd->num_destination_ports, + param->line); + + jd->ringbuffer_size = + config_get_block_unsigned(param, "ringbuffer_size", 32768); + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return jd; +} + +static void +mpd_jack_finish(void *data) +{ + struct jack_data *jd = data; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + g_free(jd->source_ports[i]); + + for (unsigned i = 0; i < jd->num_destination_ports; ++i) + g_free(jd->destination_ports[i]); + + g_free(jd); +} + +static bool +mpd_jack_enable(void *data, GError **error_r) +{ + struct jack_data *jd = (struct jack_data *)data; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + jd->ringbuffer[i] = NULL; + + return mpd_jack_connect(jd, error_r); +} + +static void +mpd_jack_disable(void *data) +{ + struct jack_data *jd = (struct jack_data *)data; + + if (jd->client != NULL) + mpd_jack_disconnect(jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] != NULL) { + jack_ringbuffer_free(jd->ringbuffer[i]); + jd->ringbuffer[i] = NULL; + } + } +} + +/** + * Stops the playback on the JACK connection. + */ +static void +mpd_jack_stop(struct jack_data *jd) +{ + assert(jd != NULL); + + if (jd->client == NULL) + return; + + if (jd->shutdown) + /* the connection has failed; close it */ + mpd_jack_disconnect(jd); + else + /* the connection is alive: just stop playback */ + jack_deactivate(jd->client); +} + +static bool +mpd_jack_start(struct jack_data *jd, GError **error_r) +{ + const char *destination_ports[MAX_PORTS], **jports; + const char *duplicate_port = NULL; + unsigned num_destination_ports; + + assert(jd->client != NULL); + assert(jd->audio_format.channels <= jd->num_source_ports); + + /* allocate the ring buffers on the first open(); these + persist until MPD exits. It's too unsafe to delete them + because we can never know when mpd_jack_process() gets + called */ + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] == NULL) + jd->ringbuffer[i] = + jack_ringbuffer_create(jd->ringbuffer_size); + + /* clear the ring buffer to be sure that data from + previous playbacks are gone */ + jack_ringbuffer_reset(jd->ringbuffer[i]); + } + + if ( jack_activate(jd->client) ) { + g_set_error(error_r, jack_output_quark(), 0, + "cannot activate client"); + mpd_jack_stop(jd); + return false; + } + + if (jd->num_destination_ports == 0) { + /* no output ports were configured - ask libjack for + defaults */ + jports = jack_get_ports(jd->client, NULL, NULL, + JackPortIsPhysical | JackPortIsInput); + if (jports == NULL) { + g_set_error(error_r, jack_output_quark(), 0, + "no ports found"); + mpd_jack_stop(jd); + return false; + } + + assert(*jports != NULL); + + for (num_destination_ports = 0; + num_destination_ports < MAX_PORTS && + jports[num_destination_ports] != NULL; + ++num_destination_ports) { + g_debug("destination_port[%u] = '%s'\n", + num_destination_ports, + jports[num_destination_ports]); + destination_ports[num_destination_ports] = + jports[num_destination_ports]; + } + } else { + /* use the configured output ports */ + + num_destination_ports = jd->num_destination_ports; + memcpy(destination_ports, jd->destination_ports, + num_destination_ports * sizeof(*destination_ports)); + + jports = NULL; + } + + assert(num_destination_ports > 0); + + if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + /* mix stereo signal on one speaker */ + + while (num_destination_ports < jd->audio_format.channels) + destination_ports[num_destination_ports++] = + destination_ports[0]; + } else if (num_destination_ports > jd->audio_format.channels) { + if (jd->audio_format.channels == 1 && num_destination_ports > 2) { + /* mono input file: connect the one source + channel to the both destination channels */ + duplicate_port = destination_ports[1]; + num_destination_ports = 1; + } else + /* connect only as many ports as we need */ + num_destination_ports = jd->audio_format.channels; + } + + assert(num_destination_ports <= jd->num_source_ports); + + for (unsigned i = 0; i < num_destination_ports; ++i) { + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), + destination_ports[i]); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + destination_ports[i]); + + if (jports != NULL) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (duplicate_port != NULL) { + /* mono input file: connect the one source channel to + the both destination channels */ + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), + duplicate_port); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + duplicate_port); + + if (jports != NULL) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (jports != NULL) + free(jports); + + return true; +} + +static bool +mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r) +{ + struct jack_data *jd = data; + + assert(jd != NULL); + + jd->pause = false; + + if (jd->client == NULL && !mpd_jack_connect(jd, error_r)) + return false; + + set_audioformat(jd, audio_format); + jd->audio_format = *audio_format; + + if (!mpd_jack_start(jd, error_r)) + return false; + + return true; +} + +static void +mpd_jack_close(G_GNUC_UNUSED void *data) +{ + struct jack_data *jd = data; + + mpd_jack_stop(jd); +} + +static inline jack_default_audio_sample_t +sample_16_to_jack(int16_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); +} + +static void +mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_16_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + sizeof(sample)); + } + } +} + +static inline jack_default_audio_sample_t +sample_24_to_jack(int32_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); +} + +static void +mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_24_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, + sizeof(sample)); + } + } +} + +static void +mpd_jack_write_samples(struct jack_data *jd, const void *src, + unsigned num_samples) +{ + switch (jd->audio_format.format) { + case SAMPLE_FORMAT_S16: + mpd_jack_write_samples_16(jd, (const int16_t*)src, + num_samples); + break; + + case SAMPLE_FORMAT_S24_P32: + mpd_jack_write_samples_24(jd, (const int32_t*)src, + num_samples); + break; + + default: + assert(false); + } +} + +static size_t +mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct jack_data *jd = data; + const size_t frame_size = audio_format_frame_size(&jd->audio_format); + size_t space = 0, space1; + + jd->pause = false; + + assert(size % frame_size == 0); + size /= frame_size; + + while (true) { + if (jd->shutdown) { + g_set_error(error_r, jack_output_quark(), 0, + "Refusing to play, because " + "there is no client thread"); + return 0; + } + + space = jack_ringbuffer_write_space(jd->ringbuffer[0]); + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); + if (space > space1) + /* send data symmetrically */ + space = space1; + } + + if (space >= frame_size) + break; + + /* XXX do something more intelligent to + synchronize */ + g_usleep(1000); + } + + space /= sample_size; + if (space < size) + size = space; + + mpd_jack_write_samples(jd, chunk, size); + return size * frame_size; +} + +static bool +mpd_jack_pause(void *data) +{ + struct jack_data *jd = data; + + if (jd->shutdown) + return false; + + jd->pause = true; + + /* due to a MPD API limitation, we have to sleep a little bit + here, to avoid hogging the CPU */ + g_usleep(50000); + + return true; +} + +const struct audio_output_plugin jack_output_plugin = { + .name = "jack", + .test_default_device = mpd_jack_test_default_device, + .init = mpd_jack_init, + .finish = mpd_jack_finish, + .enable = mpd_jack_enable, + .disable = mpd_jack_disable, + .open = mpd_jack_open, + .play = mpd_jack_play, + .pause = mpd_jack_pause, + .close = mpd_jack_close, +}; diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c deleted file mode 100644 index 5dc1eca24..000000000 --- a/src/output/jack_plugin.c +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "../output_api.h" -#include "config.h" - -#include <assert.h> - -#include <glib.h> -#include <jack/jack.h> -#include <jack/types.h> -#include <jack/ringbuffer.h> - -#include <stdlib.h> -#include <stdio.h> -#include <sys/types.h> -#include <unistd.h> -#include <errno.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "jack" - -static const size_t sample_size = sizeof(jack_default_audio_sample_t); - -static const char *const port_names[2] = { - "left", "right", -}; - -struct jack_data { - const char *name; - - /* configuration */ - char *output_ports[2]; - int ringbuffer_size; - - /* the current audio format */ - struct audio_format audio_format; - - /* jack library stuff */ - jack_port_t *ports[2]; - jack_client_t *client; - jack_ringbuffer_t *ringbuffer[2]; - - bool shutdown; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -jack_output_quark(void) -{ - return g_quark_from_static_string("jack_output"); -} - -static void -mpd_jack_client_free(struct jack_data *jd) -{ - assert(jd != NULL); - - if (jd->client != NULL) { - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = NULL; - } - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) { - if (jd->ringbuffer[i] != NULL) { - jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = NULL; - } - } -} - -static void -mpd_jack_free(struct jack_data *jd) -{ - assert(jd != NULL); - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i) - g_free(jd->output_ports[i]); - - g_free(jd); -} - -static void -mpd_jack_finish(void *data) -{ - struct jack_data *jd = data; - mpd_jack_free(jd); -} - -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jack_default_audio_sample_t *out; - size_t available; - - if (nframes <= 0) - return 0; - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) { - available = jack_ringbuffer_read_space(jd->ringbuffer[i]); - assert(available % sample_size == 0); - available /= sample_size; - if (available > nframes) - available = nframes; - - out = jack_port_get_buffer(jd->ports[i], nframes); - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * sample_size); - - while (available < nframes) - /* ringbuffer underrun, fill with silence */ - out[available++] = 0.0; - } - - return 0; -} - -static void -mpd_jack_shutdown(void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jd->shutdown = true; -} - -static void -set_audioformat(struct jack_data *jd, struct audio_format *audio_format) -{ - audio_format->sample_rate = jack_get_sample_rate(jd->client); - audio_format->channels = 2; - - if (audio_format->bits != 16 && audio_format->bits != 24) - audio_format->bits = 24; -} - -static void -mpd_jack_error(const char *msg) -{ - g_warning("%s", msg); -} - -#ifdef HAVE_JACK_SET_INFO_FUNCTION -static void -mpd_jack_info(const char *msg) -{ - g_message("%s", msg); -} -#endif - -static void * -mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, GError **error) -{ - struct jack_data *jd; - const char *value; - - jd = g_new(struct jack_data, 1); - jd->name = config_get_block_string(param, "name", "mpd_jack"); - - g_debug("mpd_jack_init (pid=%d)", getpid()); - - value = config_get_block_string(param, "ports", NULL); - if (value != NULL) { - char **ports = g_strsplit(value, ",", 0); - - if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) { - g_set_error(error, jack_output_quark(), 0, - "two port names expected in line %d", - param->line); - return NULL; - } - - jd->output_ports[0] = ports[0]; - jd->output_ports[1] = ports[1]; - - g_free(ports); - } else { - jd->output_ports[0] = NULL; - jd->output_ports[1] = NULL; - } - - jd->ringbuffer_size = - config_get_block_unsigned(param, "ringbuffer_size", 32768); - - jack_set_error_function(mpd_jack_error); - -#ifdef HAVE_JACK_SET_INFO_FUNCTION - jack_set_info_function(mpd_jack_info); -#endif - - return jd; -} - -static bool -mpd_jack_test_default_device(void) -{ - return true; -} - -static bool -mpd_jack_connect(struct jack_data *jd, GError **error) -{ - const char *output_ports[2], **jports; - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); - - jd->shutdown = false; - - if ((jd->client = jack_client_new(jd->name)) == NULL) { - g_set_error(error, jack_output_quark(), 0, - "Failed to connect to JACK server"); - return false; - } - - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) { - jd->ports[i] = jack_port_register(jd->client, port_names[i], - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - if (jd->ports[i] == NULL) { - g_set_error(error, jack_output_quark(), 0, - "Cannot register output port \"%s\"", - port_names[i]); - return false; - } - } - - if ( jack_activate(jd->client) ) { - g_set_error(error, jack_output_quark(), 0, - "cannot activate client"); - return false; - } - - if (jd->output_ports[1] == NULL) { - /* no output ports were configured - ask libjack for - defaults */ - jports = jack_get_ports(jd->client, NULL, NULL, - JackPortIsPhysical | JackPortIsInput); - if (jports == NULL) { - g_set_error(error, jack_output_quark(), 0, - "no ports found"); - return false; - } - - output_ports[0] = jports[0]; - output_ports[1] = jports[1] != NULL ? jports[1] : jports[0]; - - g_debug("output_ports: %s %s", jports[0], jports[1]); - } else { - /* use the configured output ports */ - - output_ports[0] = jd->output_ports[0]; - output_ports[1] = jd->output_ports[1]; - - jports = NULL; - } - - for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) { - int ret; - - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - output_ports[i]); - if (ret != 0) { - g_set_error(error, jack_output_quark(), 0, - "Not a valid JACK port: %s", - output_ports[i]); - - if (jports != NULL) - free(jports); - - return false; - } - } - - if (jports != NULL) - free(jports); - - return true; -} - -static bool -mpd_jack_open(void *data, struct audio_format *audio_format, GError **error) -{ - struct jack_data *jd = data; - - assert(jd != NULL); - - if (!mpd_jack_connect(jd, error)) { - mpd_jack_client_free(jd); - return false; - } - - set_audioformat(jd, audio_format); - jd->audio_format = *audio_format; - - return true; -} - -static void -mpd_jack_close(G_GNUC_UNUSED void *data) -{ - struct jack_data *jd = data; - - mpd_jack_client_free(jd); -} - -static void -mpd_jack_cancel (G_GNUC_UNUSED void *data) -{ -} - -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} - -static void -mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - - while (num_samples-- > 0) { - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, - sizeof(sample)); - - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, - sizeof(sample)); - } -} - -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} - -static void -mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - - while (num_samples-- > 0) { - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, - sizeof(sample)); - - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, - sizeof(sample)); - } -} - -static void -mpd_jack_write_samples(struct jack_data *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.bits) { - case 16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case 24: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - } -} - -static size_t -mpd_jack_play(void *data, const void *chunk, size_t size, GError **error) -{ - struct jack_data *jd = data; - const size_t frame_size = audio_format_frame_size(&jd->audio_format); - size_t space = 0, space1; - - assert(size % frame_size == 0); - size /= frame_size; - - while (true) { - if (jd->shutdown) { - g_set_error(error, jack_output_quark(), 0, - "Refusing to play, because " - "there is no client thread"); - return 0; - } - - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]); - if (space > space1) - /* send data symmetrically */ - space = space1; - - if (space >= frame_size) - break; - - /* XXX do something more intelligent to - synchronize */ - g_usleep(1000); - } - - space /= sample_size; - if (space < size) - size = space; - - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; -} - -const struct audio_output_plugin jackPlugin = { - .name = "jack", - .test_default_device = mpd_jack_test_default_device, - .init = mpd_jack_init, - .finish = mpd_jack_finish, - .open = mpd_jack_open, - .play = mpd_jack_play, - .cancel = mpd_jack_cancel, - .close = mpd_jack_close, -}; diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c index 96f9435a8..86f147e5a 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_plugin.c @@ -22,7 +22,9 @@ * http://mvpmc.sourceforge.net/ */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -115,7 +117,7 @@ mvp_output_test_default_device(void) { int fd; - fd = open("/dev/adec_pcm", O_WRONLY); + fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0); if (fd >= 0) { close(fd); @@ -170,19 +172,19 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format, } /* 0,1=24bit(24) , 2,3=16bit */ - switch (audio_format->bits) { - case 16: + switch (audio_format->format) { + case SAMPLE_FORMAT_S16: mix[1] = 2; break; - case 24: + case SAMPLE_FORMAT_S24_P32: mix[1] = 0; break; default: - g_debug("unsupported sample format %u - falling back to stereo", - audio_format->bits); - audio_format->bits = 16; + g_debug("unsupported sample format %s - falling back to 16 bit", + sample_format_to_string(audio_format->format)); + audio_format->format = SAMPLE_FORMAT_S16; mix[1] = 2; break; } @@ -230,7 +232,8 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error) int mix[5] = { 0, 2, 7, 1, 0 }; bool success; - if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) { + md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0); + if (md->fd < 0) { g_set_error(error, mvp_output_quark(), errno, "Error opening /dev/adec_pcm: %s", strerror(errno)); diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c index e9731b019..495db656b 100644 --- a/src/output/null_plugin.c +++ b/src/output/null_plugin.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" -#include "../timer.h" +#include "config.h" +#include "output_api.h" +#include "timer.h" #include <glib.h> diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c new file mode 100644 index 000000000..0aded4d9a --- /dev/null +++ b/src/output/openal_plugin.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "timer.h" + +#include <glib.h> + +#ifndef HAVE_OSX +#include <AL/al.h> +#include <AL/alc.h> +#else +#include <OpenAL/al.h> +#include <OpenAL/alc.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "openal" + +/* should be enough for buffer size = 2048 */ +#define NUM_BUFFERS 16 + +struct openal_data { + const char *device_name; + ALCdevice *device; + ALCcontext *context; + Timer *timer; + ALuint buffers[NUM_BUFFERS]; + int filled; + ALuint source; + ALenum format; + ALuint frequency; +}; + +static inline GQuark +openal_output_quark(void) +{ + return g_quark_from_static_string("openal_output"); +} + +static ALenum +openal_audio_format(struct audio_format *audio_format) +{ + switch (audio_format->format) { + case SAMPLE_FORMAT_S16: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + + case SAMPLE_FORMAT_S8: + if (audio_format->channels == 2) + return AL_FORMAT_STEREO8; + if (audio_format->channels == 1) + return AL_FORMAT_MONO8; + break; + + default: + /* fall back to 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + if (audio_format->channels == 2) + return AL_FORMAT_STEREO16; + if (audio_format->channels == 1) + return AL_FORMAT_MONO16; + break; + } + + return 0; +} + +static bool +openal_setup_context(struct openal_data *od, + GError **error) +{ + od->device = alcOpenDevice(od->device_name); + + if (od->device == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error opening OpenAL device \"%s\"\n", + od->device_name); + return false; + } + + od->context = alcCreateContext(od->device, NULL); + + if (od->context == NULL) { + g_set_error(error, openal_output_quark(), 0, + "Error creating context for \"%s\"\n", + od->device_name); + alcCloseDevice(od->device); + return false; + } + + return true; +} + +static void +openal_unqueue_buffers(struct openal_data *od) +{ + ALint num; + ALuint buffer; + + alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num); + + while (num--) { + alSourceUnqueueBuffers(od->source, 1, &buffer); + } +} + +static void * +openal_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + const char *device_name = config_get_block_string(param, "device", NULL); + struct openal_data *od; + + if (device_name == NULL) { + device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); + } + + od = g_new(struct openal_data, 1); + od->device_name = device_name; + + return od; +} + +static void +openal_finish(void *data) +{ + struct openal_data *od = data; + + g_free(od); +} + +static bool +openal_open(void *data, struct audio_format *audio_format, + GError **error) +{ + struct openal_data *od = data; + + od->format = openal_audio_format(audio_format); + + if (!od->format) { + struct audio_format_string s; + g_set_error(error, openal_output_quark(), 0, + "Unsupported audio format: %s", + audio_format_to_string(audio_format, &s)); + return false; + } + + if (!openal_setup_context(od, error)) { + return false; + } + + alcMakeContextCurrent(od->context); + alGenBuffers(NUM_BUFFERS, od->buffers); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate buffers"); + return false; + } + + alGenSources(1, &od->source); + + if (alGetError() != AL_NO_ERROR) { + g_set_error(error, openal_output_quark(), 0, + "Failed to generate source"); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + return false; + } + + od->filled = 0; + od->timer = timer_new(audio_format); + od->frequency = audio_format->sample_rate; + + return true; +} + +static void +openal_close(void *data) +{ + struct openal_data *od = data; + + timer_free(od->timer); + alcMakeContextCurrent(od->context); + alDeleteSources(1, &od->source); + alDeleteBuffers(NUM_BUFFERS, od->buffers); + alcDestroyContext(od->context); + alcCloseDevice(od->device); +} + +static size_t +openal_play(void *data, const void *chunk, size_t size, + G_GNUC_UNUSED GError **error) +{ + struct openal_data *od = data; + ALuint buffer; + ALint num, state; + + if (alcGetCurrentContext() != od->context) { + alcMakeContextCurrent(od->context); + } + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + + if (od->filled < NUM_BUFFERS) { + /* fill all buffers */ + buffer = od->buffers[od->filled]; + od->filled++; + } else { + /* wait for processed buffer */ + while (num < 1) { + if (!od->timer->started) { + timer_start(od->timer); + } else { + timer_sync(od->timer); + } + + timer_add(od->timer, size); + + alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num); + } + + alSourceUnqueueBuffers(od->source, 1, &buffer); + } + + alBufferData(buffer, od->format, chunk, size, od->frequency); + alSourceQueueBuffers(od->source, 1, &buffer); + alGetSourcei(od->source, AL_SOURCE_STATE, &state); + + if (state != AL_PLAYING) { + alSourcePlay(od->source); + } + + return size; +} + +static void +openal_cancel(void *data) +{ + struct openal_data *od = data; + + od->filled = 0; + alcMakeContextCurrent(od->context); + alSourceStop(od->source); + openal_unqueue_buffers(od); +} + +const struct audio_output_plugin openal_output_plugin = { + .name = "openal", + .init = openal_init, + .finish = openal_finish, + .open = openal_open, + .close = openal_close, + .play = openal_play, + .cancel = openal_cancel, +}; diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c index a66bc0598..f16374e39 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include "mixer_list.h" +#include "fd_util.h" #include <glib.h> @@ -343,7 +345,9 @@ oss_output_test_default_device(void) int fd, i; for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) { - if ((fd = open(default_devices[i], O_WRONLY)) >= 0) { + fd = open_cloexec(default_devices[i], O_WRONLY, 0); + + if (fd >= 0) { close(fd); return true; } @@ -486,17 +490,18 @@ oss_setup(struct oss_data *od, GError **error) } od->audio_format.sample_rate = tmp; - switch (od->audio_format.bits) { - case 8: + switch (od->audio_format.format) { + case SAMPLE_FORMAT_S8: tmp = AFMT_S8; break; - case 16: + + case SAMPLE_FORMAT_S16: tmp = AFMT_S16_MPD; break; default: /* not supported by OSS - fall back to 16 bit */ - od->audio_format.bits = 16; + od->audio_format.format = SAMPLE_FORMAT_S16; tmp = AFMT_S16_MPD; break; } @@ -516,7 +521,8 @@ oss_open(struct oss_data *od, GError **error) { bool success; - if ((od->fd = open(od->device, O_WRONLY)) < 0) { + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { g_set_error(error, oss_output_quark(), errno, "Error opening OSS device \"%s\": %s", od->device, strerror(errno)); @@ -601,5 +607,6 @@ const struct audio_output_plugin oss_output_plugin = { .close = oss_output_close, .play = oss_output_play, .cancel = oss_output_cancel, - .mixer_plugin = &oss_mixer, + + .mixer_plugin = &oss_mixer_plugin, }; diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c index 04173bf79..22b742ee5 100644 --- a/src/output/osx_plugin.c +++ b/src/output/osx_plugin.c @@ -17,7 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../output_api.h" +#include "config.h" +#include "output_api.h" #include <glib.h> #include <AudioUnit/AudioUnit.h> @@ -165,9 +166,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) OSStatus status; ComponentResult result; - if (audio_format->bits > 16) - audio_format->bits = 16; - desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; @@ -225,7 +223,21 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error) stream_description.mFramesPerPacket = 1; stream_description.mBytesPerFrame = stream_description.mBytesPerPacket; stream_description.mChannelsPerFrame = audio_format->channels; - stream_description.mBitsPerChannel = audio_format->bits; + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + stream_description.mBitsPerChannel = 8; + break; + + case SAMPLE_FORMAT_S16: + stream_description.mBitsPerChannel = 16; + break; + + default: + audio_format->format = SAMPLE_FORMAT_S16; + stream_description.mBitsPerChannel = 16; + break; + } result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c index 610ad9e8d..2a5841bae 100644 --- a/src/output/pipe_output_plugin.c +++ b/src/output/pipe_output_plugin.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_api.h" #include <stdio.h> diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c new file mode 100644 index 000000000..a64157920 --- /dev/null +++ b/src/output/pulse_output_plugin.c @@ -0,0 +1,824 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "pulse_output_plugin.h" +#include "output_api.h" +#include "mixer_list.h" +#include "mixer/pulse_mixer_plugin.h" + +#include <glib.h> + +#include <pulse/thread-mainloop.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/introspect.h> +#include <pulse/subscribe.h> +#include <pulse/error.h> + +#include <assert.h> + +#define MPD_PULSE_NAME "Music Player Daemon" + +/** + * The quark used for GError.domain. + */ +static inline GQuark +pulse_output_quark(void) +{ + return g_quark_from_static_string("pulse_output"); +} + +void +pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm) +{ + assert(po != NULL); + assert(po->mixer == NULL); + assert(pm != NULL); + + po->mixer = pm; + + if (po->mainloop == NULL) + return; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context != NULL && + pa_context_get_state(po->context) == PA_CONTEXT_READY) { + pulse_mixer_on_connect(pm, po->context); + + if (po->stream != NULL && + pa_stream_get_state(po->stream) == PA_STREAM_READY) + pulse_mixer_on_change(pm, po->context, po->stream); + } + + pa_threaded_mainloop_unlock(po->mainloop); +} + +void +pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm) +{ + assert(po != NULL); + assert(pm != NULL); + assert(po->mixer == pm); + + po->mixer = NULL; +} + +bool +pulse_output_set_volume(struct pulse_output *po, + const struct pa_cvolume *volume, GError **error_r) +{ + pa_operation *o; + + if (po->context == NULL || po->stream == NULL || + pa_stream_get_state(po->stream) != PA_STREAM_READY) { + g_set_error(error_r, pulse_output_quark(), 0, "disconnected"); + return false; + } + + o = pa_context_set_sink_input_volume(po->context, + pa_stream_get_index(po->stream), + volume, NULL, NULL); + if (o == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "failed to set PulseAudio volume: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + pa_operation_unref(o); + return true; +} + +/** + * \brief waits for a pulseaudio operation to finish, frees it and + * unlocks the mainloop + * \param operation the operation to wait for + * \return true if operation has finished normally (DONE state), + * false otherwise + */ +static bool +pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop, + struct pa_operation *operation) +{ + pa_operation_state_t state; + + assert(mainloop != NULL); + assert(operation != NULL); + + state = pa_operation_get_state(operation); + while (state == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(mainloop); + state = pa_operation_get_state(operation); + } + + pa_operation_unref(operation); + + return state == PA_OPERATION_DONE; +} + +/** + * Callback function for stream operation. It just sends a signal to + * the caller thread, to wake pulse_wait_for_operation() up. + */ +static void +pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s, + G_GNUC_UNUSED int success, void *userdata) +{ + struct pulse_output *po = userdata; + + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static void +pulse_output_context_state_cb(struct pa_context *context, void *userdata) +{ + struct pulse_output *po = userdata; + + switch (pa_context_get_state(context)) { + case PA_CONTEXT_READY: + if (po->mixer != NULL) + pulse_mixer_on_connect(po->mixer, context); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + if (po->mixer != NULL) + pulse_mixer_on_disconnect(po->mixer); + + /* the caller thread might be waiting for these + states */ + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void +pulse_output_subscribe_cb(pa_context *context, + pa_subscription_event_type_t t, + uint32_t idx, void *userdata) +{ + struct pulse_output *po = userdata; + pa_subscription_event_type_t facility + = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK; + pa_subscription_event_type_t type + = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK; + + if (po->mixer != NULL && + facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT && + po->stream != NULL && + pa_stream_get_state(po->stream) == PA_STREAM_READY && + idx == pa_stream_get_index(po->stream) && + (type == PA_SUBSCRIPTION_EVENT_NEW || + type == PA_SUBSCRIPTION_EVENT_CHANGE)) + pulse_mixer_on_change(po->mixer, context, po->stream); +} + +/** + * Attempt to connect asynchronously to the PulseAudio server. + * + * @return true on success, false on error + */ +static bool +pulse_output_connect(struct pulse_output *po, GError **error_r) +{ + int error; + + error = pa_context_connect(po->context, po->server, + (pa_context_flags_t)0, NULL); + if (error < 0) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_context_connect() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + return true; +} + +/** + * Create, set up and connect a context. + * + * @return true on success, false on error + */ +static bool +pulse_output_setup_context(struct pulse_output *po, GError **error_r) +{ + po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop), + MPD_PULSE_NAME); + if (po->context == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_context_new() has failed"); + return false; + } + + pa_context_set_state_callback(po->context, + pulse_output_context_state_cb, po); + pa_context_set_subscribe_callback(po->context, + pulse_output_subscribe_cb, po); + + if (!pulse_output_connect(po, error_r)) { + pa_context_unref(po->context); + return false; + } + + return true; +} + +/** + * Frees and clears the context. + */ +static void +pulse_output_delete_context(struct pulse_output *po) +{ + pa_context_disconnect(po->context); + pa_context_unref(po->context); + po->context = NULL; +} + +static void * +pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct pulse_output *po; + + g_setenv("PULSE_PROP_media.role", "music", true); + + po = g_new(struct pulse_output, 1); + po->name = config_get_block_string(param, "name", "mpd_pulse"); + po->server = config_get_block_string(param, "server", NULL); + po->sink = config_get_block_string(param, "sink", NULL); + + po->mixer = NULL; + po->mainloop = NULL; + po->context = NULL; + po->stream = NULL; + + return po; +} + +static void +pulse_output_finish(void *data) +{ + struct pulse_output *po = data; + + g_free(po); +} + +static bool +pulse_output_enable(void *data, GError **error_r) +{ + struct pulse_output *po = data; + + assert(po->mainloop == NULL); + assert(po->context == NULL); + + /* create the libpulse mainloop and start the thread */ + + po->mainloop = pa_threaded_mainloop_new(); + if (po->mainloop == NULL) { + g_free(po); + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_threaded_mainloop_new() has failed"); + return false; + } + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_threaded_mainloop_start(po->mainloop) < 0) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + g_free(po); + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_threaded_mainloop_start() has failed"); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + /* create the libpulse context and connect it */ + + pa_threaded_mainloop_lock(po->mainloop); + + if (!pulse_output_setup_context(po, error_r)) { + pa_threaded_mainloop_unlock(po->mainloop); + pa_threaded_mainloop_stop(po->mainloop); + pa_threaded_mainloop_free(po->mainloop); + g_free(po); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static void +pulse_output_disable(void *data) +{ + struct pulse_output *po = data; + + pa_threaded_mainloop_stop(po->mainloop); + if (po->context != NULL) + pulse_output_delete_context(po); + pa_threaded_mainloop_free(po->mainloop); + po->mainloop = NULL; +} + +/** + * Check if the context is (already) connected, and waits if not. If + * the context has been disconnected, retry to connect. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_connection(struct pulse_output *po, GError **error_r) +{ + pa_context_state_t state; + + pa_threaded_mainloop_lock(po->mainloop); + + if (po->context == NULL && !pulse_output_setup_context(po, error_r)) + return false; + + while (true) { + state = pa_context_get_state(po->context); + switch (state) { + case PA_CONTEXT_READY: + /* nothing to do */ + pa_threaded_mainloop_unlock(po->mainloop); + return true; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* failure */ + g_set_error(error_r, pulse_output_quark(), 0, + "failed to connect: %s", + pa_strerror(pa_context_errno(po->context))); + pulse_output_delete_context(po); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + /* wait some more */ + pa_threaded_mainloop_wait(po->mainloop); + break; + } + } +} + +static void +pulse_output_stream_state_cb(pa_stream *stream, void *userdata) +{ + struct pulse_output *po = userdata; + + switch (pa_stream_get_state(stream)) { + case PA_STREAM_READY: + if (po->mixer != NULL) + pulse_mixer_on_change(po->mixer, po->context, stream); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + if (po->mixer != NULL) + pulse_mixer_on_disconnect(po->mixer); + + pa_threaded_mainloop_signal(po->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void +pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes, + void *userdata) +{ + struct pulse_output *po = userdata; + + po->writable = nbytes; + pa_threaded_mainloop_signal(po->mainloop, 0); +} + +static bool +pulse_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct pulse_output *po = data; + pa_sample_spec ss; + int error; + + if (po->context != NULL) { + switch (pa_context_get_state(po->context)) { + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + /* the connection was closed meanwhile; delete + it, and pulse_output_wait_connection() will + reopen it */ + pulse_output_delete_context(po); + break; + + case PA_CONTEXT_READY: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } + } + + if (!pulse_output_wait_connection(po, error_r)) + return false; + + /* MPD doesn't support the other pulseaudio sample formats, so + we just force MPD to send us everything as 16 bit */ + audio_format->format = SAMPLE_FORMAT_S16; + + ss.format = PA_SAMPLE_S16NE; + ss.rate = audio_format->sample_rate; + ss.channels = audio_format->channels; + + pa_threaded_mainloop_lock(po->mainloop); + + /* create a stream .. */ + + po->stream = pa_stream_new(po->context, po->name, &ss, NULL); + if (po->stream == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_new() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + pa_stream_set_state_callback(po->stream, + pulse_output_stream_state_cb, po); + pa_stream_set_write_callback(po->stream, + pulse_output_stream_write_cb, po); + + /* .. and connect it (asynchronously) */ + + error = pa_stream_connect_playback(po->stream, po->sink, + NULL, 0, NULL, NULL); + if (error < 0) { + pa_stream_unref(po->stream); + po->stream = NULL; + + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_connect_playback() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + +#if !PA_CHECK_VERSION(0,9,11) + po->pause = false; +#endif + + return true; +} + +static void +pulse_output_close(void *data) +{ + struct pulse_output *po = data; + pa_operation *o; + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) == PA_STREAM_READY) { + o = pa_stream_drain(po->stream, + pulse_output_stream_success_cb, po); + if (o == NULL) { + g_warning("pa_stream_drain() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + } else + pulse_wait_for_operation(po->mainloop, o); + } + + pa_stream_disconnect(po->stream); + pa_stream_unref(po->stream); + po->stream = NULL; + + if (po->context != NULL && + pa_context_get_state(po->context) != PA_CONTEXT_READY) + pulse_output_delete_context(po); + + pa_threaded_mainloop_unlock(po->mainloop); +} + +/** + * Check if the stream is (already) connected, and waits for a signal + * if not. The mainloop must be locked before calling this function. + * + * @return the current stream state + */ +static pa_stream_state_t +pulse_output_check_stream(struct pulse_output *po) +{ + pa_stream_state_t state = pa_stream_get_state(po->stream); + + switch (state) { + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + break; + + case PA_STREAM_CREATING: + pa_threaded_mainloop_wait(po->mainloop); + state = pa_stream_get_state(po->stream); + break; + } + + return state; +} + +/** + * Check if the stream is (already) connected, and waits if not. The + * mainloop must be locked before calling this function. + * + * @return true on success, false on error + */ +static bool +pulse_output_wait_stream(struct pulse_output *po, GError **error_r) +{ + pa_stream_state_t state = pa_stream_get_state(po->stream); + + switch (state) { + case PA_STREAM_READY: + return true; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + g_set_error(error_r, pulse_output_quark(), 0, + "disconnected"); + return false; + + case PA_STREAM_CREATING: + break; + } + + do { + state = pulse_output_check_stream(po); + } while (state == PA_STREAM_CREATING); + + if (state != PA_STREAM_READY) { + g_set_error(error_r, pulse_output_quark(), 0, + "failed to connect the stream: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + return true; +} + +/** + * Determines whether the stream is paused. On libpulse older than + * 0.9.11, it uses a custom pause flag. + */ +static bool +pulse_output_stream_is_paused(struct pulse_output *po) +{ + assert(po->stream != NULL); + +#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11) + return po->pause; +#else + return pa_stream_is_corked(po->stream); +#endif +} + +/** + * Sets cork mode on the stream. + */ +static bool +pulse_output_stream_pause(struct pulse_output *po, bool pause, + GError **error_r) +{ + pa_operation *o; + + assert(po->stream != NULL); + + o = pa_stream_cork(po->stream, pause, + pulse_output_stream_success_cb, po); + if (o == NULL) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_cork() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + + if (!pulse_wait_for_operation(po->mainloop, o)) { + g_set_error(error_r, pulse_output_quark(), 0, + "pa_stream_cork() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + return false; + } + +#if !PA_CHECK_VERSION(0,9,11) + po->pause = pause; +#endif + return true; +} + +static size_t +pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct pulse_output *po = data; + int error; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already) connected */ + + if (!pulse_output_wait_stream(po, error_r)) { + pa_threaded_mainloop_unlock(po->mainloop); + return 0; + } + + assert(po->context != NULL); + + /* unpause if previously paused */ + + if (pulse_output_stream_is_paused(po) && + !pulse_output_stream_pause(po, false, error_r)) + return 0; + + /* wait until the server allows us to write */ + + while (po->writable == 0) { + pa_threaded_mainloop_wait(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + pa_threaded_mainloop_unlock(po->mainloop); + g_set_error(error_r, pulse_output_quark(), 0, + "disconnected"); + return false; + } + } + + /* now write */ + + if (size > po->writable) + /* don't send more than possible */ + size = po->writable; + + po->writable -= size; + + error = pa_stream_write(po->stream, chunk, size, NULL, + 0, PA_SEEK_RELATIVE); + pa_threaded_mainloop_unlock(po->mainloop); + if (error < 0) { + g_set_error(error_r, pulse_output_quark(), error, + "%s", pa_strerror(error)); + return 0; + } + + return size; +} + +static void +pulse_output_cancel(void *data) +{ + struct pulse_output *po = data; + pa_operation *o; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + if (pa_stream_get_state(po->stream) != PA_STREAM_READY) { + /* no need to flush when the stream isn't connected + yet */ + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + assert(po->context != NULL); + + o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po); + if (o == NULL) { + g_warning("pa_stream_flush() has failed: %s", + pa_strerror(pa_context_errno(po->context))); + pa_threaded_mainloop_unlock(po->mainloop); + return; + } + + pulse_wait_for_operation(po->mainloop, o); + pa_threaded_mainloop_unlock(po->mainloop); +} + +static bool +pulse_output_pause(void *data) +{ + struct pulse_output *po = data; + GError *error = NULL; + + assert(po->stream != NULL); + + pa_threaded_mainloop_lock(po->mainloop); + + /* check if the stream is (already/still) connected */ + + if (!pulse_output_wait_stream(po, &error)) { + pa_threaded_mainloop_unlock(po->mainloop); + g_warning("%s", error->message); + g_error_free(error); + return false; + } + + assert(po->context != NULL); + + /* cork the stream */ + + if (pulse_output_stream_is_paused(po)) { + /* already paused; due to a MPD API limitation, we + have to sleep a little bit here, to avoid hogging + the CPU */ + + g_usleep(50000); + } else if (!pulse_output_stream_pause(po, true, &error)) { + pa_threaded_mainloop_unlock(po->mainloop); + g_warning("%s", error->message); + g_error_free(error); + return false; + } + + pa_threaded_mainloop_unlock(po->mainloop); + + return true; +} + +static bool +pulse_output_test_default_device(void) +{ + struct pulse_output *po; + bool success; + + po = pulse_output_init(NULL, NULL, NULL); + if (po == NULL) + return false; + + success = pulse_output_wait_connection(po, NULL); + pulse_output_finish(po); + + return success; +} + +const struct audio_output_plugin pulse_output_plugin = { + .name = "pulse", + + .test_default_device = pulse_output_test_default_device, + .init = pulse_output_init, + .finish = pulse_output_finish, + .enable = pulse_output_enable, + .disable = pulse_output_disable, + .open = pulse_output_open, + .play = pulse_output_play, + .cancel = pulse_output_cancel, + .pause = pulse_output_pause, + .close = pulse_output_close, + + .mixer_plugin = &pulse_mixer_plugin, +}; diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h new file mode 100644 index 000000000..e6b37443f --- /dev/null +++ b/src/output/pulse_output_plugin.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PULSE_OUTPUT_PLUGIN_H +#define MPD_PULSE_OUTPUT_PLUGIN_H + +#include <stdbool.h> +#include <stddef.h> + +#include <glib.h> + +#include <pulse/version.h> + +#if !defined(PA_CHECK_VERSION) +/** + * This macro was implemented in libpulse 0.9.16. + */ +#define PA_CHECK_VERSION(a,b,c) false +#endif + +struct pa_operation; +struct pa_cvolume; + +struct pulse_output { + const char *name; + const char *server; + const char *sink; + + struct pulse_mixer *mixer; + + struct pa_threaded_mainloop *mainloop; + struct pa_context *context; + struct pa_stream *stream; + + size_t writable; + +#if !PA_CHECK_VERSION(0,9,11) + /** + * We need this variable because pa_stream_is_corked() wasn't + * added before 0.9.11. + */ + bool pause; +#endif +}; + +void +pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm); + +void +pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm); + +bool +pulse_output_set_volume(struct pulse_output *po, + const struct pa_cvolume *volume, GError **error_r); + +#endif diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c deleted file mode 100644 index ffc7abc8b..000000000 --- a/src/output/pulse_plugin.c +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2003-2009 The Music Player Daemon Project - * 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., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "../output_api.h" -#include "mixer_list.h" - -#include <glib.h> -#include <pulse/simple.h> -#include <pulse/error.h> - -#define MPD_PULSE_NAME "mpd" - -struct pulse_data { - const char *name; - const char *server; - const char *sink; - - pa_simple *s; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -pulse_output_quark(void) -{ - return g_quark_from_static_string("pulse_output"); -} - -static struct pulse_data *pulse_new_data(void) -{ - struct pulse_data *ret; - - ret = g_new(struct pulse_data, 1); - - ret->server = NULL; - ret->sink = NULL; - - return ret; -} - -static void pulse_free_data(struct pulse_data *pd) -{ - g_free(pd); -} - -static void * -pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format, - const struct config_param *param, G_GNUC_UNUSED GError **error) -{ - struct pulse_data *pd; - - pd = pulse_new_data(); - pd->name = config_get_block_string(param, "name", "mpd_pulse"); - pd->server = config_get_block_string(param, "server", NULL); - pd->sink = config_get_block_string(param, "sink", NULL); - - return pd; -} - -static void pulse_finish(void *data) -{ - struct pulse_data *pd = data; - - pulse_free_data(pd); -} - -static bool pulse_test_default_device(void) -{ - pa_simple *s; - pa_sample_spec ss; - int error; - - ss.format = PA_SAMPLE_S16NE; - ss.rate = 44100; - ss.channels = 2; - - s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL, - MPD_PULSE_NAME, &ss, NULL, NULL, &error); - if (!s) { - g_message("Cannot connect to default PulseAudio server: %s\n", - pa_strerror(error)); - return false; - } - - pa_simple_free(s); - - return true; -} - -static bool -pulse_open(void *data, struct audio_format *audio_format, GError **error_r) -{ - struct pulse_data *pd = data; - pa_sample_spec ss; - int error; - - /* MPD doesn't support the other pulseaudio sample formats, so - we just force MPD to send us everything as 16 bit */ - audio_format->bits = 16; - - ss.format = PA_SAMPLE_S16NE; - ss.rate = audio_format->sample_rate; - ss.channels = audio_format->channels; - - pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, - pd->sink, pd->name, - &ss, NULL, NULL, - &error); - if (!pd->s) { - g_set_error(error_r, pulse_output_quark(), error, - "Cannot connect to PulseAudio server: %s", - pa_strerror(error)); - return false; - } - - return true; -} - -static void pulse_cancel(void *data) -{ - struct pulse_data *pd = data; - int error; - - if (pa_simple_flush(pd->s, &error) < 0) - g_warning("Flush failed in PulseAudio output \"%s\": %s\n", - pd->name, pa_strerror(error)); -} - -static void pulse_close(void *data) -{ - struct pulse_data *pd = data; - - pa_simple_drain(pd->s, NULL); - pa_simple_free(pd->s); -} - -static size_t -pulse_play(void *data, const void *chunk, size_t size, GError **error_r) -{ - struct pulse_data *pd = data; - int error; - - if (pa_simple_write(pd->s, chunk, size, &error) < 0) { - g_set_error(error_r, pulse_output_quark(), error, - "%s", pa_strerror(error)); - return 0; - } - - return size; -} - -const struct audio_output_plugin pulse_plugin = { - .name = "pulse", - .test_default_device = pulse_test_default_device, - .init = pulse_init, - .finish = pulse_finish, - .open = pulse_open, - .play = pulse_play, - .cancel = pulse_cancel, - .close = pulse_close, - .mixer_plugin = &pulse_mixer, -}; diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c new file mode 100644 index 000000000..f56ec0328 --- /dev/null +++ b/src/output/recorder_output_plugin.c @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "output_api.h" +#include "encoder_plugin.h" +#include "encoder_list.h" +#include "fd_util.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "recorder" + +struct recorder_output { + /** + * The configured encoder plugin. + */ + struct encoder *encoder; + + /** + * The destination file name. + */ + const char *path; + + /** + * The destination file descriptor. + */ + int fd; + + /** + * The buffer for encoder_read(). + */ + char buffer[32768]; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +recorder_output_quark(void) +{ + return g_quark_from_static_string("recorder_output"); +} + +static void * +recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, GError **error_r) +{ + struct recorder_output *recorder = g_new(struct recorder_output, 1); + const char *encoder_name; + const struct encoder_plugin *encoder_plugin; + + /* read configuration */ + + encoder_name = config_get_block_string(param, "encoder", "vorbis"); + encoder_plugin = encoder_plugin_get(encoder_name); + if (encoder_plugin == NULL) { + g_set_error(error_r, recorder_output_quark(), 0, + "No such encoder: %s", encoder_name); + return NULL; + } + + recorder->path = config_get_block_string(param, "path", NULL); + if (recorder->path == NULL) { + g_set_error(error_r, recorder_output_quark(), 0, + "'path' not configured"); + return NULL; + } + + /* initialize encoder */ + + recorder->encoder = encoder_init(encoder_plugin, param, error_r); + if (recorder->encoder == NULL) + return NULL; + + return recorder; +} + +static void +recorder_output_finish(void *data) +{ + struct recorder_output *recorder = data; + + encoder_finish(recorder->encoder); + g_free(recorder); +} + +/** + * Writes pending data from the encoder to the output file. + */ +static bool +recorder_output_encoder_to_file(struct recorder_output *recorder, + GError **error_r) +{ + size_t size = 0, position, nbytes; + + assert(recorder->fd >= 0); + + /* read from the encoder */ + + size = encoder_read(recorder->encoder, recorder->buffer, + sizeof(recorder->buffer)); + if (size == 0) + return true; + + /* write everything into the file */ + + position = 0; + while (true) { + nbytes = write(recorder->fd, recorder->buffer + position, + size - position); + if (nbytes > 0) { + position += (size_t)nbytes; + if (position >= size) + return true; + } else if (nbytes == 0) { + /* shouldn't happen for files */ + g_set_error(error_r, recorder_output_quark(), 0, + "write() returned 0"); + return false; + } else if (errno != EINTR) { + g_set_error(error_r, recorder_output_quark(), 0, + "Failed to write to '%s': %s", + recorder->path, g_strerror(errno)); + return false; + } + } +} + +static bool +recorder_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct recorder_output *recorder = data; + bool success; + + /* create the output file */ + + recorder->fd = open_cloexec(recorder->path, O_CREAT|O_WRONLY|O_TRUNC, + 0666); + if (recorder->fd < 0) { + g_set_error(error_r, recorder_output_quark(), 0, + "Failed to create '%s': %s", + recorder->path, g_strerror(errno)); + return false; + } + + /* open the encoder */ + + success = encoder_open(recorder->encoder, audio_format, error_r); + if (!success) { + close(recorder->fd); + unlink(recorder->path); + return false; + } + + return true; +} + +static void +recorder_output_close(void *data) +{ + struct recorder_output *recorder = data; + + /* flush the encoder and write the rest to the file */ + + if (encoder_flush(recorder->encoder, NULL)) + recorder_output_encoder_to_file(recorder, NULL); + + /* now really close everything */ + + encoder_close(recorder->encoder); + + close(recorder->fd); +} + +static size_t +recorder_output_play(void *data, const void *chunk, size_t size, + GError **error_r) +{ + struct recorder_output *recorder = data; + + return encoder_write(recorder->encoder, chunk, size, error_r) && + recorder_output_encoder_to_file(recorder, error_r) + ? size : 0; +} + +const struct audio_output_plugin recorder_output_plugin = { + .name = "recorder", + .init = recorder_output_init, + .finish = recorder_output_finish, + .open = recorder_output_open, + .close = recorder_output_close, + .play = recorder_output_play, +}; diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c index 4412d26ff..750b09191 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_plugin.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_api.h" #include "encoder_plugin.h" #include "encoder_list.h" @@ -126,6 +127,13 @@ my_shout_init_driver(const struct audio_format *audio_format, struct block_param *block_param; int public; + if (audio_format == NULL || + !audio_format_fully_defined(audio_format)) { + g_set_error(error, shout_output_quark(), 0, + "Need full audio format specification"); + return NULL; + } + sd = new_shout_data(); if (shout_init_count == 0) @@ -191,8 +199,6 @@ my_shout_init_driver(const struct audio_format *audio_format, } } - check_block_param("format"); - encoding = config_get_block_string(param, "encoding", "ogg"); encoder_plugin = shout_encoder_plugin_get(encoding); if (encoder_plugin == NULL) { @@ -471,10 +477,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size) for (unsigned i = 0; i < tag->num_items; i++) { switch (tag->items[i]->type) { - case TAG_ITEM_ARTIST: + case TAG_ARTIST: strncpy(artist, tag->items[i]->value, size); break; - case TAG_ITEM_TITLE: + case TAG_TITLE: strncpy(title, tag->items[i]->value, size); break; diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 5febf0afc..fe84068f1 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_api.h" +#include "fd_util.h" #include <glib.h> @@ -87,11 +89,11 @@ solaris_output_open(void *data, struct audio_format *audio_format, /* support only 16 bit mono/stereo for now; nothing else has been tested */ - audio_format->bits = 16; + audio_format->format = SAMPLE_FORMAT_S16; /* open the device in non-blocking mode */ - so->fd = open(so->device, O_WRONLY|O_NONBLOCK); + so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK); if (so->fd < 0) { g_set_error(error, solaris_output_quark(), errno, "Failed to open %s: %s", @@ -117,7 +119,7 @@ solaris_output_open(void *data, struct audio_format *audio_format, info.play.sample_rate = audio_format->sample_rate; info.play.channels = audio_format->channels; - info.play.precision = audio_format->bits; + info.play.precision = 16; info.play.encoding = AUDIO_ENCODING_LINEAR; ret = ioctl(so->fd, AUDIO_SETINFO, &info); diff --git a/src/output_all.c b/src/output_all.c index 4b5ba3a6f..194a65924 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_all.h" #include "output_internal.h" #include "output_control.h" @@ -52,6 +53,11 @@ static struct music_buffer *g_music_buffer; */ static struct music_pipe *g_mp; +/** + * The "elapsed_time" stamp of the most recently finished chunk. + */ +static float audio_output_all_elapsed_time = -1.0; + unsigned int audio_output_count(void) { return num_audio_outputs; @@ -148,6 +154,25 @@ audio_output_all_finish(void) notify_deinit(&audio_output_client_notify); } +void +audio_output_all_enable_disable(void) +{ + for (unsigned i = 0; i < num_audio_outputs; i++) { + struct audio_output *ao = &audio_outputs[i]; + bool enabled; + + g_mutex_lock(ao->mutex); + enabled = ao->really_enabled; + g_mutex_unlock(ao->mutex); + + if (ao->enabled != enabled) { + if (ao->enabled) + audio_output_enable(ao); + else + audio_output_disable(ao); + } + } +} /** * Determine if all (active) outputs have finished the current @@ -156,10 +181,18 @@ audio_output_all_finish(void) static bool audio_output_all_finished(void) { - for (unsigned i = 0; i < num_audio_outputs; ++i) - if (audio_output_is_open(&audio_outputs[i]) && - !audio_output_command_is_finished(&audio_outputs[i])) + for (unsigned i = 0; i < num_audio_outputs; ++i) { + struct audio_output *ao = &audio_outputs[i]; + bool not_finished; + + g_mutex_lock(ao->mutex); + not_finished = audio_output_is_open(ao) && + !audio_output_command_is_finished(ao); + g_mutex_unlock(ao->mutex); + + if (not_finished) return false; + } return true; } @@ -170,6 +203,29 @@ static void audio_output_wait_all(void) notify_wait(&audio_output_client_notify); } +/** + * Signals the audio output if it is open. This function locks the + * mutex. + */ +static void +audio_output_lock_signal(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + g_mutex_unlock(ao->mutex); +} + +/** + * Signals all audio outputs which are open. + */ +static void +audio_output_signal_all(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_lock_signal(&audio_outputs[i]); +} + static void audio_output_reset_reopen(struct audio_output *ao) { @@ -237,8 +293,7 @@ audio_output_all_play(struct music_chunk *chunk) music_pipe_push(g_mp, chunk); for (i = 0; i < num_audio_outputs; ++i) - if (audio_output_is_open(&audio_outputs[i])) - audio_output_play(&audio_outputs[i]); + audio_output_play(&audio_outputs[i]); return true; } @@ -273,6 +328,7 @@ audio_output_all_open(const struct audio_format *audio_format, input_audio_format = *audio_format; audio_output_all_reset_reopen(); + audio_output_all_enable_disable(); audio_output_all_update(); for (i = 0; i < num_audio_outputs; ++i) { @@ -385,6 +441,11 @@ audio_output_all_check(void) this chunk */ return music_pipe_size(g_mp); + if (chunk->length > 0 && chunk->times >= 0.0) + /* only update elapsed_time if the chunk + provides a defined value */ + audio_output_all_elapsed_time = chunk->times; + is_tail = chunk->next == NULL; if (is_tail) /* this is the tail of the pipe - clear the @@ -412,10 +473,15 @@ audio_output_all_check(void) bool audio_output_all_wait(unsigned threshold) { - if (audio_output_all_check() < threshold) + player_lock(); + + if (audio_output_all_check() < threshold) { + player_unlock(); return true; + } - notify_wait(&pc.notify); + player_wait(); + player_unlock(); return audio_output_all_check() < threshold; } @@ -428,8 +494,16 @@ audio_output_all_pause(void) audio_output_all_update(); for (i = 0; i < num_audio_outputs; ++i) - if (audio_output_is_open(&audio_outputs[i])) - audio_output_pause(&audio_outputs[i]); + audio_output_pause(&audio_outputs[i]); + + audio_output_wait_all(); +} + +void +audio_output_all_drain(void) +{ + for (unsigned i = 0; i < num_audio_outputs; ++i) + audio_output_drain_async(&audio_outputs[i]); audio_output_wait_all(); } @@ -441,10 +515,8 @@ audio_output_all_cancel(void) /* send the cancel() command to all audio outputs */ - for (i = 0; i < num_audio_outputs; ++i) { - if (audio_output_is_open(&audio_outputs[i])) - audio_output_cancel(&audio_outputs[i]); - } + for (i = 0; i < num_audio_outputs; ++i) + audio_output_cancel(&audio_outputs[i]); audio_output_wait_all(); @@ -452,6 +524,15 @@ audio_output_all_cancel(void) if (g_mp != NULL) music_pipe_clear(g_mp, g_music_buffer); + + /* the audio outputs are now waiting for a signal, to + synchronize the cleared music pipe */ + + audio_output_signal_all(); + + /* invalidate elapsed_time */ + + audio_output_all_elapsed_time = -1.0; } void @@ -473,4 +554,12 @@ audio_output_all_close(void) g_music_buffer = NULL; audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +float +audio_output_all_get_elapsed_time(void) +{ + return audio_output_all_elapsed_time; } diff --git a/src/output_all.h b/src/output_all.h index 2a09514b2..6ff45fb79 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -66,6 +66,13 @@ struct audio_output * audio_output_find(const char *name); /** + * Checks the "enabled" flag of all audio outputs, and if one has + * changed, commit the change. + */ +void +audio_output_all_enable_disable(void); + +/** * Opens all audio outputs which are not disabled. * * @param audio_format the preferred audio format, or NULL to reuse @@ -123,9 +130,23 @@ void audio_output_all_pause(void); /** + * Drain all audio outputs. + */ +void +audio_output_all_drain(void); + +/** * Try to cancel data which may still be in the device's buffers. */ void audio_output_all_cancel(void); +/** + * Returns the "elapsed_time" stamp of the most recently finished + * chunk. A negative value is returned when no chunk has been + * finished yet. + */ +float +audio_output_all_get_elapsed_time(void); + #endif diff --git a/src/output_command.c b/src/output_command.c index 5da176dde..9a720904d 100644 --- a/src/output_command.c +++ b/src/output_command.c @@ -24,13 +24,17 @@ * */ +#include "config.h" #include "output_command.h" #include "output_all.h" #include "output_internal.h" #include "output_plugin.h" #include "mixer_control.h" +#include "player_control.h" #include "idle.h" +extern unsigned audio_output_state_version; + bool audio_output_enable_index(unsigned idx) { @@ -40,10 +44,16 @@ audio_output_enable_index(unsigned idx) return false; ao = audio_output_get(idx); + if (ao->enabled) + return true; ao->enabled = true; idle_add(IDLE_OUTPUT); + pc_update_audio(); + + ++audio_output_state_version; + return true; } @@ -57,6 +67,8 @@ audio_output_disable_index(unsigned idx) return false; ao = audio_output_get(idx); + if (!ao->enabled) + return true; ao->enabled = false; idle_add(IDLE_OUTPUT); @@ -67,5 +79,9 @@ audio_output_disable_index(unsigned idx) idle_add(IDLE_MIXER); } + pc_update_audio(); + + ++audio_output_state_version; + return true; } diff --git a/src/output_control.c b/src/output_control.c index 16c0dbb75..5479263de 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -17,12 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_control.h" #include "output_api.h" #include "output_internal.h" #include "output_thread.h" #include "mixer_control.h" #include "mixer_plugin.h" +#include "filter_plugin.h" +#include "notify.h" #include <assert.h> #include <stdlib.h> @@ -38,8 +41,9 @@ struct notify audio_output_client_notify; static void ao_command_wait(struct audio_output *ao) { while (ao->command != AO_COMMAND_NONE) { - notify_signal(&ao->notify); + g_mutex_unlock(ao->mutex); notify_wait(&audio_output_client_notify); + g_mutex_lock(ao->mutex); } } @@ -47,6 +51,7 @@ static void ao_command(struct audio_output *ao, enum audio_output_command cmd) { assert(ao->command == AO_COMMAND_NONE); ao->command = cmd; + g_cond_signal(ao->cond); ao_command_wait(ao); } @@ -55,7 +60,46 @@ static void ao_command_async(struct audio_output *ao, { assert(ao->command == AO_COMMAND_NONE); ao->command = cmd; - notify_signal(&ao->notify); + g_cond_signal(ao->cond); +} + +void +audio_output_enable(struct audio_output *ao) +{ + if (ao->thread == NULL) { + if (ao->plugin->enable == NULL) { + /* don't bother to start the thread now if the + device doesn't even have a enable() method; + just assign the variable and we're done */ + ao->really_enabled = true; + return; + } + + audio_output_thread_start(ao); + } + + g_mutex_lock(ao->mutex); + ao_command(ao, AO_COMMAND_ENABLE); + g_mutex_unlock(ao->mutex); +} + +void +audio_output_disable(struct audio_output *ao) +{ + if (ao->thread == NULL) { + if (ao->plugin->disable == NULL) + ao->really_enabled = false; + else + /* if there's no thread yet, the device cannot + be enabled */ + assert(!ao->really_enabled); + + return; + } + + g_mutex_lock(ao->mutex); + ao_command(ao, AO_COMMAND_DISABLE); + g_mutex_unlock(ao->mutex); } static bool @@ -85,6 +129,10 @@ audio_output_open(struct audio_output *ao, /* we're not using audio_output_cancel() here, because that function is asynchronous */ ao_command(ao, AO_COMMAND_CANCEL); + + /* the audio output is now waiting for a + signal; wake it up immediately */ + g_cond_signal(ao->cond); } return true; @@ -93,33 +141,47 @@ audio_output_open(struct audio_output *ao, ao->in_audio_format = *audio_format; ao->chunk = NULL; - if (!ao->config_audio_format) { - if (ao->open) - audio_output_close(ao); - - /* no audio format is configured: copy in->out, let - the output's open() method determine the effective - out_audio_format */ - ao->out_audio_format = ao->in_audio_format; - } - ao->pipe = mp; if (ao->thread == NULL) audio_output_thread_start(ao); + ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); open = ao->open; - if (!open) { - ao_command(ao, AO_COMMAND_OPEN); - open = ao->open; - } - if (open && ao->mixer != NULL) - mixer_open(ao->mixer); + if (open && ao->mixer != NULL) { + GError *error = NULL; + + if (!mixer_open(ao->mixer, &error)) { + g_warning("Failed to open mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + } return open; } +/** + * Same as audio_output_close(), but expects the lock to be held by + * the caller. + */ +static void +audio_output_close_locked(struct audio_output *ao) +{ + if (ao->mixer != NULL) + mixer_auto_close(ao->mixer); + + assert(!ao->open || ao->fail_timer == NULL); + + if (ao->open) + ao_command(ao, AO_COMMAND_CLOSE); + else if (ao->fail_timer != NULL) { + g_timer_destroy(ao->fail_timer); + ao->fail_timer = NULL; + } +} + bool audio_output_update(struct audio_output *ao, const struct audio_format *audio_format, @@ -127,23 +189,31 @@ audio_output_update(struct audio_output *ao, { assert(mp != NULL); - if (ao->enabled) { + g_mutex_lock(ao->mutex); + + if (ao->enabled && ao->really_enabled) { if (ao->fail_timer == NULL || - g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) - return audio_output_open(ao, audio_format, mp); + g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { + bool success = audio_output_open(ao, audio_format, mp); + g_mutex_unlock(ao->mutex); + return success; + } } else if (audio_output_is_open(ao)) - audio_output_close(ao); + audio_output_close_locked(ao); + g_mutex_unlock(ao->mutex); return false; } void audio_output_play(struct audio_output *ao) { - if (!ao->open) - return; + g_mutex_lock(ao->mutex); + + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); - notify_signal(&ao->notify); + g_mutex_unlock(ao->mutex); } void audio_output_pause(struct audio_output *ao) @@ -154,27 +224,46 @@ void audio_output_pause(struct audio_output *ao) mixer_auto_close()) */ mixer_auto_close(ao->mixer); - ao_command_async(ao, AO_COMMAND_PAUSE); + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_PAUSE); + g_mutex_unlock(ao->mutex); +} + +void +audio_output_drain_async(struct audio_output *ao) +{ + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_DRAIN); + g_mutex_unlock(ao->mutex); } void audio_output_cancel(struct audio_output *ao) { - ao_command_async(ao, AO_COMMAND_CANCEL); + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_CANCEL); + g_mutex_unlock(ao->mutex); } void audio_output_close(struct audio_output *ao) { - assert(!ao->open || ao->fail_timer == NULL); - if (ao->mixer != NULL) mixer_auto_close(ao->mixer); + g_mutex_lock(ao->mutex); + + assert(!ao->open || ao->fail_timer == NULL); + if (ao->open) ao_command(ao, AO_COMMAND_CLOSE); else if (ao->fail_timer != NULL) { g_timer_destroy(ao->fail_timer); ao->fail_timer = NULL; } + + g_mutex_unlock(ao->mutex); } void audio_output_finish(struct audio_output *ao) @@ -184,7 +273,9 @@ void audio_output_finish(struct audio_output *ao) assert(ao->fail_timer == NULL); if (ao->thread != NULL) { + g_mutex_lock(ao->mutex); ao_command(ao, AO_COMMAND_KILL); + g_mutex_unlock(ao->mutex); g_thread_join(ao->thread); } @@ -193,6 +284,8 @@ void audio_output_finish(struct audio_output *ao) ao_plugin_finish(ao->plugin, ao->data); - notify_deinit(&ao->notify); + g_cond_free(ao->cond); g_mutex_free(ao->mutex); + + filter_free(ao->filter); } diff --git a/src/output_control.h b/src/output_control.h index ce3abe3f6..692c11676 100644 --- a/src/output_control.h +++ b/src/output_control.h @@ -38,7 +38,19 @@ audio_output_quark(void) bool audio_output_init(struct audio_output *ao, const struct config_param *param, - GError **error); + GError **error_r); + +/** + * Enables the device. + */ +void +audio_output_enable(struct audio_output *ao); + +/** + * Disables the device. + */ +void +audio_output_disable(struct audio_output *ao); /** * Opens or closes the device, depending on the "enabled" flag. @@ -55,6 +67,9 @@ audio_output_play(struct audio_output *ao); void audio_output_pause(struct audio_output *ao); +void +audio_output_drain_async(struct audio_output *ao); + void audio_output_cancel(struct audio_output *ao); void audio_output_close(struct audio_output *ao); void audio_output_finish(struct audio_output *ao); diff --git a/src/output_init.c b/src/output_init.c index 927424324..fe9e4d86a 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -17,21 +17,33 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_control.h" #include "output_api.h" #include "output_internal.h" #include "output_list.h" #include "audio_parser.h" #include "mixer_control.h" +#include "mixer_type.h" +#include "mixer_list.h" +#include "mixer/software_mixer_plugin.h" +#include "filter_plugin.h" +#include "filter_registry.h" +#include "filter_config.h" +#include "filter/chain_filter_plugin.h" +#include "filter/autoconvert_filter_plugin.h" #include <glib.h> +#include <assert.h> + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "output" #define AUDIO_OUTPUT_TYPE "type" #define AUDIO_OUTPUT_NAME "name" #define AUDIO_OUTPUT_FORMAT "format" +#define AUDIO_FILTERS "filters" static const struct audio_output_plugin * audio_output_detect(GError **error) @@ -56,46 +68,109 @@ audio_output_detect(GError **error) return NULL; } +/** + * Determines the mixer type which should be used for the specified + * configuration block. + * + * This handles the deprecated options mixer_type (global) and + * mixer_enabled, if the mixer_type setting is not configured. + */ +static enum mixer_type +audio_output_mixer_type(const struct config_param *param) +{ + /* read the local "mixer_type" setting */ + const char *p = config_get_block_string(param, "mixer_type", NULL); + if (p != NULL) + return mixer_type_parse(p); + + /* try the local "mixer_enabled" setting next (deprecated) */ + if (!config_get_block_bool(param, "mixer_enabled", true)) + return MIXER_TYPE_NONE; + + /* fall back to the global "mixer_type" setting (also + deprecated) */ + return mixer_type_parse(config_get_string("mixer_type", "hardware")); +} + +static struct mixer * +audio_output_load_mixer(void *ao, const struct config_param *param, + const struct mixer_plugin *plugin, + struct filter *filter_chain, + GError **error_r) +{ + struct mixer *mixer; + + switch (audio_output_mixer_type(param)) { + case MIXER_TYPE_NONE: + case MIXER_TYPE_UNKNOWN: + return NULL; + + case MIXER_TYPE_HARDWARE: + if (plugin == NULL) + return NULL; + + return mixer_new(plugin, ao, param, error_r); + + case MIXER_TYPE_SOFTWARE: + mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL); + assert(mixer != NULL); + + filter_chain_append(filter_chain, + software_mixer_get_filter(mixer)); + return mixer; + } + + assert(false); + return NULL; +} + bool audio_output_init(struct audio_output *ao, const struct config_param *param, - GError **error) + GError **error_r) { - const char *format; const struct audio_output_plugin *plugin = NULL; + GError *error = NULL; if (param) { - const char *type = NULL; + const char *p; - type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); - if (type == NULL) { - g_set_error(error, audio_output_quark(), 0, + p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL); + if (p == NULL) { + g_set_error(error_r, audio_output_quark(), 0, "Missing \"type\" configuration"); return false; } - plugin = audio_output_plugin_get(type); + plugin = audio_output_plugin_get(p); if (plugin == NULL) { - g_set_error(error, audio_output_quark(), 0, - "No such audio output plugin: %s", - type); + g_set_error(error_r, audio_output_quark(), 0, + "No such audio output plugin: %s", p); return false; } ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME, NULL); if (ao->name == NULL) { - g_set_error(error, audio_output_quark(), 0, + g_set_error(error_r, audio_output_quark(), 0, "Missing \"name\" configuration"); return false; } - format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, + p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT, NULL); + if (p != NULL) { + bool success = + audio_format_parse(&ao->config_audio_format, + p, true, error_r); + if (!success) + return false; + } else + audio_format_clear(&ao->config_audio_format); } else { g_warning("No \"%s\" defined in config file\n", CONF_AUDIO_OUTPUT); - plugin = audio_output_detect(error); + plugin = audio_output_detect(error_r); if (plugin == NULL) return false; @@ -103,44 +178,72 @@ audio_output_init(struct audio_output *ao, const struct config_param *param, plugin->name); ao->name = "default detected output"; - format = NULL; + + audio_format_clear(&ao->config_audio_format); } ao->plugin = plugin; ao->enabled = config_get_block_bool(param, "enabled", true); + ao->really_enabled = false; ao->open = false; ao->pause = false; ao->fail_timer = NULL; - pcm_convert_init(&ao->convert_state); + /* set up the filter chain */ - ao->config_audio_format = format != NULL; - if (ao->config_audio_format) { - bool ret; + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); - ret = audio_format_parse(&ao->out_audio_format, format, - error); - if (!ret) - return false; + if (config_get_bool(CONF_VOLUME_NORMALIZATION, false)) { + struct filter *normalize_filter = + filter_new(&normalize_filter_plugin, NULL, NULL); + assert(normalize_filter != NULL); + + filter_chain_append(ao->filter, + autoconvert_filter_new(normalize_filter)); + } + + filter_chain_parse(ao->filter, + config_get_block_string(param, AUDIO_FILTERS, ""), + &error + ); + + // It's not really fatal - Part of the filter chain has been set up already + // and even an empty one will work (if only with unexpected behaviour) + if (error != NULL) { + g_warning("Failed to initialize filter chain for '%s': %s", + ao->name, error->message); + g_error_free(error); } ao->thread = NULL; - notify_init(&ao->notify); ao->command = AO_COMMAND_NONE; ao->mutex = g_mutex_new(); + ao->cond = g_cond_new(); ao->data = ao_plugin_init(plugin, - ao->config_audio_format - ? &ao->out_audio_format : NULL, - param, error); + &ao->config_audio_format, + param, error_r); if (ao->data == NULL) return false; - if (plugin->mixer_plugin != NULL && - config_get_block_bool(param, "mixer_enabled", true)) - ao->mixer = mixer_new(plugin->mixer_plugin, param); - else - ao->mixer = NULL; + ao->mixer = audio_output_load_mixer(ao->data, param, + plugin->mixer_plugin, + ao->filter, &error); + if (ao->mixer == NULL && error != NULL) { + g_warning("Failed to initialize hardware mixer for '%s': %s", + ao->name, error->message); + g_error_free(error); + } + + /* the "convert" filter must be the last one in the chain */ + + ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL); + assert(ao->convert_filter != NULL); + + filter_chain_append(ao->filter, ao->convert_filter); + + /* done */ return true; } diff --git a/src/output_internal.h b/src/output_internal.h index 72596c1c3..de1b15c29 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -21,16 +21,32 @@ #define MPD_OUTPUT_INTERNAL_H #include "audio_format.h" -#include "pcm_convert.h" -#include "notify.h" + +#include <glib.h> #include <time.h> enum audio_output_command { AO_COMMAND_NONE = 0, + AO_COMMAND_ENABLE, + AO_COMMAND_DISABLE, AO_COMMAND_OPEN, + + /** + * This command is invoked when the input audio format + * changes. + */ + AO_COMMAND_REOPEN, + AO_COMMAND_CLOSE, AO_COMMAND_PAUSE, + + /** + * Drains the internal (hardware) buffers of the device. This + * operation may take a while to complete. + */ + AO_COMMAND_DRAIN, + AO_COMMAND_CANCEL, AO_COMMAND_KILL }; @@ -60,15 +76,15 @@ struct audio_output { struct mixer *mixer; /** - * This flag is true, when the audio_format of this device is - * configured in mpd.conf. + * Has the user enabled this device? */ - bool config_audio_format; + bool enabled; /** - * Has the user enabled this device? + * Is this device actually enabled, i.e. the "enable" method + * has succeeded? */ - bool enabled; + bool really_enabled; /** * Is the device (already) open and functional? @@ -94,6 +110,11 @@ struct audio_output { GTimer *fail_timer; /** + * The configured audio format. + */ + struct audio_format config_audio_format; + + /** * The audio_format in which audio data is received from the * player thread (which in turn receives it from the decoder). */ @@ -107,18 +128,25 @@ struct audio_output { */ struct audio_format out_audio_format; - struct pcm_convert_state convert_state; + /** + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. + */ + struct filter *filter; /** - * The thread handle, or NULL if the output thread isn't - * running. + * The convert_filter_plugin instance of this audio output. + * It is the last item in the filter chain, and is responsible + * for converting the input data into the appropriate format + * for this audio output. */ - GThread *thread; + struct filter *convert_filter; /** - * Notify object for the thread. + * The thread handle, or NULL if the output thread isn't + * running. */ - struct notify notify; + GThread *thread; /** * The next command to be performed by the output thread. @@ -136,6 +164,12 @@ struct audio_output { GMutex *mutex; /** + * This condition object wakes up the output thread after + * #command has been set. + */ + GCond *cond; + + /** * The #music_chunk which is currently being played. All * chunks before this one may be returned to the * #music_buffer, because they are not going to be used by diff --git a/src/output_list.c b/src/output_list.c index 81de16649..71a294407 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_list.h" #include "output_api.h" -#include "config.h" extern const struct audio_output_plugin shoutPlugin; extern const struct audio_output_plugin null_output_plugin; @@ -28,12 +28,14 @@ extern const struct audio_output_plugin pipe_output_plugin; extern const struct audio_output_plugin alsaPlugin; extern const struct audio_output_plugin ao_output_plugin; extern const struct audio_output_plugin oss_output_plugin; +extern const struct audio_output_plugin openal_output_plugin; extern const struct audio_output_plugin osxPlugin; extern const struct audio_output_plugin solaris_output_plugin; -extern const struct audio_output_plugin pulse_plugin; +extern const struct audio_output_plugin pulse_output_plugin; extern const struct audio_output_plugin mvp_output_plugin; -extern const struct audio_output_plugin jackPlugin; +extern const struct audio_output_plugin jack_output_plugin; extern const struct audio_output_plugin httpd_output_plugin; +extern const struct audio_output_plugin recorder_output_plugin; const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_SHOUT @@ -55,6 +57,9 @@ const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_OSS &oss_output_plugin, #endif +#ifdef HAVE_OPENAL + &openal_output_plugin, +#endif #ifdef HAVE_OSX &osxPlugin, #endif @@ -62,17 +67,20 @@ const struct audio_output_plugin *audio_output_plugins[] = { &solaris_output_plugin, #endif #ifdef HAVE_PULSE - &pulse_plugin, + &pulse_output_plugin, #endif #ifdef HAVE_MVP &mvp_output_plugin, #endif #ifdef HAVE_JACK - &jackPlugin, + &jack_output_plugin, #endif #ifdef ENABLE_HTTPD_OUTPUT &httpd_output_plugin, #endif +#ifdef ENABLE_RECORDER_OUTPUT + &recorder_output_plugin, +#endif NULL }; diff --git a/src/output_plugin.h b/src/output_plugin.h index 13dba0d0b..ee7f7c73d 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -67,6 +67,24 @@ struct audio_output_plugin { void (*finish)(void *data); /** + * Enable the device. This may allocate resources, preparing + * for the device to be opened. Enabling a device cannot + * fail: if an error occurs during that, it should be reported + * by the open() method. + * + * @param error_r location to store the error occuring, or + * NULL to ignore errors + * @return true on success, false on error + */ + bool (*enable)(void *data, GError **error_r); + + /** + * Disables the device. It is closed before this method is + * called. + */ + void (*disable)(void *data); + + /** * Really open the device. * * @param audio_format the audio format in which data is going @@ -99,6 +117,11 @@ struct audio_output_plugin { GError **error); /** + * Wait until the device has finished playing. + */ + void (*drain)(void *data); + + /** * Try to cancel data which may still be in the device's * buffers. */ @@ -150,6 +173,22 @@ ao_plugin_finish(const struct audio_output_plugin *plugin, void *data) } static inline bool +ao_plugin_enable(const struct audio_output_plugin *plugin, void *data, + GError **error_r) +{ + return plugin->enable != NULL + ? plugin->enable(data, error_r) + : true; +} + +static inline void +ao_plugin_disable(const struct audio_output_plugin *plugin, void *data) +{ + if (plugin->disable != NULL) + plugin->disable(data); +} + +static inline bool ao_plugin_open(const struct audio_output_plugin *plugin, void *data, struct audio_format *audio_format, GError **error) @@ -180,6 +219,13 @@ ao_plugin_play(const struct audio_output_plugin *plugin, } static inline void +ao_plugin_drain(const struct audio_output_plugin *plugin, void *data) +{ + if (plugin->drain != NULL) + plugin->drain(data); +} + +static inline void ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data) { if (plugin->cancel != NULL) diff --git a/src/output_print.c b/src/output_print.c index 11e53c32c..9cbf75c9d 100644 --- a/src/output_print.c +++ b/src/output_print.c @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "output_print.h" #include "output_internal.h" #include "output_all.h" diff --git a/src/output_state.c b/src/output_state.c index c7e6c8579..81e3b0120 100644 --- a/src/output_state.c +++ b/src/output_state.c @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "output_state.h" #include "output_internal.h" #include "output_all.h" @@ -34,8 +35,10 @@ #define AUDIO_DEVICE_STATE "audio_device_state:" +unsigned audio_output_state_version; + void -saveAudioDevicesState(FILE *fp) +audio_output_state_save(FILE *fp) { unsigned n = audio_output_count(); @@ -49,35 +52,40 @@ saveAudioDevicesState(FILE *fp) } } -void -readAudioDevicesState(FILE *fp) +bool +audio_output_state_read(const char *line) { - char buffer[1024]; + long value; + char *endptr; + const char *name; + struct audio_output *ao; - while (fgets(buffer, sizeof(buffer), fp)) { - char *c, *name; - struct audio_output *ao; + if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE)) + return false; - g_strchomp(buffer); + line += sizeof(AUDIO_DEVICE_STATE) - 1; - if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE)) - continue; + value = strtol(line, &endptr, 10); + if (*endptr != ':' || (value != 0 && value != 1)) + return false; - c = strchr(buffer, ':'); - if (!c || !(++c)) - goto errline; + if (value != 0) + /* state is "enabled": no-op */ + return true; - name = strchr(c, ':'); - if (!name || !(++name)) - goto errline; + name = endptr + 1; + ao = audio_output_find(name); + if (ao == NULL) { + g_debug("Ignoring device state for '%s'", name); + return true; + } - ao = audio_output_find(name); - if (ao != NULL && atoi(c) == 0) - ao->enabled = false; + ao->enabled = false; + return true; +} - continue; -errline: - /* nonfatal */ - g_warning("invalid line in state_file: %s\n", buffer); - } +unsigned +audio_output_state_get_version(void) +{ + return audio_output_state_version; } diff --git a/src/output_state.h b/src/output_state.h index 8592574ab..3b865f5fe 100644 --- a/src/output_state.h +++ b/src/output_state.h @@ -25,12 +25,21 @@ #ifndef OUTPUT_STATE_H #define OUTPUT_STATE_H +#include <stdbool.h> #include <stdio.h> -void -readAudioDevicesState(FILE *fp); +bool +audio_output_state_read(const char *line); void -saveAudioDevicesState(FILE *fp); +audio_output_state_save(FILE *fp); + +/** + * Generates a version number for the current state of the audio + * outputs. This is used by timer_save_state_file() to determine + * whether the state has changed and the state file should be saved. + */ +unsigned +audio_output_state_get_version(void); #endif diff --git a/src/output_thread.c b/src/output_thread.c index 770b377e8..fccbad5eb 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -17,12 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "output_thread.h" #include "output_api.h" #include "output_internal.h" #include "chunk.h" #include "pipe.h" #include "player_control.h" +#include "filter_plugin.h" +#include "filter/convert_filter_plugin.h" #include <glib.h> @@ -37,27 +40,208 @@ static void ao_command_finished(struct audio_output *ao) { assert(ao->command != AO_COMMAND_NONE); ao->command = AO_COMMAND_NONE; + + g_mutex_unlock(ao->mutex); notify_signal(&audio_output_client_notify); + g_mutex_lock(ao->mutex); +} + +static bool +ao_enable(struct audio_output *ao) +{ + GError *error = NULL; + bool success; + + if (ao->really_enabled) + return true; + + g_mutex_unlock(ao->mutex); + success = ao_plugin_enable(ao->plugin, ao->data, &error); + g_mutex_lock(ao->mutex); + if (!success) { + g_warning("Failed to enable \"%s\" [%s]: %s\n", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return false; + } + + ao->really_enabled = true; + return true; +} + +static void +ao_close(struct audio_output *ao, bool drain); + +static void +ao_disable(struct audio_output *ao) +{ + if (ao->open) + ao_close(ao, false); + + if (ao->really_enabled) { + ao->really_enabled = false; + + g_mutex_unlock(ao->mutex); + ao_plugin_disable(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + } +} + +static void +ao_open(struct audio_output *ao) +{ + bool success; + GError *error = NULL; + const struct audio_format *filter_audio_format; + struct audio_format_string af_string; + + assert(!ao->open); + assert(ao->fail_timer == NULL); + assert(ao->pipe != NULL); + assert(ao->chunk == NULL); + + /* enable the device (just in case the last enable has failed) */ + + if (!ao_enable(ao)) + /* still no luck */ + return; + + /* open the filter */ + + filter_audio_format = filter_open(ao->filter, &ao->in_audio_format, + &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao->fail_timer = g_timer_new(); + return; + } + + ao->out_audio_format = *filter_audio_format; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_audio_format); + + g_mutex_unlock(ao->mutex); + success = ao_plugin_open(ao->plugin, ao->data, + &ao->out_audio_format, + &error); + g_mutex_lock(ao->mutex); + + assert(!ao->open); + + if (!success) { + g_warning("Failed to open \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + filter_close(ao->filter); + ao->fail_timer = g_timer_new(); + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); + + ao->open = true; + + g_debug("opened plugin=%s name=\"%s\" " + "audio_format=%s", + ao->plugin->name, ao->name, + audio_format_to_string(&ao->out_audio_format, &af_string)); + + if (!audio_format_equals(&ao->in_audio_format, + &ao->out_audio_format)) + g_debug("converting from %s", + audio_format_to_string(&ao->in_audio_format, + &af_string)); } static void -ao_close(struct audio_output *ao) +ao_close(struct audio_output *ao, bool drain) { assert(ao->open); ao->pipe = NULL; - g_mutex_lock(ao->mutex); ao->chunk = NULL; ao->open = false; + g_mutex_unlock(ao->mutex); + if (drain) + ao_plugin_drain(ao->plugin, ao->data); + else + ao_plugin_cancel(ao->plugin, ao->data); + ao_plugin_close(ao->plugin, ao->data); - pcm_convert_deinit(&ao->convert_state); + filter_close(ao->filter); + + g_mutex_lock(ao->mutex); g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); } +static void +ao_reopen_filter(struct audio_output *ao) +{ + const struct audio_format *filter_audio_format; + GError *error = NULL; + + filter_close(ao->filter); + filter_audio_format = filter_open(ao->filter, &ao->in_audio_format, + &error); + if (filter_audio_format == NULL) { + g_warning("Failed to open filter for \"%s\" [%s]: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + /* this is a little code duplication fro ao_close(), + but we cannot call this function because we must + not call filter_close(ao->filter) again */ + + ao->pipe = NULL; + + ao->chunk = NULL; + ao->open = false; + ao->fail_timer = g_timer_new(); + + g_mutex_unlock(ao->mutex); + ao_plugin_close(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + + return; + } + + convert_filter_set(ao->convert_filter, &ao->out_audio_format); +} + +static void +ao_reopen(struct audio_output *ao) +{ + if (!audio_format_fully_defined(&ao->config_audio_format)) { + if (ao->open) { + const struct music_pipe *mp = ao->pipe; + ao_close(ao, true); + ao->pipe = mp; + } + + /* no audio format is configured: copy in->out, let + the output's open() method determine the effective + out_audio_format */ + ao->out_audio_format = ao->in_audio_format; + audio_format_mask_apply(&ao->out_audio_format, + &ao->config_audio_format); + } + + if (ao->open) + /* the audio format has changed, and all filters have + to be reconfigured */ + ao_reopen_filter(ao); + else + ao_open(ao); +} + static bool ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) { @@ -65,43 +249,49 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) size_t size = chunk->length; GError *error = NULL; + assert(ao != NULL); + assert(ao->filter != NULL); assert(!music_chunk_is_empty(chunk)); assert(music_chunk_check_format(chunk, &ao->in_audio_format)); assert(size % audio_format_frame_size(&ao->in_audio_format) == 0); - if (chunk->tag != NULL) + if (chunk->tag != NULL) { + g_mutex_unlock(ao->mutex); ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag); + g_mutex_lock(ao->mutex); + } if (size == 0) return true; - if (!audio_format_equals(&ao->in_audio_format, - &ao->out_audio_format)) { - data = pcm_convert(&ao->convert_state, - &ao->in_audio_format, data, size, - &ao->out_audio_format, &size); - - /* under certain circumstances, pcm_convert() may - return an empty buffer - this condition should be - investigated further, but for now, do this check as - a workaround: */ - if (data == NULL) - return true; + data = filter_filter(ao->filter, data, size, &size, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + + ao_close(ao, false); + + /* don't automatically reopen this device for 10 + seconds */ + ao->fail_timer = g_timer_new(); + return false; } while (size > 0 && ao->command == AO_COMMAND_NONE) { size_t nbytes; + g_mutex_unlock(ao->mutex); nbytes = ao_plugin_play(ao->plugin, ao->data, data, size, &error); + g_mutex_lock(ao->mutex); if (nbytes == 0) { /* play()==0 means failure */ g_warning("\"%s\" [%s] failed to play: %s", ao->name, ao->plugin->name, error->message); g_error_free(error); - ao_plugin_cancel(ao->plugin, ao->data); - ao_close(ao); + ao_close(ao, false); /* don't automatically reopen this device for 10 seconds */ @@ -119,32 +309,45 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) return true; } -static void ao_play(struct audio_output *ao) +static const struct music_chunk * +ao_next_chunk(struct audio_output *ao) +{ + return ao->chunk != NULL + /* continue the previous play() call */ + ? ao->chunk->next + /* get the first chunk from the pipe */ + : music_pipe_peek(ao->pipe); +} + +/** + * Plays all remaining chunks, until the tail of the pipe has been + * reached (and no more chunks are queued), or until a command is + * received. + * + * @return true if at least one chunk has been available, false if the + * tail of the pipe was already reached + */ +static bool +ao_play(struct audio_output *ao) { bool success; const struct music_chunk *chunk; assert(ao->pipe != NULL); - g_mutex_lock(ao->mutex); - chunk = ao->chunk; - if (chunk != NULL) - /* continue the previous play() call */ - chunk = chunk->next; - else - chunk = music_pipe_peek(ao->pipe); + chunk = ao_next_chunk(ao); + if (chunk == NULL) + /* no chunk available */ + return false; + ao->chunk_finished = false; while (chunk != NULL && ao->command == AO_COMMAND_NONE) { assert(!ao->chunk_finished); ao->chunk = chunk; - g_mutex_unlock(ao->mutex); success = ao_play_chunk(ao, chunk); - - g_mutex_lock(ao->mutex); - if (!success) { assert(ao->chunk == NULL); break; @@ -155,23 +358,32 @@ static void ao_play(struct audio_output *ao) } ao->chunk_finished = true; + g_mutex_unlock(ao->mutex); + player_lock_signal(); + g_mutex_lock(ao->mutex); - notify_signal(&pc.notify); + return true; } static void ao_pause(struct audio_output *ao) { bool ret; + g_mutex_unlock(ao->mutex); ao_plugin_cancel(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + ao->pause = true; ao_command_finished(ao); do { + g_mutex_unlock(ao->mutex); ret = ao_plugin_pause(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + if (!ret) { - ao_close(ao); + ao_close(ao, false); break; } } while (ao->command == AO_COMMAND_NONE); @@ -182,56 +394,31 @@ static void ao_pause(struct audio_output *ao) static gpointer audio_output_task(gpointer arg) { struct audio_output *ao = arg; - bool ret; - GError *error; + + g_mutex_lock(ao->mutex); while (1) { switch (ao->command) { case AO_COMMAND_NONE: break; - case AO_COMMAND_OPEN: - assert(!ao->open); - assert(ao->fail_timer == NULL); - assert(ao->pipe != NULL); - assert(ao->chunk == NULL); - - error = NULL; - ret = ao_plugin_open(ao->plugin, ao->data, - &ao->out_audio_format, - &error); - - assert(!ao->open); - if (ret) { - pcm_convert_init(&ao->convert_state); + case AO_COMMAND_ENABLE: + ao_enable(ao); + ao_command_finished(ao); + break; - g_mutex_lock(ao->mutex); - ao->open = true; - g_mutex_unlock(ao->mutex); + case AO_COMMAND_DISABLE: + ao_disable(ao); + ao_command_finished(ao); + break; - g_debug("opened plugin=%s name=\"%s\" " - "audio_format=%u:%u:%u", - ao->plugin->name, - ao->name, - ao->out_audio_format.sample_rate, - ao->out_audio_format.bits, - ao->out_audio_format.channels); - - if (!audio_format_equals(&ao->in_audio_format, - &ao->out_audio_format)) - g_debug("converting from %u:%u:%u", - ao->in_audio_format.sample_rate, - ao->in_audio_format.bits, - ao->in_audio_format.channels); - } else { - g_warning("Failed to open \"%s\" [%s]: %s", - ao->name, ao->plugin->name, - error->message); - g_error_free(error); - - ao->fail_timer = g_timer_new(); - } + case AO_COMMAND_OPEN: + ao_open(ao); + ao_command_finished(ao); + break; + case AO_COMMAND_REOPEN: + ao_reopen(ao); ao_command_finished(ao); break; @@ -239,11 +426,7 @@ static gpointer audio_output_task(gpointer arg) assert(ao->open); assert(ao->pipe != NULL); - ao->pipe = NULL; - ao->chunk = NULL; - - ao_plugin_cancel(ao->plugin, ao->data); - ao_close(ao); + ao_close(ao, false); ao_command_finished(ao); break; @@ -264,6 +447,19 @@ static gpointer audio_output_task(gpointer arg) the new command first */ continue; + case AO_COMMAND_DRAIN: + if (ao->open) { + assert(ao->chunk == NULL); + assert(music_pipe_peek(ao->pipe) == NULL); + + g_mutex_unlock(ao->mutex); + ao_plugin_drain(ao->plugin, ao->data); + g_mutex_lock(ao->mutex); + } + + ao_command_finished(ao); + continue; + case AO_COMMAND_CANCEL: ao->chunk = NULL; if (ao->open) @@ -273,19 +469,24 @@ static gpointer audio_output_task(gpointer arg) /* the player thread will now clear our music pipe - wait for a notify, to give it some time */ - notify_wait(&ao->notify); + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); continue; case AO_COMMAND_KILL: ao->chunk = NULL; ao_command_finished(ao); + g_mutex_unlock(ao->mutex); return NULL; } - if (ao->open) - ao_play(ao); + if (ao->open && ao_play(ao)) + /* don't wait for an event if there are more + chunks in the pipe */ + continue; - notify_wait(&ao->notify); + if (ao->command == AO_COMMAND_NONE) + g_cond_wait(ao->cond, ao->mutex); } } diff --git a/src/page.c b/src/page.c index 5ea03cd02..537137697 100644 --- a/src/page.c +++ b/src/page.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "page.h" #include <glib.h> diff --git a/src/path.c b/src/path.c index fc73ee7c9..62732fcb4 100644 --- a/src/path.c +++ b/src/path.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "path.h" #include "conf.h" diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c new file mode 100644 index 000000000..5bd23398d --- /dev/null +++ b/src/pcm_byteswap.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "pcm_byteswap.h" +#include "pcm_buffer.h" + +#include <glib.h> + +#include <assert.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "pcm" + +static inline uint16_t swab16(uint16_t x) +{ + return (x << 8) | (x >> 8); +} + +const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer, + const int16_t *src, size_t len) +{ + unsigned i; + int16_t *buf = pcm_buffer_get(buffer, len); + + assert(buf != NULL); + + for (i = 0; i < len / 2; i++) + buf[i] = swab16(src[i]); + + return buf; +} + +static inline uint32_t swab32(uint32_t x) +{ + return (x << 24) | + ((x & 0xff00) << 8) | + ((x & 0xff0000) >> 8) | + (x >> 24); +} + +const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer, + const int32_t *src, size_t len) +{ + unsigned i; + int32_t *buf = pcm_buffer_get(buffer, len); + + assert(buf != NULL); + + for (i = 0; i < len / 4; i++) + buf[i] = swab32(src[i]); + + return buf; +} diff --git a/src/pcm_byteswap.h b/src/pcm_byteswap.h new file mode 100644 index 000000000..e1196d9b2 --- /dev/null +++ b/src/pcm_byteswap.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PCM_BYTESWAP_H +#define MPD_PCM_BYTESWAP_H + +#include <stdint.h> +#include <stddef.h> + +struct pcm_buffer; + +/** + * Changes the endianness of 16 bit PCM data. + * + * @param buffer the destination pcm_buffer object + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @return the destination buffer + */ +const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer, + const int16_t *src, size_t len); + +/** + * Changes the endianness of 32-bit (or 24-bit) PCM data. + * + * @param buffer the destination pcm_buffer object + * @param src the source PCM buffer + * @param src_size the number of bytes in #src + * @return the destination buffer + */ +const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer, + const int32_t *src, size_t len); + +#endif diff --git a/src/pcm_channels.c b/src/pcm_channels.c index 969ddff32..d82e46a67 100644 --- a/src/pcm_channels.c +++ b/src/pcm_channels.c @@ -17,16 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_channels.h" #include "pcm_buffer.h" -#include <glib.h> - #include <assert.h> -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "pcm" - static void pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src, unsigned num_frames) @@ -75,8 +71,8 @@ pcm_convert_channels_16_n_to_2(int16_t *dest, const int16_t * pcm_convert_channels_16(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int16_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int16_t *src, size_t src_size, size_t *dest_size_r) { unsigned num_frames = src_size / src_channels / sizeof(*src); @@ -92,11 +88,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer, else if (dest_channels == 2) pcm_convert_channels_16_n_to_2(dest, src_channels, src, num_frames); - else { - g_warning("conversion %u->%u channels is not supported", - src_channels, dest_channels); + else return NULL; - } return dest; } @@ -149,8 +142,8 @@ pcm_convert_channels_24_n_to_2(int32_t *dest, const int32_t * pcm_convert_channels_24(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r) { unsigned num_frames = src_size / src_channels / sizeof(*src); @@ -166,11 +159,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer, else if (dest_channels == 2) pcm_convert_channels_24_n_to_2(dest, src_channels, src, num_frames); - else { - g_warning("conversion %u->%u channels is not supported", - src_channels, dest_channels); + else return NULL; - } return dest; } @@ -218,8 +208,8 @@ pcm_convert_channels_32_n_to_2(int32_t *dest, const int32_t * pcm_convert_channels_32(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r) { unsigned num_frames = src_size / src_channels / sizeof(*src); @@ -235,11 +225,8 @@ pcm_convert_channels_32(struct pcm_buffer *buffer, else if (dest_channels == 2) pcm_convert_channels_32_n_to_2(dest, src_channels, src, num_frames); - else { - g_warning("conversion %u->%u channels is not supported", - src_channels, dest_channels); + else return NULL; - } return dest; } diff --git a/src/pcm_channels.h b/src/pcm_channels.h index accf4b07b..13b7ffbf3 100644 --- a/src/pcm_channels.h +++ b/src/pcm_channels.h @@ -38,8 +38,8 @@ struct pcm_buffer; */ const int16_t * pcm_convert_channels_16(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int16_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int16_t *src, size_t src_size, size_t *dest_size_r); /** @@ -56,8 +56,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer, */ const int32_t * pcm_convert_channels_24(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r); /** @@ -73,8 +73,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer, */ const int32_t * pcm_convert_channels_32(struct pcm_buffer *buffer, - int8_t dest_channels, - int8_t src_channels, const int32_t *src, + uint8_t dest_channels, + uint8_t src_channels, const int32_t *src, size_t src_size, size_t *dest_size_r); #endif diff --git a/src/pcm_convert.c b/src/pcm_convert.c index ebb4adff5..8d529dd5f 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -17,9 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_convert.h" #include "pcm_channels.h" #include "pcm_format.h" +#include "pcm_byteswap.h" #include "audio_format.h" #include <assert.h> @@ -39,6 +41,7 @@ void pcm_convert_init(struct pcm_convert_state *state) pcm_buffer_init(&state->format_buffer); pcm_buffer_init(&state->channels_buffer); + pcm_buffer_init(&state->byteswap_buffer); } void pcm_convert_deinit(struct pcm_convert_state *state) @@ -47,41 +50,60 @@ void pcm_convert_deinit(struct pcm_convert_state *state) pcm_buffer_deinit(&state->format_buffer); pcm_buffer_deinit(&state->channels_buffer); + pcm_buffer_deinit(&state->byteswap_buffer); } static const int16_t * pcm_convert_16(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r) + const struct audio_format *dest_format, size_t *dest_size_r, + GError **error_r) { const int16_t *buf; size_t len; - assert(dest_format->bits == 16); + assert(dest_format->format == SAMPLE_FORMAT_S16); buf = pcm_convert_to_16(&state->format_buffer, &state->dither, - src_format->bits, src_buffer, src_size, + src_format->format, src_buffer, src_size, &len); - if (!buf) - g_error("pcm_convert_to_16() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 16 bit is not implemented", + sample_format_to_string(src_format->format)); + return NULL; + } if (src_format->channels != dest_format->channels) { buf = pcm_convert_channels_16(&state->channels_buffer, dest_format->channels, src_format->channels, buf, len, &len); - if (!buf) - g_error("pcm_convert_channels_16() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } } - if (src_format->sample_rate != dest_format->sample_rate) + if (src_format->sample_rate != dest_format->sample_rate) { buf = pcm_resample_16(&state->resample, dest_format->channels, src_format->sample_rate, buf, len, - dest_format->sample_rate, - &len); + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return NULL; + } + + if (dest_format->reverse_endian) { + buf = pcm_byteswap_16(&state->byteswap_buffer, buf, len); + assert(buf != NULL); + } *dest_size_r = len; return buf; @@ -91,34 +113,52 @@ static const int32_t * pcm_convert_24(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r) + const struct audio_format *dest_format, size_t *dest_size_r, + GError **error_r) { const int32_t *buf; size_t len; - assert(dest_format->bits == 24); + assert(dest_format->format == SAMPLE_FORMAT_S24_P32); - buf = pcm_convert_to_24(&state->format_buffer, src_format->bits, + buf = pcm_convert_to_24(&state->format_buffer, src_format->format, src_buffer, src_size, &len); - if (!buf) - g_error("pcm_convert_to_24() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 24 bit is not implemented", + sample_format_to_string(src_format->format)); + return NULL; + } if (src_format->channels != dest_format->channels) { buf = pcm_convert_channels_24(&state->channels_buffer, dest_format->channels, src_format->channels, buf, len, &len); - if (!buf) - g_error("pcm_convert_channels_24() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } } - if (src_format->sample_rate != dest_format->sample_rate) + if (src_format->sample_rate != dest_format->sample_rate) { buf = pcm_resample_24(&state->resample, dest_format->channels, src_format->sample_rate, buf, len, - dest_format->sample_rate, - &len); + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return NULL; + } + + if (dest_format->reverse_endian) { + buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len); + assert(buf != NULL); + } *dest_size_r = len; return buf; @@ -128,34 +168,52 @@ static const int32_t * pcm_convert_32(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src_buffer, size_t src_size, - const struct audio_format *dest_format, - size_t *dest_size_r) + const struct audio_format *dest_format, size_t *dest_size_r, + GError **error_r) { const int32_t *buf; size_t len; - assert(dest_format->bits == 32); + assert(dest_format->format == SAMPLE_FORMAT_S32); - buf = pcm_convert_to_32(&state->format_buffer, src_format->bits, + buf = pcm_convert_to_32(&state->format_buffer, src_format->format, src_buffer, src_size, &len); - if (!buf) - g_error("pcm_convert_to_32() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %s to 24 bit is not implemented", + sample_format_to_string(src_format->format)); + return NULL; + } if (src_format->channels != dest_format->channels) { buf = pcm_convert_channels_32(&state->channels_buffer, dest_format->channels, src_format->channels, buf, len, &len); - if (!buf) - g_error("pcm_convert_channels_32() failed"); + if (buf == NULL) { + g_set_error(error_r, pcm_convert_quark(), 0, + "Conversion from %u to %u channels " + "is not implemented", + src_format->channels, + dest_format->channels); + return NULL; + } } - if (src_format->sample_rate != dest_format->sample_rate) + if (src_format->sample_rate != dest_format->sample_rate) { buf = pcm_resample_32(&state->resample, dest_format->channels, src_format->sample_rate, buf, len, - dest_format->sample_rate, - &len); + dest_format->sample_rate, &len, + error_r); + if (buf == NULL) + return buf; + } + + if (dest_format->reverse_endian) { + buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len); + assert(buf != NULL); + } *dest_size_r = len; return buf; @@ -166,26 +224,32 @@ pcm_convert(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src, size_t src_size, const struct audio_format *dest_format, - size_t *dest_size_r) + size_t *dest_size_r, + GError **error_r) { - switch (dest_format->bits) { - case 16: + switch (dest_format->format) { + case SAMPLE_FORMAT_S16: return pcm_convert_16(state, src_format, src, src_size, - dest_format, dest_size_r); + dest_format, dest_size_r, + error_r); - case 24: + case SAMPLE_FORMAT_S24_P32: return pcm_convert_24(state, src_format, src, src_size, - dest_format, dest_size_r); + dest_format, dest_size_r, + error_r); - case 32: + case SAMPLE_FORMAT_S32: return pcm_convert_32(state, src_format, src, src_size, - dest_format, dest_size_r); + dest_format, dest_size_r, + error_r); default: - g_error("cannot convert to %u bit\n", dest_format->bits); + g_set_error(error_r, pcm_convert_quark(), 0, + "PCM conversion to %s is not implemented", + sample_format_to_string(dest_format->format)); return NULL; } } diff --git a/src/pcm_convert.h b/src/pcm_convert.h index be08ad8a8..7ef0782df 100644 --- a/src/pcm_convert.h +++ b/src/pcm_convert.h @@ -41,8 +41,17 @@ struct pcm_convert_state { /** the buffer for converting the channel count */ struct pcm_buffer channels_buffer; + + /** the buffer for swapping the byte order */ + struct pcm_buffer byteswap_buffer; }; +static inline GQuark +pcm_convert_quark(void) +{ + return g_quark_from_static_string("pcm_convert"); +} + /** * Initializes a pcm_convert_state object. */ @@ -63,13 +72,16 @@ void pcm_convert_deinit(struct pcm_convert_state *state); * @param src_size the size of #src in bytes * @param dest_format the requested destination audio format * @param dest_size_r returns the number of bytes of the destination buffer - * @return the destination buffer + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return the destination buffer, or NULL on error */ const void * pcm_convert(struct pcm_convert_state *state, const struct audio_format *src_format, const void *src, size_t src_size, const struct audio_format *dest_format, - size_t *dest_size_r); + size_t *dest_size_r, + GError **error_r); #endif diff --git a/src/pcm_dither.c b/src/pcm_dither.c index 45c11790c..0d1c7e004 100644 --- a/src/pcm_dither.c +++ b/src/pcm_dither.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_dither.h" #include "pcm_prng.h" diff --git a/src/pcm_format.c b/src/pcm_format.c index 0e686e17c..b0dad2ba3 100644 --- a/src/pcm_format.c +++ b/src/pcm_format.c @@ -17,12 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_format.h" #include "pcm_dither.h" #include "pcm_buffer.h" -#include <glib.h> - static void pcm_convert_8_to_16(int16_t *out, const int8_t *in, unsigned num_samples) @@ -51,14 +50,17 @@ pcm_convert_32_to_16(struct pcm_dither *dither, const int16_t * pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { unsigned num_samples; int16_t *dest; - switch (bits) { - case 8: + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + break; + + case SAMPLE_FORMAT_S8: num_samples = src_size; *dest_size_r = src_size * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -68,11 +70,11 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, num_samples); return dest; - case 16: + case SAMPLE_FORMAT_S16: *dest_size_r = src_size; return src; - case 24: + case SAMPLE_FORMAT_S24_P32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -82,7 +84,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, num_samples); return dest; - case 32: + case SAMPLE_FORMAT_S32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -93,7 +95,6 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, return dest; } - g_warning("only 8 or 16 bits are supported for conversion!\n"); return NULL; } @@ -129,14 +130,17 @@ pcm_convert_32_to_24(int32_t *out, const int16_t *in, const int32_t * pcm_convert_to_24(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { unsigned num_samples; int32_t *dest; - switch (bits) { - case 8: + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + break; + + case SAMPLE_FORMAT_S8: num_samples = src_size; *dest_size_r = src_size * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -145,7 +149,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer, num_samples); return dest; - case 16: + case SAMPLE_FORMAT_S16: num_samples = src_size / 2; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -154,11 +158,11 @@ pcm_convert_to_24(struct pcm_buffer *buffer, num_samples); return dest; - case 24: + case SAMPLE_FORMAT_S24_P32: *dest_size_r = src_size; return src; - case 32: + case SAMPLE_FORMAT_S32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -168,7 +172,6 @@ pcm_convert_to_24(struct pcm_buffer *buffer, return dest; } - g_warning("only 8 or 24 bits are supported for conversion!\n"); return NULL; } @@ -204,14 +207,17 @@ pcm_convert_24_to_32(int32_t *out, const int32_t *in, const int32_t * pcm_convert_to_32(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r) { unsigned num_samples; int32_t *dest; - switch (bits) { - case 8: + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + break; + + case SAMPLE_FORMAT_S8: num_samples = src_size; *dest_size_r = src_size * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -220,7 +226,7 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; - case 16: + case SAMPLE_FORMAT_S16: num_samples = src_size / 2; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -229,7 +235,7 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; - case 24: + case SAMPLE_FORMAT_S24_P32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); dest = pcm_buffer_get(buffer, *dest_size_r); @@ -238,11 +244,10 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; - case 32: + case SAMPLE_FORMAT_S32: *dest_size_r = src_size; return src; } - g_warning("only 8 or 32 bits are supported for conversion!\n"); return NULL; } diff --git a/src/pcm_format.h b/src/pcm_format.h index 350566827..6ea5573bd 100644 --- a/src/pcm_format.h +++ b/src/pcm_format.h @@ -20,6 +20,8 @@ #ifndef PCM_FORMAT_H #define PCM_FORMAT_H +#include "audio_format.h" + #include <stdint.h> #include <stddef.h> @@ -40,7 +42,7 @@ struct pcm_dither; */ const int16_t * pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); /** @@ -55,7 +57,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, */ const int32_t * pcm_convert_to_24(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); /** @@ -70,7 +72,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer, */ const int32_t * pcm_convert_to_32(struct pcm_buffer *buffer, - uint8_t bits, const void *src, + enum sample_format src_format, const void *src, size_t src_size, size_t *dest_size_r); #endif diff --git a/src/pcm_mix.c b/src/pcm_mix.c index d1e716731..f5a8eb90e 100644 --- a/src/pcm_mix.c +++ b/src/pcm_mix.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_mix.h" #include "pcm_volume.h" #include "pcm_utils.h" @@ -81,29 +82,53 @@ pcm_add_24(int32_t *buffer1, const int32_t *buffer2, } static void +pcm_add_32(int32_t *buffer1, const int32_t *buffer2, + unsigned num_samples, unsigned volume1, unsigned volume2) +{ + while (num_samples > 0) { + int64_t sample1 = *buffer1; + int64_t sample2 = *buffer2++; + + sample1 = ((sample1 * volume1 + sample2 * volume2) + + pcm_volume_dither() + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + + *buffer1++ = pcm_range_64(sample1, 32); + --num_samples; + } +} + +static void pcm_add(void *buffer1, const void *buffer2, size_t size, int vol1, int vol2, const struct audio_format *format) { - switch (format->bits) { - case 8: + switch (format->format) { + case SAMPLE_FORMAT_S8: pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size, vol1, vol2); break; - case 16: + case SAMPLE_FORMAT_S16: pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2, vol1, vol2); break; - case 24: + case SAMPLE_FORMAT_S24_P32: pcm_add_24((int32_t*)buffer1, (const int32_t*)buffer2, size / 4, vol1, vol2); break; + case SAMPLE_FORMAT_S32: + pcm_add_32((int32_t*)buffer1, + (const int32_t*)buffer2, + size / 4, vol1, vol2); + break; + default: - g_error("%u bits not supported by pcm_add!\n", format->bits); + g_error("format %s not supported by pcm_add", + sample_format_to_string(format->format)); } } diff --git a/src/pcm_resample.c b/src/pcm_resample.c index d1360d02a..fea499e07 100644 --- a/src/pcm_resample.c +++ b/src/pcm_resample.c @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "pcm_resample_internal.h" #include "config.h" +#include "pcm_resample_internal.h" #ifdef HAVE_LIBSAMPLERATE #include "conf.h" @@ -62,16 +62,18 @@ void pcm_resample_deinit(struct pcm_resample_state *state) const int16_t * pcm_resample_16(struct pcm_resample_state *state, uint8_t channels, - unsigned src_rate, - const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned src_rate, const int16_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { #ifdef HAVE_LIBSAMPLERATE if (pcm_resample_lsr_enabled()) return pcm_resample_lsr_16(state, channels, src_rate, src_buffer, src_size, - dest_rate, dest_size_r); + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; #endif return pcm_resample_fallback_16(state, channels, @@ -82,16 +84,18 @@ pcm_resample_16(struct pcm_resample_state *state, const int32_t * pcm_resample_32(struct pcm_resample_state *state, uint8_t channels, - unsigned src_rate, - const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned src_rate, const int32_t *src_buffer, size_t src_size, + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { #ifdef HAVE_LIBSAMPLERATE if (pcm_resample_lsr_enabled()) return pcm_resample_lsr_32(state, channels, src_rate, src_buffer, src_size, - dest_rate, dest_size_r); + dest_rate, dest_size_r, + error_r); +#else + (void)error_r; #endif return pcm_resample_fallback_32(state, channels, diff --git a/src/pcm_resample.h b/src/pcm_resample.h index 44720f7b2..a17b12d8b 100644 --- a/src/pcm_resample.h +++ b/src/pcm_resample.h @@ -20,8 +20,8 @@ #ifndef MPD_PCM_RESAMPLE_H #define MPD_PCM_RESAMPLE_H +#include "check.h" #include "pcm_buffer.h" -#include "config.h" #include <stdint.h> #include <stddef.h> @@ -48,7 +48,7 @@ struct pcm_resample_state { uint8_t channels; } prev; - bool error; + int error; #endif struct pcm_buffer buffer; @@ -82,8 +82,8 @@ pcm_resample_16(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); /** * Resamples 32 bit PCM data. @@ -102,8 +102,8 @@ pcm_resample_32(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); /** * Resamples 24 bit PCM data. @@ -122,14 +122,14 @@ pcm_resample_24(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { /* reuse the 32 bit code - the resampler code doesn't care if the upper 8 bits are actually used */ return pcm_resample_32(state, channels, src_rate, src_buffer, src_size, - dest_rate, dest_size_r); + dest_rate, dest_size_r, error_r); } #endif diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c index 36af51ad0..fcc97d9cd 100644 --- a/src/pcm_resample_fallback.c +++ b/src/pcm_resample_fallback.c @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_resample_internal.h" #include <assert.h> -#include <glib.h> void pcm_resample_fallback_deinit(struct pcm_resample_state *state) @@ -74,8 +74,7 @@ const int32_t * pcm_resample_fallback_32(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, - const int32_t *src_buffer, - G_GNUC_UNUSED size_t src_size, + const int32_t *src_buffer, size_t src_size, unsigned dest_rate, size_t *dest_size_r) { diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h index a10ba08cd..cdb3c9a2f 100644 --- a/src/pcm_resample_internal.h +++ b/src/pcm_resample_internal.h @@ -27,8 +27,8 @@ #ifndef MPD_PCM_RESAMPLE_INTERNAL_H #define MPD_PCM_RESAMPLE_INTERNAL_H +#include "check.h" #include "pcm_resample.h" -#include "config.h" #ifdef HAVE_LIBSAMPLERATE @@ -40,8 +40,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); const int32_t * pcm_resample_lsr_32(struct pcm_resample_state *state, @@ -49,8 +49,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, unsigned src_rate, const int32_t *src_buffer, G_GNUC_UNUSED size_t src_size, - unsigned dest_rate, - size_t *dest_size_r); + unsigned dest_rate, size_t *dest_size_r, + GError **error_r); #endif diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c index 6d019e892..71b76b86d 100644 --- a/src/pcm_resample_libsamplerate.c +++ b/src/pcm_resample_libsamplerate.c @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_resample_internal.h" #include "conf.h" -#include "config.h" #include <glib.h> @@ -30,6 +30,12 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "pcm" +static inline GQuark +libsamplerate_quark(void) +{ + return g_quark_from_static_string("libsamplerate"); +} + void pcm_resample_lsr_deinit(struct pcm_resample_state *state) { @@ -77,9 +83,10 @@ out: return convalgo; } -static void +static bool pcm_resample_set(struct pcm_resample_state *state, - uint8_t channels, unsigned src_rate, unsigned dest_rate) + uint8_t channels, unsigned src_rate, unsigned dest_rate, + GError **error_r) { static int convalgo = -1; int error; @@ -92,9 +99,9 @@ pcm_resample_set(struct pcm_resample_state *state, if (channels == state->prev.channels && src_rate == state->prev.src_rate && dest_rate == state->prev.dest_rate) - return; + return true; - state->error = false; + state->error = 0; state->prev.channels = channels; state->prev.src_rate = src_rate; state->prev.dest_rate = dest_rate; @@ -104,16 +111,18 @@ pcm_resample_set(struct pcm_resample_state *state, state->state = src_new(convalgo, channels, &error); if (!state->state) { - g_warning("cannot create new libsamplerate state: %s", - src_strerror(error)); - state->error = true; - return; + g_set_error(error_r, libsamplerate_quark(), state->error, + "libsamplerate initialization has failed: %s", + src_strerror(error)); + return false; } data->src_ratio = (double)dest_rate / (double)src_rate; g_debug("setting samplerate conversion ratio to %.2lf", data->src_ratio); src_set_ratio(state->state, data->src_ratio); + + return true; } const int16_t * @@ -121,9 +130,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int16_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { + bool success; SRC_DATA *data = &state->data; size_t data_in_size; size_t data_out_size; @@ -132,11 +142,18 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - pcm_resample_set(state, channels, src_rate, dest_rate); + success = pcm_resample_set(state, channels, src_rate, dest_rate, + error_r); + if (!success) + return NULL; /* there was an error previously, and nothing has changed */ - if (state->error) + if (state->error) { + g_set_error(error_r, libsamplerate_quark(), state->error, + "libsamplerate has failed: %s", + src_strerror(state->error)); return NULL; + } data->input_frames = src_size / sizeof(*src_buffer) / channels; data_in_size = data->input_frames * sizeof(float) * channels; @@ -151,9 +168,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state, error = src_process(state->state, data); if (error) { - g_warning("error processing samples with libsamplerate: %s", - src_strerror(error)); - state->error = true; + g_set_error(error_r, libsamplerate_quark(), error, + "libsamplerate has failed: %s", + src_strerror(error)); + state->error = error; return NULL; } @@ -191,9 +209,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, uint8_t channels, unsigned src_rate, const int32_t *src_buffer, size_t src_size, - unsigned dest_rate, - size_t *dest_size_r) + unsigned dest_rate, size_t *dest_size_r, + GError **error_r) { + bool success; SRC_DATA *data = &state->data; size_t data_in_size; size_t data_out_size; @@ -202,11 +221,18 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, assert((src_size % (sizeof(*src_buffer) * channels)) == 0); - pcm_resample_set(state, channels, src_rate, dest_rate); + success = pcm_resample_set(state, channels, src_rate, dest_rate, + error_r); + if (!success) + return NULL; /* there was an error previously, and nothing has changed */ - if (state->error) + if (state->error) { + g_set_error(error_r, libsamplerate_quark(), state->error, + "libsamplerate has failed: %s", + src_strerror(state->error)); return NULL; + } data->input_frames = src_size / sizeof(*src_buffer) / channels; data_in_size = data->input_frames * sizeof(float) * channels; @@ -221,9 +247,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state, error = src_process(state->state, data); if (error) { - g_warning("error processing samples with libsamplerate: %s", - src_strerror(error)); - state->error = true; + g_set_error(error_r, libsamplerate_quark(), error, + "libsamplerate has failed: %s", + src_strerror(error)); + state->error = error; return NULL; } diff --git a/src/pcm_utils.h b/src/pcm_utils.h index 93f414231..8b2259655 100644 --- a/src/pcm_utils.h +++ b/src/pcm_utils.h @@ -38,4 +38,18 @@ pcm_range(int32_t sample, unsigned bits) return sample; } +/** + * Check if the value is within the range of the provided bit size, + * and caps it if necessary. + */ +static inline int64_t +pcm_range_64(int64_t sample, unsigned bits) +{ + if (G_UNLIKELY(sample < ((int64_t)-1 << (bits - 1)))) + return (int64_t)-1 << (bits - 1); + if (G_UNLIKELY(sample >= ((int64_t)1 << (bits - 1)))) + return ((int64_t)1 << (bits - 1)) - 1; + return sample; +} + #endif diff --git a/src/pcm_volume.c b/src/pcm_volume.c index 2a94c1890..34981118d 100644 --- a/src/pcm_volume.c +++ b/src/pcm_volume.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pcm_volume.h" #include "pcm_utils.h" #include "audio_format.h" @@ -113,6 +114,29 @@ pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume) } } +static void +pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume) +{ + while (num_samples > 0) { +#ifdef __i386__ + /* assembly version for i386 */ + int32_t sample = *buffer; + + *buffer++ = pcm_volume_sample_24(sample, volume, 0); +#else + /* portable version */ + int64_t sample = *buffer; + + sample = (sample * volume + pcm_volume_dither() + + PCM_VOLUME_1 / 2) + / PCM_VOLUME_1; + *buffer++ = pcm_range_64(sample, 32); +#endif + + --num_samples; + } +} + bool pcm_volume(void *buffer, int length, const struct audio_format *format, @@ -126,21 +150,26 @@ pcm_volume(void *buffer, int length, return true; } - switch (format->bits) { - case 8: + switch (format->format) { + case SAMPLE_FORMAT_S8: pcm_volume_change_8((int8_t *)buffer, length, volume); return true; - case 16: + case SAMPLE_FORMAT_S16: pcm_volume_change_16((int16_t *)buffer, length / 2, volume); return true; - case 24: + case SAMPLE_FORMAT_S24_P32: pcm_volume_change_24((int32_t*)buffer, length / 4, volume); return true; + case SAMPLE_FORMAT_S32: + pcm_volume_change_32((int32_t*)buffer, length / 4, + volume); + return true; + default: return false; } diff --git a/src/permission.c b/src/permission.c index 7df4e27fc..94aca70cf 100644 --- a/src/permission.c +++ b/src/permission.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "permission.h" #include "conf.h" @@ -111,7 +112,7 @@ void initPermissions(void) permission_default = parsePermissions(param->value); } -int getPermissionFromPassword(char *password, unsigned *permission) +int getPermissionFromPassword(char const* password, unsigned* permission) { bool found; gpointer key, value; diff --git a/src/permission.h b/src/permission.h index bad26aa3c..2a5d999ce 100644 --- a/src/permission.h +++ b/src/permission.h @@ -27,7 +27,7 @@ #define PERMISSION_ADMIN 8 -int getPermissionFromPassword(char *password, unsigned *permission); +int getPermissionFromPassword(char const* password, unsigned* permission); void finishPermissions(void); diff --git a/src/pipe.c b/src/pipe.c index c9f0d159c..7cce06075 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "pipe.h" #include "buffer.h" #include "chunk.h" diff --git a/src/player_control.c b/src/player_control.c index ac4b006dd..b7e802b9d 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "player_control.h" +#include "decoder_control.h" #include "path.h" #include "log.h" #include "tag.h" @@ -35,17 +37,28 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play) { pc.buffer_chunks = buffer_chunks; pc.buffered_before_play = buffered_before_play; - notify_init(&pc.notify); + + pc.mutex = g_mutex_new(); + pc.cond = g_cond_new(); + pc.command = PLAYER_COMMAND_NONE; pc.error = PLAYER_ERROR_NOERROR; pc.state = PLAYER_STATE_STOP; pc.cross_fade_seconds = 0; - pc.software_volume = PCM_VOLUME_1; } void pc_deinit(void) { - notify_deinit(&pc.notify); + g_cond_free(pc.cond); + g_mutex_free(pc.mutex); +} + +void +player_wait_decoder(struct decoder_control *dc) +{ + /* during this function, the decoder lock is held, because + we're waiting for the decoder thread */ + g_cond_wait(pc.cond, dc->mutex); } void @@ -57,27 +70,44 @@ pc_song_deleted(const struct song *song) } } -static void player_command(enum player_command cmd) +static void +player_command_wait_locked(void) +{ + while (pc.command != PLAYER_COMMAND_NONE) + g_cond_wait(main_cond, pc.mutex); +} + +static void +player_command_locked(enum player_command cmd) { assert(pc.command == PLAYER_COMMAND_NONE); pc.command = cmd; - while (pc.command != PLAYER_COMMAND_NONE) { - notify_signal(&pc.notify); - notify_wait(&main_notify); - } + player_signal(); + player_command_wait_locked(); +} + +static void +player_command(enum player_command cmd) +{ + player_lock(); + player_command_locked(cmd); + player_unlock(); } void -playerPlay(struct song *song) +pc_play(struct song *song) { assert(song != NULL); if (pc.state != PLAYER_STATE_STOP) player_command(PLAYER_COMMAND_STOP); - pc.next_song = song; - player_command(PLAYER_COMMAND_PLAY); + assert(pc.next_song == NULL); + + pc_enqueue_song(song); + + assert(pc.next_song == NULL); idle_add(IDLE_PLAYER); } @@ -85,16 +115,26 @@ playerPlay(struct song *song) void pc_cancel(void) { player_command(PLAYER_COMMAND_CANCEL); + assert(pc.next_song == NULL); } -void playerWait(void) +void +pc_stop(void) { player_command(PLAYER_COMMAND_CLOSE_AUDIO); + assert(pc.next_song == NULL); idle_add(IDLE_PLAYER); } -void playerKill(void) +void +pc_update_audio(void) +{ + player_command(PLAYER_COMMAND_UPDATE_AUDIO); +} + +void +pc_kill(void) { assert(pc.thread != NULL); @@ -105,7 +145,8 @@ void playerKill(void) idle_add(IDLE_PLAYER); } -void playerPause(void) +void +pc_pause(void) { if (pc.state != PLAYER_STATE_STOP) { player_command(PLAYER_COMMAND_PAUSE); @@ -113,7 +154,8 @@ void playerPause(void) } } -void playerSetPause(int pause_flag) +void +pc_set_pause(bool pause_flag) { switch (pc.state) { case PLAYER_STATE_STOP: @@ -121,41 +163,49 @@ void playerSetPause(int pause_flag) case PLAYER_STATE_PLAY: if (pause_flag) - playerPause(); + pc_pause(); break; + case PLAYER_STATE_PAUSE: if (!pause_flag) - playerPause(); + pc_pause(); break; } } -int getPlayerElapsedTime(void) +void +pc_get_status(struct player_status *status) { - return (int)(pc.elapsed_time + 0.5); -} + player_lock(); + player_command_locked(PLAYER_COMMAND_REFRESH); -unsigned long getPlayerBitRate(void) -{ - return pc.bit_rate; -} + status->state = pc.state; -int getPlayerTotalTime(void) -{ - return (int)(pc.total_time + 0.5); + if (pc.state != PLAYER_STATE_STOP) { + status->bit_rate = pc.bit_rate; + status->audio_format = pc.audio_format; + status->total_time = pc.total_time; + status->elapsed_time = pc.elapsed_time; + } + + player_unlock(); } -enum player_state getPlayerState(void) +enum player_state +pc_get_state(void) { return pc.state; } -void clearPlayerError(void) +void +pc_clear_error(void) { - pc.error = 0; + pc.error = PLAYER_ERROR_NOERROR; + pc.errored_song = NULL; } -enum player_error getPlayerError(void) +enum player_error +pc_get_error(void) { return pc.error; } @@ -166,58 +216,56 @@ pc_errored_song_uri(void) return song_get_uri(pc.errored_song); } -char *getPlayerErrorStr(void) +char * +pc_get_error_message(void) { - /* static OK here, only one user in main task */ - static char error[MPD_PATH_MAX + 64]; /* still too much */ - static const size_t errorlen = sizeof(error); + char *error; char *uri; - *error = '\0'; /* likely */ - switch (pc.error) { case PLAYER_ERROR_NOERROR: - break; + return NULL; case PLAYER_ERROR_FILENOTFOUND: uri = pc_errored_song_uri(); - snprintf(error, errorlen, - "file \"%s\" does not exist or is inaccessible", uri); + error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri); g_free(uri); - break; + return error; case PLAYER_ERROR_FILE: uri = pc_errored_song_uri(); - snprintf(error, errorlen, "problems decoding \"%s\"", uri); + error = g_strdup_printf("problems decoding \"%s\"", uri); g_free(uri); - break; + return error; case PLAYER_ERROR_AUDIO: - strcpy(error, "problems opening audio device"); - break; + return g_strdup("problems opening audio device"); case PLAYER_ERROR_SYSTEM: - strcpy(error, "system error occured"); - break; + return g_strdup("system error occured"); case PLAYER_ERROR_UNKTYPE: uri = pc_errored_song_uri(); - snprintf(error, errorlen, - "file type of \"%s\" is unknown", uri); + error = g_strdup_printf("file type of \"%s\" is unknown", uri); g_free(uri); - break; + return error; } - return *error ? error : NULL; + + assert(false); + return NULL; } void -queueSong(struct song *song) +pc_enqueue_song(struct song *song) { assert(song != NULL); + + player_lock(); assert(pc.next_song == NULL); pc.next_song = song; - player_command(PLAYER_COMMAND_QUEUE); + player_command_locked(PLAYER_COMMAND_QUEUE); + player_unlock(); } bool @@ -228,9 +276,11 @@ pc_seek(struct song *song, float seek_time) if (pc.state == PLAYER_STATE_STOP) return false; + player_lock(); pc.next_song = song; pc.seek_where = seek_time; - player_command(PLAYER_COMMAND_SEEK); + player_command_locked(PLAYER_COMMAND_SEEK); + player_unlock(); assert(pc.next_song == NULL); @@ -239,31 +289,24 @@ pc_seek(struct song *song, float seek_time) return true; } -float getPlayerCrossFade(void) +float +pc_get_cross_fade(void) { return pc.cross_fade_seconds; } -void setPlayerCrossFade(float crossFadeInSeconds) +void +pc_set_cross_fade(float cross_fade_seconds) { - if (crossFadeInSeconds < 0) - crossFadeInSeconds = 0; - pc.cross_fade_seconds = crossFadeInSeconds; + if (cross_fade_seconds < 0) + cross_fade_seconds = 0; + pc.cross_fade_seconds = cross_fade_seconds; idle_add(IDLE_OPTIONS); } -void setPlayerSoftwareVolume(int volume) -{ - if (volume > PCM_VOLUME_1) - volume = PCM_VOLUME_1; - else if (volume < 0) - volume = 0; - - pc.software_volume = volume; -} - -double getPlayerTotalPlayTime(void) +double +pc_get_total_play_time(void) { return pc.total_play_time; } diff --git a/src/player_control.h b/src/player_control.h index b1f7481cd..e279067d5 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -25,6 +25,8 @@ #include <stdint.h> +struct decoder_control; + enum player_state { PLAYER_STATE_STOP = 0, PLAYER_STATE_PAUSE, @@ -35,11 +37,16 @@ enum player_command { PLAYER_COMMAND_NONE = 0, PLAYER_COMMAND_EXIT, PLAYER_COMMAND_STOP, - PLAYER_COMMAND_PLAY, PLAYER_COMMAND_PAUSE, PLAYER_COMMAND_SEEK, PLAYER_COMMAND_CLOSE_AUDIO, + /** + * At least one audio_output.enabled flag has been modified; + * commit those changes to the output threads. + */ + PLAYER_COMMAND_UPDATE_AUDIO, + /** player_control.next_song has been updated */ PLAYER_COMMAND_QUEUE, @@ -49,6 +56,12 @@ enum player_command { * stop */ PLAYER_COMMAND_CANCEL, + + /** + * Refresh status information in the #player_control struct, + * e.g. elapsed_time. + */ + PLAYER_COMMAND_REFRESH, }; enum player_error { @@ -60,6 +73,14 @@ enum player_error { PLAYER_ERROR_FILENOTFOUND, }; +struct player_status { + enum player_state state; + uint16_t bit_rate; + struct audio_format audio_format; + float total_time; + float elapsed_time; +}; + struct player_control { unsigned buffer_chunks; @@ -69,19 +90,27 @@ struct player_control { thread isn't running */ GThread *thread; - struct notify notify; - volatile enum player_command command; - volatile enum player_state state; - volatile enum player_error error; + /** + * This lock protects #command, #state, #error. + */ + GMutex *mutex; + + /** + * Trigger this object after you have modified #command. + */ + GCond *cond; + + enum player_command command; + enum player_state state; + enum player_error error; uint16_t bit_rate; struct audio_format audio_format; float total_time; float elapsed_time; - struct song *volatile next_song; - struct song *errored_song; - volatile double seek_where; + struct song *next_song; + const struct song *errored_song; + double seek_where; float cross_fade_seconds; - uint16_t software_volume; double total_play_time; }; @@ -92,6 +121,67 @@ void pc_init(unsigned buffer_chunks, unsigned buffered_before_play); void pc_deinit(void); /** + * Locks the #player_control object. + */ +static inline void +player_lock(void) +{ + g_mutex_lock(pc.mutex); +} + +/** + * Unlocks the #player_control object. + */ +static inline void +player_unlock(void) +{ + g_mutex_unlock(pc.mutex); +} + +/** + * Waits for a signal on the #player_control object. This function is + * only valid in the player thread. The object must be locked prior + * to calling this function. + */ +static inline void +player_wait(void) +{ + g_cond_wait(pc.cond, pc.mutex); +} + +/** + * Waits for a signal on the #player_control object. This function is + * only valid in the player thread. The #decoder_control object must + * be locked prior to calling this function. + * + * Note the small difference to the player_wait() function! + */ +void +player_wait_decoder(struct decoder_control *dc); + +/** + * Signals the #player_control object. The object should be locked + * prior to calling this function. + */ +static inline void +player_signal(void) +{ + g_cond_signal(pc.cond); +} + +/** + * Signals the #player_control object. The object is temporarily + * locked by this function. + */ +static inline void +player_lock_signal(void) +{ + player_lock(); + player_signal(); + player_unlock(); +} + +/** * Call this function when the specified song pointer is about to be * invalidated. This makes sure that player_control.errored_song does * not point to an invalid pointer. @@ -100,37 +190,50 @@ void pc_song_deleted(const struct song *song); void -playerPlay(struct song *song); +pc_play(struct song *song); /** * see PLAYER_COMMAND_CANCEL */ void pc_cancel(void); -void playerSetPause(int pause_flag); - -void playerPause(void); +void +pc_set_pause(bool pause_flag); -void playerKill(void); +void +pc_pause(void); -int getPlayerTotalTime(void); +void +pc_kill(void); -int getPlayerElapsedTime(void); +void +pc_get_status(struct player_status *status); -unsigned long getPlayerBitRate(void); +enum player_state +pc_get_state(void); -enum player_state getPlayerState(void); +void +pc_clear_error(void); -void clearPlayerError(void); +/** + * Returns the human-readable message describing the last error during + * playback, NULL if no error occurred. The caller has to free the + * returned string. + */ +char * +pc_get_error_message(void); -char *getPlayerErrorStr(void); +enum player_error +pc_get_error(void); -enum player_error getPlayerError(void); +void +pc_stop(void); -void playerWait(void); +void +pc_update_audio(void); void -queueSong(struct song *song); +pc_enqueue_song(struct song *song); /** * Makes the player thread seek the specified song to a position. @@ -141,20 +244,13 @@ queueSong(struct song *song); bool pc_seek(struct song *song, float seek_time); -void setPlayerCrossFade(float crossFadeInSeconds); - -float getPlayerCrossFade(void); - -void setPlayerSoftwareVolume(int volume); - -double getPlayerTotalPlayTime(void); +void +pc_set_cross_fade(float cross_fade_seconds); -static inline const struct audio_format * -player_get_audio_format(void) -{ - return &pc.audio_format; -} +float +pc_get_cross_fade(void); -void playerInit(void); +double +pc_get_total_play_time(void); #endif diff --git a/src/player_thread.c b/src/player_thread.c index 7fc55d3d1..5823b433e 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "player_thread.h" #include "player_control.h" #include "decoder_control.h" @@ -46,6 +47,8 @@ enum xfade_state { }; struct player { + struct decoder_control *dc; + struct music_pipe *pipe; /** @@ -95,75 +98,129 @@ struct player { struct audio_format play_audio_format; /** - * Coefficient for converting a PCM buffer size into a time - * span. + * The time stamp of the chunk most recently sent to the + * output thread. This attribute is only used if + * audio_output_all_get_elapsed_time() didn't return a usable + * value; the output thread can estimate the elapsed time more + * precisly. */ - double size_to_time; + float elapsed_time; }; static struct music_buffer *player_buffer; -static void player_command_finished(void) +static void player_command_finished_locked(void) { assert(pc.command != PLAYER_COMMAND_NONE); pc.command = PLAYER_COMMAND_NONE; - notify_signal(&main_notify); + g_cond_signal(main_cond); +} + +static void player_command_finished(void) +{ + player_lock(); + player_command_finished_locked(); + player_unlock(); +} + +/** + * Start the decoder. + * + * Player lock is not held. + */ +static void +player_dc_start(struct player *player, struct music_pipe *pipe) +{ + struct decoder_control *dc = player->dc; + + assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); + assert(pc.next_song != NULL); + + dc_start(dc, pc.next_song, player_buffer, pipe); } /** * Stop the decoder and clears (and frees) its music pipe. + * + * Player lock is not held. */ static void player_dc_stop(struct player *player) { - dc_stop(&pc.notify); + struct decoder_control *dc = player->dc; + + dc_stop(dc); - if (dc.pipe != NULL) { + if (dc->pipe != NULL) { /* clear and free the decoder pipe */ - music_pipe_clear(dc.pipe, player_buffer); + music_pipe_clear(dc->pipe, player_buffer); - if (dc.pipe != player->pipe) - music_pipe_free(dc.pipe); + if (dc->pipe != player->pipe) + music_pipe_free(dc->pipe); - dc.pipe = NULL; + dc->pipe = NULL; } } /** + * Returns true if the decoder is decoding the next song (or has begun + * decoding it, or has finished doing it), and the player hasn't + * switched to that song yet. + */ +static bool +decoding_next_song(const struct player *player) +{ + return player->dc->pipe != NULL && player->dc->pipe != player->pipe; +} + +/** * After the decoder has been started asynchronously, wait for the * "START" command to finish. The decoder may not be initialized yet, * i.e. there is no audio_format information yet. + * + * The player lock is not held. */ static bool player_wait_for_decoder(struct player *player) { - dc_command_wait(&pc.notify); + struct decoder_control *dc = player->dc; - if (decoder_has_failed()) { - assert(dc.next_song == NULL || dc.next_song->url != NULL); - pc.errored_song = dc.next_song; + assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); + assert(pc.next_song != NULL); + + player->queued = false; + + if (decoder_lock_has_failed(dc)) { + player_lock(); + pc.errored_song = dc->song; pc.error = PLAYER_ERROR_FILE; pc.next_song = NULL; - player->queued = false; + player_unlock(); + return false; } - pc.total_time = pc.next_song->tag != NULL - ? pc.next_song->tag->time : 0; - pc.bit_rate = 0; - audio_format_clear(&pc.audio_format); - player->song = pc.next_song; - pc.next_song = NULL; - pc.elapsed_time = 0; - player->queued = false; + player->elapsed_time = 0.0; /* set the "starting" flag, which will be cleared by player_check_decoder_startup() */ player->decoder_starting = true; + player_lock(); + + /* update player_control's song information */ + pc.total_time = song_get_duration(pc.next_song); + pc.bit_rate = 0; + audio_format_clear(&pc.audio_format); + + /* clear the queued song */ + pc.next_song = NULL; + + player_unlock(); + /* call syncPlaylistWithQueue() in the main thread */ event_pipe_emit(PIPE_EVENT_PLAYLIST); @@ -171,54 +228,86 @@ player_wait_for_decoder(struct player *player) } /** + * Returns the real duration of the song, comprising the duration + * indicated by the decoder plugin. + */ +static double +real_song_duration(const struct song *song, double decoder_duration) +{ + assert(song != NULL); + + if (decoder_duration <= 0.0) + /* the decoder plugin didn't provide information; fall + back to song_get_duration() */ + return song_get_duration(song); + + if (song->end_ms > 0 && song->end_ms / 1000.0 < decoder_duration) + return (song->end_ms - song->start_ms) / 1000.0; + + return decoder_duration - song->start_ms / 1000.0; +} + +/** * The decoder has acknowledged the "START" command (see * player_wait_for_decoder()). This function checks if the decoder * initialization has completed yet. + * + * The player lock is not held. */ static bool player_check_decoder_startup(struct player *player) { + struct decoder_control *dc = player->dc; + assert(player->decoder_starting); - if (decoder_has_failed()) { + decoder_lock(dc); + + if (decoder_has_failed(dc)) { /* the decoder failed */ - assert(dc.next_song == NULL || dc.next_song->url != NULL); + decoder_unlock(dc); - pc.errored_song = dc.next_song; + player_lock(); + pc.errored_song = dc->song; pc.error = PLAYER_ERROR_FILE; + player_unlock(); return false; - } else if (!decoder_is_starting()) { + } else if (!decoder_is_starting(dc)) { /* the decoder is ready and ok */ + decoder_unlock(dc); + if (audio_format_defined(&player->play_audio_format) && !audio_output_all_wait(1)) /* the output devices havn't finished playing all chunks yet - wait for that */ return true; - pc.total_time = dc.total_time; - pc.audio_format = dc.in_audio_format; - player->play_audio_format = dc.out_audio_format; - player->size_to_time = - audioFormatSizeToTime(&dc.out_audio_format); + player_lock(); + pc.total_time = real_song_duration(dc->song, dc->total_time); + pc.audio_format = dc->in_audio_format; + player_unlock(); + + player->play_audio_format = dc->out_audio_format; player->decoder_starting = false; if (!player->paused && - !audio_output_all_open(&dc.out_audio_format, + !audio_output_all_open(&dc->out_audio_format, player_buffer)) { - char *uri = song_get_uri(dc.next_song); + char *uri = song_get_uri(dc->song); g_warning("problems opening audio device " "while playing \"%s\"", uri); g_free(uri); - assert(dc.next_song == NULL || dc.next_song->url != NULL); - pc.errored_song = dc.next_song; + player_lock(); pc.error = PLAYER_ERROR_AUDIO; /* pause: the user may resume playback as soon as an audio output becomes available */ pc.state = PLAYER_STATE_PAUSE; + player_unlock(); + player->paused = true; return true; } @@ -227,7 +316,8 @@ player_check_decoder_startup(struct player *player) } else { /* the decoder is not yet ready; wait some more */ - notify_wait(&pc.notify); + player_wait_decoder(dc); + decoder_unlock(dc); return true; } @@ -237,6 +327,8 @@ player_check_decoder_startup(struct player *player) * Sends a chunk of silence to the audio outputs. This is called when * there is not enough decoded data in the pipe yet, to prevent * underruns in the hardware buffers. + * + * The player lock is not held. */ static bool player_send_silence(struct player *player) @@ -260,6 +352,7 @@ player_send_silence(struct player *player) chunk->audio_format = player->play_audio_format; #endif + chunk->times = -1.0; /* undefined time stamp */ chunk->length = num_frames * frame_size; memset(chunk->data, 0, chunk->length); @@ -273,15 +366,19 @@ player_send_silence(struct player *player) /** * This is the handler for the #PLAYER_COMMAND_SEEK command. + * + * The player lock is not held. */ static bool player_seek_decoder(struct player *player) { + struct song *song = pc.next_song; + struct decoder_control *dc = player->dc; double where; bool ret; assert(pc.next_song != NULL); - if (decoder_current_song() != pc.next_song) { + if (decoder_current_song(dc) != song) { /* the decoder is already decoding the "next" song - stop it and start the previous song again */ @@ -290,10 +387,9 @@ static bool player_seek_decoder(struct player *player) /* clear music chunks which might still reside in the pipe */ music_pipe_clear(player->pipe, player_buffer); - dc.pipe = player->pipe; /* re-start the decoder */ - dc_start_async(pc.next_song); + player_dc_start(player, player->pipe); ret = player_wait_for_decoder(player); if (!ret) { /* decoder failure */ @@ -324,14 +420,15 @@ static bool player_seek_decoder(struct player *player) if (where < 0.0) where = 0.0; - ret = dc_seek(&pc.notify, where); + ret = dc_seek(dc, where + song->start_ms / 1000.0); if (!ret) { /* decoder failure */ player_command_finished(); return false; } - pc.elapsed_time = where; + player->elapsed_time = where; + player_command_finished(); player->xfade = XFADE_UNKNOWN; @@ -344,53 +441,73 @@ static bool player_seek_decoder(struct player *player) return true; } +/** + * Player lock must be held before calling. + */ static void player_process_command(struct player *player) { + G_GNUC_UNUSED struct decoder_control *dc = player->dc; + switch (pc.command) { case PLAYER_COMMAND_NONE: - case PLAYER_COMMAND_PLAY: case PLAYER_COMMAND_STOP: case PLAYER_COMMAND_EXIT: case PLAYER_COMMAND_CLOSE_AUDIO: break; + case PLAYER_COMMAND_UPDATE_AUDIO: + player_unlock(); + audio_output_all_enable_disable(); + player_lock(); + player_command_finished_locked(); + break; + case PLAYER_COMMAND_QUEUE: assert(pc.next_song != NULL); assert(!player->queued); - assert(dc.pipe == NULL || dc.pipe == player->pipe); + assert(dc->pipe == NULL || dc->pipe == player->pipe); player->queued = true; - player_command_finished(); + player_command_finished_locked(); break; case PLAYER_COMMAND_PAUSE: + player_unlock(); + player->paused = !player->paused; if (player->paused) { audio_output_all_pause(); + player_lock(); + pc.state = PLAYER_STATE_PAUSE; } else if (!audio_format_defined(&player->play_audio_format)) { /* the decoder hasn't provided an audio format yet - don't open the audio device yet */ + player_lock(); pc.state = PLAYER_STATE_PLAY; } else if (audio_output_all_open(&player->play_audio_format, player_buffer)) { /* unpaused, continue playing */ + player_lock(); + pc.state = PLAYER_STATE_PLAY; } else { /* the audio device has failed - rollback to pause mode */ - assert(dc.next_song == NULL || dc.next_song->url != NULL); - pc.errored_song = dc.next_song; pc.error = PLAYER_ERROR_AUDIO; player->paused = true; + + player_lock(); } - player_command_finished(); + player_command_finished_locked(); break; case PLAYER_COMMAND_SEEK: + player_unlock(); player_seek_decoder(player); + player_lock(); break; case PLAYER_COMMAND_CANCEL: @@ -402,80 +519,91 @@ static void player_process_command(struct player *player) return; } - if (dc.pipe != NULL && dc.pipe != player->pipe) + if (decoding_next_song(player)) { /* the decoder is already decoding the song - stop it and reset the position */ + player_unlock(); player_dc_stop(player); + player_lock(); + } pc.next_song = NULL; player->queued = false; - player_command_finished(); + player_command_finished_locked(); + break; + + case PLAYER_COMMAND_REFRESH: + if (audio_format_defined(&player->play_audio_format) && + !player->paused) { + player_unlock(); + audio_output_all_check(); + player_lock(); + } + + pc.elapsed_time = audio_output_all_get_elapsed_time(); + if (pc.elapsed_time < 0.0) + pc.elapsed_time = player->elapsed_time; + + player_command_finished_locked(); break; } } +static void +update_song_tag(struct song *song, const struct tag *new_tag) +{ + struct tag *old_tag; + + if (song_is_file(song)) + /* don't update tags of local files, only remote + streams may change tags dynamically */ + return; + + old_tag = song->tag; + song->tag = tag_dup(new_tag); + + if (old_tag != NULL) + tag_free(old_tag); + + /* the main thread will update the playlist version when he + receives this event */ + event_pipe_emit(PIPE_EVENT_TAG); + + /* notify all clients that the tag of the current song has + changed */ + idle_add(IDLE_PLAYER); +} + /** * Plays a #music_chunk object (after applying software volume). If * it contains a (stream) tag, copy it to the current song, so MPD's * playlist reflects the new stream tag. + * + * Player lock is not held. */ static bool play_chunk(struct song *song, struct music_chunk *chunk, - const struct audio_format *format, double sizeToTime) + const struct audio_format *format) { - bool success; - assert(music_chunk_check_format(chunk, format)); - if (chunk->tag != NULL) { - if (!song_is_file(song)) { - /* always update the tag of remote streams */ - struct tag *old_tag = song->tag; - - song->tag = tag_dup(chunk->tag); - - if (old_tag != NULL) - tag_free(old_tag); - - /* the main thread will update the playlist - version when he receives this event */ - event_pipe_emit(PIPE_EVENT_TAG); - - /* notify all clients that the tag of the - current song has changed */ - idle_add(IDLE_PLAYER); - } - } + if (chunk->tag != NULL) + update_song_tag(song, chunk->tag); if (chunk->length == 0) { music_buffer_return(player_buffer, chunk); return true; } - pc.elapsed_time = chunk->times; pc.bit_rate = chunk->bit_rate; - /* apply software volume */ - - success = pcm_volume(chunk->data, chunk->length, - format, pc.software_volume); - if (!success) { - g_warning("pcm_volume() failed on %u:%u:%u", - format->sample_rate, format->bits, format->channels); - pc.errored_song = dc.current_song; - pc.error = PLAYER_ERROR_AUDIO; - return false; - } - /* send the chunk to the audio outputs */ - if (!audio_output_all_play(chunk)) { - pc.errored_song = dc.current_song; - pc.error = PLAYER_ERROR_AUDIO; + if (!audio_output_all_play(chunk)) return false; - } - pc.total_play_time += sizeToTime * chunk->length; + pc.total_play_time += (double)chunk->length / + audio_format_time_to_size(format); return true; } @@ -488,6 +616,7 @@ play_chunk(struct song *song, struct music_chunk *chunk, static bool play_next_chunk(struct player *player) { + struct decoder_control *dc = player->dc; struct music_chunk *chunk = NULL; unsigned cross_fade_position; bool success; @@ -498,12 +627,12 @@ play_next_chunk(struct player *player) return true; if (player->xfade == XFADE_ENABLED && - dc.pipe != NULL && dc.pipe != player->pipe && + decoding_next_song(player) && (cross_fade_position = music_pipe_size(player->pipe)) <= player->cross_fade_chunks) { /* perform cross fade */ struct music_chunk *other_chunk = - music_pipe_shift(dc.pipe); + music_pipe_shift(dc->pipe); if (!player->cross_fading) { /* beginning of the cross fade - adjust @@ -519,20 +648,26 @@ play_next_chunk(struct player *player) assert(chunk != NULL); cross_fade_apply(chunk, other_chunk, - &dc.out_audio_format, + &dc->out_audio_format, cross_fade_position, player->cross_fade_chunks); music_buffer_return(player_buffer, other_chunk); } else { /* there are not enough decoded chunks yet */ - if (decoder_is_idle()) { + + decoder_lock(dc); + + if (decoder_is_idle(dc)) { /* the decoder isn't running, abort cross fading */ + decoder_unlock(dc); + player->xfade = XFADE_DISABLED; } else { /* wait for the decoder */ - notify_signal(&dc.notify); - notify_wait(&pc.notify); + decoder_signal(dc); + player_wait_decoder(dc); + decoder_unlock(dc); return true; } @@ -546,27 +681,34 @@ play_next_chunk(struct player *player) /* play the current chunk */ - success = play_chunk(player->song, chunk, &player->play_audio_format, - player->size_to_time); + success = play_chunk(player->song, chunk, &player->play_audio_format); if (!success) { music_buffer_return(player_buffer, chunk); + player_lock(); + + pc.error = PLAYER_ERROR_AUDIO; + /* pause: the user may resume playback as soon as an audio output becomes available */ pc.state = PLAYER_STATE_PAUSE; player->paused = true; + player_unlock(); + return false; } /* this formula should prevent that the decoder gets woken up with each chunk; it is more efficient to make it decode a larger block at a time */ - if (!decoder_is_idle() && - music_pipe_size(dc.pipe) <= (pc.buffered_before_play + + decoder_lock(dc); + if (!decoder_is_idle(dc) && + music_pipe_size(dc->pipe) <= (pc.buffered_before_play + music_buffer_size(player_buffer) * 3) / 4) - notify_signal(&dc.notify); + decoder_signal(dc); + decoder_unlock(dc); return true; } @@ -576,15 +718,23 @@ play_next_chunk(struct player *player) * has consumed all chunks of the current song, and we should start * sending chunks from the next one. * + * The player lock is not held. + * * @return true on success, false on error (playback will be stopped) */ static bool player_song_border(struct player *player) { + char *uri; + player->xfade = XFADE_UNKNOWN; + uri = song_get_uri(player->song); + g_message("played \"%s\"", uri); + g_free(uri); + music_pipe_free(player->pipe); - player->pipe = dc.pipe; + player->pipe = player->dc->pipe; if (!player_wait_for_decoder(player)) return false; @@ -597,53 +747,58 @@ player_song_border(struct player *player) * basically a state machine, which multiplexes data between the * decoder thread and the output threads. */ -static void do_play(void) +static void do_play(struct decoder_control *dc) { struct player player = { + .dc = dc, .buffering = true, .decoder_starting = false, .paused = false, - .queued = false, + .queued = true, .song = NULL, .xfade = XFADE_UNKNOWN, .cross_fading = false, .cross_fade_chunks = 0, - .size_to_time = 0.0, + .elapsed_time = 0.0, }; + player_unlock(); + player.pipe = music_pipe_new(); - dc.buffer = player_buffer; - dc.pipe = player.pipe; - dc_start(&pc.notify, pc.next_song); + player_dc_start(&player, player.pipe); if (!player_wait_for_decoder(&player)) { player_dc_stop(&player); player_command_finished(); music_pipe_free(player.pipe); event_pipe_emit(PIPE_EVENT_PLAYLIST); + player_lock(); return; } - pc.elapsed_time = 0; + player_lock(); pc.state = PLAYER_STATE_PLAY; - player_command_finished(); + player_command_finished_locked(); while (true) { player_process_command(&player); if (pc.command == PLAYER_COMMAND_STOP || pc.command == PLAYER_COMMAND_EXIT || pc.command == PLAYER_COMMAND_CLOSE_AUDIO) { + player_unlock(); audio_output_all_cancel(); break; } + player_unlock(); + if (player.buffering) { /* buffering at the start of the song - wait until the buffer is large enough, to prevent stuttering on slow machines */ if (music_pipe_size(player.pipe) < pc.buffered_before_play && - !decoder_is_idle()) { + !decoder_lock_is_idle(dc)) { /* not enough decoded buffer space yet */ if (!player.paused && @@ -652,7 +807,11 @@ static void do_play(void) !player_send_silence(&player)) break; - notify_wait(&pc.notify); + decoder_lock(dc); + /* XXX race condition: check decoder again */ + player_wait_decoder(dc); + decoder_unlock(dc); + player_lock(); continue; } else { /* buffering is complete */ @@ -663,10 +822,19 @@ static void do_play(void) if (player.decoder_starting) { /* wait until the decoder is initialized completely */ bool success; + const struct song *song; success = player_check_decoder_startup(&player); if (!success) break; + + /* seek to the beginning of the range */ + song = decoder_current_song(dc); + if (song != NULL && song->start_ms > 0 && + !dc_seek(dc, song->start_ms / 1000.0)) + player_dc_stop(&player); + + player_lock(); continue; } @@ -674,30 +842,28 @@ static void do_play(void) /* music_pipe_check_format(&play_audio_format, player.next_song_chunk, - &dc.out_audio_format); + &dc->out_audio_format); */ #endif - if (decoder_is_idle() && player.queued) { + if (decoder_lock_is_idle(dc) && player.queued && + dc->pipe == player.pipe) { /* the decoder has finished the current song; make it decode the next song */ - assert(pc.next_song != NULL); - assert(dc.pipe == NULL || dc.pipe == player.pipe); + assert(dc->pipe == NULL || dc->pipe == player.pipe); - player.queued = false; - dc.pipe = music_pipe_new(); - dc_start_async(pc.next_song); + player_dc_start(&player, music_pipe_new()); } - if (dc.pipe != NULL && dc.pipe != player.pipe && + if (decoding_next_song(&player) && player.xfade == XFADE_UNKNOWN && - !decoder_is_starting()) { + !decoder_lock_is_starting(dc)) { /* enable cross fading in this song? if yes, calculate how many chunks will be required for it */ player.cross_fade_chunks = - cross_fade_calc(pc.cross_fade_seconds, dc.total_time, - &dc.out_audio_format, + cross_fade_calc(pc.cross_fade_seconds, dc->total_time, + &dc->out_audio_format, &player.play_audio_format, music_buffer_size(player_buffer) - pc.buffered_before_play); @@ -710,9 +876,13 @@ static void do_play(void) player.xfade = XFADE_DISABLED; } - if (player.paused) - notify_wait(&pc.notify); - else if (music_pipe_size(player.pipe) > 0) { + if (player.paused) { + player_lock(); + + if (pc.command == PLAYER_COMMAND_NONE) + player_wait(); + continue; + } else if (music_pipe_size(player.pipe) > 0) { /* at least one music chunk is ready - send it to the audio output */ @@ -724,17 +894,21 @@ static void do_play(void) /* XXX synchronize in a better way */ g_usleep(10000); - } else if (dc.pipe != NULL && dc.pipe != player.pipe) { + } else if (decoding_next_song(&player)) { /* at the beginning of a new song */ if (!player_song_border(&player)) break; - } else if (decoder_is_idle()) { + } else if (decoder_lock_is_idle(dc)) { /* check the size of the pipe again, because the decoder thread may have added something since we last checked */ - if (music_pipe_size(player.pipe) == 0) + if (music_pipe_size(player.pipe) == 0) { + /* wait for the hardware to finish + playback */ + audio_output_all_drain(); break; + } } else { /* the decoder is too busy and hasn't provided new PCM data in time: send silence (if the @@ -742,11 +916,8 @@ static void do_play(void) if (!player_send_silence(&player)) break; } - } - if (player.queued) { - assert(pc.next_song != NULL); - pc.next_song = NULL; + player_lock(); } player_dc_stop(&player); @@ -754,38 +925,61 @@ static void do_play(void) music_pipe_clear(player.pipe, player_buffer); music_pipe_free(player.pipe); + player_lock(); + + if (player.queued) { + assert(pc.next_song != NULL); + pc.next_song = NULL; + } + pc.state = PLAYER_STATE_STOP; + + player_unlock(); + event_pipe_emit(PIPE_EVENT_PLAYLIST); + + player_lock(); } static gpointer player_task(G_GNUC_UNUSED gpointer arg) { - decoder_thread_start(); + struct decoder_control dc; + + dc_init(&dc); + decoder_thread_start(&dc); player_buffer = music_buffer_new(pc.buffer_chunks); + player_lock(); + while (1) { switch (pc.command) { - case PLAYER_COMMAND_PLAY: case PLAYER_COMMAND_QUEUE: assert(pc.next_song != NULL); - do_play(); + do_play(&dc); break; case PLAYER_COMMAND_STOP: + player_unlock(); audio_output_all_cancel(); + player_lock(); + /* fall through */ case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_PAUSE: pc.next_song = NULL; - player_command_finished(); + player_command_finished_locked(); break; case PLAYER_COMMAND_CLOSE_AUDIO: + player_unlock(); + audio_output_all_close(); - player_command_finished(); + + player_lock(); + player_command_finished_locked(); #ifndef NDEBUG /* in the DEBUG build, check for leaked @@ -797,25 +991,39 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg) break; + case PLAYER_COMMAND_UPDATE_AUDIO: + player_unlock(); + audio_output_all_enable_disable(); + player_lock(); + player_command_finished_locked(); + break; + case PLAYER_COMMAND_EXIT: - dc_quit(); + player_unlock(); + + dc_quit(&dc); + dc_deinit(&dc); audio_output_all_close(); music_buffer_free(player_buffer); + player_command_finished(); - g_thread_exit(NULL); - break; + return NULL; case PLAYER_COMMAND_CANCEL: pc.next_song = NULL; - player_command_finished(); + player_command_finished_locked(); + break; + + case PLAYER_COMMAND_REFRESH: + /* no-op when not playing */ + player_command_finished_locked(); break; case PLAYER_COMMAND_NONE: - notify_wait(&pc.notify); + player_wait(); break; } } - return NULL; } void player_create(void) diff --git a/src/playlist.c b/src/playlist.c index 35c09329a..691fe5d26 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_internal.h" #include "playlist_save.h" #include "player_control.h" @@ -34,7 +35,8 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -void playlistVersionChange(struct playlist *playlist) +void +playlist_increment_version_all(struct playlist *playlist) { queue_modify_all(&playlist->queue); idle_add(IDLE_PLAYLIST); @@ -61,16 +63,12 @@ playlist_init(struct playlist *playlist) playlist->queued = -1; playlist->current = -1; - - playlist->prev_elapsed = g_timer_new(); } void playlist_finish(struct playlist *playlist) { queue_finish(&playlist->queue); - - g_timer_destroy(playlist->prev_elapsed); } /** @@ -91,14 +89,15 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order) g_debug("queue song %i:\"%s\"", playlist->queued, uri); g_free(uri); - queueSong(song); + pc_enqueue_song(song); } /** * Check if the player thread has already started playing the "queued" * song. */ -static void syncPlaylistWithQueue(struct playlist *playlist) +static void +playlist_sync_with_queue(struct playlist *playlist) { if (pc.next_song == NULL && playlist->queued != -1) { /* queued song has started: copy queued to current, @@ -109,7 +108,7 @@ static void syncPlaylistWithQueue(struct playlist *playlist) playlist->queued = -1; if(playlist->queue.consume) - deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); idle_add(IDLE_PLAYER); } @@ -178,7 +177,7 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev) } void -playPlaylistOrderNumber(struct playlist *playlist, int orderNum) +playlist_play_order(struct playlist *playlist, int orderNum) { struct song *song; char *uri; @@ -192,34 +191,35 @@ playPlaylistOrderNumber(struct playlist *playlist, int orderNum) g_debug("play %i:\"%s\"", orderNum, uri); g_free(uri); - playerPlay(song); + pc_play(song); playlist->current = orderNum; } static void -playPlaylistIfPlayerStopped(struct playlist *playlist); +playlist_resume_playback(struct playlist *playlist); /** * This is the "PLAYLIST" event handler. It is invoked by the player * thread whenever it requests a new queued song, or when it exits. */ -void syncPlayerAndPlaylist(struct playlist *playlist) +void +playlist_sync(struct playlist *playlist) { if (!playlist->playing) /* this event has reached us out of sync: we aren't playing anymore; ignore the event */ return; - if (getPlayerState() == PLAYER_STATE_STOP) + if (pc_get_state() == PLAYER_STATE_STOP) /* the player thread has stopped: check if playback should be restarted with the next song. That can happen if the playlist isn't filling the queue fast enough */ - playPlaylistIfPlayerStopped(playlist); + playlist_resume_playback(playlist); else { /* check if the player thread has already started playing the queued song */ - syncPlaylistWithQueue(playlist); + playlist_sync_with_queue(playlist); /* make sure the queued song is always set (if possible) */ @@ -233,14 +233,14 @@ void syncPlayerAndPlaylist(struct playlist *playlist) * decide whether to re-start playback */ static void -playPlaylistIfPlayerStopped(struct playlist *playlist) +playlist_resume_playback(struct playlist *playlist) { enum player_error error; assert(playlist->playing); - assert(getPlayerState() == PLAYER_STATE_STOP); + assert(pc_get_state() == PLAYER_STATE_STOP); - error = getPlayerError(); + error = pc_get_error(); if (error == PLAYER_ERROR_NOERROR) playlist->error_count = 0; else @@ -251,37 +251,38 @@ playPlaylistIfPlayerStopped(struct playlist *playlist) playlist->error_count >= queue_length(&playlist->queue)) /* too many errors, or critical error: stop playback */ - stopPlaylist(playlist); + playlist_stop(playlist); else /* continue playback at the next song */ - nextSongInPlaylist(playlist); + playlist_next(playlist); } bool -getPlaylistRepeatStatus(const struct playlist *playlist) +playlist_get_repeat(const struct playlist *playlist) { return playlist->queue.repeat; } bool -getPlaylistRandomStatus(const struct playlist *playlist) +playlist_get_random(const struct playlist *playlist) { return playlist->queue.random; } bool -getPlaylistSingleStatus(const struct playlist *playlist) +playlist_get_single(const struct playlist *playlist) { return playlist->queue.single; } bool -getPlaylistConsumeStatus(const struct playlist *playlist) +playlist_get_consume(const struct playlist *playlist) { return playlist->queue.consume; } -void setPlaylistRepeatStatus(struct playlist *playlist, bool status) +void +playlist_set_repeat(struct playlist *playlist, bool status) { if (status == playlist->queue.repeat) return; @@ -296,7 +297,8 @@ void setPlaylistRepeatStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -static void orderPlaylist(struct playlist *playlist) +static void +playlist_order(struct playlist *playlist) { if (playlist->current >= 0) /* update playlist.current, order==position now */ @@ -306,7 +308,8 @@ static void orderPlaylist(struct playlist *playlist) queue_restore_order(&playlist->queue); } -void setPlaylistSingleStatus(struct playlist *playlist, bool status) +void +playlist_set_single(struct playlist *playlist, bool status) { if (status == playlist->queue.single) return; @@ -321,7 +324,8 @@ void setPlaylistSingleStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -void setPlaylistConsumeStatus(struct playlist *playlist, bool status) +void +playlist_set_consume(struct playlist *playlist, bool status) { if (status == playlist->queue.consume) return; @@ -330,7 +334,8 @@ void setPlaylistConsumeStatus(struct playlist *playlist, bool status) idle_add(IDLE_OPTIONS); } -void setPlaylistRandomStatus(struct playlist *playlist, bool status) +void +playlist_set_random(struct playlist *playlist, bool status) { const struct song *queued; @@ -365,14 +370,15 @@ void setPlaylistRandomStatus(struct playlist *playlist, bool status) } else playlist->current = -1; } else - orderPlaylist(playlist); + playlist_order(playlist); playlist_update_queued_song(playlist, queued); idle_add(IDLE_OPTIONS); } -int getPlaylistCurrentSong(const struct playlist *playlist) +int +playlist_get_current_song(const struct playlist *playlist) { if (playlist->current >= 0) return queue_order_to_position(&playlist->queue, @@ -381,7 +387,8 @@ int getPlaylistCurrentSong(const struct playlist *playlist) return -1; } -int getPlaylistNextSong(const struct playlist *playlist) +int +playlist_get_next_song(const struct playlist *playlist) { if (playlist->current >= 0) { @@ -404,19 +411,19 @@ int getPlaylistNextSong(const struct playlist *playlist) } unsigned long -getPlaylistVersion(const struct playlist *playlist) +playlist_get_version(const struct playlist *playlist) { return playlist->queue.version; } int -getPlaylistLength(const struct playlist *playlist) +playlist_get_length(const struct playlist *playlist) { return queue_length(&playlist->queue); } unsigned -getPlaylistSongId(const struct playlist *playlist, unsigned song) +playlist_get_song_id(const struct playlist *playlist, unsigned song) { return queue_position_to_id(&playlist->queue, song); } diff --git a/src/playlist.h b/src/playlist.h index 57b2450fa..f8f5bd942 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -23,7 +23,6 @@ #include "queue.h" #include <stdbool.h> -#include <stdio.h> #define PLAYLIST_COMMENT '#' @@ -82,21 +81,16 @@ struct playlist { * This variable is only valid if #playing is true. */ int queued; - - /** - * This timer tracks the time elapsed since the last "prev" - * command. If that is less than one second ago, "prev" jumps - * to the previous song instead of rewinding the current song. - */ - GTimer *prev_elapsed; }; /** the global playlist object */ extern struct playlist g_playlist; -void initPlaylist(void); +void +playlist_global_init(void); -void finishPlaylist(void); +void +playlist_global_finish(void); void playlist_init(struct playlist *playlist); @@ -116,11 +110,8 @@ playlist_get_queue(const struct playlist *playlist) return &playlist->queue; } -void readPlaylistState(FILE *); - -void savePlaylistState(FILE *); - -void clearPlaylist(struct playlist *playlist); +void +playlist_clear(struct playlist *playlist); #ifndef WIN32 /** @@ -133,90 +124,111 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid, #endif enum playlist_result -addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id); +playlist_append_uri(struct playlist *playlist, const char *file, + unsigned *added_id); enum playlist_result -addSongToPlaylist(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct song *song, unsigned *added_id); enum playlist_result -deleteFromPlaylist(struct playlist *playlist, unsigned song); +playlist_delete(struct playlist *playlist, unsigned song); + +/** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ +enum playlist_result +playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end); enum playlist_result -deleteFromPlaylistById(struct playlist *playlist, unsigned song); +playlist_delete_id(struct playlist *playlist, unsigned song); -void stopPlaylist(struct playlist *playlist); +void +playlist_stop(struct playlist *playlist); enum playlist_result -playPlaylist(struct playlist *playlist, int song); +playlist_play(struct playlist *playlist, int song); enum playlist_result -playPlaylistById(struct playlist *playlist, int song); +playlist_play_id(struct playlist *playlist, int song); -void nextSongInPlaylist(struct playlist *playlist); +void +playlist_next(struct playlist *playlist); -void syncPlayerAndPlaylist(struct playlist *playlist); +void +playlist_sync(struct playlist *playlist); -void previousSongInPlaylist(struct playlist *playlist); +void +playlist_previous(struct playlist *playlist); -void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end); +void +playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end); void -deleteASongFromPlaylist(struct playlist *playlist, const struct song *song); +playlist_delete_song(struct playlist *playlist, const struct song *song); enum playlist_result -moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to); +playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to); enum playlist_result -moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to); +playlist_move_id(struct playlist *playlist, unsigned id, int to); enum playlist_result -swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2); +playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2); enum playlist_result -swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2); +playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2); bool -getPlaylistRepeatStatus(const struct playlist *playlist); +playlist_get_repeat(const struct playlist *playlist); -void setPlaylistRepeatStatus(struct playlist *playlist, bool status); +void +playlist_set_repeat(struct playlist *playlist, bool status); bool -getPlaylistRandomStatus(const struct playlist *playlist); +playlist_get_random(const struct playlist *playlist); -void setPlaylistRandomStatus(struct playlist *playlist, bool status); +void +playlist_set_random(struct playlist *playlist, bool status); bool -getPlaylistSingleStatus(const struct playlist *playlist); +playlist_get_single(const struct playlist *playlist); -void setPlaylistSingleStatus(struct playlist *playlist, bool status); +void +playlist_set_single(struct playlist *playlist, bool status); bool -getPlaylistConsumeStatus(const struct playlist *playlist); +playlist_get_consume(const struct playlist *playlist); -void setPlaylistConsumeStatus(struct playlist *playlist, bool status); +void +playlist_set_consume(struct playlist *playlist, bool status); -int getPlaylistCurrentSong(const struct playlist *playlist); +int +playlist_get_current_song(const struct playlist *playlist); -int getPlaylistNextSong(const struct playlist *playlist); +int +playlist_get_next_song(const struct playlist *playlist); unsigned -getPlaylistSongId(const struct playlist *playlist, unsigned song); +playlist_get_song_id(const struct playlist *playlist, unsigned song); -int getPlaylistLength(const struct playlist *playlist); +int +playlist_get_length(const struct playlist *playlist); unsigned long -getPlaylistVersion(const struct playlist *playlist); +playlist_get_version(const struct playlist *playlist); enum playlist_result -seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time); +playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time); enum playlist_result -seekSongInPlaylistById(struct playlist *playlist, +playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time); -void playlistVersionChange(struct playlist *playlist); - -int is_valid_playlist_name(const char *utf8path); +void +playlist_increment_version_all(struct playlist *playlist); #endif diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c new file mode 100644 index 000000000..03a5edea6 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/asx_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "asx" + +/** + * This is the state object for the GLib XML parser. + */ +struct asx_parser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + GSList *songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, ENTRY, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; +}; + +static const gchar * +get_attribute(const gchar **attribute_names, const gchar **attribute_values, + const gchar *name) +{ + for (unsigned i = 0; attribute_names[i] != NULL; ++i) + if (g_ascii_strcasecmp(attribute_names[i], name) == 0) + return attribute_values[i]; + + return NULL; +} + +static void +asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + parser->state = ENTRY; + parser->song = song_remote_new("asx:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case ENTRY: + if (g_ascii_strcasecmp(element_name, "ref") == 0) { + const gchar *href = get_attribute(attribute_names, + attribute_values, + "href"); + if (href != NULL) { + /* create new song object, and copy + the existing tag over; we cannot + replace the existing song's URI, + because that attribute is + immutable */ + struct song *song = song_remote_new(href); + + if (parser->song != NULL) { + song->tag = parser->song->tag; + parser->song->tag = NULL; + song_free(parser->song); + } + + parser->song = song; + } + } else if (g_ascii_strcasecmp(element_name, "author") == 0) + /* is that correct? or should it be COMPOSER + or PERFORMER? */ + parser->tag = TAG_ARTIST; + else if (g_ascii_strcasecmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + + break; + } +} + +static void +asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ENTRY: + if (g_ascii_strcasecmp(element_name, "entry") == 0) { + if (strcmp(parser->song->uri, "asx:") != 0) + parser->songs = g_slist_prepend(parser->songs, + parser->song); + else + song_free(parser->song); + + parser->state = ROOT; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + } +} + +static void +asx_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct asx_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ENTRY: + if (parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + } +} + +static const GMarkupParser asx_parser = { + .start_element = asx_start_element, + .end_element = asx_end_element, + .text = asx_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +asx_parser_destroy(gpointer data) +{ + struct asx_parser *parser = data; + + if (parser->state >= ENTRY) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct asx_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +asx_open_stream(struct input_stream *is) +{ + struct asx_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct asx_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the ASX XML file */ + + context = g_markup_parse_context_new(&asx_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, asx_parser_destroy); + + while (true) { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + /* create a #asx_playlist object from the parsed song list */ + + playlist = g_new(struct asx_playlist, 1); + playlist_provider_init(&playlist->base, &asx_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +asx_close(struct playlist_provider *_playlist) +{ + struct asx_playlist *playlist = (struct asx_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +asx_read(struct playlist_provider *_playlist) +{ + struct asx_playlist *playlist = (struct asx_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const asx_suffixes[] = { + "asx", + NULL +}; + +static const char *const asx_mime_types[] = { + "video/x-ms-asf", + NULL +}; + +const struct playlist_plugin asx_playlist_plugin = { + .name = "asx", + + .open_stream = asx_open_stream, + .close = asx_close, + .read = asx_read, + + .suffixes = asx_suffixes, + .mime_types = asx_mime_types, +}; diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h new file mode 100644 index 000000000..021e97f56 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin asx_playlist_plugin; + +#endif diff --git a/src/playlist/cue_playlist_plugin.c b/src/playlist/cue_playlist_plugin.c new file mode 100644 index 000000000..a9b596560 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.c @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/cue_playlist_plugin.h" +#include "playlist_plugin.h" +#include "tag.h" +#include "song.h" +#include "cue/cue_tag.h" + +#include <glib.h> +#include <libcue/libcue.h> +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cue" + +struct cue_playlist { + struct playlist_provider base; + + struct Cd *cd; + + unsigned next; +}; + +static struct playlist_provider * +cue_playlist_open_uri(const char *uri) +{ + struct cue_playlist *playlist; + FILE *file; + struct Cd *cd; + + file = fopen(uri, "rt"); + if (file == NULL) + return NULL; + + cd = cue_parse_file(file); + fclose(file); + if (cd == NULL) + return NULL; + + playlist = g_new(struct cue_playlist, 1); + playlist_provider_init(&playlist->base, &cue_playlist_plugin); + playlist->cd = cd; + playlist->next = 1; + + return &playlist->base; +} + +static void +cue_playlist_close(struct playlist_provider *_playlist) +{ + struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + + cd_delete(playlist->cd); + g_free(playlist); +} + +static struct song * +cue_playlist_read(struct playlist_provider *_playlist) +{ + struct cue_playlist *playlist = (struct cue_playlist *)_playlist; + struct Track *track; + struct tag *tag; + const char *filename; + struct song *song; + + track = cd_get_track(playlist->cd, playlist->next); + if (track == NULL) + return NULL; + + tag = cue_tag(playlist->cd, playlist->next); + if (tag == NULL) + return NULL; + + ++playlist->next; + + filename = track_get_filename(track); + if (*filename == 0 || filename[0] == '.' || + strchr(filename, '/') != NULL) { + /* unsafe characters found, bail out */ + tag_free(tag); + return NULL; + } + + song = song_remote_new(filename); + song->tag = tag; + song->start_ms = (track_get_start(track) * 1000) / 75; + song->end_ms = ((track_get_start(track) + track_get_length(track)) + * 1000) / 75; + + return song; +} + +static const char *const cue_playlist_suffixes[] = { + "cue", + NULL +}; + +static const char *const cue_playlist_mime_types[] = { + "application/x-cue", + NULL +}; + +const struct playlist_plugin cue_playlist_plugin = { + .name = "cue", + + .open_uri = cue_playlist_open_uri, + .close = cue_playlist_close, + .read = cue_playlist_read, + + .suffixes = cue_playlist_suffixes, + .mime_types = cue_playlist_mime_types, +}; diff --git a/src/playlist/cue_playlist_plugin.h b/src/playlist/cue_playlist_plugin.h new file mode 100644 index 000000000..2b5319388 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_CUE_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin cue_playlist_plugin; + +#endif diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c new file mode 100644 index 000000000..bd81ff9fb --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/extm3u_playlist_plugin.h" +#include "playlist_plugin.h" +#include "text_input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <string.h> +#include <stdlib.h> + +struct extm3u_playlist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +extm3u_open_stream(struct input_stream *is) +{ + struct extm3u_playlist *playlist; + const char *line; + + playlist = g_new(struct extm3u_playlist, 1); + playlist->tis = text_input_stream_new(is); + + line = text_input_stream_read(playlist->tis); + if (line == NULL || strcmp(line, "#EXTM3U") != 0) { + /* no EXTM3U header: fall back to the plain m3u + plugin */ + text_input_stream_free(playlist->tis); + g_free(playlist); + return NULL; + } + + playlist_provider_init(&playlist->base, &extm3u_playlist_plugin); + return &playlist->base; +} + +static void +extm3u_close(struct playlist_provider *_playlist) +{ + struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +/** + * Parse a EXTINF line. + * + * @param line the rest of the input line after the colon + */ +static struct tag * +extm3u_parse_tag(const char *line) +{ + long duration; + char *endptr; + const char *name; + struct tag *tag; + + duration = strtol(line, &endptr, 10); + if (endptr[0] != ',') + /* malformed line */ + return NULL; + + if (duration < 0) + /* 0 means unknown duration */ + duration = 0; + + name = g_strchug(endptr + 1); + if (*name == 0 && duration == 0) + /* no information available; don't allocate a tag + object */ + return NULL; + + tag = tag_new(); + tag->time = duration; + + /* unfortunately, there is no real specification for the + EXTM3U format, so we must assume that the string after the + comma is opaque, and is just the song name*/ + if (*name != 0) + tag_add_item(tag, TAG_NAME, name); + + return tag; +} + +static struct song * +extm3u_read(struct playlist_provider *_playlist) +{ + struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist; + struct tag *tag = NULL; + const char *line; + struct song *song; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) { + if (tag != NULL) + tag_free(tag); + return NULL; + } + + if (g_str_has_prefix(line, "#EXTINF:")) { + if (tag != NULL) + tag_free(tag); + tag = extm3u_parse_tag(line + 8); + continue; + } + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + song = song_remote_new(line); + song->tag = tag; + return song; +} + +static const char *const extm3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const extm3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin extm3u_playlist_plugin = { + .name = "extm3u", + + .open_stream = extm3u_open_stream, + .close = extm3u_close, + .read = extm3u_read, + + .suffixes = extm3u_suffixes, + .mime_types = extm3u_mime_types, +}; diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h new file mode 100644 index 000000000..07f18eafa --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin extm3u_playlist_plugin; + +#endif diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c new file mode 100644 index 000000000..ec499925a --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/lastfm_playlist_plugin.h" +#include "playlist_plugin.h" +#include "playlist_list.h" +#include "conf.h" +#include "uri.h" +#include "song.h" +#include "input_stream.h" +#include "glib_compat.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +struct lastfm_playlist { + struct playlist_provider base; + + struct input_stream is; + + struct playlist_provider *xspf; +}; + +static struct { + char *user; + char *md5; +} lastfm_config; + +static bool +lastfm_init(const struct config_param *param) +{ + const char *user = config_get_block_string(param, "user", NULL); + const char *passwd = config_get_block_string(param, "password", NULL); + + if (user == NULL || passwd == NULL) { + g_debug("disabling the last.fm playlist plugin " + "because account is not configured"); + return false; + } + + lastfm_config.user = g_uri_escape_string(user, NULL, false); + +#if GLIB_CHECK_VERSION(2,16,0) + if (strlen(passwd) != 32) + lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5, + passwd, strlen(passwd)); + else +#endif + lastfm_config.md5 = g_strdup(passwd); + + return true; +} + +static void +lastfm_finish(void) +{ + g_free(lastfm_config.user); + g_free(lastfm_config.md5); +} + +/** + * Simple data fetcher. + * @param url path or url of data to fetch. + * @return data fetched, or NULL on error. Must be freed with g_free. + */ +static char * +lastfm_get(const char *url) +{ + struct input_stream input_stream; + GError *error = NULL; + bool success; + int ret; + char buffer[4096]; + size_t length = 0, nbytes; + + success = input_stream_open(&input_stream, url, &error); + if (!success) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + while (!input_stream.ready) { + ret = input_stream_buffer(&input_stream, &error); + if (ret < 0) { + input_stream_close(&input_stream); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + do { + nbytes = input_stream_read(&input_stream, buffer + length, + sizeof(buffer) - length, &error); + if (nbytes == 0) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + if (input_stream_eof(&input_stream)) + break; + + /* I/O error */ + input_stream_close(&input_stream); + return NULL; + } + + length += nbytes; + } while (length < sizeof(buffer)); + + input_stream_close(&input_stream); + return g_strndup(buffer, length); +} + +/** + * Ini-style value fetcher. + * @param response data through which to search. + * @param name name of value to search for. + * @return value for param name in param reponse or NULL on error. Free with g_free. + */ +static char * +lastfm_find(const char *response, const char *name) +{ + size_t name_length = strlen(name); + + while (true) { + const char *eol = strchr(response, '\n'); + if (eol == NULL) + return NULL; + + if (strncmp(response, name, name_length) == 0 && + response[name_length] == '=') { + response += name_length + 1; + return g_strndup(response, eol - response); + } + + response = eol + 1; + } +} + +static struct playlist_provider * +lastfm_open_uri(const char *uri) +{ + struct lastfm_playlist *playlist; + GError *error = NULL; + char *p, *q, *response, *session; + bool success; + + /* handshake */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?" + "version=1.1.1&platform=linux&" + "username=", lastfm_config.user, "&" + "passwordmd5=", lastfm_config.md5, "&" + "debug=0&partner=", NULL); + response = lastfm_get(p); + g_free(p); + if (response == NULL) + return NULL; + + /* extract session id from response */ + + session = lastfm_find(response, "session"); + g_free(response); + if (session == NULL) { + g_warning("last.fm handshake failed"); + return NULL; + } + + q = g_uri_escape_string(session, NULL, false); + g_free(session); + session = q; + + g_debug("session='%s'", session); + + /* "adjust" last.fm radio */ + + if (strlen(uri) > 9) { + char *escaped_uri; + + escaped_uri = g_uri_escape_string(uri, NULL, false); + + p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?" + "session=", session, "&url=", escaped_uri, "&debug=0", + NULL); + g_free(escaped_uri); + + response = lastfm_get(p); + g_free(response); + g_free(p); + + if (response == NULL) { + g_free(session); + return NULL; + } + } + + /* create the playlist object */ + + playlist = g_new(struct lastfm_playlist, 1); + playlist_provider_init(&playlist->base, &lastfm_playlist_plugin); + + /* open the last.fm playlist */ + + p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?" + "sk=", session, "&discovery=0&desktop=1.5.1.31879", + NULL); + g_free(session); + + success = input_stream_open(&playlist->is, p, &error); + g_free(p); + + if (!success) { + if (error != NULL) { + g_warning("Failed to load XSPF playlist: %s", + error->message); + g_error_free(error); + } else + g_warning("Failed to load XSPF playlist"); + g_free(playlist); + return NULL; + } + + while (!playlist->is.ready) { + int ret = input_stream_buffer(&playlist->is, &error); + if (ret < 0) { + input_stream_close(&playlist->is); + g_free(playlist); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + if (ret == 0) + /* nothing was buffered - wait */ + g_usleep(10000); + } + + /* last.fm does not send a MIME type, we have to fake it here + :-( */ + g_free(playlist->is.mime); + playlist->is.mime = g_strdup("application/xspf+xml"); + + /* parse the XSPF playlist */ + + playlist->xspf = playlist_list_open_stream(&playlist->is, NULL); + if (playlist->xspf == NULL) { + input_stream_close(&playlist->is); + g_free(playlist); + g_warning("Failed to parse XSPF playlist"); + return NULL; + } + + return &playlist->base; +} + +static void +lastfm_close(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + playlist_plugin_close(playlist->xspf); + input_stream_close(&playlist->is); + g_free(playlist); +} + +static struct song * +lastfm_read(struct playlist_provider *_playlist) +{ + struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist; + + return playlist_plugin_read(playlist->xspf); +} + +static const char *const lastfm_schemes[] = { + "lastfm", + NULL +}; + +const struct playlist_plugin lastfm_playlist_plugin = { + .name = "lastfm", + + .init = lastfm_init, + .finish = lastfm_finish, + .open_uri = lastfm_open_uri, + .close = lastfm_close, + .read = lastfm_read, + + .schemes = lastfm_schemes, +}; diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h new file mode 100644 index 000000000..871a4a043 --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin lastfm_playlist_plugin; + +#endif diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c new file mode 100644 index 000000000..dbabea2e6 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/m3u_playlist_plugin.h" +#include "playlist_plugin.h" +#include "text_input_stream.h" +#include "uri.h" +#include "song.h" + +#include <glib.h> + +struct m3u_playlist { + struct playlist_provider base; + + struct text_input_stream *tis; +}; + +static struct playlist_provider * +m3u_open_stream(struct input_stream *is) +{ + struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1); + + playlist_provider_init(&playlist->base, &m3u_playlist_plugin); + playlist->tis = text_input_stream_new(is); + + return &playlist->base; +} + +static void +m3u_close(struct playlist_provider *_playlist) +{ + struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + + text_input_stream_free(playlist->tis); + g_free(playlist); +} + +static struct song * +m3u_read(struct playlist_provider *_playlist) +{ + struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist; + const char *line; + + do { + line = text_input_stream_read(playlist->tis); + if (line == NULL) + return NULL; + + while (*line != 0 && g_ascii_isspace(*line)) + ++line; + } while (line[0] == '#' || *line == 0); + + return song_remote_new(line); +} + +static const char *const m3u_suffixes[] = { + "m3u", + NULL +}; + +static const char *const m3u_mime_types[] = { + "audio/x-mpegurl", + NULL +}; + +const struct playlist_plugin m3u_playlist_plugin = { + .name = "m3u", + + .open_stream = m3u_open_stream, + .close = m3u_close, + .read = m3u_read, + + .suffixes = m3u_suffixes, + .mime_types = m3u_mime_types, +}; diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h new file mode 100644 index 000000000..3cb4b8874 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin m3u_playlist_plugin; + +#endif diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c new file mode 100644 index 000000000..30c62b76e --- /dev/null +++ b/src/playlist/pls_playlist_plugin.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/pls_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" +#include <glib.h> + +struct pls_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist) +{ + gchar *key; + gchar *value; + int length; + GError *error = NULL; + int num_entries = g_key_file_get_integer(keyfile, "playlist", + "NumberOfEntries", &error); + if (error) { + g_debug("Invalid PLS file: '%s'", error->message); + g_error_free(error); + error = NULL; + + /* Hack to work around shoutcast failure to comform to spec */ + num_entries = g_key_file_get_integer(keyfile, "playlist", + "numberofentries", &error); + if (error) { + g_error_free(error); + error = NULL; + } + } + + while (num_entries > 0) { + struct song *song; + key = g_strdup_printf("File%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + if(error) { + g_debug("Invalid PLS entry %s: '%s'",key, error->message); + g_error_free(error); + g_free(key); + return; + } + g_free(key); + + song = song_remote_new(value); + g_free(value); + + key = g_strdup_printf("Title%i", num_entries); + value = g_key_file_get_string(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && value){ + if (song->tag == NULL) + song->tag = tag_new(); + tag_add_item(song->tag,TAG_TITLE, value); + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + g_free(value); + + key = g_strdup_printf("Length%i", num_entries); + length = g_key_file_get_integer(keyfile, "playlist", key, + &error); + g_free(key); + if(error == NULL && length > 0){ + if (song->tag == NULL) + song->tag = tag_new(); + song->tag->time = length; + } + /* Ignore errors? Most likely value not present */ + if(error) g_error_free(error); + error = NULL; + + playlist->songs = g_slist_prepend(playlist->songs, song); + num_entries--; + } + +} + +static struct playlist_provider * +pls_open_stream(struct input_stream *is) +{ + GError *error = NULL; + size_t nbytes; + char buffer[1024]; + bool success; + GKeyFile *keyfile; + struct pls_playlist *playlist; + GString *kf_data = g_string_new(""); + + do { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_string_free(kf_data, TRUE); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + kf_data = g_string_append_len(kf_data, buffer,nbytes); + /* Limit to 64k */ + } while(kf_data->len < 65536); + + if (kf_data->len == 0) { + g_warning("KeyFile parser failed: No Data"); + g_string_free(kf_data, TRUE); + return NULL; + } + + keyfile = g_key_file_new(); + success = g_key_file_load_from_data(keyfile, + kf_data->str, kf_data->len, + G_KEY_FILE_NONE, &error); + + g_string_free(kf_data, TRUE); + + if (!success) { + g_warning("KeyFile parser failed: %s", error->message); + g_error_free(error); + g_key_file_free(keyfile); + return NULL; + } + + playlist = g_new(struct pls_playlist, 1); + playlist_provider_init(&playlist->base, &pls_playlist_plugin); + playlist->songs = NULL; + + pls_parser(keyfile, playlist); + + g_key_file_free(keyfile); + return &playlist->base; +} + + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +pls_close(struct playlist_provider *_playlist) +{ + struct pls_playlist *playlist = (struct pls_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + + g_free(playlist); + +} + +static struct song * +pls_read(struct playlist_provider *_playlist) +{ + struct pls_playlist *playlist = (struct pls_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const pls_suffixes[] = { + "pls", + NULL +}; + +static const char *const pls_mime_types[] = { + "audio/x-scpls", + NULL +}; + +const struct playlist_plugin pls_playlist_plugin = { + .name = "pls", + + .open_stream = pls_open_stream, + .close = pls_close, + .read = pls_read, + + .suffixes = pls_suffixes, + .mime_types = pls_mime_types, +}; diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h new file mode 100644 index 000000000..7bf1951b8 --- /dev/null +++ b/src/playlist/pls_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin pls_playlist_plugin; + +#endif diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c new file mode 100644 index 000000000..a36463500 --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist/xspf_playlist_plugin.h" +#include "playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "song.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "xspf" + +/** + * This is the state object for the GLib XML parser. + */ +struct xspf_parser { + /** + * The list of songs (in reverse order because that's faster + * while adding). + */ + GSList *songs; + + /** + * The current position in the XML file. + */ + enum { + ROOT, PLAYLIST, TRACKLIST, TRACK, + LOCATION, + } state; + + /** + * The current tag within the "track" element. This is only + * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there + * is no (known) tag. + */ + enum tag_type tag; + + /** + * The current song. It is allocated after the "location" + * element. + */ + struct song *song; +}; + +static void +xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + G_GNUC_UNUSED const gchar **attribute_names, + G_GNUC_UNUSED const gchar **attribute_values, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (strcmp(element_name, "playlist") == 0) + parser->state = PLAYLIST; + + break; + + case PLAYLIST: + if (strcmp(element_name, "trackList") == 0) + parser->state = TRACKLIST; + + break; + + case TRACKLIST: + if (strcmp(element_name, "track") == 0) { + parser->state = TRACK; + parser->song = NULL; + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case TRACK: + if (strcmp(element_name, "location") == 0) + parser->state = LOCATION; + else if (strcmp(element_name, "title") == 0) + parser->tag = TAG_TITLE; + else if (strcmp(element_name, "creator") == 0) + /* TAG_COMPOSER would be more correct + according to the XSPF spec */ + parser->tag = TAG_ARTIST; + else if (strcmp(element_name, "annotation") == 0) + parser->tag = TAG_COMMENT; + else if (strcmp(element_name, "album") == 0) + parser->tag = TAG_ALBUM; + else if (strcmp(element_name, "trackNum") == 0) + parser->tag = TAG_TRACK; + + break; + + case LOCATION: + break; + } +} + +static void +xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case PLAYLIST: + if (strcmp(element_name, "playlist") == 0) + parser->state = ROOT; + + break; + + case TRACKLIST: + if (strcmp(element_name, "tracklist") == 0) + parser->state = PLAYLIST; + + break; + + case TRACK: + if (strcmp(element_name, "track") == 0) { + if (parser->song != NULL) + parser->songs = g_slist_prepend(parser->songs, + parser->song); + + parser->state = TRACKLIST; + } else + parser->tag = TAG_NUM_OF_ITEM_TYPES; + + break; + + case LOCATION: + parser->state = TRACK; + break; + } +} + +static void +xspf_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct xspf_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + case PLAYLIST: + case TRACKLIST: + break; + + case TRACK: + if (parser->song != NULL && + parser->tag != TAG_NUM_OF_ITEM_TYPES) { + if (parser->song->tag == NULL) + parser->song->tag = tag_new(); + tag_add_item_n(parser->song->tag, parser->tag, + text, text_len); + } + + break; + + case LOCATION: + if (parser->song == NULL) { + char *uri = g_strndup(text, text_len); + parser->song = song_remote_new(uri); + g_free(uri); + } + + break; + } +} + +static const GMarkupParser xspf_parser = { + .start_element = xspf_start_element, + .end_element = xspf_end_element, + .text = xspf_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +xspf_parser_destroy(gpointer data) +{ + struct xspf_parser *parser = data; + + if (parser->state >= TRACK && parser->song != NULL) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct xspf_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +xspf_open_stream(struct input_stream *is) +{ + struct xspf_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct xspf_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the XSPF XML file */ + + context = g_markup_parse_context_new(&xspf_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, xspf_parser_destroy); + + while (true) { + nbytes = input_stream_read(is, buffer, sizeof(buffer), &error); + if (nbytes == 0) { + if (error != NULL) { + g_markup_parse_context_free(context); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + + break; + } + + success = g_markup_parse_context_parse(context, buffer, nbytes, + &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + } + + success = g_markup_parse_context_end_parse(context, &error); + if (!success) { + g_warning("XML parser failed: %s", error->message); + g_error_free(error); + g_markup_parse_context_free(context); + return NULL; + } + + /* create a #xspf_playlist object from the parsed song list */ + + playlist = g_new(struct xspf_playlist, 1); + playlist_provider_init(&playlist->base, &xspf_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +xspf_close(struct playlist_provider *_playlist) +{ + struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +xspf_read(struct playlist_provider *_playlist) +{ + struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist; + struct song *song; + + if (playlist->songs == NULL) + return NULL; + + song = playlist->songs->data; + playlist->songs = g_slist_remove(playlist->songs, song); + + return song; +} + +static const char *const xspf_suffixes[] = { + "xspf", + NULL +}; + +static const char *const xspf_mime_types[] = { + "application/xspf+xml", + NULL +}; + +const struct playlist_plugin xspf_playlist_plugin = { + .name = "xspf", + + .open_stream = xspf_open_stream, + .close = xspf_close, + .read = xspf_read, + + .suffixes = xspf_suffixes, + .mime_types = xspf_mime_types, +}; diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h new file mode 100644 index 000000000..c2c36fbed --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin xspf_playlist_plugin; + +#endif diff --git a/src/playlist_control.c b/src/playlist_control.c index 4359611fd..2f75b504f 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist_internal.h" #include "player_control.h" @@ -30,15 +31,7 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "playlist" -enum { - /** - * When the "prev" command is received, rewind the current - * track if this number of seconds has already elapsed. - */ - PLAYLIST_PREV_UNLESS_ELAPSED = 10, -}; - -void stopPlaylist(struct playlist *playlist) +void playlist_stop(struct playlist *playlist) { if (!playlist->playing) return; @@ -46,7 +39,7 @@ void stopPlaylist(struct playlist *playlist) assert(playlist->current >= 0); g_debug("stop"); - playerWait(); + pc_stop(); playlist->queued = -1; playlist->playing = false; @@ -68,11 +61,11 @@ void stopPlaylist(struct playlist *playlist) } } -enum playlist_result playPlaylist(struct playlist *playlist, int song) +enum playlist_result playlist_play(struct playlist *playlist, int song) { unsigned i = song; - clearPlayerError(); + pc_clear_error(); if (song == -1) { /* play any song ("current" song, or the first song */ @@ -83,7 +76,7 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song) if (playlist->playing) { /* already playing: unpause playback, just in case it was paused, and return */ - playerSetPause(0); + pc_set_pause(false); return PLAYLIST_RESULT_SUCCESS; } @@ -115,28 +108,28 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song) playlist->stop_on_error = false; playlist->error_count = 0; - playPlaylistOrderNumber(playlist, i); + playlist_play_order(playlist, i); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -playPlaylistById(struct playlist *playlist, int id) +playlist_play_id(struct playlist *playlist, int id) { int song; if (id == -1) { - return playPlaylist(playlist, id); + return playlist_play(playlist, id); } song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return playPlaylist(playlist, song); + return playlist_play(playlist, song); } void -nextSongInPlaylist(struct playlist *playlist) +playlist_next(struct playlist *playlist) { int next_order; int current; @@ -157,7 +150,7 @@ nextSongInPlaylist(struct playlist *playlist) /* cancel single */ playlist->queue.single = false; /* no song after this one: stop playback */ - stopPlaylist(playlist); + playlist_stop(playlist); /* reset "current song" */ playlist->current = -1; @@ -174,50 +167,42 @@ nextSongInPlaylist(struct playlist *playlist) queue_shuffle_order(&playlist->queue); /* note that playlist->current and playlist->queued are - now invalid, but playPlaylistOrderNumber() will + now invalid, but playlist_play_order() will discard them anyway */ } - playPlaylistOrderNumber(playlist, next_order); + playlist_play_order(playlist, next_order); } /* Consume mode removes each played songs. */ if(playlist->queue.consume) - deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current)); + playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); } -void previousSongInPlaylist(struct playlist *playlist) +void playlist_previous(struct playlist *playlist) { if (!playlist->playing) return; - if (g_timer_elapsed(playlist->prev_elapsed, NULL) >= 1.0 && - getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) { - /* re-start playing the current song (just like the - "prev" button on CD players) */ + assert(queue_length(&playlist->queue) > 0); - playPlaylistOrderNumber(playlist, playlist->current); + if (playlist->current > 0) { + /* play the preceding song */ + playlist_play_order(playlist, + playlist->current - 1); + } else if (playlist->queue.repeat) { + /* play the last song in "repeat" mode */ + playlist_play_order(playlist, + queue_length(&playlist->queue) - 1); } else { - if (playlist->current > 0) { - /* play the preceding song */ - playPlaylistOrderNumber(playlist, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playPlaylistOrderNumber(playlist, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playPlaylistOrderNumber(playlist, playlist->current); - } + /* re-start playing the current song if it's + the first one */ + playlist_play_order(playlist, playlist->current); } - - g_timer_start(playlist->prev_elapsed); } enum playlist_result -seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) +playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time) { const struct song *queued; unsigned i; @@ -233,7 +218,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) else i = song; - clearPlayerError(); + pc_clear_error(); playlist->stop_on_error = true; playlist->error_count = 0; @@ -241,7 +226,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) /* seeking is not within the current song - first start playing the new song */ - playPlaylistOrderNumber(playlist, i); + playlist_play_order(playlist, i); queued = NULL; } @@ -259,11 +244,11 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time) } enum playlist_result -seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time) +playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return seekSongInPlaylist(playlist, song, seek_time); + return playlist_seek_song(playlist, song, seek_time); } diff --git a/src/playlist_edit.c b/src/playlist_edit.c index b83dc0933..956c33d8e 100644 --- a/src/playlist_edit.c +++ b/src/playlist_edit.c @@ -23,6 +23,7 @@ * */ +#include "config.h" #include "playlist_internal.h" #include "player_control.h" #include "database.h" @@ -35,16 +36,16 @@ #include <unistd.h> #include <stdlib.h> -static void incrPlaylistVersion(struct playlist *playlist) +static void playlist_increment_version(struct playlist *playlist) { queue_increment_version(&playlist->queue); idle_add(IDLE_PLAYLIST); } -void clearPlaylist(struct playlist *playlist) +void playlist_clear(struct playlist *playlist) { - stopPlaylist(playlist); + playlist_stop(playlist); /* make sure there are no references to allocated songs anymore */ @@ -58,7 +59,7 @@ void clearPlaylist(struct playlist *playlist) playlist->current = -1; - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); } #ifndef WIN32 @@ -86,41 +87,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid, if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; - return addSongToPlaylist(playlist, song, added_id); + return playlist_append_song(playlist, song, added_id); } #endif -static struct song * -song_by_url(const char *url) -{ - struct song *song; - - song = db_get_song(url); - if (song != NULL) - return song; - - if (uri_has_scheme(url)) - return song_remote_new(url); - - return NULL; -} - -enum playlist_result -addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id) -{ - struct song *song; - - g_debug("add to playlist: %s", url); - - song = song_by_url(url); - if (song == NULL) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return addSongToPlaylist(playlist, song, added_id); -} - enum playlist_result -addSongToPlaylist(struct playlist *playlist, +playlist_append_song(struct playlist *playlist, struct song *song, unsigned *added_id) { const struct song *queued; @@ -147,7 +119,7 @@ addSongToPlaylist(struct playlist *playlist, queue_length(&playlist->queue)); } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -157,8 +129,38 @@ addSongToPlaylist(struct playlist *playlist, return PLAYLIST_RESULT_SUCCESS; } +static struct song * +song_by_uri(const char *uri) +{ + struct song *song; + + song = db_get_song(uri); + if (song != NULL) + return song; + + if (uri_has_scheme(uri)) + return song_remote_new(uri); + + return NULL; +} + +enum playlist_result +playlist_append_uri(struct playlist *playlist, const char *uri, + unsigned *added_id) +{ + struct song *song; + + g_debug("add to playlist: %s", uri); + + song = song_by_uri(uri); + if (song == NULL) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return playlist_append_song(playlist, song, added_id); +} + enum playlist_result -swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) +playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2) { const struct song *queued; @@ -188,7 +190,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) playlist->current = song1; } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -196,7 +198,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2) } enum playlist_result -swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2) +playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2) { int song1 = queue_id_to_position(&playlist->queue, id1); int song2 = queue_id_to_position(&playlist->queue, id2); @@ -204,28 +206,25 @@ swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2) if (song1 < 0 || song2 < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return swapSongsInPlaylist(playlist, song1, song2); + return playlist_swap_songs(playlist, song1, song2); } -enum playlist_result -deleteFromPlaylist(struct playlist *playlist, unsigned song) +static void +playlist_delete_internal(struct playlist *playlist, unsigned song, + const struct song **queued_p) { - const struct song *queued; unsigned songOrder; - if (song >= queue_length(&playlist->queue)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); + assert(song < queue_length(&playlist->queue)); songOrder = queue_position_to_order(&playlist->queue, song); if (playlist->playing && playlist->current == (int)songOrder) { - bool paused = getPlayerState() == PLAYER_STATE_PAUSE; + bool paused = pc_get_state() == PLAYER_STATE_PAUSE; /* the current song is going to be deleted: stop the player */ - playerWait(); + pc_stop(); playlist->playing = false; /* see which song is going to be played instead */ @@ -237,13 +236,13 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song) if (playlist->current >= 0 && !paused) /* play the song after the deleted one */ - playPlaylistOrderNumber(playlist, playlist->current); + playlist_play_order(playlist, playlist->current); else /* no songs left to play, stop playback completely */ - stopPlaylist(playlist); + playlist_stop(playlist); - queued = NULL; + *queued_p = NULL; } else if (playlist->current == (int)songOrder) /* there's a "current song" but we're not playing currently - clear "current" */ @@ -256,41 +255,80 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song) queue_delete(&playlist->queue, song); - incrPlaylistVersion(playlist); - /* update the "current" and "queued" variables */ if (playlist->current > (int)songOrder) { playlist->current--; } +} + +enum playlist_result +playlist_delete(struct playlist *playlist, unsigned song) +{ + const struct song *queued; + + if (song >= queue_length(&playlist->queue)) + return PLAYLIST_RESULT_BAD_RANGE; + + queued = playlist_get_queued_song(playlist); + + playlist_delete_internal(playlist, song, &queued); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); return PLAYLIST_RESULT_SUCCESS; } enum playlist_result -deleteFromPlaylistById(struct playlist *playlist, unsigned id) +playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end) +{ + const struct song *queued; + + if (start >= queue_length(&playlist->queue)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (end > queue_length(&playlist->queue)) + end = queue_length(&playlist->queue); + + if (start >= end) + return PLAYLIST_RESULT_SUCCESS; + + queued = playlist_get_queued_song(playlist); + + do { + playlist_delete_internal(playlist, --end, &queued); + } while (end != start); + + playlist_increment_version(playlist); + playlist_update_queued_song(playlist, queued); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_delete_id(struct playlist *playlist, unsigned id) { int song = queue_id_to_position(&playlist->queue, id); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return deleteFromPlaylist(playlist, song); + return playlist_delete(playlist, song); } void -deleteASongFromPlaylist(struct playlist *playlist, const struct song *song) +playlist_delete_song(struct playlist *playlist, const struct song *song) { for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i) if (song == queue_get(&playlist->queue, i)) - deleteFromPlaylist(playlist, i); + playlist_delete(playlist, i); pc_song_deleted(song); } enum playlist_result -moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to) +playlist_move_range(struct playlist *playlist, + unsigned start, unsigned end, int to) { const struct song *queued; int currentSong; @@ -342,7 +380,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, } } - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); @@ -350,16 +388,17 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, } enum playlist_result -moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to) +playlist_move_id(struct playlist *playlist, unsigned id1, int to) { int song = queue_id_to_position(&playlist->queue, id1); if (song < 0) return PLAYLIST_RESULT_NO_SUCH_SONG; - return moveSongRangeInPlaylist(playlist, song, song+1, to); + return playlist_move_range(playlist, song, song+1, to); } -void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end) +void +playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end) { const struct song *queued; @@ -399,7 +438,7 @@ void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end) queue_shuffle_range(&playlist->queue, start, end); - incrPlaylistVersion(playlist); + playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); } diff --git a/src/playlist_global.c b/src/playlist_global.c index fa810bbc3..8cfbf2c5d 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist.h" #include "playlist_state.h" #include "event_pipe.h" @@ -37,10 +38,11 @@ playlist_tag_event(void) static void playlist_event(void) { - syncPlayerAndPlaylist(&g_playlist); + playlist_sync(&g_playlist); } -void initPlaylist(void) +void +playlist_global_init(void) { playlist_init(&g_playlist); @@ -48,17 +50,8 @@ void initPlaylist(void) event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); } -void finishPlaylist(void) +void +playlist_global_finish(void) { playlist_finish(&g_playlist); } - -void savePlaylistState(FILE *fp) -{ - playlist_state_save(fp, &g_playlist); -} - -void readPlaylistState(FILE *fp) -{ - playlist_state_restore(fp, &g_playlist); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h index af880691b..8b2f780cc 100644 --- a/src/playlist_internal.h +++ b/src/playlist_internal.h @@ -47,6 +47,6 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev); void -playPlaylistOrderNumber(struct playlist *playlist, int orderNum); +playlist_play_order(struct playlist *playlist, int orderNum); #endif diff --git a/src/playlist_list.c b/src/playlist_list.c new file mode 100644 index 000000000..e0013b619 --- /dev/null +++ b/src/playlist_list.c @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_list.h" +#include "playlist_plugin.h" +#include "playlist/extm3u_playlist_plugin.h" +#include "playlist/m3u_playlist_plugin.h" +#include "playlist/xspf_playlist_plugin.h" +#include "playlist/lastfm_playlist_plugin.h" +#include "playlist/pls_playlist_plugin.h" +#include "playlist/asx_playlist_plugin.h" +#include "playlist/cue_playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "utils.h" +#include "conf.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> +#include <stdio.h> + +static const struct playlist_plugin *const playlist_plugins[] = { + &extm3u_playlist_plugin, + &m3u_playlist_plugin, + &xspf_playlist_plugin, + &pls_playlist_plugin, + &asx_playlist_plugin, +#ifdef ENABLE_LASTFM + &lastfm_playlist_plugin, +#endif +#ifdef HAVE_CUE + &cue_playlist_plugin, +#endif + NULL +}; + +/** which plugins have been initialized successfully? */ +static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)]; + +/** + * Find the "playlist" configuration block for the specified plugin. + * + * @param plugin_name the name of the playlist plugin + * @return the configuration block, or NULL if none was configured + */ +static const struct config_param * +playlist_plugin_config(const char *plugin_name) +{ + const struct config_param *param = NULL; + + assert(plugin_name != NULL); + + while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) { + const char *name = + config_get_block_string(param, "name", NULL); + if (name == NULL) + g_error("playlist configuration without 'plugin' name in line %d", + param->line); + + if (strcmp(name, plugin_name) == 0) + return param; + } + + return NULL; +} + +void +playlist_list_global_init(void) +{ + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + const struct config_param *param = + playlist_plugin_config(plugin->name); + + if (!config_get_block_bool(param, "enabled", true)) + /* the plugin is disabled in mpd.conf */ + continue; + + playlist_plugins_enabled[i] = + playlist_plugin_init(playlist_plugins[i], param); + } +} + +void +playlist_list_global_finish(void) +{ + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) + if (playlist_plugins_enabled[i]) + playlist_plugin_finish(playlist_plugins[i]); +} + +/* g_uri_parse_scheme() was introduced in GLib 2.16 */ +#if !GLIB_CHECK_VERSION(2,16,0) +static char * +g_uri_parse_scheme(const char *uri) +{ + const char *end = strstr(uri, "://"); + if (end == NULL) + return NULL; + return g_strndup(uri, end - uri); +} +#endif + +static struct playlist_provider * +playlist_list_open_uri_scheme(const char *uri, bool *tried) +{ + char *scheme; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + scheme = g_uri_parse_scheme(uri); + if (scheme == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + assert(!tried[i]); + + if (playlist_plugins_enabled[i] && plugin->open_uri != NULL && + plugin->schemes != NULL && + string_array_contains(plugin->schemes, scheme)) { + playlist = playlist_plugin_open_uri(plugin, uri); + if (playlist != NULL) + break; + + tried[i] = true; + } + } + + g_free(scheme); + return playlist; +} + +static struct playlist_provider * +playlist_list_open_uri_suffix(const char *uri, const bool *tried) +{ + const char *suffix; + struct playlist_provider *playlist = NULL; + + assert(uri != NULL); + + suffix = uri_get_suffix(uri); + if (suffix == NULL) + return NULL; + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && !tried[i] && + plugin->open_uri != NULL && plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + playlist = playlist_plugin_open_uri(plugin, uri); + if (playlist != NULL) + break; + } + } + + return playlist; +} + +struct playlist_provider * +playlist_list_open_uri(const char *uri) +{ + struct playlist_provider *playlist; + /** this array tracks which plugins have already been tried by + playlist_list_open_uri_scheme() */ + bool tried[G_N_ELEMENTS(playlist_plugins) - 1]; + + assert(uri != NULL); + + memset(tried, false, sizeof(tried)); + + playlist = playlist_list_open_uri_scheme(uri, tried); + if (playlist == NULL) + playlist = playlist_list_open_uri_suffix(uri, tried); + + return playlist; +} + +static struct playlist_provider * +playlist_list_open_stream_mime(struct input_stream *is) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(is->mime != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && + plugin->open_stream != NULL && + plugin->mime_types != NULL && + string_array_contains(plugin->mime_types, is->mime)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +static struct playlist_provider * +playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(suffix != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && + plugin->open_stream != NULL && + plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) { + /* rewind the stream, so each plugin gets a + fresh start */ + input_stream_seek(is, 0, SEEK_SET, NULL); + + playlist = playlist_plugin_open_stream(plugin, is); + if (playlist != NULL) + return playlist; + } + } + + return NULL; +} + +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri) +{ + const char *suffix; + struct playlist_provider *playlist; + + if (is->mime != NULL) { + playlist = playlist_list_open_stream_mime(is); + if (playlist != NULL) + return playlist; + } + + suffix = uri != NULL ? uri_get_suffix(uri) : NULL; + if (suffix != NULL) { + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist != NULL) + return playlist; + } + + return NULL; +} + +static bool +playlist_suffix_supported(const char *suffix) +{ + assert(suffix != NULL); + + for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) { + const struct playlist_plugin *plugin = playlist_plugins[i]; + + if (playlist_plugins_enabled[i] && plugin->suffixes != NULL && + string_array_contains(plugin->suffixes, suffix)) + return true; + } + + return false; +} + +struct playlist_provider * +playlist_list_open_path(struct input_stream *is, const char *path_fs) +{ + GError *error = NULL; + const char *suffix; + struct playlist_provider *playlist; + + assert(path_fs != NULL); + + suffix = uri_get_suffix(path_fs); + if (suffix == NULL || !playlist_suffix_supported(suffix)) + return NULL; + + if (!input_stream_open(is, path_fs, &error)) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + while (!is->ready) { + int ret = input_stream_buffer(is, &error); + if (ret < 0) { + input_stream_close(is); + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + playlist = playlist_list_open_stream_suffix(is, suffix); + if (playlist == NULL) + input_stream_close(is); + + return playlist; +} diff --git a/src/playlist_list.h b/src/playlist_list.h new file mode 100644 index 000000000..ebfc5c509 --- /dev/null +++ b/src/playlist_list.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_LIST_H +#define MPD_PLAYLIST_LIST_H + +struct playlist_provider; +struct input_stream; + +/** + * Initializes all playlist plugins. + */ +void +playlist_list_global_init(void); + +/** + * Deinitializes all playlist plugins. + */ +void +playlist_list_global_finish(void); + +/** + * Opens a playlist by its URI. + */ +struct playlist_provider * +playlist_list_open_uri(const char *uri); + +/** + * Opens a playlist from an input stream. + * + * @param is an #input_stream object which is open and ready + * @param uri optional URI which was used to open the stream; may be + * used to select the appropriate playlist plugin + */ +struct playlist_provider * +playlist_list_open_stream(struct input_stream *is, const char *uri); + +/** + * Opens a playlist from a local file. + * + * @param is an uninitialized #input_stream object (must be closed + * with input_stream_close() if this function succeeds) + * @param path_fs the path of the playlist file + * @return a playlist, or NULL on error + */ +struct playlist_provider * +playlist_list_open_path(struct input_stream *is, const char *path_fs); + +#endif diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h new file mode 100644 index 000000000..3515af109 --- /dev/null +++ b/src/playlist_plugin.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_PLUGIN_H + +#include <stdbool.h> +#include <stddef.h> + +struct config_param; +struct input_stream; +struct tag; + +/** + * An object which provides the contents of a playlist. + */ +struct playlist_provider { + const struct playlist_plugin *plugin; +}; + +static inline void +playlist_provider_init(struct playlist_provider *playlist, + const struct playlist_plugin *plugin) +{ + playlist->plugin = plugin; +} + +struct playlist_plugin { + const char *name; + + /** + * Initialize the plugin. Optional method. + * + * @param param a configuration block for this plugin, or NULL + * if none is configured + * @return true if the plugin was initialized successfully, + * false if the plugin is not available + */ + bool (*init)(const struct config_param *param); + + /** + * Deinitialize a plugin which was initialized successfully. + * Optional method. + */ + void (*finish)(void); + + /** + * Opens the playlist on the specified URI. This URI has + * either matched one of the schemes or one of the suffixes. + */ + struct playlist_provider *(*open_uri)(const char *uri); + + /** + * Opens the playlist in the specified input stream. It has + * either matched one of the suffixes or one of the MIME + * types. + */ + struct playlist_provider *(*open_stream)(struct input_stream *is); + + void (*close)(struct playlist_provider *playlist); + + struct song *(*read)(struct playlist_provider *playlist); + + const char *const*schemes; + const char *const*suffixes; + const char *const*mime_types; +}; + +/** + * Initialize a plugin. + * + * @param param a configuration block for this plugin, or NULL if none + * is configured + * @return true if the plugin was initialized successfully, false if + * the plugin is not available + */ +static inline bool +playlist_plugin_init(const struct playlist_plugin *plugin, + const struct config_param *param) +{ + return plugin->init != NULL + ? plugin->init(param) + : true; +} + +/** + * Deinitialize a plugin which was initialized successfully. + */ +static inline void +playlist_plugin_finish(const struct playlist_plugin *plugin) +{ + if (plugin->finish != NULL) + plugin->finish(); +} + +static inline struct playlist_provider * +playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri) +{ + return plugin->open_uri(uri); +} + +static inline struct playlist_provider * +playlist_plugin_open_stream(const struct playlist_plugin *plugin, + struct input_stream *is) +{ + return plugin->open_stream(is); +} + +static inline void +playlist_plugin_close(struct playlist_provider *playlist) +{ + playlist->plugin->close(playlist); +} + +static inline struct song * +playlist_plugin_read(struct playlist_provider *playlist) +{ + return playlist->plugin->read(playlist); +} + +#endif diff --git a/src/playlist_print.c b/src/playlist_print.c index fd61ab62c..f414ee0ac 100644 --- a/src/playlist_print.c +++ b/src/playlist_print.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_print.h" #include "queue_print.h" #include "stored_playlist.h" @@ -69,7 +70,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist, bool playlist_print_current(struct client *client, const struct playlist *playlist) { - int current_position = getPlaylistCurrentSong(playlist); + int current_position = playlist_get_current_song(playlist); if (current_position < 0) return false; diff --git a/src/playlist_queue.c b/src/playlist_queue.c new file mode 100644 index 000000000..af7444e10 --- /dev/null +++ b/src/playlist_queue.c @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "playlist_queue.h" +#include "playlist_list.h" +#include "playlist_plugin.h" +#include "stored_playlist.h" +#include "database.h" +#include "mapper.h" +#include "song.h" +#include "tag.h" +#include "uri.h" +#include "ls.h" +#include "input_stream.h" + +static void +merge_song_metadata(struct song *dest, const struct song *base, + const struct song *add) +{ + dest->tag = base->tag != NULL + ? (add->tag != NULL + ? tag_merge(base->tag, add->tag) + : tag_dup(base->tag)) + : (add->tag != NULL + ? tag_dup(add->tag) + : NULL); + + dest->mtime = base->mtime; + dest->start_ms = add->start_ms; + dest->end_ms = add->end_ms; +} + +static struct song * +apply_song_metadata(struct song *dest, const struct song *src) +{ + struct song *tmp; + + assert(dest != NULL); + assert(src != NULL); + + if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0) + return dest; + + if (song_in_database(dest)) { + char *path_fs = map_song_fs(dest); + if (path_fs == NULL) + return dest; + + tmp = song_file_new(path_fs, NULL); + merge_song_metadata(tmp, dest, src); + } else { + tmp = song_file_new(dest->uri, NULL); + merge_song_metadata(tmp, dest, src); + song_free(dest); + } + + return tmp; +} + +/** + * Verifies the song, returns NULL if it is unsafe. Translate the + * song to a new song object within the database, if it is a local + * file. The old song object is freed. + */ +static struct song * +check_translate_song(struct song *song, const char *base_uri) +{ + struct song *dest; + char *uri; + + if (song_in_database(song)) + /* already ok */ + return song; + + if (uri_has_scheme(song->uri)) { + if (uri_supported_scheme(song->uri)) + /* valid remote song */ + return song; + else { + /* unsupported remote song */ + song_free(song); + return NULL; + } + } + + if (g_path_is_absolute(song->uri)) { + /* local files must be relative to the music + directory */ + song_free(song); + return NULL; + } + + if (base_uri != NULL) + uri = g_build_filename(base_uri, song->uri, NULL); + else + uri = g_strdup(song->uri); + + if (uri_has_scheme(base_uri)) { + dest = song_remote_new(uri); + g_free(uri); + } else { + dest = db_get_song(uri); + g_free(uri); + if (dest == NULL) { + /* not found in database */ + song_free(song); + return dest; + } + } + + dest = apply_song_metadata(dest, song); + song_free(song); + + return dest; +} + +enum playlist_result +playlist_load_into_queue(const char *uri, struct playlist_provider *source, + struct playlist *dest) +{ + enum playlist_result result; + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + while ((song = playlist_plugin_read(source)) != NULL) { + song = check_translate_song(song, base_uri); + if (song == NULL) + continue; + + result = playlist_append_song(dest, song, NULL); + if (result != PLAYLIST_RESULT_SUCCESS) { + if (!song_in_database(song)) + song_free(song); + g_free(base_uri); + return result; + } + } + + g_free(base_uri); + + return PLAYLIST_RESULT_SUCCESS; +} + +static enum playlist_result +playlist_open_remote_into_queue(const char *uri, struct playlist *dest) +{ + GError *error = NULL; + struct playlist_provider *playlist; + bool stream = false; + struct input_stream is; + enum playlist_result result; + + assert(uri_has_scheme(uri)); + + playlist = playlist_list_open_uri(uri); + if (playlist == NULL) { + stream = input_stream_open(&is, uri, &error); + if (!stream) { + if (error != NULL) { + g_warning("Failed to open %s: %s", + uri, error->message); + g_error_free(error); + } + + return PLAYLIST_RESULT_NO_SUCH_LIST; + } + + playlist = playlist_list_open_stream(&is, uri); + if (playlist == NULL) { + input_stream_close(&is); + return PLAYLIST_RESULT_NO_SUCH_LIST; + } + } + + result = playlist_load_into_queue(uri, playlist, dest); + playlist_plugin_close(playlist); + + if (stream) + input_stream_close(&is); + + return result; +} + +static enum playlist_result +playlist_open_path_into_queue(const char *path_fs, const char *uri, + struct playlist *dest) +{ + struct playlist_provider *playlist; + struct input_stream is; + enum playlist_result result; + + if ((playlist = playlist_list_open_uri(path_fs)) != NULL) + result = playlist_load_into_queue(uri, playlist, dest); + else if ((playlist = playlist_list_open_path(&is, path_fs)) != NULL) { + result = playlist_load_into_queue(uri, playlist, dest); + input_stream_close(&is); + } else + return PLAYLIST_RESULT_NO_SUCH_LIST; + + playlist_plugin_close(playlist); + + return result; +} + +/** + * Load a playlist from the configured playlist directory. + */ +static enum playlist_result +playlist_open_local_into_queue(const char *uri, struct playlist *dest) +{ + const char *playlist_directory_fs; + char *path_fs; + enum playlist_result result; + + assert(spl_valid_name(uri)); + + playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs == NULL) + return PLAYLIST_RESULT_DISABLED; + + path_fs = g_build_filename(playlist_directory_fs, uri, NULL); + result = playlist_open_path_into_queue(path_fs, NULL, dest); + g_free(path_fs); + + return result; +} + +/** + * Load a playlist from the configured music directory. + */ +static enum playlist_result +playlist_open_local_into_queue2(const char *uri, struct playlist *dest) +{ + char *path_fs; + enum playlist_result result; + + assert(uri_safe_local(uri)); + + path_fs = map_uri_fs(uri); + if (path_fs == NULL) + return PLAYLIST_RESULT_NO_SUCH_LIST; + + result = playlist_open_path_into_queue(path_fs, uri, dest); + g_free(path_fs); + + return result; +} + +enum playlist_result +playlist_open_into_queue(const char *uri, struct playlist *dest) +{ + if (uri_has_scheme(uri)) + return playlist_open_remote_into_queue(uri, dest); + + if (spl_valid_name(uri)) { + enum playlist_result result = + playlist_open_local_into_queue(uri, dest); + if (result != PLAYLIST_RESULT_NO_SUCH_LIST) + return result; + } + + if (uri_safe_local(uri)) + return playlist_open_local_into_queue2(uri, dest); + + return PLAYLIST_RESULT_NO_SUCH_LIST; +} diff --git a/src/playlist_queue.h b/src/playlist_queue.h new file mode 100644 index 000000000..b1fc9dde9 --- /dev/null +++ b/src/playlist_queue.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/*! \file + * \brief Glue between playlist plugin and the play queue + */ + +#ifndef MPD_PLAYLIST_QUEUE_H +#define MPD_PLAYLIST_QUEUE_H + +#include "playlist.h" + +struct playlist_provider; +struct playlist; + +/** + * Loads the contents of a playlist and append it to the specified + * play queue. + * + * @param uri the URI of the playlist, used to resolve relative song + * URIs + */ +enum playlist_result +playlist_load_into_queue(const char *uri, struct playlist_provider *source, + struct playlist *dest); + +/** + * Opens a playlist with a playlist plugin and append to the specified + * play queue. + */ +enum playlist_result +playlist_open_into_queue(const char *uri, struct playlist *dest); + +#endif + diff --git a/src/playlist_save.c b/src/playlist_save.c index 13dbc721d..235842ae8 100644 --- a/src/playlist_save.c +++ b/src/playlist_save.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_save.h" #include "stored_playlist.h" #include "song.h" @@ -54,7 +55,7 @@ playlist_print_uri(FILE *file, const char *uri) char *s; if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) && - uri[0] != '/') + !g_path_is_absolute(uri)) s = map_uri_fs(uri); else s = utf8_to_fs_charset(uri); @@ -118,7 +119,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) for (unsigned i = 0; i < list->len; ++i) { const char *temp = g_ptr_array_index(list, i); - if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { /* for windows compatibility, convert slashes */ char *temp2 = g_strdup(temp); char *p = temp2; @@ -127,7 +128,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8) *p = '/'; p++; } - if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { + if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) { g_warning("can't add file \"%s\"", temp2); } g_free(temp2); diff --git a/src/playlist_state.c b/src/playlist_state.c index af0f7982b..ea8b7e4f9 100644 --- a/src/playlist_state.c +++ b/src/playlist_state.c @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist_state.h" #include "playlist.h" #include "player_control.h" @@ -51,10 +52,14 @@ void playlist_state_save(FILE *fp, const struct playlist *playlist) { + struct player_status player_status; + + pc_get_status(&player_status); + fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE); if (playlist->playing) { - switch (getPlayerState()) { + switch (player_status.state) { case PLAYER_STATE_PAUSE: fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE); break; @@ -65,10 +70,16 @@ playlist_state_save(FILE *fp, const struct playlist *playlist) queue_order_to_position(&playlist->queue, playlist->current)); fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME, - getPlayerElapsedTime()); - } else + (int)player_status.elapsed_time); + } else { fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP); + if (playlist->current >= 0) + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT, + queue_order_to_position(&playlist->queue, + playlist->current)); + } + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, playlist->queue.random); fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, @@ -78,7 +89,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist) fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME, playlist->queue.consume); fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE, - (int)(getPlayerCrossFade())); + (int)(pc_get_cross_fade())); fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN); queue_save(fp, &playlist->queue); fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END); @@ -109,8 +120,8 @@ playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer) queue_increment_version(&playlist->queue); } -void -playlist_state_restore(FILE *fp, struct playlist *playlist) +bool +playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist) { int current = -1; int seek_time = 0; @@ -118,50 +129,45 @@ playlist_state_restore(FILE *fp, struct playlist *playlist) char buffer[PLAYLIST_BUFFER_SIZE]; bool random_mode = false; + if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) + return false; + + line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; + + if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) + state = PLAYER_STATE_PLAY; + else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) + state = PLAYER_STATE_PAUSE; + while (fgets(buffer, sizeof(buffer), fp)) { g_strchomp(buffer); - if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) { - if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), - PLAYLIST_STATE_FILE_STATE_PLAY) == 0) { - state = PLAYER_STATE_PLAY; - } else - if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), - PLAYLIST_STATE_FILE_STATE_PAUSE) - == 0) { - state = PLAYER_STATE_PAUSE; - } - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) { + if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) { seek_time = atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)])); } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) { if (strcmp (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]), "1") == 0) { - setPlaylistRepeatStatus(playlist, true); + playlist_set_repeat(playlist, true); } else - setPlaylistRepeatStatus(playlist, false); + playlist_set_repeat(playlist, false); } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) { if (strcmp (&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]), "1") == 0) { - setPlaylistSingleStatus(playlist, true); + playlist_set_single(playlist, true); } else - setPlaylistSingleStatus(playlist, false); + playlist_set_single(playlist, false); } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) { if (strcmp (&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]), "1") == 0) { - setPlaylistConsumeStatus(playlist, true); + playlist_set_consume(playlist, true); } else - setPlaylistConsumeStatus(playlist, false); + playlist_set_consume(playlist, false); } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) { - setPlayerCrossFade(atoi - (& - (buffer - [strlen - (PLAYLIST_STATE_FILE_CROSSFADE)]))); + pc_set_cross_fade(atoi(buffer + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) { random_mode = strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM), @@ -172,24 +178,56 @@ playlist_state_restore(FILE *fp, struct playlist *playlist) (PLAYLIST_STATE_FILE_CURRENT)])); } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - if (state == PLAYER_STATE_STOP) - current = -1; playlist_state_load(fp, playlist, buffer); } } - setPlaylistRandomStatus(playlist, random_mode); + playlist_set_random(playlist, random_mode); - if (state != PLAYER_STATE_STOP && !queue_is_empty(&playlist->queue)) { + if (!queue_is_empty(&playlist->queue)) { if (!queue_valid_position(&playlist->queue, current)) current = 0; - if (seek_time == 0) - playPlaylist(playlist, current); + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PLAYER_STATE_STOP) + pc_update_audio(); + + if (state == PLAYER_STATE_STOP /* && config_option */) + playlist->current = current; + else if (seek_time == 0) + playlist_play(playlist, current); else - seekSongInPlaylist(playlist, current, seek_time); + playlist_seek_song(playlist, current, seek_time); if (state == PLAYER_STATE_PAUSE) - playerPause(); + pc_pause(); } + + return true; +} + +unsigned +playlist_state_get_hash(const struct playlist *playlist) +{ + struct player_status player_status; + + pc_get_status(&player_status); + + return playlist->queue.version ^ + (player_status.state != PLAYER_STATE_STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist->current >= 0 + ? (queue_order_to_position(&playlist->queue, + playlist->current) << 16) + : 0) ^ + ((int)pc_get_cross_fade() << 20) ^ + (player_status.state << 24) ^ + (playlist->queue.random << 27) ^ + (playlist->queue.repeat << 28) ^ + (playlist->queue.single << 29) ^ + (playlist->queue.consume << 30) ^ + (playlist->queue.random << 31); } diff --git a/src/playlist_state.h b/src/playlist_state.h index 989430264..d116aaeb1 100644 --- a/src/playlist_state.h +++ b/src/playlist_state.h @@ -25,6 +25,7 @@ #ifndef PLAYLIST_STATE_H #define PLAYLIST_STATE_H +#include <stdbool.h> #include <stdio.h> struct playlist; @@ -32,7 +33,16 @@ struct playlist; void playlist_state_save(FILE *fp, const struct playlist *playlist); -void -playlist_state_restore(FILE *fp, struct playlist *playlist); +bool +playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist); + +/** + * Generates a hash number for the current state of the playlist and + * the playback options. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +playlist_state_get_hash(const struct playlist *playlist); #endif diff --git a/src/poison.h b/src/poison.h index 5919c3cbe..ca6d73937 100644 --- a/src/poison.h +++ b/src/poison.h @@ -20,8 +20,9 @@ #ifndef MPD_POISON_H #define MPD_POISON_H +#include "check.h" + #ifndef NDEBUG -#include "config.h" #ifdef HAVE_VALGRIND_MEMCHECK_H #include <valgrind/memcheck.h> diff --git a/src/queue.c b/src/queue.c index 141222a80..bd89544ca 100644 --- a/src/queue.c +++ b/src/queue.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue.h" #include "song.h" @@ -33,7 +34,7 @@ queue_generate_id(const struct queue *queue) if (cur >= queue->max_length * QUEUE_HASH_MULT) cur = 0; - } while (queue->idToPosition[cur] != -1); + } while (queue->id_to_position[cur] != -1); return cur; } @@ -111,7 +112,7 @@ queue_append(struct queue *queue, struct song *song) }; queue->order[queue->length] = queue->length; - queue->idToPosition[id] = queue->length; + queue->id_to_position[id] = queue->length; ++queue->length; @@ -132,8 +133,8 @@ queue_swap(struct queue *queue, unsigned position1, unsigned position2) queue->items[position1].version = queue->version; queue->items[position2].version = queue->version; - queue->idToPosition[id1] = position2; - queue->idToPosition[id2] = position1; + queue->id_to_position[id1] = position2; + queue->id_to_position[id2] = position1; } static void @@ -143,7 +144,7 @@ queue_move_song_to(struct queue *queue, unsigned from, unsigned to) queue->items[to] = queue->items[from]; queue->items[to].version = queue->version; - queue->idToPosition[from_id] = to; + queue->id_to_position[from_id] = to; } void @@ -163,7 +164,7 @@ queue_move(struct queue *queue, unsigned from, unsigned to) /* put song at _to_ */ - queue->idToPosition[item.id] = to; + queue->id_to_position[item.id] = to; queue->items[to] = item; queue->items[to].version = queue->version; @@ -203,7 +204,7 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) // Copy the original block back in, starting at to. for (unsigned i = start; i< end; i++) { - queue->idToPosition[items[i-start].id] = to + i - start; + queue->id_to_position[items[i-start].id] = to + i - start; queue->items[to + i - start] = items[i-start]; queue->items[to + i - start].version = queue->version; } @@ -243,7 +244,7 @@ queue_delete(struct queue *queue, unsigned position) /* release the song id */ - queue->idToPosition[id] = -1; + queue->id_to_position[id] = -1; /* delete song from songs array */ @@ -271,7 +272,7 @@ queue_clear(struct queue *queue) if (!song_in_database(item->song)) song_free(item->song); - queue->idToPosition[item->id] = -1; + queue->id_to_position[item->id] = -1; } queue->length = 0; @@ -291,11 +292,11 @@ queue_init(struct queue *queue, unsigned max_length) queue->items = g_new(struct queue_item, max_length); queue->order = g_malloc(sizeof(queue->order[0]) * max_length); - queue->idToPosition = g_malloc(sizeof(queue->idToPosition[0]) * + queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) * max_length * QUEUE_HASH_MULT); for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i) - queue->idToPosition[i] = -1; + queue->id_to_position[i] = -1; queue->rand = g_rand_new(); } @@ -307,7 +308,7 @@ queue_finish(struct queue *queue) g_free(queue->items); g_free(queue->order); - g_free(queue->idToPosition); + g_free(queue->id_to_position); g_rand_free(queue->rand); } diff --git a/src/queue.h b/src/queue.h index 9c7228fd8..032a812c2 100644 --- a/src/queue.h +++ b/src/queue.h @@ -74,8 +74,8 @@ struct queue { /** map order numbers to positions */ unsigned *order; - /** map song ids to posiitons */ - int *idToPosition; + /** map song ids to positions */ + int *id_to_position; /** repeat playback when the end of the queue has been reached? */ @@ -146,10 +146,10 @@ queue_id_to_position(const struct queue *queue, unsigned id) if (id >= queue->max_length * QUEUE_HASH_MULT) return -1; - assert(queue->idToPosition[id] >= -1); - assert(queue->idToPosition[id] < (int)queue->length); + assert(queue->id_to_position[id] >= -1); + assert(queue->id_to_position[id] < (int)queue->length); - return queue->idToPosition[id]; + return queue->id_to_position[id]; } static inline int diff --git a/src/queue_print.c b/src/queue_print.c index 2ca9ccc34..4bc600aec 100644 --- a/src/queue_print.c +++ b/src/queue_print.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue_print.h" #include "queue.h" #include "song.h" diff --git a/src/queue_save.c b/src/queue_save.c index 9a5a0e30f..71b6a1526 100644 --- a/src/queue_save.c +++ b/src/queue_save.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue_save.h" #include "queue.h" #include "song.h" diff --git a/src/replay_gain.c b/src/replay_gain.c index bcb501e54..805d1e5c8 100644 --- a/src/replay_gain.c +++ b/src/replay_gain.c @@ -20,12 +20,16 @@ * (c)2004 replayGain code by AliasMrJones */ +#include "config.h" #include "replay_gain.h" #include "conf.h" #include "audio_format.h" #include "pcm_volume.h" +#include "idle.h" #include <glib.h> + +#include <assert.h> #include <stdlib.h> #include <string.h> #include <math.h> @@ -38,19 +42,51 @@ static const char *const replay_gain_mode_names[] = { enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; static float replay_gain_preamp = 1.0; +static float replay_gain_missing_preamp = 1.0; -void replay_gain_global_init(void) +const char * +replay_gain_get_mode_string(void) { - const struct config_param *param = config_get_param(CONF_REPLAYGAIN); + switch (replay_gain_mode) { + case REPLAY_GAIN_OFF: + return "off"; - if (!param) - return; + case REPLAY_GAIN_TRACK: + return "track"; + + case REPLAY_GAIN_ALBUM: + return "album"; + } + + /* unreachable */ + assert(false); + return "off"; +} + +bool +replay_gain_set_mode_string(const char *p) +{ + assert(p != NULL); - if (strcmp(param->value, "track") == 0) { + if (strcmp(p, "off") == 0) + replay_gain_mode = REPLAY_GAIN_OFF; + else if (strcmp(p, "track") == 0) replay_gain_mode = REPLAY_GAIN_TRACK; - } else if (strcmp(param->value, "album") == 0) { + else if (strcmp(p, "album") == 0) replay_gain_mode = REPLAY_GAIN_ALBUM; - } else { + else + return false; + + idle_add(IDLE_OPTIONS); + + return true; +} + +void replay_gain_global_init(void) +{ + const struct config_param *param = config_get_param(CONF_REPLAYGAIN); + + if (param != NULL && !replay_gain_set_mode_string(param->value)) { g_error("replaygain value \"%s\" at line %i is invalid\n", param->value, param->line); } @@ -73,6 +109,25 @@ void replay_gain_global_init(void) replay_gain_preamp = pow(10, f / 20.0); } + + param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP); + + if (param) { + char *test; + float f = strtod(param->value, &test); + + if (*test != '\0') { + g_error("Replaygain missing preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + g_error("Replaygain missing preamp \"%s\" is not between -15 and" + "15 at line %i\n", param->value, param->line); + } + + replay_gain_missing_preamp = pow(10, f / 20.0); + } } static float calc_replay_gain_scale(float gain, float peak) @@ -116,19 +171,28 @@ void replay_gain_apply(struct replay_gain_info *info, char *buffer, int size, const struct audio_format *format) { - if (replay_gain_mode == REPLAY_GAIN_OFF || !info) + float scale; + + if (replay_gain_mode == REPLAY_GAIN_OFF) return; - if (info->scale < 0) { - const struct replay_gain_tuple *tuple = - &info->tuples[replay_gain_mode]; + if (info) { + if (info->scale < 0) { + const struct replay_gain_tuple *tuple = + &info->tuples[replay_gain_mode]; - g_debug("computing ReplayGain %s scale with gain %f, peak %f\n", - replay_gain_mode_names[replay_gain_mode], - tuple->gain, tuple->peak); + g_debug("computing ReplayGain %s scale with gain %f, peak %f\n", + replay_gain_mode_names[replay_gain_mode], + tuple->gain, tuple->peak); - info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak); + info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak); + } + scale = info->scale; + } + else { + scale = replay_gain_missing_preamp; + g_debug("ReplayGain is missing, computing scale %f\n", scale); } - pcm_volume(buffer, size, format, pcm_float_to_volume(info->scale)); + pcm_volume(buffer, size, format, pcm_float_to_volume(scale)); } diff --git a/src/replay_gain.h b/src/replay_gain.h index aa48f3f14..5d0492ad8 100644 --- a/src/replay_gain.h +++ b/src/replay_gain.h @@ -23,6 +23,8 @@ #ifndef MPD_REPLAY_GAIN_H #define MPD_REPLAY_GAIN_H +#include <stdbool.h> + enum replay_gain_mode { REPLAY_GAIN_OFF = -1, REPLAY_GAIN_ALBUM, @@ -52,6 +54,20 @@ void replay_gain_info_free(struct replay_gain_info *info); void replay_gain_global_init(void); +/** + * Returns the current replay gain mode as a machine-readable string. + */ +const char * +replay_gain_get_mode_string(void); + +/** + * Sets the replay gain mode, parsed from a string. + * + * @return true on success, false if the string could not be parsed + */ +bool +replay_gain_set_mode_string(const char *p); + void replay_gain_apply(struct replay_gain_info *info, char *buffer, int bufferSize, const struct audio_format *format); diff --git a/src/riff.c b/src/riff.c index a8ea9dd42..787a63cc5 100644 --- a/src/riff.c +++ b/src/riff.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" /* must be first for large file support */ #include "riff.h" #include <glib.h> diff --git a/src/sig_handlers.c b/src/sig_handlers.c index e70e1a159..028cd4038 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "sig_handlers.h" #ifndef WIN32 diff --git a/src/socket_util.c b/src/socket_util.c index da4e414b6..bf8fe0f07 100644 --- a/src/socket_util.c +++ b/src/socket_util.c @@ -17,8 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "socket_util.h" #include "config.h" +#include "socket_util.h" +#include "fd_util.h" #include <errno.h> #include <unistd.h> @@ -102,7 +103,7 @@ socket_bind_listen(int domain, int type, int protocol, int passcred = 1; #endif - fd = socket(domain, type, protocol); + fd = socket_cloexec_nonblock(domain, type, protocol); if (fd < 0) { g_set_error(error, listen_quark(), errno, "Failed to create socket: %s", g_strerror(errno)); diff --git a/src/song.c b/src/song.c index 76c25f44f..64f476b7e 100644 --- a/src/song.c +++ b/src/song.c @@ -17,47 +17,40 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song.h" #include "uri.h" #include "directory.h" -#include "mapper.h" -#include "decoder_list.h" -#include "decoder_plugin.h" -#include "tag_ape.h" -#include "tag_id3.h" #include "tag.h" #include <glib.h> #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <string.h> static struct song * -song_alloc(const char *url, struct directory *parent) +song_alloc(const char *uri, struct directory *parent) { - size_t urllen; + size_t uri_length; struct song *song; - assert(url); - urllen = strlen(url); - assert(urllen); - song = g_malloc(sizeof(*song) - sizeof(song->url) + urllen + 1); + assert(uri); + uri_length = strlen(uri); + assert(uri_length); + song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1); song->tag = NULL; - memcpy(song->url, url, urllen + 1); + memcpy(song->uri, uri, uri_length + 1); song->parent = parent; song->mtime = 0; + song->start_ms = song->end_ms = 0; return song; } struct song * -song_remote_new(const char *url) +song_remote_new(const char *uri) { - return song_alloc(url, NULL); + return song_alloc(uri, NULL); } struct song * @@ -68,32 +61,6 @@ song_file_new(const char *path, struct directory *parent) return song_alloc(path, parent); } -struct song * -song_file_load(const char *path, struct directory *parent) -{ - struct song *song; - bool ret; - - assert((parent == NULL) == (*path == '/')); - assert(!uri_has_scheme(path)); - assert(strchr(path, '\n') == NULL); - - song = song_file_new(path, parent); - - //in archive ? - if (parent != NULL && parent->device == DEVICE_INARCHIVE) { - ret = song_file_update_inarchive(song); - } else { - ret = song_file_update(song); - } - if (!ret) { - song_free(song); - return NULL; - } - - return song; -} - void song_free(struct song *song) { @@ -102,127 +69,27 @@ song_free(struct song *song) g_free(song); } -/** - * Attempts to load APE or ID3 tags from the specified file. - */ -static struct tag * -tag_load_fallback(const char *path) -{ - struct tag *tag = tag_ape_load(path); - if (tag == NULL) - tag = tag_id3_load(path); - return tag; -} - -/** - * The decoder plugin failed to load any tags: fall back to the APE or - * ID3 tag loader. - */ -static struct tag * -tag_fallback(const char *path, struct tag *tag) -{ - struct tag *fallback = tag_load_fallback(path); - - if (fallback != NULL) { - /* tag was successfully loaded: copy the song - duration, and destroy the old (empty) tag */ - fallback->time = tag->time; - tag_free(tag); - return fallback; - } else - /* no APE/ID3 tag found: return the empty tag */ - return tag; -} - -bool -song_file_update(struct song *song) -{ - const char *suffix; - char *path_fs; - const struct decoder_plugin *plugin; - struct stat st; - - assert(song_is_file(song)); - - /* check if there's a suffix and a plugin */ - - suffix = uri_get_suffix(song->url); - if (suffix == NULL) - return false; - - plugin = decoder_plugin_from_suffix(suffix, false); - if (plugin == NULL) - return false; - - path_fs = map_song_fs(song); - if (path_fs == NULL) - return false; - - if (song->tag != NULL) { - tag_free(song->tag); - song->tag = NULL; - } - - if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) { - g_free(path_fs); - return false; - } - - song->mtime = st.st_mtime; - - do { - song->tag = plugin->tag_dup(path_fs); - if (song->tag != NULL) - break; - - plugin = decoder_plugin_from_suffix(suffix, true); - } while (plugin != NULL); - - if (song->tag != NULL && tag_is_empty(song->tag)) - song->tag = tag_fallback(path_fs, song->tag); - - g_free(path_fs); - return song->tag != NULL; -} - -bool -song_file_update_inarchive(struct song *song) -{ - const char *suffix; - const struct decoder_plugin *plugin; - - assert(song_is_file(song)); - - /* check if there's a suffix and a plugin */ - - suffix = uri_get_suffix(song->url); - if (suffix == NULL) - return false; - - plugin = decoder_plugin_from_suffix(suffix, false); - if (plugin == NULL) - return false; - - if (song->tag != NULL) - tag_free(song->tag); - - //accept every file that has music suffix - //because we dont support tag reading throught - //input streams - song->tag = tag_new(); - - return true; -} - char * song_get_uri(const struct song *song) { assert(song != NULL); - assert(*song->url); + assert(*song->uri); if (!song_in_database(song) || directory_is_root(song->parent)) - return g_strdup(song->url); + return g_strdup(song->uri); else return g_strconcat(directory_get_path(song->parent), - "/", song->url, NULL); + "/", song->uri, NULL); +} + +double +song_get_duration(const struct song *song) +{ + if (song->end_ms > 0) + return (song->end_ms - song->start_ms) / 1000.0; + + if (song->tag == NULL) + return 0; + + return song->tag->time - song->start_ms / 1000.0; } diff --git a/src/song.h b/src/song.h index 3044e910f..2bebee64e 100644 --- a/src/song.h +++ b/src/song.h @@ -24,9 +24,6 @@ #include <stdbool.h> #include <sys/time.h> -#define SONG_BEGIN "songList begin" -#define SONG_END "songList end" - #define SONG_FILE "file: " #define SONG_TIME "Time: " @@ -34,12 +31,24 @@ struct song { struct tag *tag; struct directory *parent; time_t mtime; - char url[sizeof(int)]; + + /** + * Start of this sub-song within the file in milliseconds. + */ + unsigned start_ms; + + /** + * End of this sub-song within the file in milliseconds. + * Unused if zero. + */ + unsigned end_ms; + + char uri[sizeof(int)]; }; /** allocate a new song with a remote URL */ struct song * -song_remote_new(const char *url); +song_remote_new(const char *uri); /** allocate a new song with a local file name */ struct song * @@ -72,6 +81,9 @@ song_file_update_inarchive(struct song *song); char * song_get_uri(const struct song *song); +double +song_get_duration(const struct song *song); + static inline bool song_in_database(const struct song *song) { @@ -81,7 +93,7 @@ song_in_database(const struct song *song) static inline bool song_is_file(const struct song *song) { - return song_in_database(song) || song->url[0] == '/'; + return song_in_database(song) || song->uri[0] == '/'; } #endif diff --git a/src/song_print.c b/src/song_print.c index 64ab9f6b1..3ba653d1c 100644 --- a/src/song_print.c +++ b/src/song_print.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song_print.h" #include "song.h" #include "songvec.h" @@ -26,18 +27,18 @@ #include "uri.h" void -song_print_url(struct client *client, struct song *song) +song_print_uri(struct client *client, struct song *song) { if (song_in_database(song) && !directory_is_root(song->parent)) { client_printf(client, "%s%s/%s\n", SONG_FILE, - directory_get_path(song->parent), song->url); + directory_get_path(song->parent), song->uri); } else { char *allocated; const char *uri; - uri = allocated = uri_remove_auth(song->url); + uri = allocated = uri_remove_auth(song->uri); if (uri == NULL) - uri = song->url; + uri = song->uri; client_printf(client, "%s%s\n", SONG_FILE, uri); @@ -48,7 +49,41 @@ song_print_url(struct client *client, struct song *song) int song_print_info(struct client *client, struct song *song) { - song_print_url(client, song); + song_print_uri(client, song); + + if (song->start_ms > 0 || song->end_ms > 0) { + if (song->end_ms > 0) + client_printf(client, "Range: %u.%03u-%u.%03u\n", + song->start_ms / 1000, + song->start_ms % 1000, + song->end_ms / 1000, + song->end_ms % 1000); + else + client_printf(client, "Range: %u.%03u-\n", + song->start_ms / 1000, + song->start_ms % 1000); + } + + if (song->mtime > 0) { +#ifndef G_OS_WIN32 + struct tm tm; +#endif + const struct tm *tm2; + +#ifdef G_OS_WIN32 + tm2 = gmtime(&song->mtime); +#else + tm2 = gmtime_r(&song->mtime, &tm); +#endif + + if (tm2 != NULL) { + char timestamp[32]; + + strftime(timestamp, sizeof(timestamp), "%FT%TZ", tm2); + client_printf(client, "Last-Modified: %s\n", + timestamp); + } + } if (song->tag) tag_print(client, song->tag); diff --git a/src/song_print.h b/src/song_print.h index 291fd81c8..c16bc7387 100644 --- a/src/song_print.h +++ b/src/song_print.h @@ -30,6 +30,6 @@ song_print_info(struct client *client, struct song *song); int songvec_print(struct client *client, const struct songvec *sv); void -song_print_url(struct client *client, struct song *song); +song_print_uri(struct client *client, struct song *song); #endif diff --git a/src/song_save.c b/src/song_save.c index 2d6297f3e..37df5067d 100644 --- a/src/song_save.c +++ b/src/song_save.c @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song_save.h" #include "song.h" #include "tag_save.h" #include "directory.h" #include "tag.h" +#include "text_file.h" #include <glib.h> @@ -30,18 +32,13 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "song" -#define SONG_KEY "key: " -#define SONG_MTIME "mtime: " +#define SONG_MTIME "mtime" +#define SONG_END "song_end" -static void -song_save_url(FILE *fp, struct song *song) +static GQuark +song_save_quark(void) { - if (song->parent != NULL && song->parent->path != NULL) - fprintf(fp, SONG_FILE "%s/%s\n", - directory_get_path(song->parent), song->url); - else - fprintf(fp, SONG_FILE "%s\n", - song->url); + return g_quark_from_static_string("song_save"); } static int @@ -49,117 +46,76 @@ song_save(struct song *song, void *data) { FILE *fp = data; - fprintf(fp, SONG_KEY "%s\n", song->url); - - song_save_url(fp, song); + fprintf(fp, SONG_BEGIN "%s\n", song->uri); if (song->tag != NULL) tag_save(fp, song->tag); - fprintf(fp, SONG_MTIME "%li\n", (long)song->mtime); + fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime); + fprintf(fp, SONG_END "\n"); return 0; } void songvec_save(FILE *fp, struct songvec *sv) { - fprintf(fp, "%s\n", SONG_BEGIN); songvec_for_each(sv, song_save, fp); - fprintf(fp, "%s\n", SONG_END); } -static void -insertSongIntoList(struct songvec *sv, struct song *newsong) +struct song * +song_load(FILE *fp, struct directory *parent, const char *uri, + GString *buffer, GError **error_r) { - struct song *existing = songvec_find(sv, newsong->url); - - if (!existing) { - songvec_add(sv, newsong); - if (newsong->tag) - tag_end_add(newsong->tag); - } else { /* prevent dupes, just update the existing song info */ - if (existing->mtime != newsong->mtime) { - if (existing->tag != NULL) - tag_free(existing->tag); - if (newsong->tag) - tag_end_add(newsong->tag); - existing->tag = newsong->tag; - existing->mtime = newsong->mtime; - newsong->tag = NULL; - } - song_free(newsong); - } -} - -static char * -matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType) -{ - int i; - - for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { - size_t len = strlen(tag_item_names[i]); + struct song *song = song_file_new(uri, parent); + char *line, *colon; + enum tag_type type; + const char *value; - if (0 == strncmp(tag_item_names[i], buffer, len) && - buffer[len] == ':') { - *itemType = i; - return g_strchug(buffer + len + 1); + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, SONG_END) != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + if (song->tag != NULL) + tag_end_add(song->tag); + song_free(song); + + g_set_error(error_r, song_save_quark(), 0, + "unknown line in db: %s", line); + return false; } - } - return NULL; -} + *colon++ = 0; + value = g_strchug(colon); -void readSongInfoIntoList(FILE *fp, struct songvec *sv, - struct directory *parent) -{ - enum { - buffer_size = 32768, - }; - char *buffer = g_malloc(buffer_size); - struct song *song = NULL; - enum tag_type itemType; - const char *value; - - while (fgets(buffer, buffer_size, fp) && - !g_str_has_prefix(buffer, SONG_END)) { - g_strchomp(buffer); - - if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) { - if (song) - insertSongIntoList(sv, song); - - song = song_file_new(buffer + strlen(SONG_KEY), - parent); - } else if (*buffer == 0) { - /* ignore empty lines (starting with '\0') */ - } else if (song == NULL) { - g_error("Problems reading song info"); - } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) { - /* we don't need this info anymore */ - } else if ((value = matchesAnMpdTagItemKey(buffer, - &itemType)) != NULL) { + if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { if (!song->tag) { song->tag = tag_new(); tag_begin_add(song->tag); } - tag_add_item(song->tag, itemType, value); - } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) { + tag_add_item(song->tag, type, value); + } else if (strcmp(line, "Time") == 0) { if (!song->tag) { song->tag = tag_new(); tag_begin_add(song->tag); } - song->tag->time = atoi(&(buffer[strlen(SONG_TIME)])); - } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) { - song->mtime = atoi(&(buffer[strlen(SONG_MTIME)])); + song->tag->time = atoi(value); + } else if (strcmp(line, SONG_MTIME) == 0) { + song->mtime = atoi(value); + } else { + if (song->tag != NULL) + tag_end_add(song->tag); + song_free(song); + + g_set_error(error_r, song_save_quark(), 0, + "unknown line in db: %s", line); + return false; } - else - g_error("unknown line in db: %s", buffer); } - g_free(buffer); + if (song->tag != NULL) + tag_end_add(song->tag); - if (song) - insertSongIntoList(sv, song); + return song; } diff --git a/src/song_save.h b/src/song_save.h index 370e42730..70ddb249e 100644 --- a/src/song_save.h +++ b/src/song_save.h @@ -20,14 +20,28 @@ #ifndef MPD_SONG_SAVE_H #define MPD_SONG_SAVE_H +#include <glib.h> + +#include <stdbool.h> #include <stdio.h> +#define SONG_BEGIN "song_begin: " + struct songvec; struct directory; void songvec_save(FILE *fp, struct songvec *sv); -void readSongInfoIntoList(FILE * fp, struct songvec *sv, - struct directory *parent); +/** + * Loads a song from the input file. Reading stops after the + * "song_end" line. + * + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success, false on error + */ +struct song * +song_load(FILE *fp, struct directory *parent, const char *uri, + GString *buffer, GError **error_r); #endif diff --git a/src/song_sticker.c b/src/song_sticker.c index 2758ff534..83131ba00 100644 --- a/src/song_sticker.c +++ b/src/song_sticker.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "song_sticker.h" #include "song.h" #include "directory.h" diff --git a/src/song_update.c b/src/song_update.c new file mode 100644 index 000000000..ab366b0c7 --- /dev/null +++ b/src/song_update.c @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "song.h" +#include "uri.h" +#include "directory.h" +#include "mapper.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "tag_ape.h" +#include "tag_id3.h" +#include "tag.h" + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> + +struct song * +song_file_load(const char *path, struct directory *parent) +{ + struct song *song; + bool ret; + + assert((parent == NULL) == g_path_is_absolute(path)); + assert(!uri_has_scheme(path)); + assert(strchr(path, '\n') == NULL); + + song = song_file_new(path, parent); + + //in archive ? + if (parent != NULL && parent->device == DEVICE_INARCHIVE) { + ret = song_file_update_inarchive(song); + } else { + ret = song_file_update(song); + } + if (!ret) { + song_free(song); + return NULL; + } + + return song; +} + +/** + * Attempts to load APE or ID3 tags from the specified file. + */ +static struct tag * +tag_load_fallback(const char *path) +{ + struct tag *tag = tag_ape_load(path); + if (tag == NULL) + tag = tag_id3_load(path); + return tag; +} + +/** + * The decoder plugin failed to load any tags: fall back to the APE or + * ID3 tag loader. + */ +static struct tag * +tag_fallback(const char *path, struct tag *tag) +{ + struct tag *fallback = tag_load_fallback(path); + + if (fallback != NULL) { + /* tag was successfully loaded: copy the song + duration, and destroy the old (empty) tag */ + fallback->time = tag->time; + tag_free(tag); + return fallback; + } else + /* no APE/ID3 tag found: return the empty tag */ + return tag; +} + +bool +song_file_update(struct song *song) +{ + const char *suffix; + char *path_fs; + const struct decoder_plugin *plugin; + struct stat st; + + assert(song_is_file(song)); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(song->uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, NULL); + if (plugin == NULL) + return false; + + path_fs = map_song_fs(song); + if (path_fs == NULL) + return false; + + if (song->tag != NULL) { + tag_free(song->tag); + song->tag = NULL; + } + + if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) { + g_free(path_fs); + return false; + } + + song->mtime = st.st_mtime; + + do { + song->tag = plugin->tag_dup(path_fs); + if (song->tag != NULL) + break; + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (song->tag != NULL && tag_is_empty(song->tag)) + song->tag = tag_fallback(path_fs, song->tag); + + g_free(path_fs); + return song->tag != NULL; +} + +bool +song_file_update_inarchive(struct song *song) +{ + const char *suffix; + const struct decoder_plugin *plugin; + + assert(song_is_file(song)); + + /* check if there's a suffix and a plugin */ + + suffix = uri_get_suffix(song->uri); + if (suffix == NULL) + return false; + + plugin = decoder_plugin_from_suffix(suffix, false); + if (plugin == NULL) + return false; + + if (song->tag != NULL) + tag_free(song->tag); + + //accept every file that has music suffix + //because we dont support tag reading throught + //input streams + song->tag = tag_new(); + + return true; +} diff --git a/src/songvec.c b/src/songvec.c index efef02216..bdc90da32 100644 --- a/src/songvec.c +++ b/src/songvec.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "songvec.h" #include "song.h" #include "tag.h" @@ -29,6 +30,38 @@ static GMutex *nr_lock = NULL; +static const char * +tag_get_value_checked(const struct tag *tag, enum tag_type type) +{ + return tag != NULL + ? tag_get_value(tag, type) + : NULL; +} + +static int +compare_utf8_string(const char *a, const char *b) +{ + if (a == NULL) + return b == NULL ? 0 : -1; + + if (b == NULL) + return 1; + + return g_utf8_collate(a, b); +} + +/** + * Compare two string tag values, ignoring case. Either one may be + * NULL. + */ +static int +compare_string_tag_item(const struct tag *a, const struct tag *b, + enum tag_type type) +{ + return compare_utf8_string(tag_get_value_checked(a, type), + tag_get_value_checked(b, type)); +} + /** * Compare two tag values which should contain an integer value * (e.g. disc or track number). Either one may be NULL. @@ -51,14 +84,8 @@ compare_number_string(const char *a, const char *b) static int compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type) { - if (a == NULL) - return b == NULL ? 0 : -1; - - if (b == NULL) - return 1; - - return compare_number_string(tag_get_value(a, type), - tag_get_value(b, type)); + return compare_number_string(tag_get_value_checked(a, type), + tag_get_value_checked(b, type)); } /* Only used for sorting/searchin a songvec, not general purpose compares */ @@ -68,18 +95,23 @@ static int songvec_cmp(const void *s1, const void *s2) const struct song *b = ((const struct song * const *)s2)[0]; int ret; - /* first sort by disc */ - ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_DISC); + /* first sort by album */ + ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM); + if (ret != 0) + return ret; + + /* then sort by disc */ + ret = compare_tag_item(a->tag, b->tag, TAG_DISC); if (ret != 0) return ret; /* then by track number */ - ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_TRACK); + ret = compare_tag_item(a->tag, b->tag, TAG_TRACK); if (ret != 0) return ret; /* still no difference? compare file name */ - return g_utf8_collate(a->url, b->url); + return g_utf8_collate(a->uri, b->uri); } static size_t sv_size(const struct songvec *sv) @@ -108,14 +140,14 @@ void songvec_sort(struct songvec *sv) } struct song * -songvec_find(const struct songvec *sv, const char *url) +songvec_find(const struct songvec *sv, const char *uri) { int i; struct song *ret = NULL; g_mutex_lock(nr_lock); for (i = sv->nr; --i >= 0; ) { - if (strcmp(sv->base[i]->url, url)) + if (strcmp(sv->base[i]->uri, uri)) continue; ret = sv->base[i]; break; @@ -182,7 +214,7 @@ songvec_for_each(const struct songvec *sv, struct song *song = sv->base[i]; assert(song); - assert(*song->url); + assert(*song->uri); prev_nr = sv->nr; g_mutex_unlock(nr_lock); /* fn() may block */ diff --git a/src/songvec.h b/src/songvec.h index 0fd207ed0..1d07dd6e9 100644 --- a/src/songvec.h +++ b/src/songvec.h @@ -34,7 +34,7 @@ void songvec_deinit(void); void songvec_sort(struct songvec *sv); struct song * -songvec_find(const struct songvec *sv, const char *url); +songvec_find(const struct songvec *sv, const char *uri); int songvec_delete(struct songvec *sv, const struct song *del); diff --git a/src/state_file.c b/src/state_file.c index 9c6475cc8..fd9832313 100644 --- a/src/state_file.c +++ b/src/state_file.c @@ -17,10 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "state_file.h" #include "output_state.h" #include "playlist.h" +#include "playlist_state.h" #include "volume.h" +#include "glib_compat.h" #include <glib.h> #include <assert.h> @@ -30,28 +33,26 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "state_file" -static struct _sf_cb { - void (*reader)(FILE *); - void (*writer)(FILE *); -} sf_callbacks [] = { - { read_sw_volume_state, save_sw_volume_state }, - { readAudioDevicesState, saveAudioDevicesState }, - { readPlaylistState, savePlaylistState }, -}; - static char *state_file_path; /** the GLib source id for the save timer */ static guint save_state_source_id; +/** + * These version numbers determine whether we need to save the state + * file. If nothing has changed, we won't let the hard drive spin up. + */ +static unsigned prev_volume_version, prev_output_version, + prev_playlist_version; + static void state_file_write(void) { - unsigned int i; FILE *fp; - if (state_file_path == NULL) - return; + assert(state_file_path != NULL); + + g_debug("Saving state file %s", state_file_path); fp = fopen(state_file_path, "w"); if (G_UNLIKELY(!fp)) { @@ -60,21 +61,27 @@ state_file_write(void) return; } - for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++) - sf_callbacks[i].writer(fp); + save_sw_volume_state(fp); + audio_output_state_save(fp); + playlist_state_save(fp, &g_playlist); while(fclose(fp) && errno == EINTR) /* nothing */; + + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&g_playlist); } static void state_file_read(void) { - unsigned int i; FILE *fp; + char line[1024]; + bool success; assert(state_file_path != NULL); - g_debug("Saving state file"); + g_debug("Loading state file %s", state_file_path); fp = fopen(state_file_path, "r"); if (G_UNLIKELY(!fp)) { @@ -82,12 +89,22 @@ state_file_read(void) state_file_path, strerror(errno)); return; } - for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++) { - sf_callbacks[i].reader(fp); - rewind(fp); + + while (fgets(line, sizeof(line), fp) != NULL) { + g_strchomp(line); + + success = read_sw_volume_state(line) || + audio_output_state_read(line) || + playlist_state_restore(line, fp, &g_playlist); + if (!success) + g_warning("Unrecognized line in state file: %s", line); } while(fclose(fp) && errno == EINTR) /* nothing */; + + prev_volume_version = sw_volume_state_get_hash(); + prev_output_version = audio_output_state_get_version(); + prev_playlist_version = playlist_state_get_hash(&g_playlist); } /** @@ -97,6 +114,13 @@ state_file_read(void) static gboolean timer_save_state_file(G_GNUC_UNUSED gpointer data) { + if (prev_volume_version == sw_volume_state_get_hash() && + prev_output_version == audio_output_state_get_version() && + prev_playlist_version == playlist_state_get_hash(&g_playlist)) + /* nothing has changed - don't save the state file, + don't spin up the hard disk */ + return true; + state_file_write(); return true; } @@ -112,18 +136,22 @@ state_file_init(const char *path) state_file_path = g_strdup(path); state_file_read(); - save_state_source_id = g_timeout_add(5 * 60 * 1000, - timer_save_state_file, NULL); + save_state_source_id = g_timeout_add_seconds(5 * 60, + timer_save_state_file, + NULL); } void state_file_finish(void) { + if (state_file_path == NULL) + /* no state file configured, no cleanup required */ + return; + if (save_state_source_id != 0) g_source_remove(save_state_source_id); - if (state_file_path != NULL) - state_file_write(); + state_file_write(); g_free(state_file_path); } diff --git a/src/stats.c b/src/stats.c index 01f6761f3..93e492387 100644 --- a/src/stats.c +++ b/src/stats.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "stats.h" #include "database.h" #include "tag.h" @@ -52,11 +53,11 @@ visit_tag(struct visit_data *data, const struct tag *tag) const struct tag_item *item = tag->items[i]; switch (item->type) { - case TAG_ITEM_ARTIST: + case TAG_ARTIST: strset_add(data->artists, item->value); break; - case TAG_ITEM_ALBUM: + case TAG_ALBUM: strset_add(data->albums, item->value); break; @@ -113,7 +114,7 @@ int stats_print(struct client *client) stats.album_count, stats.song_count, (long)g_timer_elapsed(stats.timer, NULL), - (long)(getPlayerTotalPlayTime() + 0.5), + (long)(pc_get_total_play_time() + 0.5), stats.song_duration, db_get_mtime()); return 0; diff --git a/src/sticker.c b/src/sticker.c index cded09fca..222ae05bc 100644 --- a/src/sticker.c +++ b/src/sticker.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "sticker.h" #include "idle.h" @@ -76,50 +77,69 @@ static const char sticker_sql_create[] = static sqlite3 *sticker_db; static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)]; +static GQuark +sticker_quark(void) +{ + return g_quark_from_static_string("sticker"); +} + static sqlite3_stmt * -sticker_prepare(const char *sql) +sticker_prepare(const char *sql, GError **error_r) { int ret; sqlite3_stmt *stmt; ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL); - if (ret != SQLITE_OK) - g_error("sqlite3_prepare_v2() failed: %s", - sqlite3_errmsg(sticker_db)); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "sqlite3_prepare_v2() failed: %s", + sqlite3_errmsg(sticker_db)); + return NULL; + } return stmt; } -void -sticker_global_init(const char *path) +bool +sticker_global_init(const char *path, GError **error_r) { int ret; if (path == NULL) /* not configured */ - return; + return true; /* open/create the sqlite database */ ret = sqlite3_open(path, &sticker_db); - if (ret != SQLITE_OK) - g_error("Failed to open sqlite database '%s': %s", - path, sqlite3_errmsg(sticker_db)); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "Failed to open sqlite database '%s': %s", + path, sqlite3_errmsg(sticker_db)); + return false; + } /* create the table and index */ ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL); - if (ret != SQLITE_OK) - g_error("Failed to create sticker table: %s", - sqlite3_errmsg(sticker_db)); + if (ret != SQLITE_OK) { + g_set_error(error_r, sticker_quark(), ret, + "Failed to create sticker table: %s", + sqlite3_errmsg(sticker_db)); + return false; + } /* prepare the statements we're going to use */ for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) { assert(sticker_sql[i] != NULL); - sticker_stmt[i] = sticker_prepare(sticker_sql[i]); + sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r); + if (sticker_stmt[i] == NULL) + return false; } + + return true; } void diff --git a/src/sticker.h b/src/sticker.h index 8e6410914..30d85fa18 100644 --- a/src/sticker.h +++ b/src/sticker.h @@ -50,9 +50,13 @@ struct sticker; /** * Opens the sticker database (if path is not NULL). + * + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success, false on error */ -void -sticker_global_init(const char *path); +bool +sticker_global_init(const char *path, GError **error_r); /** * Close the sticker database. diff --git a/src/sticker_print.c b/src/sticker_print.c index 12dafd3f7..6bcc41d77 100644 --- a/src/sticker_print.c +++ b/src/sticker_print.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "sticker_print.h" #include "sticker.h" #include "client.h" diff --git a/src/stored_playlist.c b/src/stored_playlist.c index 5ed7182f6..1007da6f8 100644 --- a/src/stored_playlist.c +++ b/src/stored_playlist.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "stored_playlist.h" #include "playlist_save.h" #include "song.h" diff --git a/src/strset.c b/src/strset.c index 474dd6642..0c911d5ac 100644 --- a/src/strset.c +++ b/src/strset.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "strset.h" #include <assert.h> @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag.h" #include "tag_internal.h" #include "tag_pool.h" @@ -42,18 +43,20 @@ static struct { } bulk; const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { - "Artist", - "Album", - "AlbumArtist", - "Title", - "Track", - "Name", - "Genre", - "Date", - "Composer", - "Performer", - "Comment", - "Disc", + [TAG_ARTIST] = "Artist", + [TAG_ARTIST_SORT] = "ArtistSort", + [TAG_ALBUM] = "Album", + [TAG_ALBUM_ARTIST] = "AlbumArtist", + [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort", + [TAG_TITLE] = "Title", + [TAG_TRACK] = "Track", + [TAG_NAME] = "Name", + [TAG_GENRE] = "Genre", + [TAG_DATE] = "Date", + [TAG_COMPOSER] = "Composer", + [TAG_PERFORMER] = "Performer", + [TAG_COMMENT] = "Comment", + [TAG_DISC] = "Disc", /* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */ [TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID", @@ -64,6 +67,36 @@ const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = { bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES]; +enum tag_type +tag_name_parse(const char *name) +{ + assert(name != NULL); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != NULL); + + if (strcmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + +enum tag_type +tag_name_parse_i(const char *name) +{ + assert(name != NULL); + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) { + assert(tag_item_names[i] != NULL); + + if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0) + return (enum tag_type)i; + } + + return TAG_NUM_OF_ITEM_TYPES; +} + static size_t items_size(const struct tag *tag) { return tag->num_items * sizeof(struct tag_item *); @@ -76,12 +109,12 @@ void tag_lib_init(void) char *temp; char *s; char *c; - int i; + enum tag_type type; /* parse the "metadata_to_use" config parameter below */ /* ignore comments by default */ - ignore_tag_items[TAG_ITEM_COMMENT] = true; + ignore_tag_items[TAG_COMMENT] = true; value = config_get_string(CONF_METADATA_TO_USE, NULL); if (value == NULL) @@ -98,16 +131,18 @@ void tag_lib_init(void) if (*s == '\0') quit = 1; *s = '\0'; - for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { - if (g_ascii_strcasecmp(c, tag_item_names[i]) == 0) { - ignore_tag_items[i] = false; - break; - } - } - if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) { + + c = g_strstrip(c); + if (*c == 0) + continue; + + type = tag_name_parse_i(c); + if (type == TAG_NUM_OF_ITEM_TYPES) g_error("error parsing metadata item \"%s\"", c); - } + + ignore_tag_items[type] = false; + s++; c = s; } @@ -31,18 +31,20 @@ * Codes for the type of a tag item. */ enum tag_type { - TAG_ITEM_ARTIST, - TAG_ITEM_ALBUM, - TAG_ITEM_ALBUM_ARTIST, - TAG_ITEM_TITLE, - TAG_ITEM_TRACK, - TAG_ITEM_NAME, - TAG_ITEM_GENRE, - TAG_ITEM_DATE, - TAG_ITEM_COMPOSER, - TAG_ITEM_PERFORMER, - TAG_ITEM_COMMENT, - TAG_ITEM_DISC, + TAG_ARTIST, + TAG_ARTIST_SORT, + TAG_ALBUM, + TAG_ALBUM_ARTIST, + TAG_ALBUM_ARTIST_SORT, + TAG_TITLE, + TAG_TRACK, + TAG_NAME, + TAG_GENRE, + TAG_DATE, + TAG_COMPOSER, + TAG_PERFORMER, + TAG_COMMENT, + TAG_DISC, TAG_MUSICBRAINZ_ARTISTID, TAG_MUSICBRAINZ_ALBUMID, @@ -94,6 +96,22 @@ struct tag { }; /** + * Parse the string, and convert it into a #tag_type. Returns + * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. + */ +enum tag_type +tag_name_parse(const char *name); + +/** + * Parse the string, and convert it into a #tag_type. Returns + * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized. + * + * Case does not matter. + */ +enum tag_type +tag_name_parse_i(const char *name); + +/** * Creates an empty #tag. */ struct tag *tag_new(void); diff --git a/src/tag_ape.c b/src/tag_ape.c index 7cbf32208..d18cc84ee 100644 --- a/src/tag_ape.c +++ b/src/tag_ape.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_ape.h" #include "tag.h" @@ -25,6 +26,36 @@ #include <assert.h> #include <stdio.h> +static const char *const ape_tag_names[] = { + [TAG_TITLE] = "title", + [TAG_ARTIST] = "artist", + [TAG_ALBUM] = "album", + [TAG_COMMENT] = "comment", + [TAG_GENRE] = "genre", + [TAG_TRACK] = "track", + [TAG_DATE] = "year" +}; + +static struct tag * +tag_ape_import_item(struct tag *tag, unsigned long flags, + const char *key, const char *value, size_t value_length) +{ + /* we only care about utf-8 text tags */ + if ((flags & (0x3 << 1)) != 0) + return tag; + + for (unsigned i = 0; i < G_N_ELEMENTS(ape_tag_names); i++) { + if (ape_tag_names[i] != NULL && + g_ascii_strcasecmp(key, ape_tag_names[i]) == 0) { + if (tag == NULL) + tag = tag_new(); + tag_add_item_n(tag, i, value, value_length); + } + } + + return tag; +} + struct tag * tag_ape_load(const char *file) { @@ -36,7 +67,6 @@ tag_ape_load(const char *file) size_t tagLen; size_t size; unsigned long flags; - int i; char *key; struct { @@ -48,26 +78,6 @@ tag_ape_load(const char *file) unsigned char reserved[8]; } footer; - const char *apeItems[7] = { - "title", - "artist", - "album", - "comment", - "genre", - "track", - "year" - }; - - int tagItems[7] = { - TAG_ITEM_TITLE, - TAG_ITEM_ARTIST, - TAG_ITEM_ALBUM, - TAG_ITEM_COMMENT, - TAG_ITEM_GENRE, - TAG_ITEM_TRACK, - TAG_ITEM_DATE, - }; - fp = fopen(file, "r"); if (!fp) return NULL; @@ -127,17 +137,8 @@ tag_ape_load(const char *file) if (tagLen < size) goto fail; - /* we only care about utf-8 text tags */ - if (!(flags & (0x3 << 1))) { - for (i = 0; i < 7; i++) { - if (g_ascii_strcasecmp(key, apeItems[i]) == 0) { - if (!ret) - ret = tag_new(); - tag_add_item_n(ret, tagItems[i], - p, size); - } - } - } + ret = tag_ape_import_item(ret, flags, key, p, size); + p += size; tagLen -= size; } diff --git a/src/tag_id3.c b/src/tag_id3.c index a33ebc00b..4f1a248b3 100644 --- a/src/tag_id3.c +++ b/src/tag_id3.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_id3.h" #include "tag.h" #include "riff.h" @@ -34,25 +35,31 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "id3" -# define isId3v1(tag) (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) # ifndef ID3_FRAME_COMPOSER # define ID3_FRAME_COMPOSER "TCOM" # endif -# ifndef ID3_FRAME_PERFORMER -# define ID3_FRAME_PERFORMER "TOPE" -# endif # ifndef ID3_FRAME_DISC # define ID3_FRAME_DISC "TPOS" # endif +#ifndef ID3_FRAME_ARTIST_SORT +#define ID3_FRAME_ARTIST_SORT "TSOP" +#endif + #ifndef ID3_FRAME_ALBUM_ARTIST_SORT -#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" +#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */ #endif #ifndef ID3_FRAME_ALBUM_ARTIST #define ID3_FRAME_ALBUM_ARTIST "TPE2" #endif +static inline bool +tag_is_id3v1(struct id3_tag *tag) +{ + return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0; +} + static id3_utf8_t * tag_id3_getstring(const struct id3_frame *frame, unsigned i) { @@ -72,14 +79,13 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i) /* This will try to convert a string to utf-8, */ -static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, int type) +static id3_utf8_t * +import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4) { id3_utf8_t *utf8, *utf8_stripped; id3_latin1_t *isostr; const char *encoding; - if (type == TAG_ITEM_GENRE) - ucs4 = id3_genre_name(ucs4); /* use encoding field here? */ if (is_id3v1 && (encoding = config_get_string(CONF_ID3V1_ENCODING, NULL)) != NULL) { @@ -112,8 +118,16 @@ static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, return utf8_stripped; } +/** + * Import a "Text information frame" (ID3v2.4.0 section 4.2). It + * contains 2 fields: + * + * - encoding + * - string list + */ static void -getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag) +tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id, + enum tag_type type) { struct id3_frame const *frame; id3_ucs4_t const *ucs4; @@ -122,108 +136,77 @@ getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag) unsigned int nstrings, i; frame = id3_tag_findframe(tag, id, 0); - /* Check frame */ - if (!frame) - { + if (frame == NULL || frame->nfields != 2) return; - } - /* Check fields in frame */ - if(frame->nfields == 0) - { - g_debug("Frame has no fields"); + + /* check the encoding field */ + + field = id3_frame_field(frame, 0); + if (field == NULL || field->type != ID3_FIELD_TYPE_TEXTENCODING) return; - } - /* Starting with T is a stringlist */ - if (id[0] == 'T') - { - /* This one contains 2 fields: - * 1st: Text encoding - * 2: Stringlist - * Shamefully this isn't the RL case. - * But I am going to enforce it anyway. - */ - if(frame->nfields != 2) - { - g_debug("Invalid number '%i' of fields for TXX frame", - frame->nfields); - return; - } - field = &frame->fields[0]; - /** - * First field is encoding field. - * This is ignored by mpd. - */ - if(field->type != ID3_FIELD_TYPE_TEXTENCODING) - { - g_debug("Expected encoding, found: %i", - field->type); - } - /* Process remaining fields, should be only one */ - field = &frame->fields[1]; - /* Encoding field */ - if(field->type == ID3_FIELD_TYPE_STRINGLIST) { - /* Get the number of strings available */ - nstrings = id3_field_getnstrings(field); - for (i = 0; i < nstrings; i++) { - ucs4 = id3_field_getstrings(field,i); - if(!ucs4) - continue; - utf8 = processID3FieldString(isId3v1(tag),ucs4, type); - if(!utf8) - continue; - - tag_add_item(mpdTag, type, (char *)utf8); - g_free(utf8); - } - } - else { - g_warning("Field type not processed: %i", - (int)id3_field_gettextencoding(field)); - } - } - /* A comment frame */ - else if(!strcmp(ID3_FRAME_COMMENT, id)) - { - /* A comment frame is different... */ - /* 1st: encoding - * 2nd: Language - * 3rd: String - * 4th: FullString. - * The 'value' we want is in the 4th field - */ - if(frame->nfields == 4) - { - /* for now I only read the 4th field, with the fullstring */ - field = &frame->fields[3]; - if(field->type == ID3_FIELD_TYPE_STRINGFULL) - { - ucs4 = id3_field_getfullstring(field); - if(ucs4) - { - utf8 = processID3FieldString(isId3v1(tag),ucs4, type); - if(utf8) - { - tag_add_item(mpdTag, type, (char *)utf8); - g_free(utf8); - } - } - } - else - { - g_debug("4th field in comment frame differs from expected, got '%i': ignoring", - field->type); - } - } - else - { - g_debug("Invalid 'comments' tag, got '%i' fields instead of 4", - frame->nfields); - } + /* process the value(s) */ + + field = id3_frame_field(frame, 1); + if (field == NULL || field->type != ID3_FIELD_TYPE_STRINGLIST) + return; + + /* Get the number of strings available */ + nstrings = id3_field_getnstrings(field); + for (i = 0; i < nstrings; i++) { + ucs4 = id3_field_getstrings(field, i); + if (ucs4 == NULL) + continue; + + if (type == TAG_GENRE) + ucs4 = id3_genre_name(ucs4); + + utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + if (utf8 == NULL) + continue; + + tag_add_item(dest, type, (char *)utf8); + g_free(utf8); } - /* Unsupported */ - else - g_debug("Unsupported tag type requrested"); +} + +/** + * Import a "Comment frame" (ID3v2.4.0 section 4.10). It + * contains 4 fields: + * + * - encoding + * - language + * - string + * - full string (we use this one) + */ +static void +tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id, + enum tag_type type) +{ + struct id3_frame const *frame; + id3_ucs4_t const *ucs4; + id3_utf8_t *utf8; + union id3_field const *field; + + frame = id3_tag_findframe(tag, id, 0); + if (frame == NULL || frame->nfields != 4) + return; + + /* for now I only read the 4th field, with the fullstring */ + field = id3_frame_field(frame, 3); + if (field == NULL) + return; + + ucs4 = id3_field_getfullstring(field); + if (ucs4 == NULL) + return; + + utf8 = import_id3_string(tag_is_id3v1(tag), ucs4); + if (utf8 == NULL) + return; + + tag_add_item(dest, type, (char *)utf8); + g_free(utf8); } /** @@ -237,6 +220,7 @@ tag_id3_parse_txxx_name(const char *name) enum tag_type type; const char *name; } musicbrainz_txxx[] = { + { TAG_ALBUM_ARTIST_SORT, "ALBUMARTISTSORT" }, { TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id" }, { TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id" }, { TAG_MUSICBRAINZ_ALBUMARTISTID, @@ -328,20 +312,23 @@ struct tag *tag_id3_import(struct id3_tag * tag) { struct tag *ret = tag_new(); - getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret); - getID3Info(tag, ID3_FRAME_ALBUM_ARTIST, - TAG_ITEM_ALBUM_ARTIST, ret); - getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT, - TAG_ITEM_ALBUM_ARTIST, ret); - getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret); - getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret); - getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret); - getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret); - getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret); - getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret); - getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret); - getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret); - getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret); + tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST, TAG_ARTIST); + tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST, + TAG_ALBUM_ARTIST); + tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST_SORT, + TAG_ARTIST_SORT); + tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST_SORT, + TAG_ALBUM_ARTIST_SORT); + tag_id3_import_text(ret, tag, ID3_FRAME_TITLE, TAG_TITLE); + tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM, TAG_ALBUM); + tag_id3_import_text(ret, tag, ID3_FRAME_TRACK, TAG_TRACK); + tag_id3_import_text(ret, tag, ID3_FRAME_YEAR, TAG_DATE); + tag_id3_import_text(ret, tag, ID3_FRAME_GENRE, TAG_GENRE); + tag_id3_import_text(ret, tag, ID3_FRAME_COMPOSER, TAG_COMPOSER); + tag_id3_import_text(ret, tag, "TPE3", TAG_PERFORMER); + tag_id3_import_text(ret, tag, "TPE4", TAG_PERFORMER); + tag_id3_import_comment(ret, tag, ID3_FRAME_COMMENT, TAG_COMMENT); + tag_id3_import_text(ret, tag, ID3_FRAME_DISC, TAG_DISC); tag_id3_import_musicbrainz(ret, tag); tag_id3_import_ufid(ret, tag); @@ -354,69 +341,72 @@ struct tag *tag_id3_import(struct id3_tag * tag) return ret; } -static int fillBuffer(void *buf, size_t size, FILE * stream, - long offset, int whence) +static int +fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence) { if (fseek(stream, offset, whence) != 0) return 0; return fread(buf, 1, size, stream); } -static int getId3v2FooterSize(FILE * stream, long offset, int whence) +static int +get_id3v2_footer_size(FILE *stream, long offset, int whence) { id3_byte_t buf[ID3_TAG_QUERYSIZE]; int bufsize; - bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); + bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); if (bufsize <= 0) return 0; return id3_tag_query(buf, bufsize); } -static struct id3_tag *getId3Tag(FILE * stream, long offset, int whence) +static struct id3_tag * +tag_id3_read(FILE *stream, long offset, int whence) { struct id3_tag *tag; - id3_byte_t queryBuf[ID3_TAG_QUERYSIZE]; - id3_byte_t *tagBuf; - int tagSize; - int queryBufSize; - int tagBufSize; + id3_byte_t query_buffer[ID3_TAG_QUERYSIZE]; + id3_byte_t *tag_buffer; + int tag_size; + int query_buffer_size; + int tag_buffer_size; /* It's ok if we get less than we asked for */ - queryBufSize = fillBuffer(queryBuf, ID3_TAG_QUERYSIZE, - stream, offset, whence); - if (queryBufSize <= 0) return NULL; + query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE, + stream, offset, whence); + if (query_buffer_size <= 0) return NULL; /* Look for a tag header */ - tagSize = id3_tag_query(queryBuf, queryBufSize); - if (tagSize <= 0) return NULL; + tag_size = id3_tag_query(query_buffer, query_buffer_size); + if (tag_size <= 0) return NULL; /* Found a tag. Allocate a buffer and read it in. */ - tagBuf = g_malloc(tagSize); - if (!tagBuf) return NULL; + tag_buffer = g_malloc(tag_size); + if (!tag_buffer) return NULL; - tagBufSize = fillBuffer(tagBuf, tagSize, stream, offset, whence); - if (tagBufSize < tagSize) { - g_free(tagBuf); + tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence); + if (tag_buffer_size < tag_size) { + g_free(tag_buffer); return NULL; } - tag = id3_tag_parse(tagBuf, tagBufSize); + tag = id3_tag_parse(tag_buffer, tag_buffer_size); - g_free(tagBuf); + g_free(tag_buffer); return tag; } -static struct id3_tag *findId3TagFromBeginning(FILE * stream) +static struct id3_tag * +tag_id3_find_from_beginning(FILE *stream) { struct id3_tag *tag; struct id3_tag *seektag; struct id3_frame *frame; int seek; - tag = getId3Tag(stream, 0, SEEK_SET); + tag = tag_id3_read(stream, 0, SEEK_SET); if (!tag) { return NULL; - } else if (isId3v1(tag)) { + } else if (tag_is_id3v1(tag)) { /* id3v1 tags don't belong here */ id3_tag_delete(tag); return NULL; @@ -430,8 +420,8 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream) break; /* Get the tag specified by the SEEK frame */ - seektag = getId3Tag(stream, seek, SEEK_CUR); - if (!seektag || isId3v1(seektag)) + seektag = tag_id3_read(stream, seek, SEEK_CUR); + if (!seektag || tag_is_id3v1(seektag)) break; /* Replace the old tag with the new one */ @@ -442,22 +432,23 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream) return tag; } -static struct id3_tag *findId3TagFromEnd(FILE * stream) +static struct id3_tag * +tag_id3_find_from_end(FILE *stream) { struct id3_tag *tag; struct id3_tag *v1tag; int tagsize; /* Get an id3v1 tag from the end of file for later use */ - v1tag = getId3Tag(stream, -128, SEEK_END); + v1tag = tag_id3_read(stream, -128, SEEK_END); /* Get the id3v2 tag size from the footer (located before v1tag) */ - tagsize = getId3v2FooterSize(stream, (v1tag ? -128 : 0) - 10, SEEK_END); + tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END); if (tagsize >= 0) return v1tag; /* Get the tag which the footer belongs to */ - tag = getId3Tag(stream, tagsize, SEEK_CUR); + tag = tag_id3_read(stream, tagsize, SEEK_CUR); if (!tag) return v1tag; @@ -511,11 +502,11 @@ struct tag *tag_id3_load(const char *file) return NULL; } - tag = findId3TagFromBeginning(stream); + tag = tag_id3_find_from_beginning(stream); if (tag == NULL) tag = tag_id3_riff_aiff_load(stream); if (!tag) - tag = findId3TagFromEnd(stream); + tag = tag_id3_find_from_end(stream); fclose(stream); diff --git a/src/tag_id3.h b/src/tag_id3.h index 4f51a70b8..b32a834b5 100644 --- a/src/tag_id3.h +++ b/src/tag_id3.h @@ -20,7 +20,7 @@ #ifndef MPD_TAG_ID3_H #define MPD_TAG_ID3_H -#include "config.h" +#include "check.h" struct tag; diff --git a/src/tag_pool.c b/src/tag_pool.c index 6aef12941..25629ffb7 100644 --- a/src/tag_pool.c +++ b/src/tag_pool.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_pool.h" #include <assert.h> diff --git a/src/tag_print.c b/src/tag_print.c index dddbbbe67..d3b84568e 100644 --- a/src/tag_print.c +++ b/src/tag_print.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_print.h" #include "tag.h" #include "tag_internal.h" diff --git a/src/tag_save.c b/src/tag_save.c index fac948b9f..bd0ef7b76 100644 --- a/src/tag_save.c +++ b/src/tag_save.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_save.h" #include "tag.h" #include "tag_internal.h" diff --git a/src/text_file.c b/src/text_file.c new file mode 100644 index 000000000..776e57023 --- /dev/null +++ b/src/text_file.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "text_file.h" + +#include <assert.h> +#include <string.h> + +char * +read_text_line(FILE *file, GString *buffer) +{ + enum { + max_length = 512 * 1024, + step = 1024, + }; + + gsize length = 0, i; + char *p; + + assert(file != NULL); + assert(buffer != NULL); + + if (buffer->allocated_len < step) + g_string_set_size(buffer, step); + + while (buffer->len < max_length) { + p = fgets(buffer->str + length, + buffer->allocated_len - length, file); + if (p == NULL) { + if (length == 0 || ferror(file)) + return NULL; + break; + } + + i = strlen(buffer->str + length); + length += i; + if (i < step - 1 || buffer->str[length - 1] == '\n') + break; + + g_string_set_size(buffer, length + step); + } + + g_string_set_size(buffer, length); + g_strchomp(buffer->str); + return buffer->str; +} diff --git a/src/text_file.h b/src/text_file.h new file mode 100644 index 000000000..bc5c92870 --- /dev/null +++ b/src/text_file.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_FILE_H +#define MPD_TEXT_FILE_H + +#include <glib.h> + +#include <stdio.h> + +/** + * Reads a line from the input file, and strips trailing space. There + * is a reasonable maximum line length, only to prevent denial of + * service. + * + * @param file the source file, opened in text mode + * @param buffer an allocator for the buffer + * @return a pointer to the line, or NULL on end-of-file or error + */ +char * +read_text_line(FILE *file, GString *buffer); + +#endif diff --git a/src/text_input_stream.c b/src/text_input_stream.c new file mode 100644 index 000000000..3836430c7 --- /dev/null +++ b/src/text_input_stream.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "text_input_stream.h" +#include "input_stream.h" +#include "fifo_buffer.h" + +#include <glib.h> + +#include <string.h> + +struct text_input_stream { + struct input_stream *is; + + struct fifo_buffer *buffer; + + char *line; +}; + +struct text_input_stream * +text_input_stream_new(struct input_stream *is) +{ + struct text_input_stream *tis = g_new(struct text_input_stream, 1); + + tis->is = is; + tis->buffer = fifo_buffer_new(4096); + tis->line = NULL; + + return tis; +} + +void +text_input_stream_free(struct text_input_stream *tis) +{ + fifo_buffer_free(tis->buffer); + g_free(tis->line); + g_free(tis); +} + +const char * +text_input_stream_read(struct text_input_stream *tis) +{ + GError *error = NULL; + void *dest; + const char *src, *p; + size_t length, nbytes; + + g_free(tis->line); + tis->line = NULL; + + do { + dest = fifo_buffer_write(tis->buffer, &length); + if (dest != NULL) { + nbytes = input_stream_read(tis->is, dest, length, + &error); + if (nbytes > 0) + fifo_buffer_append(tis->buffer, nbytes); + else if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + return NULL; + } + } + + src = fifo_buffer_read(tis->buffer, &length); + if (src == NULL) + return NULL; + + p = memchr(src, '\n', length); + } while (p == NULL); + + length = p - src + 1; + while (p > src && g_ascii_isspace(p[-1])) + --p; + + tis->line = g_strndup(src, p - src); + fifo_buffer_consume(tis->buffer, length); + return tis->line; +} diff --git a/src/text_input_stream.h b/src/text_input_stream.h new file mode 100644 index 000000000..2b93ae180 --- /dev/null +++ b/src/text_input_stream.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TEXT_INPUT_STREAM_H +#define MPD_TEXT_INPUT_STREAM_H + +struct input_stream; +struct text_input_stream; + +/** + * Wraps an existing #input_stream object into a #text_input_stream, + * to read its contents as text lines. + * + * @param is an open #input_stream object + * @return the new #text_input_stream object + */ +struct text_input_stream * +text_input_stream_new(struct input_stream *is); + +/** + * Frees the #text_input_stream object. Does not close or free the + * underlying #input_stream. + */ +void +text_input_stream_free(struct text_input_stream *tis); + +/** + * Reads the next line from the stream. + * + * @return a line (newline character stripped), or NULL on end of file + * or error + */ +const char * +text_input_stream_read(struct text_input_stream *tis); + +#endif diff --git a/src/timer.c b/src/timer.c index d9a143bcc..39347bd57 100644 --- a/src/timer.c +++ b/src/timer.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "timer.h" #include "audio_format.h" diff --git a/src/tokenizer.c b/src/tokenizer.c new file mode 100644 index 000000000..52f847671 --- /dev/null +++ b/src/tokenizer.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "tokenizer.h" + +#include <stdbool.h> +#include <assert.h> +#include <string.h> + +G_GNUC_CONST +static GQuark +tokenizer_quark(void) +{ + return g_quark_from_static_string("tokenizer"); +} + +static inline bool +valid_word_first_char(char ch) +{ + return g_ascii_isalpha(ch); +} + +static inline bool +valid_word_char(char ch) +{ + return g_ascii_isalnum(ch) || ch == '_'; +} + +char * +tokenizer_next_word(char **input_p, GError **error_r) +{ + char *word, *input; + + assert(input_p != NULL); + assert(*input_p != NULL); + + word = input = *input_p; + + if (*input == 0) + return NULL; + + /* check the first character */ + + if (!valid_word_first_char(*input)) { + g_set_error(error_r, tokenizer_quark(), 0, + "Letter expected"); + return NULL; + } + + /* now iterate over the other characters until we find a + whitespace or end-of-string */ + + while (*++input != 0) { + if (g_ascii_isspace(*input)) { + /* a whitespace: the word ends here */ + *input = 0; + /* skip all following spaces, too */ + input = g_strchug(input + 1); + break; + } + + if (!valid_word_char(*input)) { + *input_p = input; + g_set_error(error_r, tokenizer_quark(), 0, + "Invalid word character"); + return NULL; + } + } + + /* end of string: the string is already null-terminated + here */ + + *input_p = input; + return word; +} + +static inline bool +valid_unquoted_char(char ch) +{ + return (unsigned char)ch > 0x20 && ch != '"' && ch != '\''; +} + +char * +tokenizer_next_unquoted(char **input_p, GError **error_r) +{ + char *word, *input; + + assert(input_p != NULL); + assert(*input_p != NULL); + + word = input = *input_p; + + if (*input == 0) + return NULL; + + /* check the first character */ + + if (!valid_unquoted_char(*input)) { + g_set_error(error_r, tokenizer_quark(), 0, + "Invalid unquoted character"); + return NULL; + } + + /* now iterate over the other characters until we find a + whitespace or end-of-string */ + + while (*++input != 0) { + if (g_ascii_isspace(*input)) { + /* a whitespace: the word ends here */ + *input = 0; + /* skip all following spaces, too */ + input = g_strchug(input + 1); + break; + } + + if (!valid_unquoted_char(*input)) { + *input_p = input; + g_set_error(error_r, tokenizer_quark(), 0, + "Invalid unquoted character"); + return NULL; + } + } + + /* end of string: the string is already null-terminated + here */ + + *input_p = input; + return word; +} + +char * +tokenizer_next_string(char **input_p, GError **error_r) +{ + char *word, *dest, *input; + + assert(input_p != NULL); + assert(*input_p != NULL); + + word = dest = input = *input_p; + + if (*input == 0) + /* end of line */ + return NULL; + + /* check for the opening " */ + + if (*input != '"') { + g_set_error(error_r, tokenizer_quark(), 0, + "'\"' expected"); + return NULL; + } + + ++input; + + /* copy all characters */ + + while (*input != '"') { + if (*input == '\\') + /* the backslash escapes the following + character */ + ++input; + + if (*input == 0) { + /* return input-1 so the caller can see the + difference between "end of line" and + "error" */ + *input_p = input - 1; + g_set_error(error_r, tokenizer_quark(), 0, + "Missing closing '\"'"); + return NULL; + } + + /* copy one character */ + *dest++ = *input++; + } + + /* the following character must be a whitespace (or end of + line) */ + + ++input; + if (*input != 0 && !g_ascii_isspace(*input)) { + *input_p = input; + g_set_error(error_r, tokenizer_quark(), 0, + "Space expected after closing '\"'"); + return NULL; + } + + /* finish the string and return it */ + + *dest = 0; + *input_p = g_strchug(input); + return word; +} + +char * +tokenizer_next_param(char **input_p, GError **error_r) +{ + assert(input_p != NULL); + assert(*input_p != NULL); + + if (**input_p == '"') + return tokenizer_next_string(input_p, error_r); + else + return tokenizer_next_unquoted(input_p, error_r); +} diff --git a/src/tokenizer.h b/src/tokenizer.h new file mode 100644 index 000000000..ce4c37ccd --- /dev/null +++ b/src/tokenizer.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_TOKENIZER_H +#define MPD_TOKENIZER_H + +#include <glib.h> + +/** + * Reads the next word from the input string. This function modifies + * the input string. + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated word, or NULL on error or + * end of line + */ +char * +tokenizer_next_word(char **input_p, GError **error_r); + +/** + * Reads the next unquoted word from the input string. This function + * modifies the input string. + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated word, or NULL on error or + * end of line + */ +char * +tokenizer_next_unquoted(char **input_p, GError **error_r); + +/** + * Reads the next quoted string from the input string. A backslash + * escapes the following character. This function modifies the input + * string. + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated string, or NULL on error + * or end of line + */ +char * +tokenizer_next_string(char **input_p, GError **error_r); + +/** + * Reads the next unquoted word or quoted string from the input. This + * is a wrapper for tokenizer_next_unquoted() and + * tokenizer_next_string(). + * + * @param input_p the input string; this function returns a pointer to + * the first non-whitespace character of the following token + * @param error_r if this function returns NULL and **input_p!=0, it + * optionally provides a GError object in this argument + * @return a pointer to the null-terminated string, or NULL on error + * or end of line + */ +char * +tokenizer_next_param(char **input_p, GError **error_r); + +#endif diff --git a/src/update.c b/src/update.c index d5c9779c8..ee946f357 100644 --- a/src/update.c +++ b/src/update.c @@ -17,45 +17,21 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" +#include "update_internal.h" #include "update.h" #include "database.h" -#include "directory.h" -#include "song.h" -#include "uri.h" #include "mapper.h" -#include "path.h" -#include "decoder_list.h" -#include "archive_list.h" #include "playlist.h" #include "event_pipe.h" -#include "notify.h" #include "update.h" #include "idle.h" -#include "conf.h" #include "stats.h" #include "main.h" -#include "config.h" - -#ifdef ENABLE_SQLITE -#include "sticker.h" -#include "song_sticker.h" -#endif #include <glib.h> #include <assert.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <dirent.h> -#include <string.h> -#include <stdlib.h> -#include <errno.h> - -#include "decoder_plugin.h" - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "update" static enum update_progress { UPDATE_PROGRESS_IDLE = 0, @@ -65,32 +41,14 @@ static enum update_progress { static bool modified; -/* make this dynamic?, or maybe this is big enough... */ -static char *update_paths[32]; -static size_t update_paths_nr; - static GThread *update_thr; static const unsigned update_task_id_max = 1 << 15; static unsigned update_task_id; -static struct song *delete; - -/** used by the main thread to notify the update thread */ -static struct notify update_notify; - -#ifndef WIN32 - -enum { - DEFAULT_FOLLOW_INSIDE_SYMLINKS = true, - DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true, -}; - -static bool follow_inside_symlinks; -static bool follow_outside_symlinks; - -#endif +/* XXX this flag is passed to update_task() */ +static bool discard; unsigned isUpdatingDB(void) @@ -98,692 +56,11 @@ isUpdatingDB(void) return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; } -static void -directory_set_stat(struct directory *dir, const struct stat *st) -{ - dir->inode = st->st_ino; - dir->device = st->st_dev; - dir->stat = 1; -} - -static void -delete_song(struct directory *dir, struct song *del) -{ - /* first, prevent traversers in main task from getting this */ - songvec_delete(&dir->songs, del); - - /* now take it out of the playlist (in the main_task) */ - assert(!delete); - delete = del; - event_pipe_emit(PIPE_EVENT_DELETE); - - do { - notify_wait(&update_notify); - } while (delete != NULL); - - /* finally, all possible references gone, free it */ - song_free(del); -} - -static int -delete_each_song(struct song *song, G_GNUC_UNUSED void *data) -{ - struct directory *directory = data; - assert(song->parent == directory); - delete_song(directory, song); - return 0; -} - -static void -delete_directory(struct directory *directory); - -/** - * Recursively remove all sub directories and songs from a directory, - * leaving an empty directory. - */ -static void -clear_directory(struct directory *directory) -{ - int i; - - for (i = directory->children.nr; --i >= 0;) - delete_directory(directory->children.base[i]); - - assert(directory->children.nr == 0); - - songvec_for_each(&directory->songs, delete_each_song, directory); -} - -/** - * Recursively free a directory and all its contents. - */ -static void -delete_directory(struct directory *directory) -{ - assert(directory->parent != NULL); - - clear_directory(directory); - - dirvec_delete(&directory->parent->children, directory); - directory_free(directory); -} - -static void -delete_name_in(struct directory *parent, const char *name) -{ - struct directory *directory = directory_get_child(parent, name); - struct song *song = songvec_find(&parent->songs, name); - - if (directory != NULL) { - delete_directory(directory); - modified = true; - } - - if (song != NULL) { - delete_song(parent, song); - modified = true; - } -} - -/* passed to songvec_for_each */ -static int -delete_song_if_removed(struct song *song, void *_data) -{ - struct directory *dir = _data; - char *path; - struct stat st; - - if ((path = map_song_fs(song)) == NULL || - stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { - delete_song(dir, song); - modified = true; - } - - g_free(path); - return 0; -} - -static bool -directory_exists(const struct directory *directory) -{ - char *path_fs; - GFileTest test; - bool exists; - - path_fs = map_directory_fs(directory); - if (path_fs == NULL) - /* invalid path: cannot exist */ - return false; - - test = directory->device == DEVICE_INARCHIVE || - directory->device == DEVICE_CONTAINER - ? G_FILE_TEST_IS_REGULAR - : G_FILE_TEST_IS_DIR; - - exists = g_file_test(path_fs, test); - g_free(path_fs); - - return exists; -} - -static void -removeDeletedFromDirectory(struct directory *directory) -{ - int i; - struct dirvec *dv = &directory->children; - - for (i = dv->nr; --i >= 0; ) { - if (directory_exists(dv->base[i])) - continue; - - g_debug("removing directory: %s", dv->base[i]->path); - delete_directory(dv->base[i]); - modified = true; - } - - songvec_for_each(&directory->songs, delete_song_if_removed, directory); -} - -static int -stat_directory(const struct directory *directory, struct stat *st) -{ - char *path_fs; - int ret; - - path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return -1; - ret = stat(path_fs, st); - g_free(path_fs); - return ret; -} - -static int -stat_directory_child(const struct directory *parent, const char *name, - struct stat *st) -{ - char *path_fs; - int ret; - - path_fs = map_directory_child_fs(parent, name); - if (path_fs == NULL) - return -1; - - ret = stat(path_fs, st); - g_free(path_fs); - return ret; -} - -static int -statDirectory(struct directory *dir) -{ - struct stat st; - - if (stat_directory(dir, &st) < 0) - return -1; - - directory_set_stat(dir, &st); - - return 0; -} - -static int -inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device) -{ - while (parent) { - if (!parent->stat && statDirectory(parent) < 0) - return -1; - if (parent->inode == inode && parent->device == device) { - g_debug("recursive directory found"); - return 1; - } - parent = parent->parent; - } - - return 0; -} - -static struct directory * -make_subdir(struct directory *parent, const char *name) -{ - struct directory *directory; - - directory = directory_get_child(parent, name); - if (directory == NULL) { - char *path; - - if (directory_is_root(parent)) - path = NULL; - else - name = path = g_strconcat(directory_get_path(parent), - "/", name, NULL); - - directory = directory_new_child(parent, name); - g_free(path); - } - - return directory; -} - -#ifdef ENABLE_ARCHIVE -static void -update_archive_tree(struct directory *directory, char *name) -{ - struct directory *subdir; - struct song *song; - char *tmp; - - tmp = strchr(name, '/'); - if (tmp) { - *tmp = 0; - //add dir is not there already - if ((subdir = dirvec_find(&directory->children, name)) == NULL) { - //create new directory - subdir = make_subdir(directory, name); - subdir->device = DEVICE_INARCHIVE; - } - //create directories first - update_archive_tree(subdir, tmp+1); - } else { - if (strlen(name) == 0) { - g_warning("archive returned directory only"); - return; - } - //add file - song = songvec_find(&directory->songs, name); - if (song == NULL) { - song = song_file_load(name, directory); - if (song != NULL) { - songvec_add(&directory->songs, song); - modified = true; - g_message("added %s/%s", - directory_get_path(directory), name); - } - } - } -} - -/** - * Updates the file listing from an archive file. - * - * @param parent the parent directory the archive file resides in - * @param name the UTF-8 encoded base name of the archive file - * @param st stat() information on the archive file - * @param plugin the archive plugin which fits this archive type - */ -static void -update_archive_file(struct directory *parent, const char *name, - const struct stat *st, - const struct archive_plugin *plugin) -{ - char *path_fs; - struct archive_file *file; - struct directory *directory; - char *filepath; - - directory = dirvec_find(&parent->children, name); - if (directory != NULL && directory->mtime == st->st_mtime) - /* MPD has already scanned the archive, and it hasn't - changed since - don't consider updating it */ - return; - - path_fs = map_directory_child_fs(parent, name); - - /* open archive */ - file = plugin->open(path_fs); - if (file == NULL) { - g_warning("unable to open archive %s", path_fs); - g_free(path_fs); - return; - } - - g_debug("archive %s opened", path_fs); - g_free(path_fs); - - if (directory == NULL) { - g_debug("creating archive directory: %s", name); - directory = make_subdir(parent, name); - /* mark this directory as archive (we use device for - this) */ - directory->device = DEVICE_INARCHIVE; - } - - directory->mtime = st->st_mtime; - - plugin->scan_reset(file); - - while ((filepath = plugin->scan_next(file)) != NULL) { - /* split name into directory and file */ - g_debug("adding archive file: %s", filepath); - update_archive_tree(directory, filepath); - } - - plugin->close(file); -} -#endif - -static bool -update_container_file( struct directory* directory, - const char* name, - const struct stat* st, - const struct decoder_plugin* plugin) -{ - char* vtrack = NULL; - unsigned int tnum = 0; - char* pathname = map_directory_child_fs(directory, name); - struct directory* contdir = dirvec_find(&directory->children, name); - - // directory exists already - if (contdir != NULL) - { - // modification time not eq. file mod. time - if (contdir->mtime != st->st_mtime) - { - g_message("removing container file: %s", pathname); - - delete_directory(contdir); - contdir = NULL; - - modified = true; - } - else { - g_free(pathname); - return true; - } - } - - contdir = make_subdir(directory, name); - contdir->mtime = st->st_mtime; - contdir->device = DEVICE_CONTAINER; - - while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) - { - struct song* song = song_file_new(vtrack, contdir); - char *child_path_fs; - - // shouldn't be necessary but it's there.. - song->mtime = st->st_mtime; - - child_path_fs = map_directory_child_fs(contdir, vtrack); - g_free(vtrack); - - song->tag = plugin->tag_dup(child_path_fs); - g_free(child_path_fs); - - songvec_add(&contdir->songs, song); - - modified = true; - } - - g_free(pathname); - - if (tnum == 1) - { - delete_directory(contdir); - return false; - } - else - return true; -} - -static void -update_regular_file(struct directory *directory, - const char *name, const struct stat *st) -{ - const char *suffix = uri_get_suffix(name); - const struct decoder_plugin* plugin; -#ifdef ENABLE_ARCHIVE - const struct archive_plugin *archive; -#endif - if (suffix == NULL) - return; - - if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL) - { - struct song* song = songvec_find(&directory->songs, name); - - if (!(song != NULL && st->st_mtime == song->mtime) && - plugin->container_scan != NULL) - { - if (update_container_file(directory, name, st, plugin)) - { - if (song != NULL) - delete_song(directory, song); - - return; - } - } - - if (song == NULL) { - song = song_file_load(name, directory); - if (song == NULL) - return; - - songvec_add(&directory->songs, song); - modified = true; - g_message("added %s/%s", - directory_get_path(directory), name); - } else if (st->st_mtime != song->mtime) { - g_message("updating %s/%s", - directory_get_path(directory), name); - if (!song_file_update(song)) - delete_song(directory, song); - modified = true; - } -#ifdef ENABLE_ARCHIVE - } else if ((archive = archive_plugin_from_suffix(suffix))) { - update_archive_file(directory, name, st, archive); -#endif - } -} - -static bool -updateDirectory(struct directory *directory, const struct stat *st); - -static void -updateInDirectory(struct directory *directory, - const char *name, const struct stat *st) -{ - assert(strchr(name, '/') == NULL); - - if (S_ISREG(st->st_mode)) { - update_regular_file(directory, name, st); - } else if (S_ISDIR(st->st_mode)) { - struct directory *subdir; - bool ret; - - if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) - return; - - subdir = make_subdir(directory, name); - assert(directory == subdir->parent); - - ret = updateDirectory(subdir, st); - if (!ret) - delete_directory(subdir); - } else { - g_debug("update: %s is not a directory, archive or music", name); - } -} - -/* we don't look at "." / ".." nor files with newlines in their name */ -static bool skip_path(const char *path) -{ - return (path[0] == '.' && path[1] == 0) || - (path[0] == '.' && path[1] == '.' && path[2] == 0) || - strchr(path, '\n') != NULL; -} - -static bool -skip_symlink(const struct directory *directory, const char *utf8_name) -{ -#ifndef WIN32 - char buffer[MPD_PATH_MAX]; - char *path_fs; - const char *p; - ssize_t ret; - - path_fs = map_directory_child_fs(directory, utf8_name); - if (path_fs == NULL) - return true; - - ret = readlink(path_fs, buffer, sizeof(buffer)); - g_free(path_fs); - if (ret < 0) - /* don't skip if this is not a symlink */ - return errno != EINVAL; - - if (!follow_inside_symlinks && !follow_outside_symlinks) { - /* ignore all symlinks */ - return true; - } else if (follow_inside_symlinks && follow_outside_symlinks) { - /* consider all symlinks */ - return false; - } - - if (buffer[0] == '/') - return !follow_outside_symlinks; - - p = buffer; - while (*p == '.') { - if (p[1] == '.' && p[2] == '/') { - /* "../" moves to parent directory */ - directory = directory->parent; - if (directory == NULL) { - /* we have moved outside the music - directory - skip this symlink - if such symlinks are not allowed */ - return !follow_outside_symlinks; - } - p += 3; - } else if (p[1] == '/') - /* eliminate "./" */ - p += 2; - else - break; - } - - /* we are still in the music directory, so this symlink points - to a song which is already in the database - skip according - to the follow_inside_symlinks param*/ - return !follow_inside_symlinks; -#else - /* no symlink checking on WIN32 */ - - (void)directory; - (void)utf8_name; - - return false; -#endif -} - -static bool -updateDirectory(struct directory *directory, const struct stat *st) -{ - DIR *dir; - struct dirent *ent; - char *path_fs; - - assert(S_ISDIR(st->st_mode)); - - directory_set_stat(directory, st); - - path_fs = map_directory_fs(directory); - if (path_fs == NULL) - return false; - - dir = opendir(path_fs); - if (!dir) { - g_warning("Failed to open directory %s: %s", - path_fs, g_strerror(errno)); - g_free(path_fs); - return false; - } - - g_free(path_fs); - - removeDeletedFromDirectory(directory); - - while ((ent = readdir(dir))) { - char *utf8; - struct stat st2; - - if (skip_path(ent->d_name)) - continue; - - utf8 = fs_charset_to_utf8(ent->d_name); - if (utf8 == NULL) - continue; - - if (skip_symlink(directory, utf8)) { - delete_name_in(directory, utf8); - g_free(utf8); - continue; - } - - if (stat_directory_child(directory, utf8, &st2) == 0) - updateInDirectory(directory, utf8, &st2); - else - delete_name_in(directory, utf8); - - g_free(utf8); - } - - closedir(dir); - - directory->mtime = st->st_mtime; - - return true; -} - -static struct directory * -directory_make_child_checked(struct directory *parent, const char *path) -{ - struct directory *directory; - char *base; - struct stat st; - struct song *conflicting; - - directory = directory_get_child(parent, path); - if (directory != NULL) - return directory; - - base = g_path_get_basename(path); - - if (stat_directory_child(parent, base, &st) < 0 || - inodeFoundInParent(parent, st.st_ino, st.st_dev)) { - g_free(base); - return NULL; - } - - /* if we're adding directory paths, make sure to delete filenames - with potentially the same name */ - conflicting = songvec_find(&parent->songs, base); - if (conflicting) - delete_song(parent, conflicting); - - g_free(base); - - directory = directory_new_child(parent, path); - directory_set_stat(directory, &st); - return directory; -} - -static struct directory * -addParentPathToDB(const char *utf8path) -{ - struct directory *directory = db_get_root(); - char *duplicated = g_strdup(utf8path); - char *slash = duplicated; - - while ((slash = strchr(slash, '/')) != NULL) { - *slash = 0; - - directory = directory_make_child_checked(directory, - duplicated); - if (directory == NULL || slash == NULL) - break; - - *slash++ = '/'; - } - - g_free(duplicated); - return directory; -} - -static void -updatePath(const char *path) -{ - struct directory *parent; - char *name; - struct stat st; - - parent = addParentPathToDB(path); - if (parent == NULL) - return; - - name = g_path_get_basename(path); - - if (stat_directory_child(parent, name, &st) == 0) - updateInDirectory(parent, name, &st); - else - delete_name_in(parent, name); - - g_free(name); -} - static void * update_task(void *_path) { - if (_path != NULL && !isRootDirectory(_path)) { - updatePath((char *)_path); - } else { - struct directory *directory = db_get_root(); - struct stat st; - - if (stat_directory(directory, &st) == 0) - updateDirectory(directory, &st); - } + const char *path = _path; + modified = update_walk(path, discard); g_free(_path); if (modified || !db_exists()) @@ -794,7 +71,8 @@ static void * update_task(void *_path) return NULL; } -static void spawn_update_task(char *path) +static void +spawn_update_task(const char *path) { GError *e = NULL; @@ -802,15 +80,18 @@ static void spawn_update_task(char *path) progress = UPDATE_PROGRESS_RUNNING; modified = false; - if (!(update_thr = g_thread_create(update_task, path, TRUE, &e))) + + update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e); + if (update_thr == NULL) g_error("Failed to spawn update task: %s", e->message); + if (++update_task_id > update_task_id_max) update_task_id = 1; g_debug("spawned thread for update job id %i", update_task_id); } unsigned -directory_update_init(char *path) +update_enqueue(const char *path, bool _discard) { assert(g_thread_self() == main_task); @@ -818,48 +99,20 @@ directory_update_init(char *path) return 0; if (progress != UPDATE_PROGRESS_IDLE) { - unsigned next_task_id; - - if (update_paths_nr == G_N_ELEMENTS(update_paths)) { - g_free(path); + unsigned next_task_id = + update_queue_push(path, discard, update_task_id); + if (next_task_id == 0) return 0; - } - - assert(update_paths_nr < G_N_ELEMENTS(update_paths)); - update_paths[update_paths_nr++] = path; - next_task_id = update_task_id + update_paths_nr; return next_task_id > update_task_id_max ? 1 : next_task_id; } - spawn_update_task(path); - return update_task_id; -} - -/** - * Safely delete a song from the database. This must be done in the - * main task, to be sure that there is no pointer left to it. - */ -static void song_delete_event(void) -{ - char *uri; - - assert(progress == UPDATE_PROGRESS_RUNNING); - assert(delete != NULL); - uri = song_get_uri(delete); - g_debug("removing: %s", uri); - g_free(uri); - -#ifdef ENABLE_SQLITE - /* if the song has a sticker, delete it */ - if (sticker_enabled()) - sticker_song_delete(delete); -#endif + discard = _discard; + spawn_update_task(path); - deleteASongFromPlaylist(&g_playlist, delete); - delete = NULL; + idle_add(IDLE_UPDATE); - notify_signal(&update_notify); + return update_task_id; } /** @@ -867,22 +120,25 @@ static void song_delete_event(void) */ static void update_finished_event(void) { + char *path; + assert(progress == UPDATE_PROGRESS_DONE); g_thread_join(update_thr); + idle_add(IDLE_UPDATE); + if (modified) { /* send "idle" events */ - playlistVersionChange(&g_playlist); + playlist_increment_version_all(&g_playlist); idle_add(IDLE_DATABASE); } - if (update_paths_nr) { + path = update_queue_shift(&discard); + if (path != NULL) { /* schedule the next path */ - char *path = update_paths[0]; - memmove(&update_paths[0], &update_paths[1], - --update_paths_nr * sizeof(char *)); spawn_update_task(path); + g_free(path); } else { progress = UPDATE_PROGRESS_IDLE; @@ -892,23 +148,14 @@ static void update_finished_event(void) void update_global_init(void) { -#ifndef WIN32 - follow_inside_symlinks = - config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, - DEFAULT_FOLLOW_INSIDE_SYMLINKS); - - follow_outside_symlinks = - config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, - DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); -#endif - - notify_init(&update_notify); - - event_pipe_register(PIPE_EVENT_DELETE, song_delete_event); event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event); + + update_remove_global_init(); + update_walk_global_init(); } void update_global_finish(void) { - notify_deinit(&update_notify); + update_walk_global_finish(); + update_remove_global_finish(); } diff --git a/src/update.h b/src/update.h index 3b7a5a332..15ff4bad1 100644 --- a/src/update.h +++ b/src/update.h @@ -20,6 +20,8 @@ #ifndef MPD_UPDATE_H #define MPD_UPDATE_H +#include <stdbool.h> + void update_global_init(void); void update_global_finish(void); @@ -27,12 +29,14 @@ void update_global_finish(void); unsigned isUpdatingDB(void); -/* - * returns the positive update job ID on success, - * returns 0 if busy - * @path will be freed by this function and should not be reused +/** + * Add this path to the database update queue. + * + * @param path a path to update; if NULL or an empty string, + * the whole music directory is updated + * @return the job id, or 0 on error */ unsigned -directory_update_init(char *path); +update_enqueue(const char *path, bool discard); #endif diff --git a/src/update_internal.h b/src/update_internal.h new file mode 100644 index 000000000..0655c727d --- /dev/null +++ b/src/update_internal.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_UPDATE_INTERNAL_H +#define MPD_UPDATE_INTERNAL_H + +#include <stdbool.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "update" + +struct stat; +struct song; +struct directory; + +unsigned +update_queue_push(const char *path, bool discard, unsigned base); + +char * +update_queue_shift(bool *discard_r); + +void +update_walk_global_init(void); + +void +update_walk_global_finish(void); + +/** + * Returns true if the database was modified. + */ +bool +update_walk(const char *path, bool discard); + +void +update_remove_global_init(void); + +void +update_remove_global_finish(void); + +/** + * Sends a signal to the main thread which will in turn remove the + * song: from the sticker database and from the playlist. This + * serialized access is implemented to avoid excessive locking. + */ +void +update_remove_song(const struct song *song); + +#endif diff --git a/src/update_queue.c b/src/update_queue.c new file mode 100644 index 000000000..be48f3043 --- /dev/null +++ b/src/update_queue.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "update_internal.h" + +#include <glib.h> + +#include <assert.h> +#include <string.h> + +/* make this dynamic?, or maybe this is big enough... */ +static struct { + char *path; + bool discard; +} update_queue[32]; + +static size_t update_queue_length; + +unsigned +update_queue_push(const char *path, bool discard, unsigned base) +{ + assert(update_queue_length <= G_N_ELEMENTS(update_queue)); + + if (update_queue_length == G_N_ELEMENTS(update_queue)) + return 0; + + update_queue[update_queue_length].path = g_strdup(path); + update_queue[update_queue_length].discard = discard; + + ++update_queue_length; + + return base + update_queue_length; +} + +char * +update_queue_shift(bool *discard_r) +{ + char *path; + + if (update_queue_length == 0) + return NULL; + + path = update_queue[0].path; + *discard_r = update_queue[0].discard; + + memmove(&update_queue[0], &update_queue[1], + --update_queue_length * sizeof(update_queue[0])); + return path; +} diff --git a/src/update_remove.c b/src/update_remove.c new file mode 100644 index 000000000..a60f0cb98 --- /dev/null +++ b/src/update_remove.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "update_internal.h" +#include "notify.h" +#include "event_pipe.h" +#include "song.h" +#include "playlist.h" + +#ifdef ENABLE_SQLITE +#include "sticker.h" +#include "song_sticker.h" +#endif + +#include <glib.h> + +#include <assert.h> + +static const struct song *removed_song; + +static struct notify remove_notify; + +/** + * Safely remove a song from the database. This must be done in the + * main task, to be sure that there is no pointer left to it. + */ +static void +song_remove_event(void) +{ + char *uri; + + assert(removed_song != NULL); + + uri = song_get_uri(removed_song); + g_debug("removing: %s", uri); + g_free(uri); + +#ifdef ENABLE_SQLITE + /* if the song has a sticker, remove it */ + if (sticker_enabled()) + sticker_song_delete(removed_song); +#endif + + playlist_delete_song(&g_playlist, removed_song); + removed_song = NULL; + + notify_signal(&remove_notify); +} + +void +update_remove_global_init(void) +{ + notify_init(&remove_notify); + + event_pipe_register(PIPE_EVENT_DELETE, song_remove_event); +} + +void +update_remove_global_finish(void) +{ + notify_deinit(&remove_notify); +} + +void +update_remove_song(const struct song *song) +{ + assert(removed_song == NULL); + + removed_song = song; + + event_pipe_emit(PIPE_EVENT_DELETE); + + do { + notify_wait(&remove_notify); + } while (removed_song != NULL); + +} diff --git a/src/update_walk.c b/src/update_walk.c new file mode 100644 index 000000000..db3111326 --- /dev/null +++ b/src/update_walk.c @@ -0,0 +1,835 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "update_internal.h" +#include "database.h" +#include "exclude.h" +#include "directory.h" +#include "song.h" +#include "uri.h" +#include "mapper.h" +#include "path.h" +#include "decoder_list.h" +#include "decoder_plugin.h" +#include "conf.h" + +#ifdef ENABLE_ARCHIVE +#include "archive_list.h" +#include "archive_plugin.h" +#endif + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> + +static bool walk_discard; +static bool modified; + +#ifndef WIN32 + +enum { + DEFAULT_FOLLOW_INSIDE_SYMLINKS = true, + DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true, +}; + +static bool follow_inside_symlinks; +static bool follow_outside_symlinks; + +#endif + +void +update_walk_global_init(void) +{ +#ifndef WIN32 + follow_inside_symlinks = + config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS, + DEFAULT_FOLLOW_INSIDE_SYMLINKS); + + follow_outside_symlinks = + config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS, + DEFAULT_FOLLOW_OUTSIDE_SYMLINKS); +#endif +} + +void +update_walk_global_finish(void) +{ +} + +static void +directory_set_stat(struct directory *dir, const struct stat *st) +{ + dir->inode = st->st_ino; + dir->device = st->st_dev; + dir->stat = 1; +} + +static void +delete_song(struct directory *dir, struct song *del) +{ + /* first, prevent traversers in main task from getting this */ + songvec_delete(&dir->songs, del); + + /* now take it out of the playlist (in the main_task) */ + update_remove_song(del); + + /* finally, all possible references gone, free it */ + song_free(del); +} + +static int +delete_each_song(struct song *song, G_GNUC_UNUSED void *data) +{ + struct directory *directory = data; + assert(song->parent == directory); + delete_song(directory, song); + return 0; +} + +static void +delete_directory(struct directory *directory); + +/** + * Recursively remove all sub directories and songs from a directory, + * leaving an empty directory. + */ +static void +clear_directory(struct directory *directory) +{ + int i; + + for (i = directory->children.nr; --i >= 0;) + delete_directory(directory->children.base[i]); + + assert(directory->children.nr == 0); + + songvec_for_each(&directory->songs, delete_each_song, directory); +} + +/** + * Recursively free a directory and all its contents. + */ +static void +delete_directory(struct directory *directory) +{ + assert(directory->parent != NULL); + + clear_directory(directory); + + dirvec_delete(&directory->parent->children, directory); + directory_free(directory); +} + +static void +delete_name_in(struct directory *parent, const char *name) +{ + struct directory *directory = directory_get_child(parent, name); + struct song *song = songvec_find(&parent->songs, name); + + if (directory != NULL) { + delete_directory(directory); + modified = true; + } + + if (song != NULL) { + delete_song(parent, song); + modified = true; + } +} + +/* passed to songvec_for_each */ +static int +delete_song_if_excluded(struct song *song, void *_data) +{ + GSList *exclude_list = _data; + char *name_fs; + + assert(song->parent != NULL); + + name_fs = utf8_to_fs_charset(song->uri); + if (exclude_list_check(exclude_list, name_fs)) { + delete_song(song->parent, song); + modified = true; + } + + g_free(name_fs); + return 0; +} + +static void +remove_excluded_from_directory(struct directory *directory, + GSList *exclude_list) +{ + int i; + struct dirvec *dv = &directory->children; + + for (i = dv->nr; --i >= 0; ) { + struct directory *child = dv->base[i]; + char *name_fs = utf8_to_fs_charset(directory_get_name(child)); + + if (exclude_list_check(exclude_list, name_fs)) { + delete_directory(child); + modified = true; + } + + g_free(name_fs); + } + + songvec_for_each(&directory->songs, + delete_song_if_excluded, exclude_list); +} + +/* passed to songvec_for_each */ +static int +delete_song_if_removed(struct song *song, void *_data) +{ + struct directory *dir = _data; + char *path; + struct stat st; + + if ((path = map_song_fs(song)) == NULL || + stat(path, &st) < 0 || !S_ISREG(st.st_mode)) { + delete_song(dir, song); + modified = true; + } + + g_free(path); + return 0; +} + +static bool +directory_exists(const struct directory *directory) +{ + char *path_fs; + GFileTest test; + bool exists; + + path_fs = map_directory_fs(directory); + if (path_fs == NULL) + /* invalid path: cannot exist */ + return false; + + test = directory->device == DEVICE_INARCHIVE || + directory->device == DEVICE_CONTAINER + ? G_FILE_TEST_IS_REGULAR + : G_FILE_TEST_IS_DIR; + + exists = g_file_test(path_fs, test); + g_free(path_fs); + + return exists; +} + +static void +removeDeletedFromDirectory(struct directory *directory) +{ + int i; + struct dirvec *dv = &directory->children; + + for (i = dv->nr; --i >= 0; ) { + if (directory_exists(dv->base[i])) + continue; + + g_debug("removing directory: %s", dv->base[i]->path); + delete_directory(dv->base[i]); + modified = true; + } + + songvec_for_each(&directory->songs, delete_song_if_removed, directory); +} + +static int +stat_directory(const struct directory *directory, struct stat *st) +{ + char *path_fs; + int ret; + + path_fs = map_directory_fs(directory); + if (path_fs == NULL) + return -1; + ret = stat(path_fs, st); + g_free(path_fs); + return ret; +} + +static int +stat_directory_child(const struct directory *parent, const char *name, + struct stat *st) +{ + char *path_fs; + int ret; + + path_fs = map_directory_child_fs(parent, name); + if (path_fs == NULL) + return -1; + + ret = stat(path_fs, st); + g_free(path_fs); + return ret; +} + +static int +statDirectory(struct directory *dir) +{ + struct stat st; + + if (stat_directory(dir, &st) < 0) + return -1; + + directory_set_stat(dir, &st); + + return 0; +} + +static int +inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device) +{ + while (parent) { + if (!parent->stat && statDirectory(parent) < 0) + return -1; + if (parent->inode == inode && parent->device == device) { + g_debug("recursive directory found"); + return 1; + } + parent = parent->parent; + } + + return 0; +} + +static struct directory * +make_subdir(struct directory *parent, const char *name) +{ + struct directory *directory; + + directory = directory_get_child(parent, name); + if (directory == NULL) { + char *path; + + if (directory_is_root(parent)) + path = NULL; + else + name = path = g_strconcat(directory_get_path(parent), + "/", name, NULL); + + directory = directory_new_child(parent, name); + g_free(path); + } + + return directory; +} + +#ifdef ENABLE_ARCHIVE +static void +update_archive_tree(struct directory *directory, char *name) +{ + struct directory *subdir; + struct song *song; + char *tmp; + + tmp = strchr(name, '/'); + if (tmp) { + *tmp = 0; + //add dir is not there already + if ((subdir = dirvec_find(&directory->children, name)) == NULL) { + //create new directory + subdir = make_subdir(directory, name); + subdir->device = DEVICE_INARCHIVE; + } + //create directories first + update_archive_tree(subdir, tmp+1); + } else { + if (strlen(name) == 0) { + g_warning("archive returned directory only"); + return; + } + //add file + song = songvec_find(&directory->songs, name); + if (song == NULL) { + song = song_file_load(name, directory); + if (song != NULL) { + songvec_add(&directory->songs, song); + modified = true; + g_message("added %s/%s", + directory_get_path(directory), name); + } + } + } +} + +/** + * Updates the file listing from an archive file. + * + * @param parent the parent directory the archive file resides in + * @param name the UTF-8 encoded base name of the archive file + * @param st stat() information on the archive file + * @param plugin the archive plugin which fits this archive type + */ +static void +update_archive_file(struct directory *parent, const char *name, + const struct stat *st, + const struct archive_plugin *plugin) +{ + GError *error = NULL; + char *path_fs; + struct archive_file *file; + struct directory *directory; + char *filepath; + + directory = dirvec_find(&parent->children, name); + if (directory != NULL && directory->mtime == st->st_mtime && + !walk_discard) + /* MPD has already scanned the archive, and it hasn't + changed since - don't consider updating it */ + return; + + path_fs = map_directory_child_fs(parent, name); + + /* open archive */ + file = archive_file_open(plugin, path_fs, &error); + if (file == NULL) { + g_free(path_fs); + g_warning("%s", error->message); + g_error_free(error); + return; + } + + g_debug("archive %s opened", path_fs); + g_free(path_fs); + + if (directory == NULL) { + g_debug("creating archive directory: %s", name); + directory = make_subdir(parent, name); + /* mark this directory as archive (we use device for + this) */ + directory->device = DEVICE_INARCHIVE; + } + + directory->mtime = st->st_mtime; + + archive_file_scan_reset(file); + + while ((filepath = archive_file_scan_next(file)) != NULL) { + /* split name into directory and file */ + g_debug("adding archive file: %s", filepath); + update_archive_tree(directory, filepath); + } + + archive_file_close(file); +} +#endif + +static bool +update_container_file( struct directory* directory, + const char* name, + const struct stat* st, + const struct decoder_plugin* plugin) +{ + char* vtrack = NULL; + unsigned int tnum = 0; + char* pathname = map_directory_child_fs(directory, name); + struct directory* contdir = dirvec_find(&directory->children, name); + + // directory exists already + if (contdir != NULL) + { + // modification time not eq. file mod. time + if (contdir->mtime != st->st_mtime || walk_discard) + { + g_message("removing container file: %s", pathname); + + delete_directory(contdir); + contdir = NULL; + + modified = true; + } + else { + g_free(pathname); + return true; + } + } + + contdir = make_subdir(directory, name); + contdir->mtime = st->st_mtime; + contdir->device = DEVICE_CONTAINER; + + while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL) + { + struct song* song = song_file_new(vtrack, contdir); + char *child_path_fs; + + // shouldn't be necessary but it's there.. + song->mtime = st->st_mtime; + + child_path_fs = map_directory_child_fs(contdir, vtrack); + + song->tag = plugin->tag_dup(child_path_fs); + g_free(child_path_fs); + + songvec_add(&contdir->songs, song); + + modified = true; + + g_message("added %s/%s", + directory_get_path(directory), vtrack); + g_free(vtrack); + } + + g_free(pathname); + + if (tnum == 1) + { + delete_directory(contdir); + return false; + } + else + return true; +} + +static void +update_regular_file(struct directory *directory, + const char *name, const struct stat *st) +{ + const char *suffix = uri_get_suffix(name); + const struct decoder_plugin* plugin; +#ifdef ENABLE_ARCHIVE + const struct archive_plugin *archive; +#endif + if (suffix == NULL) + return; + + if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL) + { + struct song* song = songvec_find(&directory->songs, name); + + if (!(song != NULL && st->st_mtime == song->mtime && + !walk_discard) && + plugin->container_scan != NULL) + { + if (update_container_file(directory, name, st, plugin)) + { + if (song != NULL) + delete_song(directory, song); + + return; + } + } + + if (song == NULL) { + song = song_file_load(name, directory); + if (song == NULL) { + g_debug("ignoring unrecognized file %s/%s", + directory_get_path(directory), name); + return; + } + + songvec_add(&directory->songs, song); + modified = true; + g_message("added %s/%s", + directory_get_path(directory), name); + } else if (st->st_mtime != song->mtime || walk_discard) { + g_message("updating %s/%s", + directory_get_path(directory), name); + if (!song_file_update(song)) { + g_debug("deleting unrecognized file %s/%s", + directory_get_path(directory), name); + delete_song(directory, song); + } + + modified = true; + } +#ifdef ENABLE_ARCHIVE + } else if ((archive = archive_plugin_from_suffix(suffix))) { + update_archive_file(directory, name, st, archive); +#endif + } +} + +static bool +updateDirectory(struct directory *directory, const struct stat *st); + +static void +updateInDirectory(struct directory *directory, + const char *name, const struct stat *st) +{ + assert(strchr(name, '/') == NULL); + + if (S_ISREG(st->st_mode)) { + update_regular_file(directory, name, st); + } else if (S_ISDIR(st->st_mode)) { + struct directory *subdir; + bool ret; + + if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) + return; + + subdir = make_subdir(directory, name); + assert(directory == subdir->parent); + + ret = updateDirectory(subdir, st); + if (!ret) + delete_directory(subdir); + } else { + g_debug("update: %s is not a directory, archive or music", name); + } +} + +/* we don't look at "." / ".." nor files with newlines in their name */ +static bool skip_path(const char *path) +{ + return (path[0] == '.' && path[1] == 0) || + (path[0] == '.' && path[1] == '.' && path[2] == 0) || + strchr(path, '\n') != NULL; +} + +static bool +skip_symlink(const struct directory *directory, const char *utf8_name) +{ +#ifndef WIN32 + char buffer[MPD_PATH_MAX]; + char *path_fs; + const char *p; + ssize_t ret; + + path_fs = map_directory_child_fs(directory, utf8_name); + if (path_fs == NULL) + return true; + + ret = readlink(path_fs, buffer, sizeof(buffer)); + g_free(path_fs); + if (ret < 0) + /* don't skip if this is not a symlink */ + return errno != EINVAL; + + if (!follow_inside_symlinks && !follow_outside_symlinks) { + /* ignore all symlinks */ + return true; + } else if (follow_inside_symlinks && follow_outside_symlinks) { + /* consider all symlinks */ + return false; + } + + if (buffer[0] == '/') + return !follow_outside_symlinks; + + p = buffer; + while (*p == '.') { + if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) { + /* "../" moves to parent directory */ + directory = directory->parent; + if (directory == NULL) { + /* we have moved outside the music + directory - skip this symlink + if such symlinks are not allowed */ + return !follow_outside_symlinks; + } + p += 3; + } else if (G_IS_DIR_SEPARATOR(p[1])) + /* eliminate "./" */ + p += 2; + else + break; + } + + /* we are still in the music directory, so this symlink points + to a song which is already in the database - skip according + to the follow_inside_symlinks param*/ + return !follow_inside_symlinks; +#else + /* no symlink checking on WIN32 */ + + (void)directory; + (void)utf8_name; + + return false; +#endif +} + +static bool +updateDirectory(struct directory *directory, const struct stat *st) +{ + DIR *dir; + struct dirent *ent; + char *path_fs, *exclude_path_fs; + GSList *exclude_list; + + assert(S_ISDIR(st->st_mode)); + + directory_set_stat(directory, st); + + path_fs = map_directory_fs(directory); + if (path_fs == NULL) + return false; + + dir = opendir(path_fs); + if (!dir) { + g_warning("Failed to open directory %s: %s", + path_fs, g_strerror(errno)); + g_free(path_fs); + return false; + } + + exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL); + exclude_list = exclude_list_load(exclude_path_fs); + g_free(exclude_path_fs); + + g_free(path_fs); + + if (exclude_list != NULL) + remove_excluded_from_directory(directory, exclude_list); + + removeDeletedFromDirectory(directory); + + while ((ent = readdir(dir))) { + char *utf8; + struct stat st2; + + if (skip_path(ent->d_name) || + exclude_list_check(exclude_list, ent->d_name)) + continue; + + utf8 = fs_charset_to_utf8(ent->d_name); + if (utf8 == NULL) + continue; + + if (skip_symlink(directory, utf8)) { + delete_name_in(directory, utf8); + g_free(utf8); + continue; + } + + if (stat_directory_child(directory, utf8, &st2) == 0) + updateInDirectory(directory, utf8, &st2); + else + delete_name_in(directory, utf8); + + g_free(utf8); + } + + exclude_list_free(exclude_list); + + closedir(dir); + + directory->mtime = st->st_mtime; + + return true; +} + +static struct directory * +directory_make_child_checked(struct directory *parent, const char *path) +{ + struct directory *directory; + char *base; + struct stat st; + struct song *conflicting; + + directory = directory_get_child(parent, path); + if (directory != NULL) + return directory; + + base = g_path_get_basename(path); + + if (stat_directory_child(parent, base, &st) < 0 || + inodeFoundInParent(parent, st.st_ino, st.st_dev)) { + g_free(base); + return NULL; + } + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + conflicting = songvec_find(&parent->songs, base); + if (conflicting) + delete_song(parent, conflicting); + + g_free(base); + + directory = directory_new_child(parent, path); + directory_set_stat(directory, &st); + return directory; +} + +static struct directory * +addParentPathToDB(const char *utf8path) +{ + struct directory *directory = db_get_root(); + char *duplicated = g_strdup(utf8path); + char *slash = duplicated; + + while ((slash = strchr(slash, '/')) != NULL) { + *slash = 0; + + directory = directory_make_child_checked(directory, + duplicated); + if (directory == NULL || slash == NULL) + break; + + *slash++ = '/'; + } + + g_free(duplicated); + return directory; +} + +static void +updatePath(const char *path) +{ + struct directory *parent; + char *name; + struct stat st; + + parent = addParentPathToDB(path); + if (parent == NULL) + return; + + name = g_path_get_basename(path); + + if (stat_directory_child(parent, name, &st) == 0) + updateInDirectory(parent, name, &st); + else + delete_name_in(parent, name); + + g_free(name); +} + +bool +update_walk(const char *path, bool discard) +{ + walk_discard = discard; + modified = false; + + if (path != NULL && !isRootDirectory(path)) { + updatePath(path); + } else { + struct directory *directory = db_get_root(); + struct stat st; + + if (stat_directory(directory, &st) == 0) + updateDirectory(directory, &st); + } + + return modified; +} @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "uri.h" #include <glib.h> +#include <assert.h> #include <string.h> bool uri_has_scheme(const char *uri) @@ -32,9 +34,45 @@ bool uri_has_scheme(const char *uri) const char * uri_get_suffix(const char *uri) { - const char *dot = strrchr(g_basename(uri), '.'); + const char *suffix = strrchr(g_basename(uri), '.'); + if (suffix == NULL) + return NULL; + + ++suffix; + + if (strchr(suffix, '/') != NULL) + return NULL; + + return suffix; +} + +static const char * +verify_uri_segment(const char *p) +{ + const char *q; + + if (*p == 0 || *p == '/' || *p == '.') + return NULL; + + q = strchr(p + 1, '/'); + return q != NULL ? q : ""; +} + +bool +uri_safe_local(const char *uri) +{ + while (true) { + uri = verify_uri_segment(uri); + if (uri == NULL) + return false; + + if (*uri == 0) + return true; + + assert(*uri == '/'); - return dot != NULL ? dot + 1 : NULL; + ++uri; + } } char * @@ -20,23 +20,40 @@ #ifndef MPD_URI_H #define MPD_URI_H +#include <glib.h> + #include <stdbool.h> /** * Checks whether the specified URI has a schema in the form * "scheme://". */ +G_GNUC_PURE bool uri_has_scheme(const char *uri); +G_GNUC_PURE const char * uri_get_suffix(const char *uri); /** + * Returns true if this is a safe "local" URI: + * + * - non-empty + * - does not begin or end with a slash + * - no double slashes + * - no path component begins with a dot + */ +G_GNUC_PURE +bool +uri_safe_local(const char *uri); + +/** * Removes HTTP username and password from the URI. This may be * useful for displaying an URI without disclosing secrets. Returns * NULL if nothing needs to be removed, or if the URI is not * recognized. */ +G_GNUC_MALLOC char * uri_remove_auth(const char *uri); diff --git a/src/utils.c b/src/utils.c index fc27b13c9..0e9584d68 100644 --- a/src/utils.c +++ b/src/utils.c @@ -17,9 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "utils.h" #include "conf.h" -#include "config.h" #include <glib.h> @@ -44,7 +44,7 @@ char *parsePath(char *path) { #ifndef WIN32 - if (path[0] != '/' && path[0] != '~') { + if (!g_path_is_absolute(path) && path[0] != '~') { g_warning("\"%s\" is not an absolute path", path); return NULL; } else if (path[0] == '~') { @@ -102,43 +102,15 @@ char *parsePath(char *path) #endif } -int set_nonblocking(int fd) +bool +string_array_contains(const char *const* haystack, const char *needle) { -#ifdef WIN32 - u_long val = 1; - int retval; - int lasterr = 0; - retval = ioctlsocket(fd, FIONBIO, &val); - if(retval == SOCKET_ERROR) - g_error("Error: ioctlsocket could not set FIONBIO;" - " Error %d on socket %d", lasterr = WSAGetLastError(), fd); - if(lasterr == 10038) - g_debug("Code-up error! Attempt to set non-blocking I/O on " - "something that is not a Winsock2 socket. This can't " - "be done on Windows!\n"); - return retval; -#else - int ret, flags; - - assert(fd >= 0); - - while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ; - if (flags < 0) - return flags; - - flags |= O_NONBLOCK; - while ((ret = fcntl(fd, F_SETFL, flags)) < 0 && errno == EINTR) ; - return ret; -#endif -} + assert(haystack != NULL); + assert(needle != NULL); -int stringFoundInStringArray(const char *const*array, const char *suffix) -{ - while (array && *array) { - if (g_ascii_strcasecmp(*array, suffix) == 0) - return 1; - array++; - } + for (; *haystack != NULL; ++haystack) + if (g_ascii_strcasecmp(*haystack, needle) == 0) + return true; - return 0; + return false; } diff --git a/src/utils.h b/src/utils.h index d114003be..9d891be4a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -20,6 +20,8 @@ #ifndef MPD_UTILS_H #define MPD_UTILS_H +#include <stdbool.h> + #ifndef assert_static /* Compile time assertion developed by Ralf Holly */ /* http://pera-software.com/articles/compile-time-assertions.pdf */ @@ -31,8 +33,15 @@ char *parsePath(char *path); -int set_nonblocking(int fd); - -int stringFoundInStringArray(const char *const*array, const char *suffix); +/** + * Checks whether a string array contains the specified string. + * + * @param haystack a NULL terminated list of strings + * @param needle the string to search for; the comparison is + * case-insensitive for ASCII characters + * @return true if found + */ +bool +string_array_contains(const char *const* haystack, const char *needle); #endif diff --git a/src/volume.c b/src/volume.c index e7fa20a62..8a74e10ae 100644 --- a/src/volume.c +++ b/src/volume.c @@ -17,15 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "volume.h" #include "conf.h" #include "player_control.h" #include "idle.h" #include "pcm_volume.h" -#include "config.h" #include "output_all.h" #include "mixer_control.h" #include "mixer_all.h" +#include "mixer_type.h" +#include "event_pipe.h" #include <glib.h> @@ -39,132 +41,39 @@ #define SW_VOLUME_STATE "sw_volume: " -static enum { - VOLUME_MIXER_TYPE_SOFTWARE, - VOLUME_MIXER_TYPE_HARDWARE, - VOLUME_MIXER_TYPE_DISABLED, -} volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE; - -static int volume_software_set = 100; +static unsigned volume_software_set = 100; /** the cached hardware mixer value; invalid if negative */ static int last_hardware_volume = -1; /** the age of #last_hardware_volume */ static GTimer *hardware_volume_timer; -void volume_finish(void) -{ - if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE) - g_timer_destroy(hardware_volume_timer); -} - /** - * Finds the first audio_output configuration section with the - * specified type. - */ -static struct config_param * -find_output_config(const char *type) -{ - struct config_param *param = NULL; - - while ((param = config_get_next_param(CONF_AUDIO_OUTPUT, - param)) != NULL) { - const char *param_type = - config_get_block_string(param, "type", NULL); - if (param_type != NULL && strcmp(param_type, type) == 0) - return param; - } - - return NULL; -} - -/** - * Copy a (top-level) legacy mixer configuration parameter to the - * audio_output section. + * Handler for #PIPE_EVENT_MIXER. */ static void -mixer_copy_legacy_param(const char *type, const char *name) +mixer_event_callback(void) { - const struct config_param *param; - struct config_param *output; - const struct block_param *bp; - - /* see if the deprecated configuration exists */ - - param = config_get_param(name); - if (param == NULL) - return; - - g_warning("deprecated option '%s' found, moving to '%s' audio output", - name, type); - - /* determine the configuration section */ - - output = find_output_config(type); - if (output == NULL) { - /* if there is no output configuration at all, create - a new and empty configuration section for the - legacy mixer */ - - if (config_get_next_param(CONF_AUDIO_OUTPUT, NULL) != NULL) - /* there is an audio_output configuration, but - it does not match the mixer_type setting */ - g_error("no '%s' audio output found", type); - - output = config_new_param(NULL, param->line); - config_add_block_param(output, "type", type, param->line); - config_add_block_param(output, "name", type, param->line); - config_add_param(CONF_AUDIO_OUTPUT, output); - } - - bp = config_get_block_param(output, name); - if (bp != NULL) - g_error("the '%s' audio output already has a '%s' setting", - type, name); - - /* duplicate the parameter in the configuration section */ + /* flush the hardware volume cache */ + last_hardware_volume = -1; - config_add_block_param(output, name, param->value, param->line); + /* notify clients */ + idle_add(IDLE_MIXER); } -static void -mixer_reconfigure(const char *type) +void volume_finish(void) { - mixer_copy_legacy_param(type, CONF_MIXER_DEVICE); - mixer_copy_legacy_param(type, CONF_MIXER_CONTROL); + g_timer_destroy(hardware_volume_timer); } void volume_init(void) { - const struct config_param *param = config_get_param(CONF_MIXER_TYPE); - //hw mixing is by default - if (param) { - if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) { - volume_mixer_type = VOLUME_MIXER_TYPE_SOFTWARE; - mixer_disable_all(); - } else if (strcmp(param->value, VOLUME_MIXER_DISABLED) == 0) { - volume_mixer_type = VOLUME_MIXER_TYPE_DISABLED; - mixer_disable_all(); - } else if (strcmp(param->value, VOLUME_MIXER_HARDWARE) == 0) { - //nothing to do - } else { - //fallback to old config behaviour - if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) { - mixer_reconfigure(param->value); - } else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) { - mixer_reconfigure(param->value); - } else { - g_error("unknown mixer type %s at line %i\n", - param->value, param->line); - } - } - } + hardware_volume_timer = g_timer_new(); - if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE) - hardware_volume_timer = g_timer_new(); + event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback); } -static int hardware_volume_get(void) +int volume_level_get(void) { assert(hardware_volume_timer != NULL); @@ -178,101 +87,60 @@ static int hardware_volume_get(void) return last_hardware_volume; } -static int software_volume_get(void) +static bool software_volume_change(unsigned volume) { - return volume_software_set; -} - -int volume_level_get(void) -{ - switch (volume_mixer_type) { - case VOLUME_MIXER_TYPE_SOFTWARE: - return software_volume_get(); - case VOLUME_MIXER_TYPE_HARDWARE: - return hardware_volume_get(); - case VOLUME_MIXER_TYPE_DISABLED: - return -1; - } - - /* unreachable */ - assert(false); - return -1; -} - -static bool software_volume_change(int change, bool rel) -{ - int new = change; - - if (rel) - new += volume_software_set; + assert(volume <= 100); - if (new > 100) - new = 100; - else if (new < 0) - new = 0; - - volume_software_set = new; - - /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */ - if (new >= 100) - new = PCM_VOLUME_1; - else if (new <= 0) - new = 0; - else - new = pcm_float_to_volume((exp(new / 25.0) - 1) / - (54.5981500331F - 1)); - - setPlayerSoftwareVolume(new); + volume_software_set = volume; + mixer_all_set_software_volume(volume); return true; } -static bool hardware_volume_change(int change, bool rel) +static bool hardware_volume_change(unsigned volume) { /* reset the cache */ last_hardware_volume = -1; - return mixer_all_set_volume(change, rel); + return mixer_all_set_volume(volume); } -bool volume_level_change(int change, bool rel) +bool volume_level_change(unsigned volume) { + assert(volume <= 100); + + volume_software_set = volume; + idle_add(IDLE_MIXER); - switch (volume_mixer_type) { - case VOLUME_MIXER_TYPE_HARDWARE: - return hardware_volume_change(change, rel); - case VOLUME_MIXER_TYPE_SOFTWARE: - return software_volume_change(change, rel); - default: - return true; - } + return hardware_volume_change(volume); } -void read_sw_volume_state(FILE *fp) +bool +read_sw_volume_state(const char *line) { - char buf[sizeof(SW_VOLUME_STATE) + sizeof("100") - 1]; char *end = NULL; long int sv; - if (volume_mixer_type != VOLUME_MIXER_TYPE_SOFTWARE) - return; - while (fgets(buf, sizeof(buf), fp)) { - if (!g_str_has_prefix(buf, SW_VOLUME_STATE)) - continue; + if (!g_str_has_prefix(line, SW_VOLUME_STATE)) + return false; - g_strchomp(buf); - sv = strtol(buf + strlen(SW_VOLUME_STATE), &end, 10); - if (G_LIKELY(!*end)) - software_volume_change(sv, 0); - else - g_warning("Can't parse software volume: %s\n", buf); - return; - } + line += sizeof(SW_VOLUME_STATE) - 1; + sv = strtol(line, &end, 10); + if (*end == 0 && sv >= 0 && sv <= 100) + software_volume_change(sv); + else + g_warning("Can't parse software volume: %s\n", line); + return true; } void save_sw_volume_state(FILE *fp) { - if (volume_mixer_type == VOLUME_MIXER_TYPE_SOFTWARE) - fprintf(fp, SW_VOLUME_STATE "%d\n", volume_software_set); + fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set); +} + +unsigned +sw_volume_state_get_hash(void) +{ + return volume_software_set; } diff --git a/src/volume.h b/src/volume.h index 99d31da4e..66e7c43f0 100644 --- a/src/volume.h +++ b/src/volume.h @@ -23,22 +23,26 @@ #include <stdbool.h> #include <stdio.h> -#define VOLUME_MIXER_OSS "oss" -#define VOLUME_MIXER_ALSA "alsa" -#define VOLUME_MIXER_SOFTWARE "software" -#define VOLUME_MIXER_HARDWARE "hardware" -#define VOLUME_MIXER_DISABLED "disabled" - void volume_init(void); void volume_finish(void); int volume_level_get(void); -bool volume_level_change(int change, bool rel); +bool volume_level_change(unsigned volume); -void read_sw_volume_state(FILE *fp); +bool +read_sw_volume_state(const char *line); void save_sw_volume_state(FILE *fp); +/** + * Generates a hash number for the current state of the software + * volume control. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +sw_volume_state_get_hash(void); + #endif diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c index 648f36e03..63ad0f65b 100644 --- a/src/zeroconf-avahi.c +++ b/src/zeroconf-avahi.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf-internal.h" #include "listen.h" diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c index 4e06319e7..41734d3bc 100644 --- a/src/zeroconf-bonjour.c +++ b/src/zeroconf-bonjour.c @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf-internal.h" #include "listen.h" diff --git a/src/zeroconf.c b/src/zeroconf.c index 42e995c45..9a386d53c 100644 --- a/src/zeroconf.c +++ b/src/zeroconf.c @@ -17,10 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf.h" #include "zeroconf-internal.h" #include "conf.h" -#include "config.h" #include <glib.h> diff --git a/src/zeroconf.h b/src/zeroconf.h index 6a5934ed5..0aafbdef2 100644 --- a/src/zeroconf.h +++ b/src/zeroconf.h @@ -20,7 +20,7 @@ #ifndef MPD_ZEROCONF_H #define MPD_ZEROCONF_H -#include "config.h" +#include "check.h" #ifdef HAVE_ZEROCONF |