diff options
Diffstat (limited to '')
434 files changed, 30884 insertions, 11959 deletions
diff --git a/src/AudioCompress/compress.c b/src/AudioCompress/compress.c new file mode 100644 index 000000000..d5c08372c --- /dev/null +++ b/src/AudioCompress/compress.c @@ -0,0 +1,185 @@ +/* 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 <stdint.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..073d4af9a --- /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 <stdint.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 + @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/aiff.c b/src/aiff.c index d4bec628b..e2ca0dfe4 100644 --- a/src/aiff.c +++ b/src/aiff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/aiff.h b/src/aiff.h index 3388a52fe..52c0a73ec 100644 --- a/src/aiff.h +++ b/src/aiff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/archive/bz2_archive_plugin.c b/src/archive/bz2_archive_plugin.c new file mode 100644 index 000000000..2414eb519 --- /dev/null +++ b/src/archive/bz2_archive_plugin.c @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/** + * single bz2 archive handling (requires libbz2) + */ + +#include "config.h" +#include "archive/bz2_archive_plugin.h" +#include "archive_api.h" +#include "input_plugin.h" +#include "refcount.h" + +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <glib.h> +#include <bzlib.h> + +#ifdef HAVE_OLDER_BZIP2 +#define BZ2_bzDecompressInit bzDecompressInit +#define BZ2_bzDecompress bzDecompress +#endif + +struct bz2_archive_file { + struct archive_file base; + + struct refcount ref; + + char *name; + bool reset; + struct input_stream *istream; +}; + +struct bz2_input_stream { + struct input_stream base; + + struct bz2_archive_file *archive; + + bool eof; + + bz_stream bzstream; + + char buffer[5000]; +}; + +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(struct bz2_input_stream *data, GError **error_r) +{ + int ret; + + data->bzstream.bzalloc = NULL; + data->bzstream.bzfree = NULL; + data->bzstream.opaque = NULL; + + data->bzstream.next_in = (void *) data->buffer; + data->bzstream.avail_in = 0; + + ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0); + if (ret != BZ_OK) { + g_free(data); + + g_set_error(error_r, bz2_quark(), ret, + "BZ2_bzDecompressInit() has failed"); + return false; + } + + return true; +} + +static void +bz2_destroy(struct bz2_input_stream *data) +{ + BZ2_bzDecompressEnd(&data->bzstream); +} + +/* archive open && listing routine */ + +static struct archive_file * +bz2_open(const char *pathname, GError **error_r) +{ + struct bz2_archive_file *context; + int len; + + context = g_malloc(sizeof(*context)); + archive_file_init(&context->base, &bz2_archive_plugin); + refcount_init(&context->ref); + + //open archive + context->istream = input_stream_open(pathname, error_r); + if (context->istream == NULL) { + g_free(context); + return NULL; + } + + context->name = g_path_get_basename(pathname); + + //remove suffix + len = strlen(context->name); + if (len > 4) { + context->name[len - 4] = 0; //remove .bz2 suffix + } + + return &context->base; +} + +static void +bz2_scan_reset(struct archive_file *file) +{ + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + context->reset = true; +} + +static char * +bz2_scan_next(struct archive_file *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) +{ + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + + if (!refcount_dec(&context->ref)) + return; + + g_free(context->name); + + input_stream_close(context->istream); + g_free(context); +} + +/* single archive handling */ + +static struct input_stream * +bz2_open_stream(struct archive_file *file, const char *path, GError **error_r) +{ + struct bz2_archive_file *context = (struct bz2_archive_file *) file; + struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1); + + input_stream_init(&bis->base, &bz2_inputplugin, path); + + bis->archive = context; + + bis->base.ready = true; + bis->base.seekable = false; + + if (!bz2_alloc(bis, error_r)) { + input_stream_deinit(&bis->base); + g_free(bis); + return NULL; + } + + bis->eof = false; + + refcount_inc(&context->ref); + + return &bis->base; +} + +static void +bz2_is_close(struct input_stream *is) +{ + struct bz2_input_stream *bis = (struct bz2_input_stream *)is; + + bz2_destroy(bis); + + bz2_close(&bis->archive->base); + + input_stream_deinit(&bis->base); + g_free(bis); +} + +static bool +bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r) +{ + size_t count; + bz_stream *bzstream; + + bzstream = &bis->bzstream; + + if (bzstream->avail_in > 0) + return true; + + count = input_stream_read(bis->archive->istream, + bis->buffer, sizeof(bis->buffer), + error_r); + if (count == 0) + return false; + + bzstream->next_in = bis->buffer; + bzstream->avail_in = count; + return true; +} + +static size_t +bz2_is_read(struct input_stream *is, void *ptr, size_t length, + GError **error_r) +{ + struct bz2_input_stream *bis = (struct bz2_input_stream *)is; + bz_stream *bzstream; + int bz_result; + size_t nbytes = 0; + + if (bis->eof) + return 0; + + bzstream = &bis->bzstream; + bzstream->next_out = ptr; + bzstream->avail_out = length; + + do { + if (!bz2_fillbuffer(bis, error_r)) + return 0; + + bz_result = BZ2_bzDecompress(bzstream); + + if (bz_result == BZ_STREAM_END) { + bis->eof = true; + 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); + + nbytes = length - bzstream->avail_out; + is->offset += nbytes; + + return nbytes; +} + +static bool +bz2_is_eof(struct input_stream *is) +{ + struct bz2_input_stream *bis = (struct bz2_input_stream *)is; + + return bis->eof; +} + +/* exported structures */ + +static const char *const bz2_extensions[] = { + "bz2", + NULL +}; + +static const struct input_plugin bz2_inputplugin = { + .close = bz2_is_close, + .read = bz2_is_read, + .eof = bz2_is_eof, +}; + +const struct archive_plugin bz2_archive_plugin = { + .name = "bz2", + .open = bz2_open, + .scan_reset = bz2_scan_reset, + .scan_next = bz2_scan_next, + .open_stream = bz2_open_stream, + .close = bz2_close, + .suffixes = bz2_extensions +}; + diff --git a/src/input/lastfm_input_plugin.h b/src/archive/bz2_archive_plugin.h index d0eaf5a55..199049008 100644 --- a/src/input/lastfm_input_plugin.h +++ b/src/archive/bz2_archive_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/bz2_plugin.c b/src/archive/bz2_plugin.c deleted file mode 100644 index 0ef042e90..000000000 --- a/src/archive/bz2_plugin.c +++ /dev/null @@ -1,284 +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. - */ - -/** - * single bz2 archive handling (requires libbz2) - */ - -#include "archive_api.h" -#include "input_plugin.h" -#include "config.h" - -#include <stdint.h> -#include <stddef.h> -#include <string.h> -#include <glib.h> -#include <bzlib.h> - -#ifdef HAVE_OLDER_BZIP2 -#define BZ2_bzDecompressInit bzDecompressInit -#define BZ2_bzDecompress bzDecompress -#endif - -#define BZ_BUFSIZE 5000 - -typedef struct { - char *name; - bool reset; - struct input_stream istream; - int last_bz_result; - int last_parent_result; - bz_stream bzstream; - char *buffer; -} bz2_context; - - -static const struct input_plugin bz2_inputplugin; - -/* single archive handling allocation helpers */ - -static bool -bz2_alloc(bz2_context *data) -{ - data->bzstream.bzalloc = NULL; - data->bzstream.bzfree = NULL; - data->bzstream.opaque = NULL; - - data->buffer = g_malloc(BZ_BUFSIZE); - data->bzstream.next_in = (void *) data->buffer; - data->bzstream.avail_in = 0; - - if (BZ2_bzDecompressInit(&data->bzstream, 0, 0) != BZ_OK) { - g_free(data->buffer); - g_free(data); - return false; - } - - data->last_bz_result = BZ_OK; - data->last_parent_result = 0; - return true; -} - -static void -bz2_destroy(bz2_context *data) -{ - BZ2_bzDecompressEnd(&data->bzstream); - g_free(data->buffer); -} - -/* archive open && listing routine */ - -static struct archive_file * -bz2_open(char * pathname) -{ - bz2_context *context; - char *name; - int len; - - context = g_malloc(sizeof(bz2_context)); - if (!context) { - return NULL; - } - //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); - g_free(context); - return NULL; - } - context->name = g_strdup(name+1); - //remove suffix - len = strlen(context->name); - if (len > 4) { - context->name[len-4] = 0; //remove .bz2 suffix - } - return (struct archive_file *) context; -} - -static void -bz2_scan_reset(struct archive_file *file) -{ - bz2_context *context = (bz2_context *) file; - context->reset = true; -} - -static char * -bz2_scan_next(struct archive_file *file) -{ - bz2_context *context = (bz2_context *) 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; - - g_free(context->name); - - input_stream_close(&context->istream); - g_free(context); -} - -/* single archive handling */ - -static bool -bz2_open_stream(struct archive_file *file, struct input_stream *is, - G_GNUC_UNUSED const char *path) -{ - bz2_context *context = (bz2_context *) 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"); - return false; - } - return true; -} - -static void -bz2_is_close(struct input_stream *is) -{ - bz2_context *context = (bz2_context *) is->data; - bz2_destroy(context); - is->data = NULL; - - bz2_close((struct archive_file *)context); -} - -static int -bz2_fillbuffer(bz2_context *context, - size_t numBytes) -{ - size_t count; - bz_stream *bzstream; - - bzstream = &context->bzstream; - - if (bzstream->avail_in > 0) - return 0; - - 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; - } - - return 0; -} - -static size_t -bz2_is_read(struct input_stream *is, void *ptr, size_t size) -{ - bz2_context *context = (bz2_context *) is->data; - bz_stream *bzstream; - int bz_result; - size_t numBytes = size; - size_t bytesRead = 0; - - if (context->last_bz_result != BZ_OK) - return 0; - if (context->last_parent_result != 0) - return 0; - - bzstream = &context->bzstream; - bzstream->next_out = ptr; - bzstream->avail_out = numBytes; - - while (bzstream->avail_out != 0) { - if (bz2_fillbuffer(context, numBytes) != 0) - break; - - bz_result = BZ2_bzDecompress(bzstream); - - if (context->last_bz_result != BZ_OK - && bzstream->avail_out == numBytes) { - context->last_bz_result = bz_result; - break; - } - - if (bz_result == BZ_STREAM_END) { - context->last_bz_result = bz_result; - break; - } - } - - bytesRead = numBytes - bzstream->avail_out; - is->offset += bytesRead; - - return bytesRead; -} - -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; - } - - return false; -} - -/* exported structures */ - -static const char *const bz2_extensions[] = { - "bz2", - NULL -}; - -static const struct input_plugin bz2_inputplugin = { - .close = bz2_is_close, - .read = bz2_is_read, - .eof = bz2_is_eof, -}; - -const struct archive_plugin bz2_plugin = { - .name = "bz2", - .open = bz2_open, - .scan_reset = bz2_scan_reset, - .scan_next = bz2_scan_next, - .open_stream = bz2_open_stream, - .close = bz2_close, - .suffixes = bz2_extensions -}; - diff --git a/src/archive/iso9660_archive_plugin.c b/src/archive/iso9660_archive_plugin.c new file mode 100644 index 000000000..142fa10e0 --- /dev/null +++ b/src/archive/iso9660_archive_plugin.c @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/** + * iso archive handling (requires cdio, and iso9660) + */ + +#include "config.h" +#include "archive/iso9660_archive_plugin.h" +#include "archive_api.h" +#include "input_plugin.h" +#include "refcount.h" + +#include <cdio/cdio.h> +#include <cdio/iso9660.h> + +#include <glib.h> +#include <string.h> + +#define CEILING(x, y) ((x+(y-1))/y) + +struct iso9660_archive_file { + struct archive_file base; + + struct refcount ref; + + iso9660_t *iso; + GSList *list; + GSList *iter; +}; + +static const struct input_plugin iso9660_input_plugin; + +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, struct iso9660_archive_file *context) +{ + iso9660_t *iso = context->iso; + CdioList_t *entlist; + CdioListNode_t *entnode; + iso9660_stat_t *statbuf; + char pathname[4096]; + + entlist = iso9660_ifs_readdir (iso, psz_path); + if (!entlist) { + return; + } + /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ + _CDIO_LIST_FOREACH (entnode, entlist) { + statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); + + strcpy(pathname, psz_path); + strcat(pathname, statbuf->filename); + + if (_STAT_DIR == statbuf->type ) { + if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { + strcat(pathname, "/"); + listdir_recur(pathname, context); + } + } else { + //remove leading / + context->list = g_slist_prepend( context->list, + g_strdup(pathname + 1)); + } + } + _cdio_list_free (entlist, true); +} + +static struct archive_file * +iso9660_archive_open(const char *pathname, GError **error_r) +{ + struct iso9660_archive_file *context = + g_new(struct iso9660_archive_file, 1); + + archive_file_init(&context->base, &iso9660_archive_plugin); + refcount_init(&context->ref); + + context->list = NULL; + + /* open archive */ + context->iso = iso9660_open (pathname); + if (context->iso == NULL) { + g_set_error(error_r, iso9660_quark(), 0, + "Failed to open ISO9660 file %s", pathname); + return NULL; + } + + listdir_recur("/", context); + + return &context->base; +} + +static void +iso9660_archive_scan_reset(struct archive_file *file) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + + //reset iterator + context->iter = context->list; +} + +static char * +iso9660_archive_scan_next(struct archive_file *file) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + + char *data = NULL; + if (context->iter != NULL) { + ///fetch data and goto next + data = context->iter->data; + context->iter = g_slist_next(context->iter); + } + return data; +} + +static void +iso9660_archive_close(struct archive_file *file) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + GSList *tmp; + + if (!refcount_dec(&context->ref)) + return; + + if (context->list) { + //free list + for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) + g_free(tmp->data); + g_slist_free(context->list); + } + //close archive + iso9660_close(context->iso); + + g_free(context); +} + +/* single archive handling */ + +struct iso9660_input_stream { + struct input_stream base; + + struct iso9660_archive_file *archive; + + iso9660_stat_t *statbuf; + size_t max_blocks; +}; + +static struct input_stream * +iso9660_archive_open_stream(struct archive_file *file, + const char *pathname, GError **error_r) +{ + struct iso9660_archive_file *context = + (struct iso9660_archive_file *)file; + struct iso9660_input_stream *iis; + + iis = g_new(struct iso9660_input_stream, 1); + input_stream_init(&iis->base, &iso9660_input_plugin, pathname); + + iis->archive = context; + iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname); + if (iis->statbuf == NULL) { + g_free(iis); + g_set_error(error_r, iso9660_quark(), 0, + "not found in the ISO file: %s", pathname); + return NULL; + } + + iis->base.ready = true; + //we are not seekable + iis->base.seekable = false; + + iis->base.size = iis->statbuf->size; + + iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE); + + refcount_inc(&context->ref); + + return &iis->base; +} + +static void +iso9660_input_close(struct input_stream *is) +{ + struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; + + g_free(iis->statbuf); + + iso9660_archive_close(&iis->archive->base); + + input_stream_deinit(&iis->base); + g_free(iis); +} + + +static size_t +iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r) +{ + struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is; + int toread, readed = 0; + int no_blocks, cur_block; + size_t left_bytes = iis->statbuf->size - is->offset; + + size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; + + if (left_bytes < size) { + toread = left_bytes; + no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); + } else { + toread = size; + no_blocks = toread / ISO_BLOCKSIZE; + } + if (no_blocks > 0) { + + cur_block = is->offset / ISO_BLOCKSIZE; + + readed = iso9660_iso_seek_read (iis->archive->iso, ptr, + iis->statbuf->lsn + cur_block, no_blocks); + + if (readed != no_blocks * ISO_BLOCKSIZE) { + 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; + } + + is->offset += readed; + } + return readed; +} + +static bool +iso9660_input_eof(struct input_stream *is) +{ + return is->offset == is->size; +} + +/* exported structures */ + +static const char *const iso9660_archive_extensions[] = { + "iso", + NULL +}; + +static const struct input_plugin iso9660_input_plugin = { + .close = iso9660_input_close, + .read = iso9660_input_read, + .eof = iso9660_input_eof, +}; + +const struct archive_plugin iso9660_archive_plugin = { + .name = "iso", + .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..2a3864cee --- /dev/null +++ b/src/archive/iso9660_archive_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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/iso_plugin.c b/src/archive/iso_plugin.c deleted file mode 100644 index 9063af0fc..000000000 --- a/src/archive/iso_plugin.c +++ /dev/null @@ -1,239 +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. - */ - -/** - * iso archive handling (requires cdio, and iso9660) - */ - -#include "archive_api.h" -#include "input_plugin.h" - -#include <cdio/cdio.h> -#include <cdio/iso9660.h> - -#include <glib.h> -#include <string.h> - -#define CEILING(x, y) ((x+(y-1))/y) - -typedef struct { - 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 iso_inputplugin; - -/* archive open && listing routine */ - -static void -listdir_recur(const char *psz_path, iso_context *context) -{ - iso9660_t *iso = context->iso; - CdioList_t *entlist; - CdioListNode_t *entnode; - iso9660_stat_t *statbuf; - char pathname[4096]; - - entlist = iso9660_ifs_readdir (iso, psz_path); - if (!entlist) { - return; - } - /* Iterate over the list of nodes that iso9660_ifs_readdir gives */ - _CDIO_LIST_FOREACH (entnode, entlist) { - statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode); - - strcpy(pathname, psz_path); - strcat(pathname, statbuf->filename); - - if (_STAT_DIR == statbuf->type ) { - if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) { - strcat(pathname, "/"); - listdir_recur(pathname, context); - } - } else { - //remove leading / - context->list = g_slist_prepend( context->list, - g_strdup(pathname + 1)); - } - } - _cdio_list_free (entlist, true); -} - -static struct archive_file * -iso_open(char * pathname) -{ - iso_context *context = g_malloc(sizeof(iso_context)); - - context->list = NULL; - - /* open archive */ - context->iso = iso9660_open (pathname); - if (context->iso == NULL) { - g_warning("iso %s open failed\n", pathname); - return NULL; - } - - listdir_recur("/", context); - - return (struct archive_file *)context; -} - -static void -iso_scan_reset(struct archive_file *file) -{ - iso_context *context = (iso_context *) file; - //reset iterator - context->iter = context->list; -} - -static char * -iso_scan_next(struct archive_file *file) -{ - iso_context *context = (iso_context *) file; - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -iso_close(struct archive_file *file) -{ - iso_context *context = (iso_context *) file; - GSList *tmp; - if (context->list) { - //free list - for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - iso9660_close(context->iso); - - g_free(context); -} - -/* single archive handling */ - -static bool -iso_open_stream(struct archive_file *file, struct input_stream *is, - const char *pathname) -{ - iso_context *context = (iso_context *) file; - //setup file ops - is->plugin = &iso_inputplugin; - //insert back reference - is->data = context; - //we are not seekable - is->seekable = false; - - context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname); - - if (context->statbuf == NULL) { - g_warning("file %s not found in iso\n", pathname); - return false; - } - context->cur_ofs = 0; - context->max_blocks = CEILING(context->statbuf->size, ISO_BLOCKSIZE); - return true; -} - -static void -iso_is_close(struct input_stream *is) -{ - iso_context *context = (iso_context *) is->data; - g_free(context->statbuf); - - iso_close((struct archive_file *)context); -} - - -static size_t -iso_is_read(struct input_stream *is, void *ptr, size_t size) -{ - iso_context *context = (iso_context *) is->data; - int toread, readed = 0; - int no_blocks, cur_block; - size_t left_bytes = context->statbuf->size - context->cur_ofs; - - size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE; - - if (left_bytes < size) { - toread = left_bytes; - no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE); - } else { - toread = size; - no_blocks = toread / ISO_BLOCKSIZE; - } - if (no_blocks > 0) { - - cur_block = context->cur_ofs / ISO_BLOCKSIZE; - - readed = iso9660_iso_seek_read (context->iso, ptr, - 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; - } - if (left_bytes < size) { - readed = left_bytes; - } - context->cur_ofs += readed; - } - return readed; -} - -static bool -iso_is_eof(struct input_stream *is) -{ - iso_context *context = (iso_context *) is->data; - return (context->cur_ofs == context->statbuf->size); -} - -/* exported structures */ - -static const char *const iso_extensions[] = { - "iso", - NULL -}; - -static const struct input_plugin iso_inputplugin = { - .close = iso_is_close, - .read = iso_is_read, - .eof = iso_is_eof, -}; - -const struct archive_plugin iso_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 -}; diff --git a/src/archive/zip_plugin.c b/src/archive/zip_plugin.c deleted file mode 100644 index 243d46418..000000000 --- a/src/archive/zip_plugin.c +++ /dev/null @@ -1,196 +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. - */ - -/** - * zip archive handling (requires zziplib) - */ - -#include "archive_api.h" -#include "archive_api.h" -#include "input_plugin.h" - -#include <zzip/zzip.h> -#include <glib.h> -#include <string.h> - -typedef struct { - ZZIP_DIR *dir; - ZZIP_FILE *file; - size_t length; - GSList *list; - GSList *iter; -} zip_context; - -static const struct input_plugin zip_inputplugin; - -/* archive open && listing routine */ - -static struct archive_file * -zip_open(char * pathname) -{ - zip_context *context = g_malloc(sizeof(zip_context)); - ZZIP_DIRENT dirent; - - // open archive - context->list = NULL; - context->dir = zzip_dir_open(pathname, NULL); - if (context->dir == NULL) { - g_warning("zipfile %s open failed\n", pathname); - return NULL; - } - - while (zzip_dir_read(context->dir, &dirent)) { - //add only files - if (dirent.st_size > 0) { - context->list = g_slist_prepend(context->list, - g_strdup(dirent.d_name)); - } - } - - return (struct archive_file *)context; -} - -static void -zip_scan_reset(struct archive_file *file) -{ - zip_context *context = (zip_context *) file; - //reset iterator - context->iter = context->list; -} - -static char * -zip_scan_next(struct archive_file *file) -{ - zip_context *context = (zip_context *) file; - char *data = NULL; - if (context->iter != NULL) { - ///fetch data and goto next - data = context->iter->data; - context->iter = g_slist_next(context->iter); - } - return data; -} - -static void -zip_close(struct archive_file *file) -{ - zip_context *context = (zip_context *) file; - if (context->list) { - //free list - for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) - g_free(tmp->data); - g_slist_free(context->list); - } - //close archive - zzip_dir_close (context->dir); - - g_free(context); -} - -/* single archive handling */ - -static bool -zip_open_stream(struct archive_file *file, struct input_stream *is, - const char *pathname) -{ - zip_context *context = (zip_context *) file; - ZZIP_STAT z_stat; - - //setup file ops - is->plugin = &zip_inputplugin; - //insert back reference - is->data = context; - //we are seekable (but its not recommendent to do so) - is->seekable = true; - - context->file = zzip_file_open(context->dir, pathname, 0); - if (!context->file) { - g_warning("file %s not found in the zipfile\n", pathname); - return false; - } - zzip_file_stat(context->file, &z_stat); - context->length = z_stat.st_size; - return true; -} - -static void -zip_is_close(struct input_stream *is) -{ - zip_context *context = (zip_context *) is->data; - zzip_file_close (context->file); - - zip_close((struct archive_file *)context); -} - -static size_t -zip_is_read(struct input_stream *is, void *ptr, size_t size) -{ - zip_context *context = (zip_context *) is->data; - int ret; - ret = zzip_file_read(context->file, ptr, size); - if (ret < 0) { - g_warning("error %d reading zipfile\n", ret); - return 0; - } - return ret; -} - -static bool -zip_is_eof(struct input_stream *is) -{ - zip_context *context = (zip_context *) 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) -{ - zip_context *context = (zip_context *) is->data; - zzip_off_t ofs = zzip_seek(context->file, offset, whence); - if (ofs != -1) { - is->offset = ofs; - return true; - } - return false; -} - -/* exported structures */ - -static const char *const zip_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, -}; - -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 -}; diff --git a/src/archive/zzip_archive_plugin.c b/src/archive/zzip_archive_plugin.c new file mode 100644 index 000000000..3c2b80318 --- /dev/null +++ b/src/archive/zzip_archive_plugin.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/** + * 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" +#include "refcount.h" + +#include <zzip/zzip.h> +#include <glib.h> +#include <string.h> + +struct zzip_archive { + struct archive_file base; + + struct refcount ref; + + ZZIP_DIR *dir; + GSList *list; + GSList *iter; +}; + +static const struct input_plugin zzip_input_plugin; + +static inline GQuark +zzip_quark(void) +{ + return g_quark_from_static_string("zzip"); +} + +/* archive open && listing routine */ + +static struct archive_file * +zzip_archive_open(const char *pathname, GError **error_r) +{ + struct zzip_archive *context = g_malloc(sizeof(*context)); + ZZIP_DIRENT dirent; + + archive_file_init(&context->base, &zzip_archive_plugin); + refcount_init(&context->ref); + + // open archive + context->list = NULL; + context->dir = zzip_dir_open(pathname, NULL); + if (context->dir == NULL) { + g_set_error(error_r, zzip_quark(), 0, + "Failed to open ZIP file %s", pathname); + return NULL; + } + + while (zzip_dir_read(context->dir, &dirent)) { + //add only files + if (dirent.st_size > 0) { + context->list = g_slist_prepend(context->list, + g_strdup(dirent.d_name)); + } + } + + return &context->base; +} + +static void +zzip_archive_scan_reset(struct archive_file *file) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + //reset iterator + context->iter = context->list; +} + +static char * +zzip_archive_scan_next(struct archive_file *file) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + char *data = NULL; + if (context->iter != NULL) { + ///fetch data and goto next + data = context->iter->data; + context->iter = g_slist_next(context->iter); + } + return data; +} + +static void +zzip_archive_close(struct archive_file *file) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + + if (!refcount_dec(&context->ref)) + return; + + if (context->list) { + //free list + for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp)) + g_free(tmp->data); + g_slist_free(context->list); + } + //close archive + zzip_dir_close (context->dir); + + g_free(context); +} + +/* single archive handling */ + +struct zzip_input_stream { + struct input_stream base; + + struct zzip_archive *archive; + + ZZIP_FILE *file; +}; + +static struct input_stream * +zzip_archive_open_stream(struct archive_file *file, + const char *pathname, GError **error_r) +{ + struct zzip_archive *context = (struct zzip_archive *) file; + struct zzip_input_stream *zis; + ZZIP_STAT z_stat; + + zis = g_new(struct zzip_input_stream, 1); + input_stream_init(&zis->base, &zzip_input_plugin, pathname); + + zis->archive = context; + zis->file = zzip_file_open(context->dir, pathname, 0); + if (zis->file == NULL) { + g_free(zis); + g_set_error(error_r, zzip_quark(), 0, + "not found in the ZIP file: %s", pathname); + return NULL; + } + + zis->base.ready = true; + //we are seekable (but its not recommendent to do so) + zis->base.seekable = true; + + zzip_file_stat(zis->file, &z_stat); + zis->base.size = z_stat.st_size; + + refcount_inc(&context->ref); + + return &zis->base; +} + +static void +zzip_input_close(struct input_stream *is) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + + zzip_file_close(zis->file); + zzip_archive_close(&zis->archive->base); + input_stream_deinit(&zis->base); + g_free(zis); +} + +static size_t +zzip_input_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + int ret; + + ret = zzip_file_read(zis->file, ptr, size); + if (ret < 0) { + g_set_error(error_r, zzip_quark(), ret, + "zzip_file_read() has failed"); + return 0; + } + + is->offset = zzip_tell(zis->file); + + return ret; +} + +static bool +zzip_input_eof(struct input_stream *is) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + + return (goffset)zzip_tell(zis->file) == is->size; +} + +static bool +zzip_input_seek(struct input_stream *is, + goffset offset, int whence, GError **error_r) +{ + struct zzip_input_stream *zis = (struct zzip_input_stream *)is; + zzip_off_t ofs = zzip_seek(zis->file, offset, whence); + if (ofs != -1) { + g_set_error(error_r, zzip_quark(), ofs, + "zzip_seek() has failed"); + is->offset = ofs; + return true; + } + return false; +} + +/* exported structures */ + +static const char *const zzip_archive_extensions[] = { + "zip", + NULL +}; + +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 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..6d5037eef --- /dev/null +++ b/src/archive/zzip_archive_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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..b15810f1b 100644 --- a/src/archive_api.c +++ b/src/archive_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..f08960c72 100644 --- a/src/archive_api.h +++ b/src/archive_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..03439e826 100644 --- a/src/archive_internal.h +++ b/src/archive_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..2656726b5 100644 --- a/src/archive_list.c +++ b/src/archive_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..b65245ce9 100644 --- a/src/archive_list.h +++ b/src/archive_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..60da4d283 --- /dev/null +++ b/src/archive_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2010 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); +} + +struct input_stream * +archive_file_open_stream(struct archive_file *file, + 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, path, error_r); +} diff --git a/src/archive_plugin.h b/src/archive_plugin.h new file mode 100644 index 000000000..b08c93389 --- /dev/null +++ b/src/archive_plugin.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003-2010 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. + * + * @param path the path within the archive + * @param error_r location to store the error occuring, or + * NULL to ignore errors + */ + struct input_stream *(*open_stream)(struct archive_file *af, + 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); + +struct input_stream * +archive_file_open_stream(struct archive_file *file, + const char *path, GError **error_r); + +#endif diff --git a/src/audio.c b/src/audio.c index d48558e46..f9894cf3c 100644 --- a/src/audio.c +++ b/src/audio.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -24,6 +25,7 @@ #include "output_plugin.h" #include "output_all.h" #include "conf.h" +#include "mpd_error.h" #include <glib.h> @@ -35,9 +37,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 +47,13 @@ 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); + MPD_ERROR("error parsing \"%s\" at line %i: %s", + CONF_AUDIO_OUTPUT_FORMAT, param->line, + error->message); } diff --git a/src/audio.h b/src/audio.h index 4d80ee0e6..cb3ab7bbe 100644 --- a/src/audio.h +++ b/src/audio.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..61d2c5833 --- /dev/null +++ b/src/audio_check.c @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2010 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..cc08c9ba1 --- /dev/null +++ b/src/audio_check.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2010 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..13403fbc1 --- /dev/null +++ b/src/audio_format.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2010 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: + return "24_3"; + + 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..dd32731c3 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,25 +23,123 @@ #include <stdint.h> #include <stdbool.h> +enum sample_format { + SAMPLE_FORMAT_UNDEFINED = 0, + + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_S16, + + /** + * Signed 24 bit integer samples, without padding. + */ + SAMPLE_FORMAT_S24, + + /** + * 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 +156,21 @@ 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: + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + return true; + + case SAMPLE_FORMAT_UNDEFINED: + break; + } + + return false; } /** @@ -79,16 +189,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 +234,65 @@ 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: + return 3; + + 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..039ffa1ab 100644 --- a/src/audio_parser.c +++ b/src/audio_parser.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,9 +22,13 @@ * */ +#include "config.h" #include "audio_parser.h" #include "audio_format.h" +#include "audio_check.h" +#include <assert.h> +#include <string.h> #include <stdlib.h> /** @@ -36,64 +40,158 @@ 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: + if (memcmp(endptr, "_3", 2) == 0) { + sample_format = SAMPLE_FORMAT_S24; + endptr += 2; + } else + 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..214ec5eb1 100644 --- a/src/audio_parser.h +++ b/src/audio_parser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..bee871700 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -117,6 +118,9 @@ music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk) assert(buffer != NULL); assert(chunk != NULL); + if (chunk->other != NULL) + music_buffer_return(buffer, chunk->other); + g_mutex_lock(buffer->mutex); music_chunk_free(chunk); diff --git a/src/buffer.h b/src/buffer.h index 441e0ea4c..75e5bc6e6 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify 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..56061621f --- /dev/null +++ b/src/check.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 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..79597506d 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -26,8 +27,10 @@ void music_chunk_init(struct music_chunk *chunk) { + chunk->other = NULL; chunk->length = 0; chunk->tag = NULL; + chunk->replay_gain_serial = 0; } void diff --git a/src/chunk.h b/src/chunk.h index 51e75d906..02e7b3650 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,6 +20,8 @@ #ifndef MPD_CHUNK_H #define MPD_CHUNK_H +#include "replay_gain_info.h" + #ifndef NDEBUG #include "audio_format.h" #endif @@ -42,6 +44,18 @@ struct music_chunk { /** the next chunk in a linked list */ struct music_chunk *next; + /** + * An optional chunk which should be mixed into this chunk. + * This is used for cross-fading. + */ + struct music_chunk *other; + + /** + * The current mix ratio for cross-fading: 1.0 means play 100% + * of this chunk, 0.0 means play 100% of the "other" chunk. + */ + float mix_ratio; + /** number of bytes stored in this chunk */ uint16_t length; @@ -59,6 +73,19 @@ struct music_chunk { */ struct tag *tag; + /** + * Replay gain information associated with this chunk. + * Only valid if the serial is not 0. + */ + struct replay_gain_info replay_gain_info; + + /** + * A serial number for checking if replay gain info has + * changed since the last chunk. The magic value 0 indicates + * that there is no replay gain info available. + */ + unsigned replay_gain_serial; + /** the data (probably PCM) */ char data[CHUNK_SIZE]; diff --git a/src/client.c b/src/client.c index 6a256998f..9668c9249 100644 --- a/src/client.c +++ b/src/client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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.h b/src/client.h index 824497aba..d46747b4f 100644 --- a/src/client.h +++ b/src/client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/client_event.c b/src/client_event.c new file mode 100644 index 000000000..93f5a9df7 --- /dev/null +++ b/src/client_event.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2003-2010 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..a5b0be047 --- /dev/null +++ b/src/client_expire.c @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2010 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..fc5adedba --- /dev/null +++ b/src/client_global.c @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2010 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..10be4d430 --- /dev/null +++ b/src/client_idle.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2003-2010 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..2b1b92433 --- /dev/null +++ b/src/client_internal.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2003-2010 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[16384]; + 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..5332ed65f --- /dev/null +++ b/src/client_list.c @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2010 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..781a36524 --- /dev/null +++ b/src/client_new.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2003-2010 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 <sys/types.h> +#ifdef WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif +#include <unistd.h> + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + + +#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); + +#ifdef HAVE_LIBWRAP + if (sa->sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(sa, sa_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, + "libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + + g_free(hostaddr); + close(fd); + return; + } + + g_free(hostaddr); + } +#endif /* HAVE_WRAP */ + + 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)send(fd, GREETING, sizeof(GREETING) - 1, 0); + + 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..aeb75bb57 --- /dev/null +++ b/src/client_process.c @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2003-2010 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..7a6bd3d5e --- /dev/null +++ b/src/client_read.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2010 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..543cdbb6c --- /dev/null +++ b/src/client_write.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2003-2010 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) +{ +#ifndef G_OS_WIN32 + 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); +#else + /* On mingw32, snprintf() expects a 64 bit integer instead of + a "long int" for "%li". This is not consistent with our + expectation, so we're using plain sprintf() here, hoping + the static buffer is large enough. Sorry for this hack, + but WIN32 development is so painful, I'm not in the mood to + do it properly now. */ + + static char buffer[4096]; + vsprintf(buffer, fmt, args); + client_write(client, buffer, strlen(buffer)); +#endif +} + +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..2c1db890b 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,14 +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" +#include "mpd_error.h" + +#ifdef ENABLE_ENCODER +#include "encoder_list.h" +#endif #ifdef ENABLE_ARCHIVE #include "archive_list.h" @@ -35,9 +41,37 @@ #include <stdio.h> #include <stdlib.h> -#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf" +#ifdef G_OS_WIN32 +#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf" +#else /* G_OS_WIN32 */ #define USER_CONFIG_FILE_LOCATION1 ".mpdconf" #define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf" +#endif + +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) @@ -45,19 +79,25 @@ static void version(void) puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n" "\n" "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" - "Copyright (C) 2008 Max Kellermann <max@duempel.org>\n" + "Copyright (C) 2008-2010 Max Kellermann <max@duempel.org>\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" "\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 +112,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,24 +145,19 @@ 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); - if (!ret) { - g_error("option parsing failed: %s\n", error->message); - exit(1); - } + if (!ret) + MPD_ERROR("option parsing failed: %s\n", error->message); if (option_version) version(); @@ -133,39 +166,71 @@ 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; - char *path2; +#ifdef G_OS_WIN32 + path1 = g_build_filename(g_get_user_config_dir(), + CONFIG_FILE_LOCATION, NULL); + if (g_file_test(path1, G_FILE_TEST_IS_REGULAR)) + ret = config_read_file(path1, error_r); + else { + int i = 0; + char *system_path = NULL; + const char * const *system_config_dirs; + + system_config_dirs = g_get_system_config_dirs(); + + while(system_config_dirs[i] != NULL) { + system_path = g_build_filename(system_config_dirs[i], + CONFIG_FILE_LOCATION, + NULL); + if(g_file_test(system_path, + G_FILE_TEST_IS_REGULAR)) { + ret = config_read_file(system_path,error_r); + g_free(system_path); + g_free(&system_config_dirs); + break; + } + ++i;; + } + g_free(system_path); + g_free(&system_config_dirs); + } +#else /* G_OS_WIN32 */ + char *path2; path1 = g_build_filename(g_get_home_dir(), USER_CONFIG_FILE_LOCATION1, NULL); 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); +#endif + g_free(path1); +#ifndef G_OS_WIN32 g_free(path2); +#endif + + 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..b7af63c5a 100644 --- a/src/cmdline.h +++ b/src/cmdline.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..df0146c16 100644 --- a/src/command.c +++ b/src/command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_config.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" @@ -74,6 +76,8 @@ #define COMMAND_STATUS_BITRATE "bitrate" #define COMMAND_STATUS_ERROR "error" #define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" +#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" #define COMMAND_STATUS_AUDIO "audio" #define COMMAND_STATUS_UPDATING_DB "updating_db" @@ -166,8 +170,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 +202,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 +212,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 +224,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 +232,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 +269,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; @@ -289,6 +296,23 @@ check_bool(struct client *client, bool *value_r, const char *s) return true; } +static bool +check_float(struct client *client, float *value_r, const char *s) +{ + float value; + char *endptr; + + value = strtof(s, &endptr); + if (*endptr != 0 && endptr == s) { + command_error(client, ACK_ERROR_ARG, + "Float expected: %s", s); + return false; + } + + *value_r = value; + return true; +} + static enum command_return print_playlist_result(struct client *client, enum playlist_result result) @@ -363,10 +387,12 @@ print_spl_list(struct client *client, GPtrArray *list) client_printf(client, "playlist: %s\n", playlist->name); t = playlist->mtime; - strftime(timestamp, sizeof(timestamp), "%FT%TZ", -#ifdef WIN32 + strftime(timestamp, sizeof(timestamp), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", gmtime(&t) #else + "%FT%TZ", gmtime_r(&t, &tm) #endif ); @@ -385,6 +411,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 +434,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 +447,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 +455,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 +475,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 +488,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 +508,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" @@ -478,34 +516,43 @@ handle_status(struct client *client, COMMAND_STATUS_PLAYLIST ": %li\n" COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" COMMAND_STATUS_CROSSFADE ": %i\n" + COMMAND_STATUS_MIXRAMPDB ": %f\n" + COMMAND_STATUS_MIXRAMPDELAY ": %f\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), + pc_get_mixramp_db(), + pc_get_mixramp_delay(), 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 +561,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 +618,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 +654,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 +664,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 +680,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 +699,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 +720,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 +728,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 +747,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); } @@ -705,6 +758,9 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) static enum command_return handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { + if (playlist_file_print(client, argv[1], false)) + return COMMAND_RETURN_OK; + bool ret; ret = spl_print(client, argv[1], false); @@ -720,6 +776,9 @@ static enum command_return handle_listplaylistinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) { + if (playlist_file_print(client, argv[1], true)) + return COMMAND_RETURN_OK; + bool ret; ret = spl_print(client, argv[1], true); @@ -808,7 +867,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 +896,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 +928,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 +1076,52 @@ 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 = g_strdup(argv[1]); + if (argc == 2) { + path = argv[1]; + + if (*path == 0 || strcmp(path, "/") == 0) + /* backwards compatibility with MPD 0.15 */ + path = NULL; + else if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + 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; - ret = directory_update_init(path); + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return COMMAND_RETURN_ERROR; + } + } + + ret = update_enqueue(path, true); if (ret > 0) { client_printf(client, "updating_db: %i\n", ret); return COMMAND_RETURN_OK; @@ -1020,7 +1141,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 +1151,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 +1173,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 +1181,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 +1210,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 +1228,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 +1246,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 +1264,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 +1279,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 +1303,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 +1348,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 +1362,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 +1376,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 +1390,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 +1405,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 +1420,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 +1470,31 @@ 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; +} + +static enum command_return +handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float db; + + if (!check_float(client, &db, argv[1])) + return COMMAND_RETURN_ERROR; + pc_set_mixramp_db(db); + + return COMMAND_RETURN_OK; +} + +static enum command_return +handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) +{ + float delay_secs; + + if (!check_float(client, &delay_secs, argv[1])) + return COMMAND_RETURN_ERROR; + pc_set_mixramp_delay(delay_secs); return COMMAND_RETURN_OK; } @@ -1477,6 +1608,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 +1672,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 +1702,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 +1725,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 +1745,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 +1768,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 +1837,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 }, @@ -1694,6 +1854,8 @@ static const struct command commands[] = { { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, { "load", PERMISSION_ADD, 1, 1, handle_load }, { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, + { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, + { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, { "move", PERMISSION_CONTROL, 2, 2, handle_move }, { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, { "next", PERMISSION_CONTROL, 0, 0, handle_next }, @@ -1719,6 +1881,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 +1905,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 +2058,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 */ - for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) { - char *cmd = cur->data; + current_command = argv[0]; - 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..39389385d 100644 --- a/src/command.h +++ b/src/command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..705942085 100644 --- a/src/conf.c +++ b/src/conf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,13 @@ * 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 "mpd_error.h" #include <glib.h> @@ -36,37 +39,84 @@ #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_REPLAYGAIN_LIMIT, 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 = CONF_AUTO_UPDATE_DEPTH, 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 +133,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 +154,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 +174,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 +244,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 +342,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 (*line != '{') { + g_set_error(error_r, config_quark(), 0, + "line %i: '{' expected", count); + return false; + } + + 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 (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 != 0 && *line != CONF_COMMENT) { + g_set_error(error_r, config_quark(), 0, + "line %i: Unknown tokens after value", + count); + return false; } - param = config_read_block(fp, &count, string); - } else - param = config_new_param(array[1], count); + + 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 +473,7 @@ config_get_next_param(const char *name, const struct config_param * last) return NULL; param = node->data; - + param->used = true; return param; } @@ -417,14 +499,32 @@ config_get_path(const char *name) path = parsePath(param->value); if (path == NULL) - g_error("error parsing \"%s\" at line %i\n", - name, param->line); + MPD_ERROR("error parsing \"%s\" at line %i\n", + name, param->line); g_free(param->value); return param->value = path; } unsigned +config_get_unsigned(const char *name, unsigned default_value) +{ + const struct config_param *param = config_get_param(name); + long value; + char *endptr; + + if (param == NULL) + return default_value; + + value = strtol(param->value, &endptr, 0); + if (*endptr != 0 || value < 0) + MPD_ERROR("Not a valid non-negative number in line %i", + param->line); + + return (unsigned)value; +} + +unsigned config_get_positive(const char *name, unsigned default_value) { const struct config_param *param = config_get_param(name); @@ -436,10 +536,10 @@ config_get_positive(const char *name, unsigned default_value) value = strtol(param->value, &endptr, 0); if (*endptr != 0) - g_error("Not a valid number in line %i", param->line); + MPD_ERROR("Not a valid number in line %i", param->line); if (value <= 0) - g_error("Not a positive number in line %i", param->line); + MPD_ERROR("Not a positive number in line %i", param->line); return (unsigned)value; } @@ -447,43 +547,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) - 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; + success = get_bool(param->value, &value); + if (!success) + MPD_ERROR("%s is not a boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + name, param->line); - return !!value; + return value; } const char * @@ -511,10 +603,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name, value = strtol(bp->value, &endptr, 0); if (*endptr != 0) - g_error("Not a valid number in line %i", bp->line); + MPD_ERROR("Not a valid number in line %i", bp->line); if (value < 0) - g_error("Not a positive number in line %i", bp->line); + MPD_ERROR("Not a positive number in line %i", bp->line); return (unsigned)value; } @@ -524,19 +616,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) - 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; + success = get_bool(bp->value, &value); + if (!success) + MPD_ERROR("%s is not a boolean value (yes, true, 1) or " + "(no, false, 0) on line %i\n", + name, bp->line); - return !!value; + return value; } diff --git a/src/conf.h b/src/conf.h index c5e49960e..8a0678312 100644 --- a/src/conf.h +++ b/src/conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,26 +28,27 @@ #define CONF_FOLLOW_INSIDE_SYMLINKS "follow_inside_symlinks" #define CONF_FOLLOW_OUTSIDE_SYMLINKS "follow_outside_symlinks" #define CONF_DB_FILE "db_file" -#define CONF_STICKER_FILE "sticker_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" #define CONF_ZEROCONF_NAME "zeroconf_name" -#define CONF_ZEROCONF_ENABLED "zeroconf_enabled" +#define CONF_ZEROCONF_ENABLED "zeroconf_enabled" #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_REPLAYGAIN_LIMIT "replaygain_limit" #define CONF_VOLUME_NORMALIZATION "volume_normalization" #define CONF_SAMPLERATE_CONVERTER "samplerate_converter" #define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size" @@ -65,20 +66,28 @@ #define CONF_ID3V1_ENCODING "id3v1_encoding" #define CONF_METADATA_TO_USE "metadata_to_use" #define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists" -#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_DECODER "decoder" +#define CONF_INPUT "input" +#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" +#define CONF_PLAYLIST_PLUGIN "playlist_plugin" +#define CONF_AUTO_UPDATE "auto_update" +#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth" #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 +96,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 +155,31 @@ 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_unsigned(const char *name, unsigned default_value); + +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 +191,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 +204,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..cdfd82879 100644 --- a/src/crossfade.c +++ b/src/crossfade.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -25,73 +26,112 @@ #include <assert.h> #include <string.h> +#include <stdlib.h> +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "crossfade" + +#ifdef G_OS_WIN32 +static char * +strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr) +{ + return strtok(str, delim); +} +#endif + +static float mixramp_interpolate(char *ramp_list, float required_db) +{ + float db, secs, last_db = nan(""), last_secs = 0; + char *ramp_str, *save_str = NULL; + + /* ramp_list is a string of pairs of dBs and seconds that describe the + * volume profile. Delimiters are semi-colons between pairs and spaces + * between the dB and seconds of a pair. + * The dB values must be monotonically increasing for this to work. */ + + while (1) { + /* Parse the dB tokens out of the input string. */ + ramp_str = strtok_r(ramp_list, " ", &save_str); + + /* Tell strtok to continue next time round. */ + ramp_list = NULL; + + /* Parse the dB value. */ + if (NULL == ramp_str) { + return nan(""); + } + db = (float)atof(ramp_str); + + /* Parse the time. */ + ramp_str = strtok_r(NULL, ";", &save_str); + if (NULL == ramp_str) { + return nan(""); + } + secs = (float)atof(ramp_str); + + /* Check for exact match. */ + if (db == required_db) { + return secs; + } + + /* Save if too quiet. */ + if (db < required_db) { + last_db = db; + last_secs = secs; + continue; + } + + /* If required db < any stored value, use the least. */ + if (isnan(last_db)) { + return secs; + } + + /* Finally, interpolate linearly. */ + secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db); + return secs; + } +} unsigned cross_fade_calc(float duration, float total_time, + float mixramp_db, float mixramp_delay, + float replay_gain_db, float replay_gain_prev_db, + char *mixramp_start, char *mixramp_prev_end, const struct audio_format *af, const struct audio_format *old_format, unsigned max_chunks) { - unsigned int chunks; + unsigned int chunks = 0; + float chunks_f; + float mixramp_overlap; - if (duration <= 0 || duration >= total_time || + if (duration < 0 || duration >= total_time || /* we can't crossfade when the audio formats are different */ !audio_format_equals(af, old_format)) return 0; - assert(duration > 0); - assert(af->bits > 0); - assert(af->channels > 0); - assert(af->sample_rate > 0); - - chunks = audio_format_time_to_size(af) / CHUNK_SIZE; - chunks = (chunks * duration + 0.5); + assert(duration >= 0); + assert(audio_format_valid(af)); + + chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE; + + if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) { + chunks = (chunks_f * duration + 0.5); + } else { + /* Calculate mixramp overlap. */ + mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db) + + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db); + if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) { + chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); + g_debug("will overlap %d chunks, %fs", chunks, + mixramp_overlap - mixramp_delay); + } + } - if (chunks > max_chunks) + if (chunks > max_chunks) { chunks = max_chunks; + g_warning("audio_buffer_size too small for computed MixRamp overlap"); + } return chunks; } - -void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b, - const struct audio_format *format, - unsigned int current_chunk, unsigned int num_chunks) -{ - size_t size; - - assert(a != NULL); - assert(b != NULL); - assert(a->length == 0 || b->length == 0 || - audio_format_equals(&a->audio_format, &b->audio_format)); - assert(current_chunk <= num_chunks); - - if (a->tag == NULL && b->tag != NULL) - /* merge the tag into the destination chunk */ - a->tag = tag_dup(b->tag); - - size = b->length > a->length - ? a->length - : b->length; - - pcm_mix(a->data, - b->data, - size, - format, - ((float)current_chunk) / num_chunks); - - if (b->length > a->length) { - /* the second buffer is larger than the first one: - there is unmixed rest at the end. Copy it over. - The output buffer API guarantees that there is - enough room in a->data. */ - -#ifndef NDEBUG - if (a->length == 0) - a->audio_format = b->audio_format; -#endif - - memcpy(a->data + a->length, - b->data + a->length, - b->length - a->length); - a->length = b->length; - } -} diff --git a/src/crossfade.h b/src/crossfade.h index 1a09ede5b..096a62020 100644 --- a/src/crossfade.h +++ b/src/crossfade.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,6 +28,12 @@ struct music_chunk; * * @param duration the requested crossfade duration * @param total_time total_time the duration of the new song + * @param mixramp_db the current mixramp_db setting + * @param mixramp_delay the current mixramp_delay setting + * @param replay_gain_db the ReplayGain adjustment used for this song + * @param replay_gain_prev_db the ReplayGain adjustment used on the last song + * @param mixramp_start the next songs mixramp_start tag + * @param mixramp_prev_end the last songs mixramp_end setting * @param af the audio format of the new song * @param old_format the audio format of the current song * @param max_chunks the maximum number of chunks @@ -35,22 +41,11 @@ struct music_chunk; * should be disabled for this song change */ unsigned cross_fade_calc(float duration, float total_time, + float mixramp_db, float mixramp_delay, + float replay_gain_db, float replay_gain_prev_db, + char *mixramp_start, char *mixramp_prev_end, const struct audio_format *af, const struct audio_format *old_format, unsigned max_chunks); -/** - * Applies cross fading to two chunks, i.e. mixes these chunks. - * Internally, this calls pcm_mix(). - * - * @param a the chunk in the current song (and the destination chunk) - * @param b the according chunk in the new song - * @param format the audio format of both chunks (must be the same) - * @param current_chunk the relative index of the current chunk - * @param num_chunks the number of chunks used for cross fading - */ -void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b, - const struct audio_format *format, - unsigned int current_chunk, unsigned int num_chunks); - #endif diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c index 6251b03e2..ba1172559 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,175 +84,152 @@ 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; - } - else + if (tag_is_empty(tag)) { + tag_free(tag); return NULL; + } + + return tag; } -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 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); - } + tag = tag_merge_replace(cd_tag, track_tag); + if (tag == NULL) + return NULL; - return tag_merge_replace(cd_tag, track_tag); + tag->time = track_get_length(track) + - track_get_index(track, 1) + + track_get_zero_pre(track); + track = cd_get_track(cd, tnum + 1); + if (track != NULL) + tag->time += track_get_index(track, 1) + - track_get_zero_pre(track); + /* libcue returns the track duration in frames, and there are + 75 frames per second; this formula rounds down */ + tag->time = tag->time / 75; + + 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..852541375 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "daemon.h" #include <glib.h> @@ -45,78 +46,72 @@ 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; if (pidfile == NULL) - g_error("no pid_file specified in the config file"); + MPD_ERROR("no pid_file specified in the config file"); fp = fopen(pidfile, "r"); if (fp == NULL) - g_error("unable to open pid file \"%s\": %s", - pidfile, g_strerror(errno)); + MPD_ERROR("unable to open pid file \"%s\": %s", + pidfile, g_strerror(errno)); if (fscanf(fp, "%i", &pid) != 1) { - g_error("unable to read the pid from file \"%s\"", - pidfile); + MPD_ERROR("unable to read the pid from file \"%s\"", + pidfile); } fclose(fp); ret = kill(pid, SIGTERM); if (ret < 0) - g_error("unable to kill proccess %i: %s", - pid, g_strerror(errno)); + MPD_ERROR("unable to kill proccess %i: %s", + 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) { + MPD_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,50 +119,58 @@ daemonize_set_user(void) #endif /* set uid */ - if (setuid(user_uid) == -1) { - g_error("cannot change to uid of user \"%s\": %s", - user_name, g_strerror(errno)); + if (user_uid != (uid_t)-1 && user_uid != getuid() && + setuid(user_uid) == -1) { + MPD_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); - /* detach from parent process */ +#ifdef HAVE_DAEMON + + if (daemon(0, 1)) + MPD_ERROR("daemon() failed: %s", g_strerror(errno)); - pid = fork(); - if (pid < 0) - g_error("fork() failed: %s", g_strerror(errno)); +#elif defined(HAVE_FORK) - if (pid > 0) + /* detach from parent process */ + + switch (fork()) { + case -1: + MPD_ERROR("fork() failed: %s", g_strerror(errno)); + case 0: + break; + default: /* exit the parent process */ _exit(EXIT_SUCCESS); + } /* release the current working directory */ if (chdir("/") < 0) - g_error("problems changing to root directory"); + MPD_ERROR("problems changing to root directory"); /* detach from the current session */ setsid(); +#else + MPD_ERROR("no support for daemonizing"); +#endif + g_debug("daemonized!"); } -#endif void daemonize(bool detach) { -#ifndef WIN32 FILE *fp = NULL; if (pidfile != NULL) { @@ -176,8 +179,8 @@ daemonize(bool detach) g_debug("opening pid file"); fp = fopen(pidfile, "w+"); if (!fp) { - g_error("could not create pid file \"%s\": %s", - pidfile, g_strerror(errno)); + MPD_ERROR("could not create pid file \"%s\": %s", + pidfile, g_strerror(errno)); } } @@ -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) + MPD_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) + MPD_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..71039543c 100644 --- a/src/daemon.h +++ b/src/daemon.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,34 +20,72 @@ #ifndef DAEMON_H #define DAEMON_H +#include "mpd_error.h" + #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 +#include <glib.h> +static inline void +daemonize_kill(void) +{ MPD_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..f255d6ca5 100644 --- a/src/database.c +++ b/src/database.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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; @@ -230,20 +239,27 @@ db_save(void) return false; } - /* 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); + fclose(fp); return false; } - while (fclose(fp) && errno == EINTR); + fclose(fp); if (stat(database_path, &st) == 0) database_mtime = st.st_mtime; @@ -256,64 +272,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) ; + fp = fopen(database_path, "r"); 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,20 +339,51 @@ 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); - while (fclose(fp) && errno == EINTR) ; + success = directory_load(fp, music_root, buffer, error); + g_string_free(buffer, true); + fclose(fp); if (!success) return false; diff --git a/src/database.h b/src/database.h index f4420928d..67149b20b 100644 --- a/src/database.h +++ b/src/database.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/dbUtils.c b/src/dbUtils.c index 2e2552698..f950d42cc 100644 --- a/src/dbUtils.c +++ b/src/dbUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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; } @@ -74,7 +75,7 @@ searchInDirectory(struct song *song, void *_data) struct search_data *data = _data; if (locate_song_search(song, data->criteria)) - return song_print_info(data->client, song); + song_print_info(data->client, song); return 0; } @@ -104,7 +105,7 @@ findInDirectory(struct song *song, void *_data) struct search_data *data = _data; if (locate_song_match(song, data->criteria)) - return song_print_info(data->client, song); + song_print_info(data->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; @@ -237,7 +259,7 @@ visitTag(struct client *client, struct strset *set, bool found = false; 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..bba253154 100644 --- a/src/dbUtils.h +++ b/src/dbUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..8dd22a253 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/_flac_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,104 @@ 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->unsupported = false; + data->initialized = false; + data->total_frames = 0; + 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) +void +flac_data_deinit(struct flac_data *data) { - 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; -} + pcm_buffer_deinit(&data->buffer); -static void -flac_parse_replay_gain(const FLAC__StreamMetadata *block, - struct flac_data *data) -{ - bool found = false; - - if (data->replay_gain_info) - 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(unsigned bits_per_sample) { - 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; - } + switch (bits_per_sample) { + case 8: + return SAMPLE_FORMAT_S8; - if (comment[name_length] == '=') { - *length_r = entry->length - name_length - 1; - return comment + name_length + 1; - } + case 16: + return SAMPLE_FORMAT_S16; - return NULL; -} + case 24: + return SAMPLE_FORMAT_S24_P32; -/** - * 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; + case 32: + return SAMPLE_FORMAT_S32; - 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; + default: + return SAMPLE_FORMAT_UNDEFINED; } - - 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) +flac_got_stream_info(struct flac_data *data, + const FLAC__StreamMetadata_StreamInfo *stream_info) { - 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)) + if (data->initialized || data->unsupported) 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; -} + GError *error = NULL; + if (!audio_format_init_checked(&data->audio_format, + stream_info->sample_rate, + flac_sample_format(stream_info->bits_per_sample), + stream_info->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + data->unsupported = true; + return; + } -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; + data->frame_size = audio_format_frame_size(&data->audio_format); - for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i) - flac_parse_comment(tag, char_tnum, comments++); + if (data->total_frames == 0) + data->total_frames = stream_info->total_samples; + + data->initialized = true; } void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data) { - const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info); + if (data->unsupported) + return; + + struct replay_gain_info rgi; + char *mixramp_start; + char *mixramp_end; + float replay_gain_db = 0; 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); + flac_got_stream_info(data, &block->data.stream_info); break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: - flac_parse_replay_gain(block, data); + if (flac_parse_replay_gain(&rgi, block)) + replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) { + g_debug("setting mixramp_tags"); + decoder_mixramp(data->decoder, replay_gain_db, + mixramp_start, mixramp_end); + } 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,187 +161,82 @@ 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! + * This function attempts to call decoder_initialized() in case there + * was no STREAMINFO block. This is allowed for nonseekable streams, + * where the server sends us only a part of the file, without + * providing the STREAMINFO block from the beginning of the file + * (e.g. when seeking with SqueezeBox Server). */ -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; +static bool +flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header) +{ + if (data->unsupported) + return false; + + GError *error = NULL; + if (!audio_format_init_checked(&data->audio_format, + header->sample_rate, + flac_sample_format(header->bits_per_sample), + header->channels, &error)) { + g_warning("%s", error->message); + g_error_free(error); + data->unsupported = true; + return false; + } - for (; position < end; ++position) - for (c_chan = 0; c_chan < num_channels; c_chan++) - *dest++ = buf[c_chan][position]; -} + data->frame_size = audio_format_frame_size(&data->audio_format); -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; + decoder_initialized(data->decoder, &data->audio_format, + data->input_stream->seekable, + (float)data->total_frames / + (float)data->audio_format.sample_rate); - case 4: - flac_convert_32((int32_t*)dest, num_channels, buf, - position, end); - break; + data->initialized = true; - case 1: - flac_convert_8((int8_t*)dest, num_channels, buf, - position, end); - break; - } + return true; } 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) { - 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; + void *buffer; + unsigned bit_rate; - if (bytes_per_sample != 1 && bytes_per_sample != 2 && - bytes_per_sample != 4) - /* exotic unsupported bit rate */ + if (!data->initialized && !flac_got_first_frame(data, &frame->header)) 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; - } - } - - return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + size_t buffer_size = frame->header.blocksize * data->frame_size; + buffer = pcm_buffer_get(&data->buffer, buffer_size); -char* -flac_cue_track( const char* pathname, - const unsigned int tnum) -{ - FLAC__bool success; - FLAC__StreamMetadata* cs; - - success = FLAC__metadata_get_cuesheet(pathname, &cs); - if (!success) - return NULL; - - assert(cs != NULL); - - if (cs->data.cue_sheet.num_tracks <= 1) - { - FLAC__metadata_object_delete(cs); - return NULL; - } + flac_convert(buffer, frame->header.channels, + data->audio_format.format, buf, + 0, frame->header.blocksize); - if (tnum > 0 && tnum < cs->data.cue_sheet.num_tracks) - { - char* track = g_strdup_printf("track_%03u.flac", tnum); + 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->next_frame += frame->header.blocksize; + switch (cmd) { + case DECODE_COMMAND_NONE: + case DECODE_COMMAND_START: + break; - FLAC__metadata_object_delete(cs); + case DECODE_COMMAND_STOP: + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; - return track; - } - else - { - FLAC__metadata_object_delete(cs); - return NULL; + case DECODE_COMMAND_SEEK: + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } -} -unsigned int -flac_vtrack_tnum(const char* fname) -{ - /* find last occurrence of '_' in fname - * which is hopefully something like track_xxx.flac - * another/better way would be to use tag struct - */ - char* ptr = strrchr(fname, '_'); - if (ptr == NULL) - return 0; - - // copy ascii tracknumber to int - return (unsigned int)strtol(++ptr, NULL, 10); + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h index 68de7e969..5c59ee123 100644 --- a/src/decoder/_flac_common.h +++ b/src/decoder/_flac_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,136 +24,62 @@ #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> +#include <FLAC/stream_decoder.h> +#include <FLAC/metadata.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 */ +struct flac_data { + struct pcm_buffer buffer; + + /** + * The size of one frame in the output buffer. + */ + unsigned frame_size; + + /** + * Has decoder_initialized() been called yet? + */ + bool initialized; + + /** + * Does the FLAC file contain an unsupported audio format? + */ + bool unsupported; + + /** + * The validated audio format of the FLAC file. This + * attribute is defined if "initialized" is true. + */ + struct audio_format audio_format; -/* - * 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 */ + /** + * The total number of frames in this song. The decoder + * plugin may initialize this attribute to override the value + * provided by libFLAC (e.g. for sub songs from a CUE sheet). + */ + FLAC__uint64 total_frames; -#include <FLAC/metadata.h> + /** + * 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; -#define FLAC_CHUNK_SIZE 4080 + /** + * The number of the next frame which is going to be decoded. + */ + FLAC__uint64 next_frame; -struct flac_data { - unsigned char chunk[FLAC_CHUNK_SIZE]; - float time; - unsigned int bit_rate; - struct audio_format audio_format; - float total_time; FLAC__uint64 position; struct decoder *decoder; struct input_stream *input_stream; - struct replay_gain_info *replay_gain_info; struct tag *tag; }; @@ -162,6 +88,9 @@ void flac_data_init(struct flac_data *data, struct decoder * decoder, struct input_stream *input_stream); +void +flac_data_deinit(struct flac_data *data); + void flac_metadata_common_cb(const FLAC__StreamMetadata * block, struct flac_data *data); @@ -169,23 +98,9 @@ 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[]); - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -char* -flac_cue_track( const char* pathname, - const unsigned int tnum); - -unsigned int -flac_vtrack_tnum( const char*); - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + const FLAC__int32 *const buf[], + FLAC__uint64 nbytes); #endif /* _FLAC_COMMON_H */ diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c index 6c6553422..bd0650ac4 100644 --- a/src/decoder/_ogg_common.c +++ b/src/decoder/_ogg_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..f8446c69c 100644 --- a/src/decoder/_ogg_common.h +++ b/src/decoder/_ogg_common.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_decoder_plugin.c index f66d90dc1..3026f3cc7 100644 --- a/src/decoder/audiofile_plugin.c +++ b/src/decoder/audiofile_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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); + bit_rate); 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_decoder_plugin.c index 7b2806a4c..8f932ad58 100644 --- a/src/decoder/faad_plugin.c +++ b/src/decoder/faad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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); } /** @@ -311,20 +322,16 @@ faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer, * file is invalid. */ static float -faad_get_file_time_float(const char *file) +faad_get_file_time_float(struct input_stream *is) { struct decoder_buffer *buffer; float length; faacDecHandle decoder; faacDecConfigurationPtr config; - struct input_stream is; - - if (!input_stream_open(&is, file)) - return -1; - buffer = decoder_buffer_new(NULL, &is, + buffer = decoder_buffer_new(NULL, is, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); - length = faad_song_duration(buffer, &is); + length = faad_song_duration(buffer, is); if (length < 0) { bool ret; @@ -338,15 +345,14 @@ 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); } decoder_buffer_free(buffer); - input_stream_close(&is); return length; } @@ -357,12 +363,12 @@ faad_get_file_time_float(const char *file) * file is invalid. */ static int -faad_get_file_time(const char *file) +faad_get_file_time(struct input_stream *is) { int file_time = -1; float length; - if ((length = faad_get_file_time_float(file)) >= 0) + if ((length = faad_get_file_time_float(is)) >= 0) file_time = length + 0.5; return file_time; @@ -371,7 +377,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 +414,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 +428,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,16 +473,13 @@ 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, - bit_rate, NULL); + (size_t)frame_info.samples * 2, + bit_rate); } while (cmd != DECODE_COMMAND_STOP); /* cleanup */ @@ -492,15 +488,13 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is) } static struct tag * -faad_tag_dup(const char *file) +faad_stream_tag(struct input_stream *is) { - int file_time = faad_get_file_time(file); + int file_time = faad_get_file_time(is); struct tag *tag; - if (file_time < 0) { - g_debug("Failed to get total song time from: %s", file); + if (file_time < 0) return NULL; - } tag = tag_new(); tag->time = file_time; @@ -515,7 +509,7 @@ static const char *const faad_mime_types[] = { const struct decoder_plugin faad_decoder_plugin = { .name = "faad", .stream_decode = faad_stream_decode, - .tag_dup = faad_tag_dup, + .stream_tag = faad_stream_tag, .suffixes = faad_suffixes, .mime_types = faad_mime_types, }; diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_decoder_plugin.c index 10894b633..f9d4eb8a9 100644 --- a/src/decoder/ffmpeg_plugin.c +++ b/src/decoder/ffmpeg_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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> @@ -39,19 +40,46 @@ #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> +#include <libavutil/log.h> #endif #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "ffmpeg" -struct ffmpeg_context { - int audio_stream; - AVFormatContext *format_context; - AVCodecContext *codec_context; - struct decoder *decoder; - struct input_stream *input; - struct tag *tag; -}; +#ifndef OLD_FFMPEG_INCLUDES + +static GLogLevelFlags +level_ffmpeg_to_glib(int level) +{ + if (level <= AV_LOG_FATAL) + return G_LOG_LEVEL_CRITICAL; + + if (level <= AV_LOG_ERROR) + return G_LOG_LEVEL_WARNING; + + if (level <= AV_LOG_INFO) + return G_LOG_LEVEL_MESSAGE; + + return G_LOG_LEVEL_DEBUG; +} + +static void +mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level, + const char *fmt, va_list vl) +{ + const AVClass * cls = NULL; + + if (ptr != NULL) + cls = *(const AVClass *const*)ptr; + + if (cls != NULL) { + char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL); + g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl); + g_free(domain); + } +} + +#endif /* !OLD_FFMPEG_INCLUDES */ struct mpd_ffmpeg_stream { struct decoder *decoder; @@ -79,7 +107,7 @@ mpd_ffmpeg_stream_seek(void *opaque, 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; @@ -115,6 +143,10 @@ mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream) static bool ffmpeg_init(G_GNUC_UNUSED const struct config_param *param) { +#ifndef OLD_FFMPEG_INCLUDES + av_log_set_callback(mpd_ffmpeg_log_callback); +#endif + av_register_all(); return true; } @@ -130,9 +162,95 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context) return -1; } +/** + * On some platforms, libavcodec wants the output buffer aligned to 16 + * bytes (because it uses SSE/Altivec internally). This function + * returns the aligned version of the specified buffer, and corrects + * the buffer size. + */ +static void * +align16(void *p, size_t *length_p) +{ + unsigned add = 16 - (size_t)p % 16; + + *length_p -= add; + return (char *)p + add; +} + +static enum decoder_command +ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, + const AVPacket *packet, + AVCodecContext *codec_context, + const AVRational *time_base) +{ + enum decoder_command cmd = DECODE_COMMAND_NONE; + uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16]; + int16_t *aligned_buffer; + size_t buffer_size; + int len, audio_size; + uint8_t *packet_data; + int packet_size; + + if (packet->pts != (int64_t)AV_NOPTS_VALUE) + decoder_timestamp(decoder, + av_rescale_q(packet->pts, *time_base, + (AVRational){1, 1})); + + packet_data = packet->data; + packet_size = packet->size; + + buffer_size = sizeof(audio_buf); + aligned_buffer = align16(audio_buf, &buffer_size); + + while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) { + audio_size = buffer_size; + len = avcodec_decode_audio2(codec_context, + aligned_buffer, &audio_size, + packet_data, packet_size); + + if (len < 0) { + /* if error, we skip the frame */ + g_message("decoding failed\n"); + break; + } + + packet_data += len; + packet_size -= len; + + if (audio_size <= 0) + continue; + + cmd = decoder_data(decoder, is, + aligned_buffer, audio_size, + codec_context->bit_rate / 1000); + } + 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) + switch (codec_context->sample_fmt) { + case SAMPLE_FMT_S16: + return SAMPLE_FORMAT_S16; + + case SAMPLE_FMT_S32: + return SAMPLE_FORMAT_S32; + + default: + g_warning("Unsupported libavcodec SampleFormat value: %d", + codec_context->sample_fmt); + return SAMPLE_FORMAT_UNDEFINED; + } +#else + /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ + return SAMPLE_FORMAT_S16; +#endif +} + static AVInputFormat * -ffmpeg_probe(struct decoder *decoder, struct input_stream *is, - const char *uri) +ffmpeg_probe(struct decoder *decoder, struct input_stream *is) { enum { BUFFER_SIZE = 16384, @@ -141,7 +259,7 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is, unsigned char *buffer = g_malloc(BUFFER_SIZE); size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); - if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET)) { + if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET, NULL)) { g_free(buffer); return NULL; } @@ -155,7 +273,7 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is, AVProbeData avpd = { .buf = buffer, .buf_size = nbytes, - .filename = uri, + .filename = is->uri, }; AVInputFormat *format = av_probe_input_format(&avpd, true); @@ -164,15 +282,12 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is, return format; } -static bool -ffmpeg_helper(const char *uri, - struct decoder *decoder, struct input_stream *input, - bool (*callback)(struct ffmpeg_context *ctx), - struct ffmpeg_context *ctx) +static void +ffmpeg_decode(struct decoder *decoder, struct input_stream *input) { - AVInputFormat *input_format = ffmpeg_probe(decoder, input, uri); + AVInputFormat *input_format = ffmpeg_probe(decoder, input); if (input_format == NULL) - return false; + return; g_debug("detected input format '%s' (%s)", input_format->name, input_format->long_name); @@ -181,28 +296,27 @@ ffmpeg_helper(const char *uri, mpd_ffmpeg_stream_open(decoder, input); if (stream == NULL) { g_warning("Failed to open stream"); - return false; + return; } AVFormatContext *format_context; AVCodecContext *codec_context; AVCodec *codec; int audio_stream; - bool ret; //ffmpeg works with ours "fileops" helper - if (av_open_input_stream(&format_context, stream->io, uri, + if (av_open_input_stream(&format_context, stream->io, input->uri, input_format, NULL) != 0) { g_warning("Open failed\n"); mpd_ffmpeg_stream_close(stream); - return false; + return; } if (av_find_stream_info(format_context)<0) { g_warning("Couldn't find stream info\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; + return; } audio_stream = ffmpeg_find_audio_stream(format_context); @@ -210,7 +324,7 @@ ffmpeg_helper(const char *uri, g_warning("No audio stream inside\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; + return; } codec_context = format_context->streams[audio_stream]->codec; @@ -223,145 +337,48 @@ ffmpeg_helper(const char *uri, g_warning("Unsupported audio codec\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; + return; } if (avcodec_open(codec_context, codec)<0) { g_warning("Could not open codec\n"); av_close_input_stream(format_context); mpd_ffmpeg_stream_close(stream); - return false; - } - - if (callback) { - ctx->audio_stream = audio_stream; - ctx->format_context = format_context; - ctx->codec_context = codec_context; - - ret = callback(ctx); - } else - ret = true; - - avcodec_close(codec_context); - av_close_input_stream(format_context); - mpd_ffmpeg_stream_close(stream); - - return ret; -} - -/** - * On some platforms, libavcodec wants the output buffer aligned to 16 - * bytes (because it uses SSE/Altivec internally). This function - * returns the aligned version of the specified buffer, and corrects - * the buffer size. - */ -static void * -align16(void *p, size_t *length_p) -{ - unsigned add = 16 - (size_t)p % 16; - - *length_p -= add; - return (char *)p + add; -} - -static enum decoder_command -ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is, - const AVPacket *packet, - AVCodecContext *codec_context, - 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; - int len, audio_size; - uint8_t *packet_data; - int packet_size; - - packet_data = packet->data; - packet_size = packet->size; - - buffer_size = sizeof(audio_buf); - aligned_buffer = align16(audio_buf, &buffer_size); - - while ((packet_size > 0) && (cmd == DECODE_COMMAND_NONE)) { - audio_size = buffer_size; - len = avcodec_decode_audio2(codec_context, - aligned_buffer, &audio_size, - packet_data, packet_size); - - if (len < 0) { - /* if error, we skip the frame */ - g_message("decoding failed\n"); - break; - } - - packet_data += len; - packet_size -= len; - - if (audio_size <= 0) - continue; - - position = packet->pts != (int64_t)AV_NOPTS_VALUE - ? av_rescale_q(packet->pts, *time_base, - (AVRational){1, 1}) - : 0; - - cmd = decoder_data(decoder, is, - aligned_buffer, audio_size, - position, - codec_context->bit_rate / 1000, NULL); + return; } - return cmd; -} -static bool -ffmpeg_decode_internal(struct ffmpeg_context *ctx) -{ - struct decoder *decoder = ctx->decoder; - AVCodecContext *codec_context = ctx->codec_context; - AVFormatContext *format_context = ctx->format_context; - AVPacket packet; + GError *error = NULL; struct audio_format audio_format; - enum decoder_command cmd; - int total_time; - - 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); - return false; + 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); + avcodec_close(codec_context); + av_close_input_stream(format_context); + mpd_ffmpeg_stream_close(stream); + return; } - //there is some problem with this on some demux (mp3 at least) - if (format_context->duration != (int64_t)AV_NOPTS_VALUE) { - total_time = format_context->duration / AV_TIME_BASE; - } + int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE + ? format_context->duration / AV_TIME_BASE + : 0; decoder_initialized(decoder, &audio_format, - ctx->input->seekable, total_time); + input->seekable, total_time); + enum decoder_command cmd; do { + AVPacket packet; if (av_read_frame(format_context, &packet) < 0) /* end of file */ break; - if (packet.stream_index == ctx->audio_stream) - cmd = ffmpeg_send_packet(decoder, ctx->input, + if (packet.stream_index == audio_stream) + cmd = ffmpeg_send_packet(decoder, input, &packet, codec_context, - &format_context->streams[ctx->audio_stream]->time_base); + &format_context->streams[audio_stream]->time_base); else cmd = decoder_get_command(decoder); @@ -378,115 +395,121 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx) } } while (cmd != DECODE_COMMAND_STOP); - return true; + avcodec_close(codec_context); + av_close_input_stream(format_context); + mpd_ffmpeg_stream_close(stream); } -static void -ffmpeg_decode(struct decoder *decoder, struct input_stream *input) -{ - struct ffmpeg_context ctx; - - ctx.input = input; - ctx.decoder = decoder; +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) +typedef struct ffmpeg_tag_map { + enum tag_type type; + const char *name; +} ffmpeg_tag_map; - char *uri = decoder_get_uri(decoder); - ffmpeg_helper(uri, decoder, input, - ffmpeg_decode_internal, &ctx); - g_free(uri); -} +static const ffmpeg_tag_map ffmpeg_tag_maps[] = { + { TAG_TITLE, "title" }, +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8)) + { TAG_ARTIST, "artist" }, + { TAG_DATE, "date" }, +#else + { TAG_ARTIST, "author" }, + { TAG_DATE, "year" }, +#endif + { TAG_ALBUM, "album" }, + { TAG_COMMENT, "comment" }, + { TAG_GENRE, "genre" }, + { TAG_TRACK, "track" }, + { TAG_ARTIST_SORT, "author-sort" }, + { TAG_ALBUM_ARTIST, "album_artist" }, + { TAG_ALBUM_ARTIST_SORT, "album_artist-sort" }, + { TAG_COMPOSER, "composer" }, + { TAG_PERFORMER, "performer" }, + { TAG_DISC, "disc" }, +}; -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) static bool ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m, - enum tag_type type, const char *name) + const ffmpeg_tag_map tag_map) { - AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0); - if (mt != NULL) - tag_add_item(tag, type, mt->value); + AVMetadataTag *mt = NULL; + + while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL) + tag_add_item(tag, tag_map.type, mt->value); return mt != NULL; } + #endif -static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx) +//no tag reading in ffmpeg, check if playable +static struct tag * +ffmpeg_stream_tag(struct input_stream *is) { - struct tag *tag = (struct tag *) ctx->tag; - AVFormatContext *f = ctx->format_context; + AVInputFormat *input_format = ffmpeg_probe(NULL, is); + if (input_format == NULL) + return NULL; - tag->time = 0; - if (f->duration != (int64_t)AV_NOPTS_VALUE) - tag->time = f->duration / AV_TIME_BASE; + struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is); + if (stream == NULL) + return NULL; + + AVFormatContext *f; + if (av_open_input_stream(&f, stream->io, is->uri, + input_format, NULL) != 0) { + mpd_ffmpeg_stream_close(stream); + return NULL; + } + + if (av_find_stream_info(f) < 0) { + av_close_input_stream(f); + mpd_ffmpeg_stream_close(stream); + return NULL; + } + + struct tag *tag = tag_new(); + + tag->time = f->duration != (int64_t)AV_NOPTS_VALUE + ? f->duration / AV_TIME_BASE + : 0; #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"); -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8)) - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "artist"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "date"); -#else - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year"); -#endif - 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_ALBUM_ARTIST, "album_artist"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMPOSER, "composer"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_PERFORMER, "performer"); - ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DISC, "disc"); + for (unsigned i = 0; i < sizeof(ffmpeg_tag_maps)/sizeof(ffmpeg_tag_map); i++) { + int idx = ffmpeg_find_audio_stream(f); + ffmpeg_copy_metadata(tag, f->metadata, ffmpeg_tag_maps[i]); + if (idx >= 0) + ffmpeg_copy_metadata(tag, f->streams[idx]->metadata, ffmpeg_tag_maps[i]); + } #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 - return true; -} - -//no tag reading in ffmpeg, check if playable -static struct tag *ffmpeg_tag(const char *file) -{ - struct input_stream input; - struct ffmpeg_context ctx; - bool ret; - - if (!input_stream_open(&input, file)) { - g_warning("failed to open %s\n", file); - return NULL; - } - - ctx.decoder = NULL; - ctx.tag = tag_new(); - - ret = ffmpeg_helper(file, NULL, &input, ffmpeg_tag_internal, &ctx); - if (!ret) { - tag_free(ctx.tag); - ctx.tag = NULL; - } - input_stream_close(&input); + av_close_input_stream(f); + mpd_ffmpeg_stream_close(stream); - return ctx.tag; + return tag; } /** @@ -592,6 +615,12 @@ static const char *const ffmpeg_mime_types[] = { "video/x-vid", "video/x-wmv", "video/x-xvid", + + /* special value for the "ffmpeg" input plugin: all streams by + the "ffmpeg" input plugin shall be decoded by this + plugin */ + "audio/x-mpd-ffmpeg", + NULL }; @@ -599,7 +628,7 @@ const struct decoder_plugin ffmpeg_decoder_plugin = { .name = "ffmpeg", .init = ffmpeg_init, .stream_decode = ffmpeg_decode, - .tag_dup = ffmpeg_tag, + .stream_tag = ffmpeg_stream_tag, .suffixes = ffmpeg_suffixes, .mime_types = ffmpeg_mime_types }; diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h new file mode 100644 index 000000000..d597690a0 --- /dev/null +++ b/src/decoder/flac_compat.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2010 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_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c new file mode 100644 index 000000000..e89e2ea11 --- /dev/null +++ b/src/decoder/flac_decoder_plugin.c @@ -0,0 +1,497 @@ +/* + * Copyright (C) 2003-2010 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 "_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> + +#include <assert.h> +#include <unistd.h> + +#include <sys/stat.h> +#include <sys/types.h> + +/* this code was based on flac123, from flac-tools */ + +static FLAC__StreamDecoderReadStatus +flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, + FLAC__byte buf[], flac_read_status_size_t *bytes, + void *fdata) +{ + struct flac_data *data = fdata; + size_t r; + + r = decoder_read(data->decoder, data->input_stream, + (void *)buf, *bytes); + *bytes = r; + + if (r == 0) { + if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || + input_stream_eof(data->input_stream)) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } + + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +} + +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 (!data->input_stream->seekable) + return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + + 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__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__STREAM_DECODER_TELL_STATUS_OK; +} + +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__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + + *length = (size_t) (data->input_stream->size); + + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool +flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata) +{ + struct flac_data *data = (struct flac_data *) fdata; + + return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && + decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || + input_stream_eof(data->input_stream); +} + +static void +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); +} + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__SEEKABLE_STREAM_DECODER_OK: + case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + str = "read error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + str = "seekable stream error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + str = "decoder already initialized"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + str = "invalid callback"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + + g_warning("%s\n", str); +} +#else /* FLAC_API_VERSION_CURRENT >= 7 */ +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: + case FLAC__STREAM_DECODER_READ_METADATA: + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + case FLAC__STREAM_DECODER_READ_FRAME: + case FLAC__STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__STREAM_DECODER_OGG_ERROR: + str = "error in the Ogg layer"; + break; + case FLAC__STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__STREAM_DECODER_ABORTED: + str = "decoder aborted by read"; + break; + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + + g_warning("%s\n", str); +} +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +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__StreamDecoder *dec, const FLAC__Frame *frame, + const FLAC__int32 *const buf[], void *vdata) +{ + struct flac_data *data = (struct flac_data *) vdata; + 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, nbytes); +} + +static struct tag * +flac_tag_dup(const char *file) +{ + return flac_tag_load(file, NULL); +} + +/** + * Some glue code around FLAC__stream_decoder_new(). + */ +static FLAC__StreamDecoder * +flac_decoder_new(void) +{ + 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(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT)) + g_debug("FLAC__stream_decoder_set_metadata_respond() has failed"); +#endif + + return sd; +} + +static bool +flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd, + FLAC__uint64 duration) +{ + data->total_frames = duration; + + if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) { + g_warning("problem reading metadata"); + return false; + } + + if (data->initialized) { + /* done */ + decoder_initialized(data->decoder, &data->audio_format, + data->input_stream->seekable, + (float)data->total_frames / + (float)data->audio_format.sample_rate); + return true; + } + + if (data->input_stream->seekable) + /* allow the workaround below only for nonseekable + streams*/ + return false; + + /* no stream_info packet found; try to initialize the decoder + from the first frame header */ + FLAC__stream_decoder_process_single(sd); + return data->initialized; +} + +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 (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 = t_start + + decoder_seek_where(decoder) * + data->audio_format.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__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM) + break; + + 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__stream_decoder_get_state(flac_dec)); + FLAC__stream_decoder_finish(flac_dec); + } +} + +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; + } + } + + if (!flac_decoder_initialize(&data, flac_dec, 0)) { + flac_data_deinit(&data); + FLAC__stream_decoder_delete(flac_dec); + return; + } + + 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); +} + +static void +flac_decode(struct decoder * decoder, struct input_stream *input_stream) +{ + flac_decode_internal(decoder, input_stream, false); +} + +#ifndef HAVE_OGGFLAC + +static bool +oggflac_init(G_GNUC_UNUSED const struct config_param *param) +{ +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + return !!FLAC_API_SUPPORTS_OGG_FLAC; +#else + /* disable oggflac when libflac is too old */ + return false; +#endif +} + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +static struct tag * +oggflac_tag_dup(const char *file) +{ + struct tag *ret = NULL; + FLAC__Metadata_Iterator *it; + FLAC__StreamMetadata *block; + FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); + + if (!(FLAC__metadata_chain_read_ogg(chain, file))) + goto out; + it = FLAC__metadata_iterator_new(); + FLAC__metadata_iterator_init(it, chain); + + ret = tag_new(); + do { + if (!(block = FLAC__metadata_iterator_get_block(it))) + break; + + flac_tag_apply_metadata(ret, NULL, block); + } while (FLAC__metadata_iterator_next(it)); + FLAC__metadata_iterator_delete(it); + + if (!tag_is_defined(ret)) { + tag_free(ret); + ret = NULL; + } + +out: + FLAC__metadata_chain_delete(chain); + return ret; +} + +static void +oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) +{ + 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, NULL); + + flac_decode_internal(decoder, input_stream, true); +} + +static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; +static const char *const oggflac_mime_types[] = { + "application/ogg", + "application/x-ogg", + "audio/ogg", + "audio/x-flac+ogg", + "audio/x-ogg", + NULL +}; + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +const struct decoder_plugin oggflac_decoder_plugin = { + .name = "oggflac", + .init = oggflac_init, +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + .stream_decode = oggflac_decode, + .tag_dup = oggflac_tag_dup, + .suffixes = oggflac_suffixes, + .mime_types = oggflac_mime_types +#endif +}; + +#endif /* HAVE_OGGFLAC */ + +static const char *const flac_suffixes[] = { "flac", NULL }; +static const char *const flac_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + NULL +}; + +const struct decoder_plugin flac_decoder_plugin = { + .name = "flac", + .stream_decode = flac_decode, + .tag_dup = flac_tag_dup, + .suffixes = flac_suffixes, + .mime_types = flac_mime_types, +}; diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c new file mode 100644 index 000000000..f2f2f954d --- /dev/null +++ b/src/decoder/flac_metadata.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2003-2010 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_info.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; +} + +bool +flac_parse_replay_gain(struct replay_gain_info *rgi, + const FLAC__StreamMetadata *block) +{ + bool found = false; + + replay_gain_info_init(rgi); + + if (flac_find_float_comment(block, "replaygain_album_gain", + &rgi->tuples[REPLAY_GAIN_ALBUM].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_album_peak", + &rgi->tuples[REPLAY_GAIN_ALBUM].peak)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_gain", + &rgi->tuples[REPLAY_GAIN_TRACK].gain)) + found = true; + if (flac_find_float_comment(block, "replaygain_track_peak", + &rgi->tuples[REPLAY_GAIN_TRACK].peak)) + found = true; + + return found; +} + +static bool +flac_find_string_comment(const FLAC__StreamMetadata *block, + const char *cmnt, char **str) +{ + int offset; + size_t pos; + int len; + const unsigned char *p; + + *str = NULL; + 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]; + *str = g_strndup((const char *)p, len); + + return true; +} + +bool +flac_parse_mixramp(char **mixramp_start, char **mixramp_end, + const FLAC__StreamMetadata *block) +{ + bool found = false; + + if (flac_find_string_comment(block, "mixramp_start", mixramp_start)) + found = true; + if (flac_find_string_comment(block, "mixramp_end", mixramp_end)) + found = true; + + return found; +} + +/** + * 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; + } +} + +struct tag * +flac_tag_load(const char *file, const char *char_tnum) +{ + struct tag *tag; + FLAC__Metadata_SimpleIterator *it; + FLAC__StreamMetadata *block = NULL; + + it = FLAC__metadata_simple_iterator_new(); + if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { + const char *err; + FLAC_API FLAC__Metadata_SimpleIteratorStatus s; + + s = FLAC__metadata_simple_iterator_status(it); + + switch (s) { /* slightly more human-friendly messages: */ + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: + err = "illegal input"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: + err = "error opening file"; + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: + err = "not a FLAC file"; + break; + default: + err = FLAC__Metadata_SimpleIteratorStatusString[s]; + } + g_debug("Reading '%s' metadata gave the following error: %s\n", + file, err); + FLAC__metadata_simple_iterator_delete(it); + return NULL; + } + + tag = tag_new(); + do { + block = FLAC__metadata_simple_iterator_get_block(it); + if (!block) + break; + + flac_tag_apply_metadata(tag, char_tnum, block); + FLAC__metadata_object_delete(block); + } while (FLAC__metadata_simple_iterator_next(it)); + + FLAC__metadata_simple_iterator_delete(it); + + if (!tag_is_defined(tag)) { + tag_free(tag); + tag = NULL; + } + + return tag; +} diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h new file mode 100644 index 000000000..06e691d1d --- /dev/null +++ b/src/decoder/flac_metadata.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2003-2010 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 <stdbool.h> +#include <FLAC/metadata.h> + +struct tag; +struct replay_gain_info; + +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; +} + +bool +flac_parse_replay_gain(struct replay_gain_info *rgi, + const FLAC__StreamMetadata *block); + +bool +flac_parse_mixramp(char **mixramp_start, char **mixramp_end, + 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); + +struct tag * +flac_tag_load(const char *file, const char *char_tnum); + +#endif diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c new file mode 100644 index 000000000..bf6e2612c --- /dev/null +++ b/src/decoder/flac_pcm.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2010 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_S24: + case SAMPLE_FORMAT_UNDEFINED: + /* unreachable */ + assert(false); + } +} diff --git a/src/normalize.c b/src/decoder/flac_pcm.h index 63c0d15cb..bccfc645c 100644 --- a/src/normalize.c +++ b/src/decoder/flac_pcm.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 deleted file mode 100644 index 1e568f70d..000000000 --- a/src/decoder/flac_plugin.c +++ /dev/null @@ -1,918 +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 "_flac_common.h" - -#include <glib.h> - -#include <assert.h> -#include <unistd.h> - -#include <sys/stat.h> -#include <sys/types.h> - -#ifdef HAVE_CUE /* libcue */ -#include "../cue/cue_tag.h" -#endif - -/* this code was based on flac123, from flac-tools */ - -static flac_read_status -flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__byte buf[], flac_read_status_size_t *bytes, - void *fdata) -{ - struct flac_data *data = fdata; - size_t r; - - r = decoder_read(data->decoder, data->input_stream, - (void *)buf, *bytes); - *bytes = r; - - if (r == 0) { - if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE || - input_stream_eof(data->input_stream)) - return flac_read_status_eof; - else - return flac_read_status_abort; - } - - return flac_read_status_continue; -} - -static flac_seek_status -flac_seek_cb(G_GNUC_UNUSED const flac_decoder *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; - - return flac_seek_status_ok; -} - -static flac_tell_status -flac_tell_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__uint64 * offset, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - *offset = (long)(data->input_stream->offset); - - return flac_tell_status_ok; -} - -static flac_length_status -flac_length_cb(G_GNUC_UNUSED const flac_decoder *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; - - *length = (size_t) (data->input_stream->size); - - return flac_length_status_ok; -} - -static FLAC__bool -flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata) -{ - struct flac_data *data = (struct flac_data *) fdata; - - return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE && - decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) || - input_stream_eof(data->input_stream); -} - -static void -flac_error_cb(G_GNUC_UNUSED const flac_decoder *fd, - FLAC__StreamDecoderErrorStatus status, void *fdata) -{ - flac_error_common_cb("flac", status, (struct flac_data *) fdata); -} - -#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 -static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) -{ - const char *str = ""; /* "" to silence compiler warning */ - switch (state) { - case FLAC__SEEKABLE_STREAM_DECODER_OK: - case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: - case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: - return; - case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - str = "allocation error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: - str = "read error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: - str = "seek error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: - str = "seekable stream error"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: - str = "decoder already initialized"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: - str = "invalid callback"; - break; - case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: - str = "decoder uninitialized"; - } - - 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) -{ - const char *str = ""; /* "" to silence compiler warning */ - switch (state) { - case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: - case FLAC__STREAM_DECODER_READ_METADATA: - case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: - case FLAC__STREAM_DECODER_READ_FRAME: - case FLAC__STREAM_DECODER_END_OF_STREAM: - return; - case FLAC__STREAM_DECODER_OGG_ERROR: - str = "error in the Ogg layer"; - break; - case FLAC__STREAM_DECODER_SEEK_ERROR: - str = "seek error"; - break; - case FLAC__STREAM_DECODER_ABORTED: - str = "decoder aborted by read"; - break; - case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: - str = "allocation error"; - break; - case FLAC__STREAM_DECODER_UNINITIALIZED: - str = "decoder uninitialized"; - } - - g_warning("%s\n", str); -} -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static void flacMetadata(G_GNUC_UNUSED const flac_decoder * 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, - 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; - - return flac_common_write(data, frame, buf); -} - -static struct tag * -flac_tag_load(const char *file, const char *char_tnum) -{ - struct tag *tag; - FLAC__Metadata_SimpleIterator *it; - FLAC__StreamMetadata *block = NULL; - - it = FLAC__metadata_simple_iterator_new(); - if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { - const char *err; - FLAC_API FLAC__Metadata_SimpleIteratorStatus s; - - s = FLAC__metadata_simple_iterator_status(it); - - switch (s) { /* slightly more human-friendly messages: */ - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: - err = "illegal input"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: - err = "error opening file"; - break; - case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: - err = "not a FLAC file"; - break; - default: - err = FLAC__Metadata_SimpleIteratorStatusString[s]; - } - g_debug("Reading '%s' metadata gave the following error: %s\n", - file, err); - FLAC__metadata_simple_iterator_delete(it); - return NULL; - } - - tag = tag_new(); - do { - 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__metadata_object_delete(block); - } while (FLAC__metadata_simple_iterator_next(it)); - - FLAC__metadata_simple_iterator_delete(it); - - if (!tag_is_defined(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -static struct tag * -flac_cue_tag_load(const char *file) -{ - struct tag* tag = NULL; - char* char_tnum = NULL; - char* ptr = NULL; - unsigned int tnum = 0; - unsigned int sample_rate = 0; - FLAC__uint64 track_time = 0; -#ifdef HAVE_CUE /* libcue */ - FLAC__StreamMetadata* vc; -#endif /* libcue */ - FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO); - FLAC__StreamMetadata* cs; - - tnum = flac_vtrack_tnum(file); - char_tnum = g_strdup_printf("%u", tnum); - - ptr = strrchr(file, '/'); - *ptr = '\0'; - -#ifdef HAVE_CUE /* libcue */ - if (FLAC__metadata_get_tags(file, &vc)) - { - for (unsigned i = 0; i < vc->data.vorbis_comment.num_comments; - i++) - { - if ((ptr = (char*)vc->data.vorbis_comment.comments[i].entry) != NULL) - { - if (g_ascii_strncasecmp(ptr, "cuesheet", 8) == 0) - { - while (*(++ptr) != '='); - tag = cue_tag_string( ++ptr, - tnum); - } - } - } - - FLAC__metadata_object_delete(vc); - } -#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); - g_free(char_tnum); - } - - if (FLAC__metadata_get_streaminfo(file, si)) - { - sample_rate = si->data.stream_info.sample_rate; - FLAC__metadata_object_delete(si); - } - - if (FLAC__metadata_get_cuesheet(file, &cs)) - { - if (cs->data.cue_sheet.tracks != NULL - && (tnum <= cs->data.cue_sheet.num_tracks - 1)) - { - track_time = cs->data.cue_sheet.tracks[tnum].offset - - cs->data.cue_sheet.tracks[tnum - 1].offset; - } - FLAC__metadata_object_delete(cs); - } - - if (sample_rate != 0) - { - tag->time = (unsigned int)(track_time/sample_rate); - } - - return tag; -} - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -static struct tag * -flac_tag_dup(const char *file) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - struct stat st; - - if (stat(file, &st) < 0) - return flac_cue_tag_load(file); - else -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - return flac_tag_load(file, NULL); -} - -static void -flac_decode_internal(struct decoder * decoder, - struct input_stream *input_stream, - bool is_ogg) -{ - 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(); - -#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_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; - } - } - - 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, - input_stream->seekable, data.total_time); - - while (true) { - if (!tag_is_empty(data.tag)) { - cmd = decoder_tag(decoder, 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; - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } else if (cmd == DECODE_COMMAND_STOP || - flac_get_state(flac_dec) == flac_decoder_eof) - break; - - if (!flac_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); - } - -fail: - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); - - tag_free(data.tag); - - if (flac_dec) - flac_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -static void -flac_decode(struct decoder * decoder, struct input_stream *input_stream) -{ - flac_decode_internal(decoder, input_stream, false); -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -/** - * @brief Decode a flac file with embedded cue sheets - * @param const char* fname filename on fs - */ -static void -flac_container_decode(struct decoder* decoder, - const char* fname, - bool is_ogg) -{ - unsigned int tnum = 0; - FLAC__uint64 t_start = 0; - FLAC__uint64 t_end = 0; - FLAC__uint64 track_time = 0; - FLAC__StreamMetadata* cs = NULL; - - flac_decoder *flac_dec; - struct flac_data data; - const char *err = NULL; - - char* pathname = g_strdup(fname); - char* slash = strrchr(pathname, '/'); - *slash = '\0'; - - tnum = flac_vtrack_tnum(fname); - - cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET); - - FLAC__metadata_get_cuesheet(pathname, &cs); - - if (cs != NULL) - { - if (cs->data.cue_sheet.tracks != NULL - && (tnum <= cs->data.cue_sheet.num_tracks - 1)) - { - t_start = cs->data.cue_sheet.tracks[tnum - 1].offset; - t_end = cs->data.cue_sheet.tracks[tnum].offset; - track_time = cs->data.cue_sheet.tracks[tnum].offset - - cs->data.cue_sheet.tracks[tnum - 1].offset; - } - - FLAC__metadata_object_delete(cs); - } - else - { - g_free(pathname); - return; - } - - if (!(flac_dec = flac_new())) - { - g_free(pathname); - 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"; - 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; - } - - // 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; - - 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); - } - -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); - - if (err) - g_warning("%s\n", err); -} - -/** - * @brief Open a flac file for decoding - * @param const char* fname filename on fs - */ -static void -flac_filedecode_internal(struct decoder* decoder, - const char* fname, - bool is_ogg) -{ - flac_decoder *flac_dec; - struct flac_data data; - const char *err = NULL; - unsigned int flac_err_state = 0; - - if (!(flac_dec = flac_new())) - 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, - fname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data )) - == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) - { - flac_container_decode(decoder, fname, is_ogg); - } - else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) - { - err = "doing Ogg init()"; - goto fail; - } - } - else - { - if ( (flac_err_state = FLAC__stream_decoder_init_file( flac_dec, - fname, - flac_write_cb, - flacMetadata, - flac_error_cb, - (void*) &data )) - == FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE) - { - flac_container_decode(decoder, fname, is_ogg); - } - else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK) - { - err = "doing init()"; - goto fail; - } - } - - 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 (decoder_get_command(decoder) != DECODE_COMMAND_STOP) - { - flacPrintErroredState(flac_get_state(flac_dec)); - flac_finish(flac_dec); - } - -fail: - if (data.replay_gain_info) - replay_gain_info_free(data.replay_gain_info); - - if (flac_dec) - flac_delete(flac_dec); - - if (err) - g_warning("%s\n", err); -} - -/** - * @brief wrapper function for - * flac_filedecode_internal method - * for decoding without ogg - */ -static void -flac_filedecode(struct decoder *decoder, const char *fname) -{ - struct stat st; - - if (stat(fname, &st) < 0) { - flac_container_decode(decoder, fname, false); - } else - flac_filedecode_internal(decoder, fname, false); -} - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -#ifndef HAVE_OGGFLAC - -static bool -oggflac_init(G_GNUC_UNUSED const struct config_param *param) -{ -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - return !!FLAC_API_SUPPORTS_OGG_FLAC; -#else - /* disable oggflac when libflac is too old */ - return false; -#endif -} - -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - -static struct tag * -oggflac_tag_dup(const char *file) -{ - struct tag *ret = NULL; - FLAC__Metadata_Iterator *it; - FLAC__StreamMetadata *block; - FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); - - if (!(FLAC__metadata_chain_read_ogg(chain, file))) - goto out; - it = FLAC__metadata_iterator_new(); - FLAC__metadata_iterator_init(it, chain); - - ret = tag_new(); - 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; - } - } while (FLAC__metadata_iterator_next(it)); - FLAC__metadata_iterator_delete(it); - - if (!tag_is_defined(ret)) { - tag_free(ret); - ret = NULL; - } - -out: - FLAC__metadata_chain_delete(chain); - return ret; -} - -static void -oggflac_decode(struct decoder *decoder, struct input_stream *input_stream) -{ - 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); - - flac_decode_internal(decoder, input_stream, true); -} - -static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL }; -static const char *const oggflac_mime_types[] = { - "application/ogg", - "application/x-ogg", - "audio/ogg", - "audio/x-flac+ogg", - "audio/x-ogg", - NULL -}; - -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - -const struct decoder_plugin oggflac_decoder_plugin = { - .name = "oggflac", - .init = oggflac_init, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .stream_decode = oggflac_decode, - .tag_dup = oggflac_tag_dup, - .suffixes = oggflac_suffixes, - .mime_types = oggflac_mime_types -#endif -}; - -#endif /* HAVE_OGGFLAC */ - -static const char *const flac_suffixes[] = { "flac", NULL }; -static const char *const flac_mime_types[] = { - "application/flac", - "application/x-flac", - "audio/flac", - "audio/x-flac", - NULL -}; - -const struct decoder_plugin flac_decoder_plugin = { - .name = "flac", - .stream_decode = flac_decode, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .file_decode = flac_filedecode, -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ - .tag_dup = flac_tag_dup, - .suffixes = flac_suffixes, - .mime_types = flac_mime_types, -#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 - .container_scan = flac_cue_track, -#endif /* FLAC_API_VERSION_CURRENT >= 7 */ -}; diff --git a/src/decoder/fluidsynth_plugin.c b/src/decoder/fluidsynth_decoder_plugin.c index 99c874c09..b9a2d0d99 100644 --- a/src/decoder/fluidsynth_plugin.c +++ b/src/decoder/fluidsynth_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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); } while (cmd == DECODE_COMMAND_NONE); /* clean up */ diff --git a/src/decoder/gme_decoder_plugin.c b/src/decoder/gme_decoder_plugin.c new file mode 100644 index 000000000..4a5220a3f --- /dev/null +++ b/src/decoder/gme_decoder_plugin.c @@ -0,0 +1,244 @@ +#include "config.h" +#include "../decoder_api.h" +#include "audio_check.h" +#include "uri.h" + +#include <glib.h> +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <gme/gme.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "gme" + +#define SUBTUNE_PREFIX "tune_" + +enum { + GME_SAMPLE_RATE = 44100, + GME_CHANNELS = 2, + GME_BUFFER_FRAMES = 2048, + GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS, +}; + +/** + * returns the file path stripped of any /tune_xxx.* subtune + * suffix + */ +static char * +get_container_name(const char *path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs); + char *path_container = g_strdup(path_fs); + char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + g_free(pat); + if (!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, NULL)) { + g_pattern_spec_free(path_with_subtune); + return path_container; + } + + char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if (ptr != NULL) + *ptr='\0'; + + g_pattern_spec_free(path_with_subtune); + return path_container; +} + +/** + * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune + * is appended. + */ +static int +get_song_num(const char *path_fs) +{ + const char *subtune_suffix = uri_get_suffix(path_fs); + char *pat = g_strconcat("*/" SUBTUNE_PREFIX "???.", subtune_suffix, NULL); + GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); + g_free(pat); + + if (g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, NULL)) { + char *sub = g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + g_pattern_spec_free(path_with_subtune); + if(!sub) + return 0; + + sub += strlen("/" SUBTUNE_PREFIX); + int song_num = strtol(sub, NULL, 10); + + return song_num - 1; + } else { + g_pattern_spec_free(path_with_subtune); + return 0; + } +} + +static char * +gme_container_scan(const char *path_fs, const unsigned int tnum) +{ + Music_Emu *emu; + const char* gme_err; + unsigned int num_songs; + + gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return NULL; + } + + num_songs = gme_track_count(emu); + /* if it only contains a single tune, don't treat as container */ + if (num_songs < 2) + return NULL; + + const char *subtune_suffix = uri_get_suffix(path_fs); + if (tnum <= num_songs){ + char *subtune = g_strdup_printf( + SUBTUNE_PREFIX "%03u.%s", tnum, subtune_suffix); + return subtune; + } else + return NULL; +} + +static void +gme_file_decode(struct decoder *decoder, const char *path_fs) +{ + float song_len; + Music_Emu *emu; + gme_info_t *ti; + struct audio_format audio_format; + enum decoder_command cmd; + short buf[GME_BUFFER_SAMPLES]; + const char* gme_err; + char *path_container = get_container_name(path_fs); + int song_num = get_song_num(path_fs); + + gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + g_free(path_container); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return; + } + + if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){ + g_warning("%s", gme_err); + gme_delete(emu); + return; + } + + if(ti->length > 0) + song_len = ti->length / 1000.0; + else song_len = -1; + + /* initialize the MPD decoder */ + + GError *error = NULL; + if (!audio_format_init_checked(&audio_format, GME_SAMPLE_RATE, + SAMPLE_FORMAT_S16, GME_CHANNELS, + &error)) { + g_warning("%s", error->message); + g_error_free(error); + gme_free_info(ti); + gme_delete(emu); + return; + } + + decoder_initialized(decoder, &audio_format, true, song_len); + + if((gme_err = gme_start_track(emu, song_num)) != NULL) + g_warning("%s", gme_err); + + /* play */ + do { + gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return; + } + cmd = decoder_data(decoder, NULL, buf, sizeof(buf), 0); + + if(cmd == DECODE_COMMAND_SEEK) { + float where = decoder_seek_where(decoder); + if((gme_err = gme_seek(emu, (int)where*1000)) != NULL) + g_warning("%s", gme_err); + decoder_command_finished(decoder); + } + + if(gme_track_ended(emu)) + break; + } while(cmd != DECODE_COMMAND_STOP); + + gme_free_info(ti); + gme_delete(emu); +} + +static struct tag * +gme_tag_dup(const char *path_fs) +{ + Music_Emu *emu; + gme_info_t *ti; + const char* gme_err; + char *path_container=get_container_name(path_fs); + int song_num; + song_num=get_song_num(path_fs); + + gme_err = gme_open_file(path_container, &emu, GME_SAMPLE_RATE); + g_free(path_container); + if (gme_err != NULL) { + g_warning("%s", gme_err); + return NULL; + } + if((gme_err = gme_track_info(emu, &ti, song_num)) != NULL){ + g_warning("%s", gme_err); + gme_delete(emu); + return NULL; + } + + struct tag *tag = tag_new(); + if(ti != NULL){ + if(ti->length > 0) + tag->time = ti->length / 1000; + if(ti->song != NULL){ + if(gme_track_count(emu) > 1){ + /* start numbering subtunes from 1 */ + char *tag_title=g_strdup_printf("%s (%d/%d)", + ti->song, song_num+1, gme_track_count(emu)); + tag_add_item(tag, TAG_TITLE, tag_title); + g_free(tag_title); + }else + tag_add_item(tag, TAG_TITLE, ti->song); + } + if(ti->author != NULL) + tag_add_item(tag, TAG_ARTIST, ti->author); + if(ti->game != NULL) + tag_add_item(tag, TAG_ALBUM, ti->game); + if(ti->comment != NULL) + tag_add_item(tag, TAG_COMMENT, ti->comment); + if(ti->copyright != NULL) + tag_add_item(tag, TAG_DATE, ti->copyright); + } + + gme_free_info(ti); + gme_delete(emu); + return tag; +} + +static const char *const gme_suffixes[] = { + "ay", "gbs", "gym", "hes", "kss", "nsf", + "nsfe", "sap", "spc", "vgm", "vgz", + NULL +}; + +extern const struct decoder_plugin gme_decoder_plugin; +const struct decoder_plugin gme_decoder_plugin = { + .name = "gme", + .file_decode = gme_file_decode, + .tag_dup = gme_tag_dup, + .suffixes = gme_suffixes, + .container_scan = gme_container_scan, +}; diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_decoder_plugin.c index 9b3259485..a11d1b020 100644 --- a/src/decoder/mad_plugin.c +++ b/src/decoder/mad_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 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 "tag_rva2.h" +#include "audio_check.h" #include <assert.h> #include <unistd.h> @@ -125,6 +127,7 @@ struct mp3_data { unsigned int drop_end_frames; unsigned int drop_start_samples; unsigned int drop_end_samples; + bool found_replay_gain; bool found_xing; bool found_first_frame; bool decoded_first_frame; @@ -148,6 +151,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder, data->drop_end_frames = 0; data->drop_start_samples = 0; data->drop_end_samples = 0; + data->found_replay_gain = false; data->found_xing = false; data->found_first_frame = false; data->decoded_first_frame = false; @@ -164,7 +168,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); @@ -208,105 +212,17 @@ mp3_fill_buffer(struct mp3_data *data) } #ifdef HAVE_ID3TAG -/* Parse mp3 RVA2 frame. Shamelessly stolen from madplay. */ static bool -parse_rva2(struct id3_tag *tag, struct replay_gain_info *replay_gain_info) -{ - struct id3_frame const * frame; - - id3_latin1_t const *id; - id3_byte_t const *data; - id3_length_t length; - - enum { - CHANNEL_OTHER = 0x00, - CHANNEL_MASTER_VOLUME = 0x01, - CHANNEL_FRONT_RIGHT = 0x02, - CHANNEL_FRONT_LEFT = 0x03, - CHANNEL_BACK_RIGHT = 0x04, - CHANNEL_BACK_LEFT = 0x05, - CHANNEL_FRONT_CENTRE = 0x06, - CHANNEL_BACK_CENTRE = 0x07, - CHANNEL_SUBWOOFER = 0x08 - }; - - /* relative volume adjustment information */ - - frame = id3_tag_findframe(tag, "RVA2", 0); - if (frame == NULL) - return false; - - id = id3_field_getlatin1(id3_frame_field(frame, 0)); - data = id3_field_getbinarydata(id3_frame_field(frame, 1), - &length); - - if (id == NULL || data == NULL) - return false; - - /* - * "The 'identification' string is used to identify the - * situation and/or device where this adjustment should apply. - * The following is then repeated for every channel - * - * Type of channel $xx - * Volume adjustment $xx xx - * Bits representing peak $xx - * Peak volume $xx (xx ...)" - */ - - while (length >= 4) { - unsigned int peak_bytes; - - peak_bytes = (data[3] + 7) / 8; - if (4 + peak_bytes > length) - break; - - if (data[0] == CHANNEL_MASTER_VOLUME) { - signed int voladj_fixed; - double voladj_float; - - /* - * "The volume adjustment is encoded as a fixed - * point decibel value, 16 bit signed integer - * representing (adjustment*512), giving +/- 64 - * dB with a precision of 0.001953125 dB." - */ - - voladj_fixed = (data[1] << 8) | (data[2] << 0); - voladj_fixed |= -(voladj_fixed & 0x8000); - - voladj_float = (double) voladj_fixed / 512; - - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = voladj_float; - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = voladj_float; - - g_debug("parseRVA2: Relative Volume " - "%+.1f dB adjustment (%s)\n", - voladj_float, id); - - return true; - } - - data += 4 + peak_bytes; - length -= 4 + peak_bytes; - } - - return false; -} -#endif - -#ifdef HAVE_ID3TAG -static struct replay_gain_info * -parse_id3_replay_gain_info(struct id3_tag *tag) +parse_id3_replay_gain_info(struct replay_gain_info *replay_gain_info, + struct id3_tag *tag) { int i; char *key; char *value; struct id3_frame *frame; bool found = false; - struct replay_gain_info *replay_gain_info; - replay_gain_info = replay_gain_info_new(); + replay_gain_info_init(replay_gain_info); for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { if (frame->nfields < 3) @@ -337,21 +253,55 @@ parse_id3_replay_gain_info(struct id3_tag *tag) free(value); } - if (!found) { + return found || /* fall back on RVA2 if no replaygain tags found */ - found = parse_rva2(tag, replay_gain_info); + tag_rva2_parse(tag, replay_gain_info); +} +#endif + +#ifdef HAVE_ID3TAG +static bool +parse_id3_mixramp(char **mixramp_start, char **mixramp_end, + struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + bool found = false; + + *mixramp_start = NULL; + *mixramp_end = NULL; + + for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (g_ascii_strcasecmp(key, "mixramp_start") == 0) { + *mixramp_start = g_strdup(value); + found = true; + } else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) { + *mixramp_end = g_strdup(value); + found = true; + } + + free(key); + free(value); } - if (found) - return replay_gain_info; - replay_gain_info_free(replay_gain_info); - return NULL; + return found; } #endif static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, - struct tag **mpd_tag, - struct replay_gain_info **replay_gain_info_r) + struct tag **mpd_tag) { #ifdef HAVE_ID3TAG struct id3_tag *id3_tag = NULL; @@ -404,13 +354,20 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, } } - if (replay_gain_info_r) { - struct replay_gain_info *tmp_rgi = - parse_id3_replay_gain_info(id3_tag); - if (tmp_rgi != NULL) { - if (*replay_gain_info_r) - replay_gain_info_free(*replay_gain_info_r); - *replay_gain_info_r = tmp_rgi; + if (data->decoder != NULL) { + struct replay_gain_info rgi; + char *mixramp_start; + char *mixramp_end; + float replay_gain_db = 0; + + if (parse_id3_replay_gain_info(&rgi, id3_tag)) { + replay_gain_db = decoder_replay_gain(data->decoder, &rgi); + data->found_replay_gain = true; + } + if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) { + g_debug("setting mixramp_tags"); + decoder_mixramp(data->decoder, replay_gain_db, + mixramp_start, mixramp_end); } } @@ -419,7 +376,6 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize, g_free(allocated); #else /* !HAVE_ID3TAG */ (void)mpd_tag; - (void)replay_gain_info_r; /* This code is enabled when libid3tag is disabled. Instead of parsing the ID3 frame, it just skips it. */ @@ -467,8 +423,7 @@ id3_tag_query(const void *p0, size_t length) #endif /* !HAVE_ID3TAG */ static enum mp3_action -decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag, - G_GNUC_UNUSED struct replay_gain_info **replay_gain_info_r) +decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag) { enum mad_layer layer; @@ -490,7 +445,7 @@ decode_next_frame_header(struct mp3_data *data, G_GNUC_UNUSED struct tag **tag, if (tagsize > 0) { if (tag && !(*tag)) { mp3_parse_id3(data, (size_t)tagsize, - tag, replay_gain_info_r); + tag); } else { mad_stream_skip(&(data->stream), tagsize); @@ -798,10 +753,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; @@ -811,7 +766,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); @@ -823,7 +778,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); @@ -838,8 +793,7 @@ mp3_filesize_to_song_length(struct mp3_data *data) } static bool -mp3_decode_first_frame(struct mp3_data *data, struct tag **tag, - struct replay_gain_info **replay_gain_info_r) +mp3_decode_first_frame(struct mp3_data *data, struct tag **tag) { struct xing xing; struct lame lame; @@ -853,8 +807,7 @@ mp3_decode_first_frame(struct mp3_data *data, struct tag **tag, while (true) { do { - ret = decode_next_frame_header(data, tag, - replay_gain_info_r); + ret = decode_next_frame_header(data, tag); } while (ret == DECODE_CONT); if (ret == DECODE_BREAK) return false; @@ -897,14 +850,17 @@ mp3_decode_first_frame(struct mp3_data *data, struct tag **tag, /* Album gain isn't currently used. See comment in * parse_lame() for details. -- jat */ - if (replay_gain_info_r && !*replay_gain_info_r && + if (data->decoder != NULL && + !data->found_replay_gain && lame.track_gain) { - *replay_gain_info_r = replay_gain_info_new(); - (*replay_gain_info_r)->tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; - (*replay_gain_info_r)->tuples[REPLAY_GAIN_TRACK].peak = lame.peak; + struct replay_gain_info rgi; + replay_gain_info_init(&rgi); + rgi.tuples[REPLAY_GAIN_TRACK].gain = lame.track_gain; + rgi.tuples[REPLAY_GAIN_TRACK].peak = lame.peak; + decoder_replay_gain(data->decoder, &rgi); } } - } + } if (!data->max_frames) return false; @@ -932,33 +888,29 @@ static void mp3_data_finish(struct mp3_data *data) } /* this is primarily used for getting total time for tags */ -static int mp3_total_file_time(const char *file) +static int +mad_decoder_total_file_time(struct input_stream *is) { - struct input_stream input_stream; struct mp3_data data; int ret; - if (!input_stream_open(&input_stream, file)) - return -1; - mp3_data_init(&data, NULL, &input_stream); - if (!mp3_decode_first_frame(&data, NULL, NULL)) + mp3_data_init(&data, NULL, is); + if (!mp3_decode_first_frame(&data, NULL)) ret = -1; else ret = data.total_time + 0.5; mp3_data_finish(&data); - input_stream_close(&input_stream); return ret; } static bool mp3_open(struct input_stream *is, struct mp3_data *data, - struct decoder *decoder, struct tag **tag, - struct replay_gain_info **replay_gain_info_r) + struct decoder *decoder, struct tag **tag) { mp3_data_init(data, decoder, is); *tag = NULL; - if (!mp3_decode_first_frame(data, tag, replay_gain_info_r)) { + if (!mp3_decode_first_frame(data, tag)) { mp3_data_finish(data); if (tag && *tag) tag_free(*tag); @@ -1017,8 +969,7 @@ mp3_update_timer_next_frame(struct mp3_data *data) * Sends the synthesized current frame via decoder_data(). */ static enum decoder_command -mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length, - struct replay_gain_info *replay_gain_info) +mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length) { unsigned max_samples; @@ -1043,9 +994,7 @@ 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); + data->bit_rate / 1000); if (cmd != DECODE_COMMAND_NONE) return cmd; } @@ -1057,8 +1006,7 @@ mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length, * Synthesize the current frame and send it via decoder_data(). */ static enum decoder_command -mp3_synth_and_send(struct mp3_data *data, - struct replay_gain_info *replay_gain_info) +mp3_synth_and_send(struct mp3_data *data) { unsigned i, pcm_length; enum decoder_command cmd; @@ -1099,7 +1047,7 @@ mp3_synth_and_send(struct mp3_data *data, pcm_length -= data->drop_end_samples; } - cmd = mp3_send_pcm(data, i, pcm_length, replay_gain_info); + cmd = mp3_send_pcm(data, i, pcm_length); if (cmd != DECODE_COMMAND_NONE) return cmd; @@ -1113,7 +1061,7 @@ mp3_synth_and_send(struct mp3_data *data, } static bool -mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) +mp3_read(struct mp3_data *data) { struct decoder *decoder = data->decoder; enum mp3_action ret; @@ -1130,9 +1078,7 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) data->mute_frame = MUTEFRAME_NONE; break; case MUTEFRAME_NONE: - cmd = mp3_synth_and_send(data, - replay_gain_info_r != NULL - ? *replay_gain_info_r : NULL); + cmd = mp3_synth_and_send(data); if (cmd == DECODE_COMMAND_SEEK) { unsigned long j; @@ -1161,8 +1107,7 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) do { struct tag *tag = NULL; - ret = decode_next_frame_header(data, &tag, - replay_gain_info_r); + ret = decode_next_frame_header(data, &tag); if (tag != NULL) { decoder_tag(decoder, data->input_stream, tag); @@ -1189,29 +1134,34 @@ 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; - if (!mp3_open(input_stream, &data, decoder, &tag, &replay_gain_info)) { + if (!mp3_open(input_stream, &data, decoder, &tag)) { if (decoder_get_command(decoder) == DECODE_COMMAND_NONE) g_warning ("Input does not appear to be a mp3 bit stream.\n"); 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); + mp3_data_finish(&data); + return; + } decoder_initialized(decoder, &audio_format, data.input_stream->seekable, data.total_time); @@ -1221,24 +1171,20 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream) tag_free(tag); } - while (mp3_read(&data, &replay_gain_info)) ; - - if (replay_gain_info) - replay_gain_info_free(replay_gain_info); + while (mp3_read(&data)) ; mp3_data_finish(&data); } -static struct tag *mp3_tag_dup(const char *file) +static struct tag * +mad_decoder_stream_tag(struct input_stream *is) { struct tag *tag; int total_time; - total_time = mp3_total_file_time(file); - if (total_time < 0) { - g_debug("Failed to get total song time from: %s", file); + total_time = mad_decoder_total_file_time(is); + if (total_time < 0) return NULL; - } tag = tag_new(); tag->time = total_time; @@ -1252,7 +1198,7 @@ const struct decoder_plugin mad_decoder_plugin = { .name = "mad", .init = mp3_plugin_init, .stream_decode = mp3_decode, - .tag_dup = mp3_tag_dup, + .stream_tag = mad_decoder_stream_tag, .suffixes = mp3_suffixes, .mime_types = mp3_mime_types }; diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_decoder_plugin.c index f60dcbc61..91478e86f 100644 --- a/src/decoder/mikmod_plugin.c +++ b/src/decoder/mikmod_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../decoder_api.h" +#include "config.h" +#include "decoder_api.h" +#include "mpd_error.h" #include <glib.h> #include <mikmod.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "mikmod" @@ -29,30 +32,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 +75,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 +101,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)) + MPD_ERROR("Invalid sample rate in line %d: %u", + param->line, mikmod_sample_rate); + md_device = 0; md_reverb = 0; @@ -106,7 +121,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,117 +134,80 @@ 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); } - 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; - char *title; + char *path2 = g_strdup(path_fs); + MODULE *handle = Player_Load(path2, 128, 0); - path2 = g_strdup(file); - moduleHandle = Player_Load(path2, 128, 0); - g_free(path2); - - if (moduleHandle == NULL) { - g_debug("Failed to open file: %s", file); + if (handle == NULL) { + g_free(path2); + g_debug("Failed to open file: %s", path_fs); return NULL; } - Player_Free(moduleHandle); - ret = tag_new(); + Player_Free(handle); - ret->time = 0; + struct tag *tag = tag_new(); - path2 = g_strdup(file); - title = Player_LoadTitle(path2); + tag->time = 0; + + char *title = Player_LoadTitle(path2); g_free(path2); - if (title) { - tag_add_item(ret, TAG_ITEM_TITLE, title); + + if (title != NULL) { + tag_add_item(tag, TAG_TITLE, title); free(title); } - return ret; + return tag; } -static const char *const modSuffixes[] = { +static const char *const mikmod_decoder_suffixes[] = { "amf", "dsm", "far", @@ -250,9 +228,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_decoder_plugin.c index f636f2fa6..037c2fd74 100644 --- a/src/decoder/modplug_plugin.c +++ b/src/decoder/modplug_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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); 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); } @@ -160,43 +149,33 @@ mod_decode(struct decoder *decoder, struct input_stream *is) ModPlug_Unload(f); } -static struct tag *mod_tagdup(const char *file) +static struct tag * +modplug_stream_tag(struct input_stream *is) { ModPlugFile *f; struct tag *ret = NULL; GByteArray *bdatas; char *title; - struct input_stream is; - if (!input_stream_open(&is, file)) { - g_warning("cant open file %s\n", file); + bdatas = mod_loadfile(NULL, is); + if (!bdatas) return NULL; - } - - bdatas = mod_loadfile(NULL, &is); - if (!bdatas) { - g_warning("cant load file %s\n", file); - return NULL; - } f = ModPlug_Load(bdatas->data, bdatas->len); g_byte_array_free(bdatas, TRUE); - if (!f) { - g_warning("could not decode file %s\n", file); + if (f == NULL) 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); - input_stream_close(&is); - return ret; } @@ -210,6 +189,6 @@ static const char *const mod_suffixes[] = { const struct decoder_plugin modplug_decoder_plugin = { .name = "modplug", .stream_decode = mod_decode, - .tag_dup = mod_tagdup, + .stream_tag = modplug_stream_tag, .suffixes = mod_suffixes, }; diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_decoder_plugin.c index 4d4d47c6c..861b08194 100644 --- a/src/decoder/mp4ff_plugin.c +++ b/src/decoder/mp4ff_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 "tag_table.h" #include <glib.h> @@ -35,7 +36,9 @@ /* all code here is either based on or copied from FAAD2's frontend code */ -struct mp4_context { +struct mp4ff_input_stream { + mp4ff_callback_t callback; + struct decoder *decoder; struct input_stream *input_stream; }; @@ -89,20 +92,38 @@ mp4_get_aac_track(mp4ff_t * infile, faacDecHandle decoder, static uint32_t mp4_read(void *user_data, void *buffer, uint32_t length) { - struct mp4_context *ctx = user_data; + struct mp4ff_input_stream *mis = user_data; - return decoder_read(ctx->decoder, ctx->input_stream, buffer, length); + return decoder_read(mis->decoder, mis->input_stream, buffer, length); } static uint32_t mp4_seek(void *user_data, uint64_t position) { - struct mp4_context *ctx = user_data; + struct mp4ff_input_stream *mis = user_data; - return input_stream_seek(ctx->input_stream, position, SEEK_SET) + return input_stream_seek(mis->input_stream, position, SEEK_SET, NULL) ? 0 : -1; } +static const mp4ff_callback_t mpd_mp4ff_callback = { + .read = mp4_read, + .seek = mp4_seek, +}; + +static mp4ff_t * +mp4ff_input_stream_open(struct mp4ff_input_stream *mis, + struct decoder *decoder, + struct input_stream *input_stream) +{ + mis->callback = mpd_mp4ff_callback; + mis->callback.user_data = mis; + mis->decoder = decoder; + mis->input_stream = input_stream; + + return mp4ff_open_read(&mis->callback); +} + static faacDecHandle mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) { @@ -111,6 +132,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(); @@ -131,37 +153,24 @@ 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; } static void mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) { - struct mp4_context ctx = { - .decoder = mpd_decoder, - .input_stream = input_stream, - }; - mp4ff_callback_t callback = { - .read = mp4_read, - .seek = mp4_seek, - .user_data = &ctx, - }; + struct mp4ff_input_stream mis; mp4ff_t *mp4fh; int32_t track; float file_time, total_time; @@ -187,7 +196,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) double seek_where = 0; enum decoder_command cmd = DECODE_COMMAND_NONE; - mp4fh = mp4ff_open_read(&callback); + mp4fh = mp4ff_input_stream_open(&mis, mpd_decoder, input_stream); if (!mp4fh) { g_warning("Input does not appear to be a mp4 stream.\n"); return; @@ -266,7 +275,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) { @@ -332,7 +341,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); } g_free(seek_table); @@ -341,9 +350,9 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream) } static const char *const mp4ff_tag_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ITEM_ALBUM_ARTIST] = "album artist", - [TAG_ITEM_COMPOSER] = "writer", - [TAG_ITEM_PERFORMER] = "band", + [TAG_ALBUM_ARTIST] = "album artist", + [TAG_COMPOSER] = "writer", + [TAG_PERFORMER] = "band", }; static enum tag_type @@ -353,57 +362,41 @@ mp4ff_tag_name_parse(const char *name) if (type == TAG_NUM_OF_ITEM_TYPES) type = tag_name_parse_i(name); + if (g_ascii_strcasecmp(name, "albumartist") == 0 || + g_ascii_strcasecmp(name, "album_artist") == 0) + return TAG_ALBUM_ARTIST; + return type; } static struct tag * -mp4_tag_dup(const char *file) +mp4_stream_tag(struct input_stream *is) { - struct tag *ret = NULL; - struct input_stream input_stream; - struct mp4_context ctx = { - .decoder = NULL, - .input_stream = &input_stream, - }; - mp4ff_callback_t callback = { - .read = mp4_read, - .seek = mp4_seek, - .user_data = &ctx, - }; - mp4ff_t *mp4fh; + struct mp4ff_input_stream mis; int32_t track; int32_t file_time; int32_t scale; int i; - if (!input_stream_open(&input_stream, file)) { - g_warning("Failed to open file: %s", file); + mp4ff_t *mp4fh = mp4ff_input_stream_open(&mis, NULL, is); + if (mp4fh == NULL) return NULL; - } - - mp4fh = mp4ff_open_read(&callback); - if (!mp4fh) { - input_stream_close(&input_stream); - return NULL; - } track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL); if (track < 0) { mp4ff_close(mp4fh); - input_stream_close(&input_stream); return NULL; } - ret = tag_new(); file_time = mp4ff_get_track_duration_use_offsets(mp4fh, track); scale = mp4ff_time_scale(mp4fh, track); if (scale < 0) { mp4ff_close(mp4fh); - input_stream_close(&input_stream); - tag_free(ret); return NULL; } - ret->time = ((float)file_time) / scale + 0.5; + + struct tag *tag = tag_new(); + tag->time = ((float)file_time) / scale + 0.5; for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { char *item; @@ -413,16 +406,15 @@ mp4_tag_dup(const char *file) enum tag_type type = mp4ff_tag_name_parse(item); if (type != TAG_NUM_OF_ITEM_TYPES) - tag_add_item(ret, type, value); + tag_add_item(tag, type, value); free(item); free(value); } mp4ff_close(mp4fh); - input_stream_close(&input_stream); - return ret; + return tag; } static const char *const mp4_suffixes[] = { @@ -435,9 +427,9 @@ static const char *const mp4_suffixes[] = { static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL }; const struct decoder_plugin mp4ff_decoder_plugin = { - .name = "mp4", + .name = "mp4ff", .stream_decode = mp4_decode, - .tag_dup = mp4_tag_dup, + .stream_tag = mp4_stream_tag, .suffixes = mp4_suffixes, .mime_types = mp4_mime_types, }; diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_decoder_plugin.c index 72a516f22..4df8dd218 100644 --- a/src/decoder/mpcdec_plugin.c +++ b/src/decoder/mpcdec_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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> @@ -28,6 +29,7 @@ #endif #include <glib.h> +#include <assert.h> #include <unistd.h> #undef G_LOG_DOMAIN @@ -59,7 +61,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 @@ -141,6 +143,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; @@ -150,11 +153,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; data.is = is; @@ -194,50 +194,47 @@ 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; } - replay_gain_info = replay_gain_info_new(); + struct replay_gain_info replay_gain_info; + replay_gain_info_init(&replay_gain_info); #ifdef MPC_IS_OLD_API - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01; - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0; + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = info.gain_album * 0.01; + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = info.peak_album / 32767.0; + replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = info.gain_title * 0.01; + replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = info.peak_title / 32767.0; #else - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); - replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; - replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); - replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.); + replay_gain_info.tuples[REPLAY_GAIN_ALBUM].peak = pow(10, info.peak_album / 256. / 20) / 32767; + replay_gain_info.tuples[REPLAY_GAIN_TRACK].gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.); + replay_gain_info.tuples[REPLAY_GAIN_TRACK].peak = pow(10, info.peak_title / 256. / 20) / 32767; #endif + decoder_replay_gain(mpd_decoder, &replay_gain_info); + decoder_initialized(mpd_decoder, &audio_format, is->seekable, mpc_streaminfo_get_length(&info)); 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) @@ -268,33 +265,26 @@ 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); + bit_rate); } while (cmd != DECODE_COMMAND_STOP); - replay_gain_info_free(replay_gain_info); - #ifndef MPC_IS_OLD_API mpc_demux_exit(demux); #endif } static float -mpcdec_get_file_duration(const char *file) +mpcdec_get_file_duration(struct input_stream *is) { - struct input_stream is; float total_time = -1; mpc_reader reader; @@ -304,10 +294,7 @@ mpcdec_get_file_duration(const char *file) mpc_streaminfo info; struct mpc_decoder_data data; - if (!input_stream_open(&is, file)) - return -1; - - data.is = &is; + data.is = is; data.decoder = NULL; reader.read = mpc_read_cb; @@ -320,16 +307,12 @@ mpcdec_get_file_duration(const char *file) #ifdef MPC_IS_OLD_API mpc_streaminfo_init(&info); - if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) { - input_stream_close(&is); + if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) return -1; - } #else demux = mpc_demux_init(&reader); - if (demux == NULL) { - input_stream_close(&is); + if (demux == NULL) return -1; - } mpc_demux_get_info(demux, &info); mpc_demux_exit(demux); @@ -337,21 +320,17 @@ mpcdec_get_file_duration(const char *file) total_time = mpc_streaminfo_get_length(&info); - input_stream_close(&is); - return total_time; } static struct tag * -mpcdec_tag_dup(const char *file) +mpcdec_stream_tag(struct input_stream *is) { - float total_time = mpcdec_get_file_duration(file); + float total_time = mpcdec_get_file_duration(is); struct tag *tag; - if (total_time < 0) { - g_debug("Failed to get duration of file: %s", file); + if (total_time < 0) return NULL; - } tag = tag_new(); tag->time = total_time; @@ -363,6 +342,6 @@ static const char *const mpcdec_suffixes[] = { "mpc", NULL }; const struct decoder_plugin mpcdec_decoder_plugin = { .name = "mpcdec", .stream_decode = mpcdec_decode, - .tag_dup = mpcdec_tag_dup, + .stream_tag = mpcdec_stream_tag, .suffixes = mpcdec_suffixes, }; diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c new file mode 100644 index 000000000..7b48ebfaf --- /dev/null +++ b/src/decoder/mpg123_decoder_plugin.c @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2003-2010 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); + + /* 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_decoder_plugin.c index bdd589ccb..7e5f48318 100644 --- a/src/decoder/oggflac_plugin.c +++ b/src/decoder/oggflac_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 */ @@ -259,24 +243,20 @@ fail: /* public functions: */ static struct tag * -oggflac_tag_dup(const char *file) +oggflac_stream_tag(struct input_stream *is) { - struct input_stream input_stream; OggFLAC__SeekableStreamDecoder *decoder; struct flac_data data; + struct tag *tag; - if (!input_stream_open(&input_stream, file)) - return NULL; - if (ogg_stream_type_detect(&input_stream) != FLAC) { - input_stream_close(&input_stream); + if (ogg_stream_type_detect(is) != FLAC) return NULL; - } /* rewind the stream, because ogg_stream_type_detect() has moved it */ - input_stream_seek(&input_stream, 0, SEEK_SET); + input_stream_seek(is, 0, SEEK_SET, NULL); - flac_data_init(&data, NULL, &input_stream); + flac_data_init(&data, NULL, is); data.tag = tag_new(); @@ -284,15 +264,17 @@ 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); - input_stream_close(&input_stream); + oggflac_cleanup(decoder); - 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 +282,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 +297,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 (!data.initialized) 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.total_frames / + (float)data.audio_format.sample_rate); while (true) { OggFLAC__seekable_stream_decoder_process_single(decoder); @@ -333,11 +313,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.audio_format.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 +331,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 }; @@ -368,7 +348,7 @@ static const char *const oggflac_mime_types[] = { const struct decoder_plugin oggflac_decoder_plugin = { .name = "oggflac", .stream_decode = oggflac_decode, - .tag_dup = oggflac_tag_dup, + .stream_tag = oggflac_stream_tag, .suffixes = oggflac_suffixes, .mime_types = oggflac_mime_types }; diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx new file mode 100644 index 000000000..6fceeb30f --- /dev/null +++ b/src/decoder/sidplay_decoder_plugin.cxx @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2003-2010 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" + +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) +{ + int ret; + int channels; + + /* load the tune */ + + 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; + } + + 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 */ + + sidplay2 player; + int iret = player.load(&tune); + if (iret != 0) { + g_warning("sidplay2.load() failed: %s", player.error()); + return; + } + + /* initialize the builder */ + + ReSIDBuilder builder("ReSID"); + if (!builder) { + g_warning("failed to initialize ReSIDBuilder"); + return; + } + + builder.create(player.info().maxsids); + if (!builder) { + g_warning("ReSIDBuilder.create() failed"); + return; + } + + builder.filter(filter_setting); + if (!builder) { + g_warning("ReSIDBuilder.filter() failed"); + return; + } + + /* configure the player */ + + sid2_config_t config = player.config(); + + config.clockDefault = SID2_CLOCK_PAL; + config.clockForced = true; + config.clockSpeed = SID2_CLOCK_CORRECT; + config.frequency = 48000; + config.optimisation = SID2_DEFAULT_OPTIMISATION; + + config.precision = 16; + config.sidDefault = SID2_MOS6581; + config.sidEmulation = &builder; + config.sidModel = SID2_MODEL_CORRECT; + config.sidSamples = true; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + config.sampleFormat = SID2_LITTLE_SIGNED; +#else + config.sampleFormat = SID2_BIG_SIGNED; +#endif + if (tune.isStereo()) { + config.playback = sid2_stereo; + channels = 2; + } else { + config.playback = sid2_mono; + channels = 1; + } + + iret = player.config(config); + if (iret != 0) { + g_warning("sidplay2.config() failed: %s", player.error()); + return; + } + + /* initialize the MPD decoder */ + + struct audio_format audio_format; + audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, channels); + assert(audio_format_valid(&audio_format)); + + 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]; + size_t nbytes; + + nbytes = player.play(buffer, sizeof(buffer)); + if (nbytes == 0) + break; + + decoder_timestamp(decoder, (double)player.time() / timebase); + + cmd = decoder_data(decoder, NULL, buffer, nbytes, 0); + + 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) +{ + 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) + 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_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", + "mus", + "str", + "prg", + "P00", + NULL +}; + +extern const struct decoder_plugin sidplay_decoder_plugin; +const struct decoder_plugin sidplay_decoder_plugin = { + "sidplay", + sidplay_init, + sidplay_finish, + NULL, /* stream_decode() */ + sidplay_file_decode, + sidplay_tag_dup, + NULL, /* stream_tag() */ + sidplay_container_scan, + sidplay_suffixes, + NULL, /* mime_types */ +}; diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx deleted file mode 100644 index c62e6b4b6..000000000 --- a/src/decoder/sidplay_plugin.cxx +++ /dev/null @@ -1,163 +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. - */ - -extern "C" { -#include "../decoder_api.h" -} - -#include <glib.h> - -#include <sidplay/sidplay2.h> -#include <sidplay/builders/resid.h> - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "sidplay" - -static void -sidplay_file_decode(struct decoder *decoder, const char *path_fs) -{ - int ret; - - /* load the tune */ - - SidTune tune(path_fs, NULL, true); - if (!tune) { - g_warning("failed to load file"); - return; - } - - tune.selectSong(1); - - /* initialize the player */ - - sidplay2 player; - int iret = player.load(&tune); - if (iret != 0) { - g_warning("sidplay2.load() failed: %s", player.error()); - return; - } - - /* initialize the builder */ - - ReSIDBuilder builder("ReSID"); - if (!builder) { - g_warning("failed to initialize ReSIDBuilder"); - return; - } - - builder.create(player.info().maxsids); - if (!builder) { - g_warning("ReSIDBuilder.create() failed"); - return; - } - - builder.filter(false); - if (!builder) { - g_warning("ReSIDBuilder.filter() failed"); - return; - } - - /* configure the player */ - - sid2_config_t config = player.config(); - - config.clockDefault = SID2_CLOCK_PAL; - config.clockForced = true; - config.clockSpeed = SID2_CLOCK_CORRECT; - config.frequency = 48000; - config.optimisation = SID2_DEFAULT_OPTIMISATION; - config.playback = sid2_stereo; - config.precision = 16; - config.sidDefault = SID2_MOS6581; - config.sidEmulation = &builder; - config.sidModel = SID2_MODEL_CORRECT; - config.sidSamples = true; -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - config.sampleFormat = SID2_LITTLE_SIGNED; -#else - config.sampleFormat = SID2_BIG_SIGNED; -#endif - - iret = player.config(config); - if (iret != 0) { - g_warning("sidplay2.config() failed: %s", player.error()); - return; - } - - /* initialize the MPD decoder */ - - struct audio_format audio_format; - audio_format.sample_rate = 48000; - audio_format.bits = 16; - audio_format.channels = 2; - - decoder_initialized(decoder, &audio_format, false, -1); - - /* .. and play */ - - enum decoder_command cmd; - do { - char buffer[4096]; - size_t nbytes; - - nbytes = player.play(buffer, sizeof(buffer)); - if (nbytes == 0) - break; - - cmd = decoder_data(decoder, NULL, buffer, nbytes, - 0, 0, NULL); - } while (cmd == DECODE_COMMAND_NONE); -} - -static struct tag * -sidplay_tag_dup(const char *path_fs) -{ - SidTune tune(path_fs, NULL, true); - if (!tune) - return NULL; - - const SidTuneInfo &info = tune.getInfo(); - struct tag *tag = tag_new(); - - if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL) - tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]); - - if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]); - - return tag; -} - -static const char *const sidplay_suffixes[] = { - "sid", - NULL -}; - -extern const struct decoder_plugin sidplay_decoder_plugin; -const struct decoder_plugin sidplay_decoder_plugin = { - "sidplay", - NULL, /* init() */ - NULL, /* finish() */ - NULL, /* stream_decode() */ - sidplay_file_decode, - sidplay_tag_dup, - NULL, /* 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..af68f117d --- /dev/null +++ b/src/decoder/sndfile_decoder_plugin.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2003-2010 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); + 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_decoder_plugin.c index 7c782a779..0a3944ad6 100644 --- a/src/decoder/vorbis_plugin.c +++ b/src/decoder/vorbis_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,19 +17,19 @@ * 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> /* Macros to make Tremor's API look like libogg. Tremor always returns host-byte-order 16-bit signed data, and uses integer - milliseconds where libogg uses double seconds. + milliseconds where libogg uses double seconds. */ #define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ ov_read(VF, BUFFER, LENGTH, BITSTREAM) @@ -55,46 +55,46 @@ #define OGG_DECODE_USE_BIGENDIAN 0 #endif -typedef struct _OggCallbackData { +struct vorbis_input_stream { 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) +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data) { + struct vorbis_input_stream *vis = data; size_t ret; - OggCallbackData *data = (OggCallbackData *) vdata; - ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb); + ret = decoder_read(vis->decoder, vis->input_stream, ptr, size * nmemb); errno = 0; return ret / size; } -static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) +static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence) { - const OggCallbackData *data = (const OggCallbackData *) vdata; + struct vorbis_input_stream *vis = data; - return data->seekable && - decoder_get_command(data->decoder) != DECODE_COMMAND_STOP && - input_stream_seek(data->input_stream, offset, whence) + return vis->seekable && + (!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) && + input_stream_seek(vis->input_stream, offset, whence, NULL) ? 0 : -1; } /* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(G_GNUC_UNUSED void *vdata) +static int ogg_close_cb(G_GNUC_UNUSED void *data) { return 0; } -static long ogg_tell_cb(void *vdata) +static long ogg_tell_cb(void *data) { - const OggCallbackData *data = (const OggCallbackData *) vdata; + const struct vorbis_input_stream *vis = data; - return (long)data->input_stream->offset; + return (long)vis->input_stream->offset; } static const ov_callbacks vorbis_is_callbacks = { @@ -105,6 +105,52 @@ static const ov_callbacks vorbis_is_callbacks = { }; 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 bool +vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf, + struct decoder *decoder, struct input_stream *input_stream) +{ + vis->decoder = decoder; + vis->input_stream = input_stream; + vis->seekable = input_stream->seekable && + (input_stream->uri == NULL || + !uri_has_scheme(input_stream->uri)); + + int ret = ov_open_callbacks(vis, vf, NULL, 0, vorbis_is_callbacks); + if (ret < 0) { + if (decoder == NULL || + decoder_get_command(decoder) == DECODE_COMMAND_NONE) + g_warning("Failed to open Ogg Vorbis stream: %s", + vorbis_strerror(ret)); + return false; + } + + return true; +} + +static const char * vorbis_comment_value(const char *comment, const char *needle) { size_t len = strlen(needle); @@ -116,14 +162,13 @@ vorbis_comment_value(const char *comment, const char *needle) return NULL; } -static struct replay_gain_info * -vorbis_comments_to_replay_gain(char **comments) +static bool +vorbis_comments_to_replay_gain(struct replay_gain_info *rgi, char **comments) { - struct replay_gain_info *rgi; const char *temp; bool found = false; - rgi = replay_gain_info_new(); + replay_gain_info_init(rgi); while (*comments) { if ((temp = @@ -147,12 +192,7 @@ vorbis_comments_to_replay_gain(char **comments) comments++; } - if (!found) { - replay_gain_info_free(rgi); - rgi = NULL; - } - - return rgi; + return found; } static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; @@ -183,11 +223,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) @@ -226,42 +266,23 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is, tag_free(tag); } -static bool -oggvorbis_seekable(struct decoder *decoder) -{ - char *uri; - bool seekable; - - uri = decoder_get_uri(decoder); - if (uri == NULL) - return false; - - /* 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 */ - seekable = !uri_has_scheme(uri); - g_free(uri); - - return seekable; -} - /* public */ static void vorbis_stream_decode(struct decoder *decoder, struct input_stream *input_stream) { + GError *error = NULL; OggVorbis_File vf; - OggCallbackData data; + struct vorbis_input_stream vis; struct audio_format audio_format; + float total_time; int current_section; int prev_section = -1; long ret; char chunk[OGG_CHUNK_SIZE]; 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) @@ -269,43 +290,30 @@ 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); - - data.decoder = decoder; - data.input_stream = input_stream; - data.seekable = input_stream->seekable && oggvorbis_seekable(decoder); + input_stream_seek(input_stream, 0, SEEK_SET, NULL); - if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, - vorbis_is_callbacks)) < 0) { - const char *error; - if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) - return; + if (!vorbis_is_open(&vis, &vf, decoder, input_stream)) + 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; - } + vi = ov_info(&vf, -1); + if (vi == NULL) { + g_warning("ov_info() has failed"); + return; + } - g_warning("Error decoding Ogg Vorbis stream: %s", error); + 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; } - audio_format.bits = 16; + + total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + + decoder_initialized(decoder, &audio_format, vis.seekable, total_time); do { if (cmd == DECODE_COMMAND_SEEK) { @@ -325,83 +333,61 @@ vorbis_stream_decode(struct decoder *decoder, break; if (current_section != prev_section) { - /*printf("new song!\n"); */ - vorbis_info *vi = ov_info(&vf, -1); - 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); + char **comments; + + 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); - if (new_rgi != NULL) { - if (replay_gain_info != NULL) - replay_gain_info_free(replay_gain_info); - replay_gain_info = new_rgi; - } - } - prev_section = current_section; + struct replay_gain_info rgi; + if (vorbis_comments_to_replay_gain(&rgi, comments)) + decoder_replay_gain(decoder, &rgi); + + 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); + bitRate); } while (cmd != DECODE_COMMAND_STOP); - if (replay_gain_info) - replay_gain_info_free(replay_gain_info); - ov_clear(&vf); } static struct tag * -vorbis_tag_dup(const char *file) +vorbis_stream_tag(struct input_stream *is) { - struct tag *ret; - FILE *fp; + struct vorbis_input_stream vis; OggVorbis_File vf; - fp = fopen(file, "r"); - if (!fp) { + if (!vorbis_is_open(&vis, &vf, NULL, is)) return NULL; - } - if (ov_open(fp, &vf, NULL, 0) < 0) { - fclose(fp); - return NULL; - } - - ret = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); + struct tag *tag = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); - if (!ret) - ret = tag_new(); - ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + if (tag == NULL) + tag = tag_new(); + tag->time = (int)(ov_time_total(&vf, -1) + 0.5); ov_clear(&vf); - return ret; + return tag; } static const char *const vorbis_suffixes[] = { @@ -423,7 +409,7 @@ static const char *const vorbis_mime_types[] = { const struct decoder_plugin vorbis_decoder_plugin = { .name = "vorbis", .stream_decode = vorbis_stream_decode, - .tag_dup = vorbis_tag_dup, + .stream_tag = vorbis_stream_tag, .suffixes = vorbis_suffixes, .mime_types = vorbis_mime_types }; diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_decoder_plugin.c index 7ad3a62b0..efed98851 100644 --- a/src/decoder/wavpack_plugin.c +++ b/src/decoder/wavpack_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,38 +123,61 @@ 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. */ static void -wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, - struct replay_gain_info *replay_gain_info) +wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek) { + 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 +197,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 +204,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 +223,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,8 +232,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, decoder_data( decoder, NULL, chunk, samples_got * output_sample_size, - current_time, bitrate, - replay_gain_info + bitrate ); } } while (samples_got > 0); @@ -246,13 +256,13 @@ wavpack_tag_float(WavpackContext *wpc, const char *key, float *value_r) return true; } -static struct replay_gain_info * -wavpack_replaygain(WavpackContext *wpc) +static bool +wavpack_replaygain(struct replay_gain_info *replay_gain_info, + WavpackContext *wpc) { - struct replay_gain_info *replay_gain_info; bool found = false; - replay_gain_info = replay_gain_info_new(); + replay_gain_info_init(replay_gain_info); found |= wavpack_tag_float( wpc, "replaygain_track_gain", @@ -271,13 +281,7 @@ wavpack_replaygain(WavpackContext *wpc) &replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak ); - if (found) { - return replay_gain_info; - } - - replay_gain_info_free(replay_gain_info); - - return NULL; + return found; } /* @@ -397,13 +401,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 @@ -452,13 +456,12 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder, isp->last_byte = EOF; } -static bool -wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc, +static struct input_stream * +wavpack_open_wvc(struct decoder *decoder, const char *uri, struct wavpack_input *wpi) { - char *utf8url; + struct input_stream *is_wvc; char *wvc_url = NULL; - bool ret; char first_byte; size_t nbytes; @@ -466,20 +469,15 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc, * As we use dc->utf8url, this function will be bad for * single files. utf8url is not absolute file path :/ */ - utf8url = decoder_get_uri(decoder); - if (utf8url == NULL) { + if (uri == NULL) return false; - } - wvc_url = g_strconcat(utf8url, "c", NULL); - g_free(utf8url); - - ret = input_stream_open(is_wvc, wvc_url); + wvc_url = g_strconcat(uri, "c", NULL); + is_wvc = input_stream_open(wvc_url, NULL); g_free(wvc_url); - if (!ret) { - return false; - } + if (is_wvc == NULL) + return NULL; /* * And we try to buffer in order to get know @@ -490,13 +488,13 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc, ); if (nbytes == 0) { input_stream_close(is_wvc); - return false; + return NULL; } /* push it back */ wavpack_input_init(wpi, decoder, is_wvc); wpi->last_byte = first_byte; - return true; + return is_wvc; } /* @@ -507,14 +505,15 @@ 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; + struct input_stream *is_wvc; + int open_flags = OPEN_NORMALIZE; struct wavpack_input isp, isp_wvc; bool can_seek = is->seekable; - if (wavpack_open_wvc(decoder, &is_wvc, &isp_wvc)) { + is_wvc = wavpack_open_wvc(decoder, is->uri, &isp_wvc); + if (is_wvc != NULL) { open_flags |= OPEN_WVC; - can_seek &= is_wvc.seekable; + can_seek &= is_wvc->seekable; } if (!can_seek) { @@ -533,11 +532,11 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is) return; } - wavpack_decode(decoder, wpc, can_seek, NULL); + wavpack_decode(decoder, wpc, can_seek); WavpackCloseFile(wpc); if (open_flags & OPEN_WVC) { - input_stream_close(&is_wvc); + input_stream_close(is_wvc); } } @@ -549,11 +548,10 @@ wavpack_filedecode(struct decoder *decoder, const char *fname) { char error[ERRORLEN]; WavpackContext *wpc; - struct replay_gain_info *replay_gain_info; 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( @@ -563,13 +561,11 @@ wavpack_filedecode(struct decoder *decoder, const char *fname) return; } - replay_gain_info = wavpack_replaygain(wpc); + struct replay_gain_info replay_gain_info; + if (wavpack_replaygain(&replay_gain_info, wpc)) + decoder_replay_gain(decoder, &replay_gain_info); - wavpack_decode(decoder, wpc, true, replay_gain_info); - - if (replay_gain_info) { - replay_gain_info_free(replay_gain_info); - } + wavpack_decode(decoder, wpc, true); WavpackCloseFile(wpc); } diff --git a/src/decoder/wildmidi_plugin.c b/src/decoder/wildmidi_decoder_plugin.c index b5e9810f9..66e6c61cf 100644 --- a/src/decoder/wildmidi_plugin.c +++ b/src/decoder/wildmidi_decoder_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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; @@ -90,10 +91,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) if (len <= 0) break; - cmd = decoder_data(decoder, NULL, buffer, len, - (float)info->current_sample / - (float)WILDMIDI_SAMPLE_RATE, - 0, NULL); + cmd = decoder_data(decoder, NULL, buffer, len, 0); if (cmd == DECODE_COMMAND_SEEK) { unsigned long seek_where = WILDMIDI_SAMPLE_RATE * @@ -116,21 +114,17 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs) static struct tag * wildmidi_tag_dup(const char *path_fs) { - midi *wm; - const struct _WM_Info *info; - struct tag *tag; - - wm = WildMidi_Open(path_fs); + midi *wm = WildMidi_Open(path_fs); if (wm == NULL) return NULL; - info = WildMidi_GetInfo(wm); + const struct _WM_Info *info = WildMidi_GetInfo(wm); if (info == NULL) { WildMidi_Close(wm); return NULL; } - tag = tag_new(); + struct tag *tag = tag_new(); tag->time = info->approx_total_samples / WILDMIDI_SAMPLE_RATE; WildMidi_Close(wm); diff --git a/src/decoder_api.c b/src/decoder_api.c index c696ba101..fe34ea34a 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,10 +25,9 @@ #include "audio.h" #include "song.h" #include "buffer.h" - -#include "normalize.h" #include "pipe.h" #include "chunk.h" +#include "replay_gain_config.h" #include <glib.h> @@ -37,12 +37,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 +55,49 @@ 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; + decoder_lock(dc); + dc->state = DECODE_STATE_DECODE; + decoder_unlock(dc); - dc.state = DECODE_STATE_DECODE; - notify_signal(&pc.notify); + player_lock_signal(); - 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, + 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) +enum decoder_command decoder_get_command(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 dc->command; } -enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder) +void +decoder_command_finished(struct decoder *decoder) { - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; - return dc.command; -} + decoder_lock(dc); -void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder) -{ - assert(dc.command != DECODE_COMMAND_NONE); - assert(dc.command != DECODE_COMMAND_SEEK || - dc.seek_error || decoder->seeking); - assert(dc.pipe != NULL); + 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 +105,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; + + assert(dc->command == DECODE_COMMAND_SEEK); + assert(dc->pipe != NULL); - dc.seek_error = true; + dc->seek_error = true; decoder->seeking = false; decoder_command_finished(decoder); @@ -135,11 +149,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 +169,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); + 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; + } + if (nbytes > 0 || input_stream_eof(is)) return nbytes; @@ -167,6 +191,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 +214,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 +258,34 @@ enum decoder_command decoder_data(struct decoder *decoder, struct input_stream *is, const void *_data, size_t length, - float data_time, uint16_t bitRate, - struct replay_gain_info *replay_gain_info) + uint16_t kbit_rate) { + 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 +296,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 +318,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; } @@ -303,26 +342,26 @@ decoder_data(struct decoder *decoder, memcpy(dest, data, nbytes); - /* apply replay gain or normalization */ - - if (replay_gain_info != NULL && - 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); - /* 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->out_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 +371,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 */ @@ -363,3 +403,52 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, return cmd; } + +float +decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info) +{ + float return_db = 0; + assert(decoder != NULL); + + if (replay_gain_info != NULL) { + static unsigned serial; + if (++serial == 0) + serial = 1; + + if (REPLAY_GAIN_OFF != replay_gain_mode) { + return_db = 20.0 * log10f( + replay_gain_tuple_scale( + &replay_gain_info->tuples[replay_gain_get_real_mode()], + replay_gain_preamp, replay_gain_missing_preamp, + replay_gain_limit)); + } + + decoder->replay_gain_info = *replay_gain_info; + decoder->replay_gain_serial = serial; + + if (decoder->chunk != NULL) { + /* flush the current chunk because the new + replay gain values affect the following + samples */ + decoder_flush_chunk(decoder); + player_lock_signal(); + } + } else + decoder->replay_gain_serial = 0; + + return return_db; +} + +void +decoder_mixramp(struct decoder *decoder, float replay_gain_db, + char *mixramp_start, char *mixramp_end) +{ + assert(decoder != NULL); + struct decoder_control *dc = decoder->dc; + assert(dc != NULL); + + dc->replay_gain_db = replay_gain_db; + dc_mixramp_start(dc, mixramp_start); + dc_mixramp_end(dc, mixramp_end); +} diff --git a/src/decoder_api.h b/src/decoder_api.h index 37090d8d0..8b5f3d82b 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,87 +17,157 @@ * 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" -#include "replay_gain.h" +#include "replay_gain_info.h" #include "tag.h" #include "audio_format.h" #include "conf.h" #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. + * Determines the pending decoder command. * - * The return value is allocated on the heap, and must be freed by the - * caller. + * @param decoder the decoder object + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ -char *decoder_get_uri(struct decoder *decoder); - -enum decoder_command decoder_get_command(struct decoder * decoder); +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); + +/** + * Call this when you have received the DECODE_COMMAND_SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the week */ -void decoder_command_finished(struct decoder * decoder); +double +decoder_seek_where(struct decoder *decoder); -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, - struct replay_gain_info *replay_gain_info); +decoder_data(struct decoder *decoder, struct input_stream *is, + const void *data, size_t length, + uint16_t kbit_rate); /** * 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, const struct tag *tag); +/** + * Set replay gain values for the following chunks. + * + * @param decoder the decoder object + * @param rgi the replay_gain_info object; may be NULL to invalidate + * the previous replay gain values + * @return the replay gain adjustment used + */ +float +decoder_replay_gain(struct decoder *decoder, + const struct replay_gain_info *replay_gain_info); + +/** + * Store MixRamp tags. + * + * @param decoder the decoder object + * @param replay_gain_db the ReplayGain adjustment used for this song + * @param mixramp_start the mixramp_start tag; may be NULL to invalidate + * @param mixramp_end the mixramp_end tag; may be NULL to invalidate + */ +void +decoder_mixramp(struct decoder *decoder, float replay_gain_db, + char *mixramp_start, char *mixramp_end); + #endif diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c index b6fa90004..8f8eb8545 100644 --- a/src/decoder_buffer.c +++ b/src/decoder_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_buffer.h b/src/decoder_buffer.h index 411e3bd88..b6051e122 100644 --- a/src/decoder_buffer.h +++ b/src/decoder_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_command.h b/src/decoder_command.h index faabaea09..4a2e49f3e 100644 --- a/src/decoder_command.h +++ b/src/decoder_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/decoder_control.c b/src/decoder_control.c index 618c78e7e..a5e6e4ad3 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,115 +17,180 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "decoder_control.h" +#include "player_control.h" #include "pipe.h" #include <assert.h> -struct decoder_control dc; +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_control" -void dc_init(void) +void +dc_init(struct decoder_control *dc) { - notify_init(&dc.notify); - dc.state = DECODE_STATE_STOP; - dc.command = DECODE_COMMAND_NONE; + dc->thread = NULL; + + dc->mutex = g_mutex_new(); + dc->cond = g_cond_new(); + + dc->state = DECODE_STATE_STOP; + dc->command = DECODE_COMMAND_NONE; + + dc->replay_gain_db = 0; + dc->replay_gain_prev_db = 0; + dc->mixramp_start = NULL; + dc->mixramp_end = NULL; + dc->mixramp_prev_end = NULL; } -void dc_deinit(void) +void +dc_deinit(struct decoder_control *dc) +{ + g_cond_free(dc->cond); + g_mutex_free(dc->mutex); + g_free(dc->mixramp_start); + g_free(dc->mixramp_end); + g_free(dc->mixramp_prev_end); + dc->mixramp_start = NULL; + dc->mixramp_end = NULL; + dc->mixramp_prev_end = NULL; +} + +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, struct music_pipe *pipe) +static void +dc_command_async(struct decoder_control *dc, enum decoder_command cmd) { - assert(dc.pipe == NULL); - assert(song != NULL); - assert(pipe != NULL); - assert(music_pipe_empty(pipe)); + decoder_lock(dc); - dc.next_song = song; - dc.pipe = pipe; - dc_command(notify, DECODE_COMMAND_START); + dc->command = cmd; + decoder_signal(dc); + + decoder_unlock(dc); } void -dc_start_async(struct song *song, struct music_pipe *pipe) +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); assert(music_pipe_empty(pipe)); - dc.next_song = song; - dc.pipe = pipe; - 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(notify, DECODE_COMMAND_STOP); + if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR) + dc_command_locked(dc, 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); + + dc->quit = true; + dc_command_async(dc, DECODE_COMMAND_STOP); + + g_thread_join(dc->thread); + dc->thread = NULL; +} + +void +dc_mixramp_start(struct decoder_control *dc, char *mixramp_start) +{ + assert(dc != NULL); + + g_free(dc->mixramp_start); + dc->mixramp_start = mixramp_start; + g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL"); +} + +void +dc_mixramp_end(struct decoder_control *dc, char *mixramp_end) { - assert(dc.thread != NULL); + assert(dc != NULL); - dc.quit = true; - dc_command_async(DECODE_COMMAND_STOP); + g_free(dc->mixramp_end); + dc->mixramp_end = mixramp_end; + g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL"); +} + +void +dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end) +{ + assert(dc != NULL); - g_thread_join(dc.thread); - dc.thread = NULL; + g_free(dc->mixramp_prev_end); + dc->mixramp_prev_end = mixramp_prev_end; + g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL"); } diff --git a/src/decoder_control.h b/src/decoder_control.h index febf53335..449e974b7 100644 --- a/src/decoder_control.h +++ b/src/decoder_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,145 @@ 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; + + float replay_gain_db; + float replay_gain_prev_db; + char *mixramp_start; + char *mixramp_end; + char *mixramp_prev_end; }; -extern struct decoder_control dc; +void +dc_init(struct decoder_control *dc); -void dc_init(void); +void +dc_deinit(struct decoder_control *dc); -void dc_deinit(void); +/** + * Locks the #decoder_control object. + */ +static inline void +decoder_lock(struct decoder_control *dc) +{ + g_mutex_lock(dc->mutex); +} -static inline bool decoder_is_idle(void) +/** + * Unlocks the #decoder_control object. + */ +static inline void +decoder_unlock(struct decoder_control *dc) { - return (dc.state == DECODE_STATE_STOP || - dc.state == DECODE_STATE_ERROR) && - dc.command != DECODE_COMMAND_START; + g_mutex_unlock(dc->mutex); } -static inline bool decoder_is_starting(void) +/** + * 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) { - return dc.command == DECODE_COMMAND_START || - dc.state == DECODE_STATE_START; + g_cond_wait(dc->cond, dc->mutex); } -static inline bool decoder_has_failed(void) +/** + * 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) { - assert(dc.command == DECODE_COMMAND_NONE); + 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; +} - return dc.state == DECODE_STATE_ERROR; +static inline bool +decoder_is_starting(const struct decoder_control *dc) +{ + return dc->state == DECODE_STATE_START; } -static inline struct song * -decoder_current_song(void) +static inline bool +decoder_has_failed(const struct decoder_control *dc) { - switch (dc.state) { + assert(dc->command == DECODE_COMMAND_NONE); + + return dc->state == DECODE_STATE_ERROR; +} + +static inline bool +decoder_lock_is_idle(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_is_idle(dc); + decoder_unlock(dc); + + return ret; +} + +static inline bool +decoder_lock_is_starting(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_is_starting(dc); + decoder_unlock(dc); + + return ret; +} + +static inline bool +decoder_lock_has_failed(struct decoder_control *dc) +{ + bool ret; + + decoder_lock(dc); + ret = decoder_has_failed(dc); + decoder_unlock(dc); + + return ret; +} + +static inline const struct song * +decoder_current_song(const struct decoder_control *dc) +{ + 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 +218,36 @@ decoder_current_song(void) } void -dc_command_wait(struct notify *notify); +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(struct notify *notify, struct song *song, struct music_pipe *pipe); +dc_start(struct decoder_control *dc, struct song *song, + struct music_buffer *buffer, struct music_pipe *pipe); void -dc_start_async(struct song *song, struct music_pipe *pipe); +dc_stop(struct decoder_control *dc); + +bool +dc_seek(struct decoder_control *dc, double where); void -dc_stop(struct notify *notify); +dc_quit(struct decoder_control *dc); -bool -dc_seek(struct notify *notify, double where); +void +dc_mixramp_start(struct decoder_control *dc, char *mixramp_start); + +void +dc_mixramp_end(struct decoder_control *dc, char *mixramp_end); void -dc_quit(void); +dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end); #endif diff --git a/src/decoder_internal.c b/src/decoder_internal.c index 4a56fa5f3..990d728e9 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,20 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is) return decoder->chunk; do { - decoder->chunk = music_buffer_allocate(dc.buffer); - if (decoder->chunk != NULL) + decoder->chunk = music_buffer_allocate(dc->buffer); + if (decoder->chunk != NULL) { + decoder->chunk->replay_gain_serial = + decoder->replay_gain_serial; + if (decoder->replay_gain_serial != 0) + decoder->chunk->replay_gain_info = + decoder->replay_gain_info; + 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 +107,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..9e7e2037a 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,12 +22,20 @@ #include "decoder_command.h" #include "pcm_convert.h" +#include "replay_gain_info.h" 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; /** @@ -45,6 +53,14 @@ struct decoder { /** the chunk currently being written to */ struct music_chunk *chunk; + + struct replay_gain_info replay_gain_info; + + /** + * A positive serial number for checking if replay gain info + * has changed since the last check. + */ + unsigned replay_gain_serial; }; /** diff --git a/src/decoder_list.c b/src/decoder_list.c index a42585e34..d76050023 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,20 +17,23 @@ * 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 "mpd_error.h" #include <glib.h> #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; @@ -42,11 +45,15 @@ extern const struct decoder_plugin sidplay_decoder_plugin; extern const struct decoder_plugin wildmidi_decoder_plugin; extern const struct decoder_plugin fluidsynth_decoder_plugin; extern const struct decoder_plugin ffmpeg_decoder_plugin; +extern const struct decoder_plugin gme_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 +63,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 +99,51 @@ static const struct decoder_plugin *const decoder_plugins[] = { #ifdef HAVE_FFMPEG &ffmpeg_decoder_plugin, #endif +#ifdef HAVE_GME + &gme_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 +159,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 +174,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 +184,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. * @@ -191,8 +199,8 @@ decoder_plugin_config(const char *plugin_name) const char *name = config_get_block_string(param, "plugin", NULL); if (name == NULL) - g_error("decoder configuration without 'plugin' name in line %d", - param->line); + MPD_ERROR("decoder configuration without 'plugin' name in line %d", + param->line); if (strcmp(name, plugin_name) == 0) return param; @@ -203,7 +211,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 +227,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..7041db0c9 100644 --- a/src/decoder_list.h +++ b/src/decoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..062dad364 --- /dev/null +++ b/src/decoder_plugin.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 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..d8371ddb8 100644 --- a/src/decoder_plugin.h +++ b/src/decoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -77,6 +77,13 @@ struct decoder_plugin { struct tag *(*tag_dup)(const char *path_fs); /** + * Read the tags of a stream. + * + * @return NULL if the operation has failed + */ + struct tag *(*stream_tag)(struct input_stream *is); + + /** * @brief Return a "virtual" filename for subtracks in * container formats like flac * @param const char* pathname full pathname for the file on fs @@ -147,7 +154,21 @@ static inline struct tag * decoder_plugin_tag_dup(const struct decoder_plugin *plugin, const char *path_fs) { - return plugin->tag_dup(path_fs); + return plugin->tag_dup != NULL + ? plugin->tag_dup(path_fs) + : NULL; +} + +/** + * Read the tag of a stream. + */ +static inline struct tag * +decoder_plugin_stream_tag(const struct decoder_plugin *plugin, + struct input_stream *is) +{ + return plugin->stream_tag != NULL + ? plugin->stream_tag(is) + : NULL; } /** @@ -161,4 +182,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..a1c2da2e5 --- /dev/null +++ b/src/decoder_print.c @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2010 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..520438871 --- /dev/null +++ b/src/decoder_print.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2010 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 cbb670616..1a91b6566 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 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" #include "decoder_list.h" #include "decoder_plugin.h" +#include "decoder_api.h" #include "input_stream.h" #include "player_control.h" #include "pipe.h" @@ -30,11 +32,73 @@ #include "mapper.h" #include "path.h" #include "uri.h" +#include "mpd_error.h" #include <glib.h> #include <unistd.h> +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "decoder_thread" + +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 an input_stream on success or if #DECODE_COMMAND_STOP is + * received, NULL on error + */ +static struct input_stream * +decoder_input_stream_open(struct decoder_control *dc, const char *uri) +{ + GError *error = NULL; + struct input_stream *is; + + is = input_stream_open(uri, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("%s", error->message); + g_error_free(error); + } + + return NULL; + } + + /* 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 NULL; + } + } + + return is; +} + static bool decoder_stream_decode(const struct decoder_plugin *plugin, struct decoder *decoder, @@ -47,17 +111,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); - return dc.state != DECODE_STATE_START; + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + + return decoder->dc->state != DECODE_STATE_START; } static bool @@ -70,155 +141,250 @@ 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) +/** + * Hack to allow tracking const decoder plugins in a GSList. + */ +static inline gpointer +deconst_plugin(const struct decoder_plugin *plugin) { - struct decoder decoder; - int ret; - bool close_instream = true; - struct input_stream input_stream; + union { + const struct decoder_plugin *in; + gpointer out; + } u = { .in = plugin }; + + return u.out; +} + +/** + * Try decoding a stream, using plugins matching the stream's MIME type. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is, + GSList **tried_r) +{ + assert(tried_r != NULL); + const struct decoder_plugin *plugin; + unsigned int next = 0; - close_instream = input_stream_open(&input_stream, uri); - if (!close_instream && !song_is_file(song)) { - dc.state = DECODE_STATE_ERROR; - return; + if (is->mime == NULL) + return false; + + while ((plugin = decoder_plugin_from_mime_type(is->mime, next++))) { + if (plugin->stream_decode == NULL) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); } - 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; + return false; +} - dc.state = DECODE_STATE_START; - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); +/** + * Try decoding a stream, using plugins matching the stream's URI + * suffix. + * + * @param tried_r a list of plugins which were tried + */ +static bool +decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is, + const char *uri, GSList **tried_r) +{ + assert(tried_r != NULL); - /* wait for the input stream to become ready; its metadata - will be available then */ + const char *suffix = uri_get_suffix(uri); + const struct decoder_plugin *plugin = NULL; - while (close_instream && !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) + continue; + + if (g_slist_find(*tried_r, plugin) != NULL) + /* don't try a plugin twice */ + continue; + + if (decoder_stream_decode(plugin, decoder, is)) + return true; + + *tried_r = g_slist_prepend(*tried_r, deconst_plugin(plugin)); } - if (dc.command == DECODE_COMMAND_STOP) { - if (close_instream) - input_stream_close(&input_stream); - dc.state = DECODE_STATE_STOP; - return; + return false; +} + +/** + * 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; + + decoder_unlock(dc); + + input_stream = decoder_input_stream_open(dc, uri); + if (input_stream == NULL) { + decoder_lock(dc); + return false; } - pcm_convert_init(&decoder.conv_state); + decoder_lock(dc); - ret = false; - if (!song_is_file(song)) { - unsigned int next = 0; + GSList *tried = NULL; + 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, &tried) || + /* if that fails, try suffix matching the URL: */ + decoder_run_stream_suffix(decoder, input_stream, uri, + &tried) || + /* fallback to mp3: this is needed for bastard streams + that don't have a suffix or set the mimeType */ + (tried == NULL && + decoder_run_stream_fallback(decoder, input_stream)); + + g_slist_free(tried); + + 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; + + input_stream = decoder_input_stream_open(dc, path_fs); + if (input_stream == NULL) 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) { - if (close_instream) { - 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); @@ -228,66 +394,91 @@ 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: + g_debug("clearing mixramp tags"); + dc_mixramp_start(dc, NULL); + dc_mixramp_prev_end(dc, dc->mixramp_end); + dc->mixramp_end = NULL; /* Don't free, it's copied above. */ + dc->replay_gain_prev_db = dc->replay_gain_db; + dc->replay_gain_db = 0; + + /* fall through */ + case DECODE_COMMAND_SEEK: - decoder_run(); + decoder_run(dc); - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + dc->command = DECODE_COMMAND_NONE; + + 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) - g_error("Failed to spawn decoder task: %s", e->message); + dc->thread = g_thread_create(decoder_task, dc, true, &e); + if (dc->thread == NULL) + MPD_ERROR("Failed to spawn decoder task: %s", e->message); } diff --git a/src/decoder_thread.h b/src/decoder_thread.h index 50ed7116e..28042d7f8 100644 --- a/src/decoder_thread.h +++ b/src/decoder_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..fa15d41b1 100644 --- a/src/directory.c +++ b/src/directory.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -41,12 +42,16 @@ directory_new(const char *path, struct directory *parent) directory->parent = parent; memcpy(directory->path, path, pathlen + 1); + playlist_vector_init(&directory->playlists); + return directory; } void directory_free(struct directory *directory) { + playlist_vector_deinit(&directory->playlists); + for (unsigned i = 0; i < directory->songs.nr; ++i) song_free(directory->songs.base[i]); diff --git a/src/directory.h b/src/directory.h index 8207bd3a2..dde741a71 100644 --- a/src/directory.h +++ b/src/directory.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,20 +20,25 @@ #ifndef MPD_DIRECTORY_H #define MPD_DIRECTORY_H +#include "check.h" #include "dirvec.h" #include "songvec.h" +#include "playlist_vector.h" #include <stdbool.h> #include <sys/types.h> #define DIRECTORY_DIR "directory: " -#define DEVICE_INARCHIVE (unsigned)(-1) -#define DEVICE_CONTAINER (unsigned)(-2) +#define DEVICE_INARCHIVE (dev_t)(-1) +#define DEVICE_CONTAINER (dev_t)(-2) struct directory { struct dirvec children; struct songvec songs; + + struct playlist_vector playlists; + struct directory *parent; time_t mtime; ino_t inode; diff --git a/src/directory_print.c b/src/directory_print.c index 1c9f23d69..74ff26cb3 100644 --- a/src/directory_print.c +++ b/src/directory_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "directory_print.h" #include "directory.h" #include "client.h" #include "song_print.h" +#include "mapper.h" +#include "decoder_list.h" +#include "path.h" +#include "uri.h" +#include "input_stream.h" + +#include <sys/types.h> +#include <dirent.h> static void dirvec_print(struct client *client, const struct dirvec *dv) @@ -32,9 +41,34 @@ dirvec_print(struct client *client, const struct dirvec *dv) directory_get_path(dv->base[i])); } +static void +print_playlist_in_directory(struct client *client, + const struct directory *directory, + const char *name_utf8) +{ + if (directory_is_root(directory)) + client_printf(client, "playlist: %s\n", name_utf8); + else + client_printf(client, "playlist: %s/%s\n", + directory_get_path(directory), name_utf8); +} + +/** + * Print a list of playlists in the specified directory. + */ +static void +directory_print_playlists(struct client *client, + const struct directory *directory) +{ + for (const struct playlist_metadata *pm = directory->playlists.head; + pm != NULL; pm = pm->next) + print_playlist_in_directory(client, directory, pm->name); +} + void directory_print(struct client *client, const struct directory *directory) { dirvec_print(client, &directory->children); songvec_print(client, &directory->songs); + directory_print_playlists(client, directory); } diff --git a/src/directory_print.h b/src/directory_print.h index 7c0110502..0933f5a97 100644 --- a/src/directory_print.h +++ b/src/directory_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/directory_save.c b/src/directory_save.c index 132508447..55896c289 100644 --- a/src/directory_save.c +++ b/src/directory_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 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 "playlist_database.h" #include <assert.h> #include <string.h> @@ -39,109 +41,152 @@ 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; + playlist_vector_save(fp, &directory->playlists); + + 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); + 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; + } + + 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; - } - g_strchomp(buffer); - name = &(buffer[strlen(DIRECTORY_BEGIN)]); - if (!g_str_has_prefix(name, directory->path) != 0) { + 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, - "Wrong path in database: '%s' in '%s'", - name, directory->path); - return false; + "Duplicate song '%s'", name); + return NULL; } - subdir = directory_get_child(directory, name); - if (subdir != NULL) { - assert(subdir->parent == directory); - } else { - subdir = directory_new(name, directory); - dirvec_add(&directory->children, subdir); - } + song = song_load(fp, directory, name, + buffer, error); + if (song == NULL) + return false; - success = directory_load(fp, subdir, error); - if (!success) + songvec_add(&directory->songs, song); + } else if (g_str_has_prefix(line, PLAYLIST_META_BEGIN)) { + /* duplicate the name, because + playlist_metadata_load() will overwrite the + buffer */ + char *name = g_strdup(line + sizeof(PLAYLIST_META_BEGIN) - 1); + + if (!playlist_metadata_load(fp, &directory->playlists, + name, buffer, error)) { + g_free(name); return false; - } else if (g_str_has_prefix(buffer, SONG_BEGIN)) { - readSongInfoIntoList(fp, &directory->songs, directory); + } + + g_free(name); } 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..9193b6c41 100644 --- a/src/directory_save.h +++ b/src/directory_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..89b32a4f4 100644 --- a/src/dirvec.c +++ b/src/dirvec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/dirvec.h b/src/dirvec.h index d0898e0ca..a1a97d9f1 100644 --- a/src/dirvec.h +++ b/src/dirvec.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c new file mode 100644 index 000000000..73328fe81 --- /dev/null +++ b/src/encoder/flac_encoder.c @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2003-2010 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..a8ef72020 100644 --- a/src/encoder/lame_encoder.c +++ b/src/encoder/lame_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..bf7e61c3b --- /dev/null +++ b/src/encoder/null_encoder.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2010 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..d20af551b --- /dev/null +++ b/src/encoder/twolame_encoder.c @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2003-2010 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..9dac3564d 100644 --- a/src/encoder/vorbis_encoder.c +++ b/src/encoder/vorbis_encoder.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "encoder_api.h" #include "encoder_plugin.h" #include "tag.h" #include "audio_format.h" +#include "mpd_error.h" #include <vorbis/vorbisenc.h> @@ -211,7 +213,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; @@ -373,7 +375,7 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length) if (nbytes > length) /* XXX better error handling */ - g_error("buffer too small"); + MPD_ERROR("buffer too small"); memcpy(dest, page.header, page.header_len); memcpy(dest + page.header_len, page.body, page.body_len); @@ -381,6 +383,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 "audio/ogg"; +} + const struct encoder_plugin vorbis_encoder_plugin = { .name = "vorbis", .init = vorbis_encoder_init, @@ -391,4 +399,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..7398b45c7 --- /dev/null +++ b/src/encoder/wave_encoder.c @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2003-2010 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_api.h b/src/encoder_api.h index b1d4044ba..5df486ebd 100644 --- a/src/encoder_api.h +++ b/src/encoder_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/encoder_list.c b/src/encoder_list.c index d563b6bc8..f49ad48f7 100644 --- a/src/encoder_list.c +++ b/src/encoder_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..95f853004 100644 --- a/src/encoder_list.h +++ b/src/encoder_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..13fb231f4 100644 --- a/src/encoder_plugin.h +++ b/src/encoder_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..5b519984f 100644 --- a/src/event_pipe.c +++ b/src/event_pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 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 "mpd_error.h" #include <stdbool.h> #include <assert.h> @@ -37,6 +39,7 @@ #define G_LOG_DOMAIN "event_pipe" static int event_pipe[2]; +static GIOChannel *event_channel; static guint event_pipe_source_id; static GMutex *event_pipe_mutex; static bool pipe_events[PIPE_EVENT_MAX]; @@ -60,12 +63,15 @@ main_notify_event(G_GNUC_UNUSED GIOChannel *source, G_GNUC_UNUSED gpointer data) { char buffer[256]; - ssize_t r = read(event_pipe[0], buffer, sizeof(buffer)); - bool events[PIPE_EVENT_MAX]; - - if (r < 0 && errno != EAGAIN && errno != EINTR) - g_error("error reading from pipe: %s", strerror(errno)); + gsize bytes_read; + GError *error = NULL; + GIOStatus status = g_io_channel_read_chars(event_channel, + buffer, sizeof(buffer), + &bytes_read, &error); + if (status == G_IO_STATUS_ERROR) + MPD_ERROR("error reading from pipe: %s", error->message); + bool events[PIPE_EVENT_MAX]; g_mutex_lock(event_pipe_mutex); memcpy(events, pipe_events, sizeof(events)); memset(pipe_events, 0, sizeof(pipe_events)); @@ -84,22 +90,22 @@ 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 + MPD_ERROR("Couldn't open pipe: %s", strerror(errno)); +#ifndef G_OS_WIN32 channel = g_io_channel_unix_new(event_pipe[0]); +#else + channel = g_io_channel_win32_new_fd(event_pipe[0]); +#endif + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, false); + event_pipe_source_id = g_io_add_watch(channel, G_IO_IN, main_notify_event, NULL); - g_io_channel_unref(channel); + + event_channel = channel; event_pipe_mutex = g_mutex_new(); } @@ -109,8 +115,12 @@ void event_pipe_deinit(void) g_mutex_free(event_pipe_mutex); g_source_remove(event_pipe_source_id); + g_io_channel_unref(event_channel); +#ifndef WIN32 + /* By some strange reason this call hangs on Win32 */ close(event_pipe[0]); +#endif close(event_pipe[1]); } @@ -141,7 +151,7 @@ void event_pipe_emit(enum pipe_event event) w = write(event_pipe[1], "", 1); if (w < 0 && errno != EAGAIN && errno != EINTR) - g_error("error writing to pipe: %s", strerror(errno)); + MPD_ERROR("error writing to pipe: %s", strerror(errno)); } void event_pipe_emit_fast(enum pipe_event event) diff --git a/src/event_pipe.h b/src/event_pipe.h index ecb7ec9e8..923544bf4 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,12 @@ enum pipe_event { /** SIGHUP received: reload configuration, roll log file */ PIPE_EVENT_RELOAD, + /** a hardware mixer plugin has detected a change */ + PIPE_EVENT_MIXER, + + /** shutdown requested */ + PIPE_EVENT_SHUTDOWN, + PIPE_EVENT_MAX }; diff --git a/src/exclude.c b/src/exclude.c new file mode 100644 index 000000000..dd46b58c7 --- /dev/null +++ b/src/exclude.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2010 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..fd7cf8795 --- /dev/null +++ b/src/exclude.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2010 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..1f3004d0c --- /dev/null +++ b/src/fd_util.c @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2003-2010 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 <ws2tcpip.h> +#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; + + return 0; +#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 +dup_cloexec(int oldfd) +{ + int newfd = dup(oldfd); + if (newfd >= 0) + fd_set_nonblock(newfd); + + return newfd; +} + +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(fd, 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(fd, 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 +} + +#ifndef WIN32 + +int +socketpair_cloexec(int domain, int type, int protocol, int sv[2]) +{ + int ret; + +#ifdef SOCK_CLOEXEC + ret = socketpair(domain, type | SOCK_CLOEXEC, protocol, sv); + if (ret >= 0 || errno != EINVAL) + return ret; +#endif + + ret = socketpair(domain, type, protocol, sv); + if (ret >= 0) { + fd_set_cloexec(sv[0], true); + fd_set_cloexec(sv[1], true); + } + + 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; +} + +#ifndef WIN32 + +ssize_t +recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags) +{ +#ifdef MSG_CMSG_CLOEXEC + flags |= MSG_CMSG_CLOEXEC; +#endif + + ssize_t result = recvmsg(sockfd, msg, flags); + if (result >= 0) { + struct cmsghdr *cmsg = CMSG_FIRSTHDR(msg); + while (cmsg != NULL) { + if (cmsg->cmsg_type == SCM_RIGHTS) { + const int *fd_p = (const int *)CMSG_DATA(cmsg); + fd_set_cloexec(*fd_p, true); + } + + cmsg = CMSG_NXTHDR(msg, cmsg); + } + } + + return result; +} + +#endif + +#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..5b80df2c7 --- /dev/null +++ b/src/fd_util.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2003-2010 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> + +#ifndef WIN32 +#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4)) +#define _GNU_SOURCE +#endif + +#include <sys/types.h> +#endif + +struct sockaddr; + +/** + * Wrapper for dup(), which sets the CLOEXEC flag on the new + * descriptor. + */ +int +dup_cloexec(int oldfd); + +/** + * 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]); + +#ifndef WIN32 + +/** + * Wrapper for socketpair(), which sets the CLOEXEC flag (atomically + * if supported by the OS). + */ +int +socketpair_cloexec(int domain, int type, int protocol, int sv[2]); + +#endif + +/** + * 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); + + +#ifndef WIN32 + +struct msghdr; + +/** + * Wrapper for recvmsg(), which sets the CLOEXEC flag (atomically if + * supported by the OS). + */ +ssize_t +recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags); + +#endif + +/** + * 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..9ac7270bb 100644 --- a/src/fifo_buffer.c +++ b/src/fifo_buffer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without @@ -28,6 +28,7 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "config.h" #include "fifo_buffer.h" #include <glib.h> diff --git a/src/fifo_buffer.h b/src/fifo_buffer.h index 4af6bde3b..661dfd57e 100644 --- a/src/fifo_buffer.h +++ b/src/fifo_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * Redistribution and use in source and binary forms, with or without diff --git a/src/filter/autoconvert_filter_plugin.c b/src/filter/autoconvert_filter_plugin.c new file mode 100644 index 000000000..9e197a5f6 --- /dev/null +++ b/src/filter/autoconvert_filter_plugin.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003-2010 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..730db197d 100644 --- a/src/buffer2array.h +++ b/src/filter/autoconvert_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..06d4d0e6b --- /dev/null +++ b/src/filter/chain_filter_plugin.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2003-2010 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..42c6a9b78 --- /dev/null +++ b/src/filter/chain_filter_plugin.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2010 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..cb9e0940a --- /dev/null +++ b/src/filter/convert_filter_plugin.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2003-2010 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..ba9180e64 --- /dev/null +++ b/src/filter/convert_filter_plugin.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 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..63bbb6e4f --- /dev/null +++ b/src/filter/normalize_filter_plugin.c @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2003-2010 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..650f95bc4 --- /dev/null +++ b/src/filter/null_filter_plugin.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2010 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/replay_gain_filter_plugin.c b/src/filter/replay_gain_filter_plugin.c new file mode 100644 index 000000000..3a0af66ff --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.c @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2003-2010 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/replay_gain_filter_plugin.h" +#include "filter_plugin.h" +#include "filter_internal.h" +#include "filter_registry.h" +#include "audio_format.h" +#include "pcm_buffer.h" +#include "pcm_volume.h" +#include "replay_gain_info.h" +#include "replay_gain_config.h" +#include "mixer_control.h" + +#include <assert.h> +#include <string.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "replay_gain" + +struct replay_gain_filter { + struct filter filter; + + /** + * If set, then this hardware mixer is used for applying + * replay gain, instead of the software volume library. + */ + struct mixer *mixer; + + /** + * The base volume level for scale=1.0, between 1 and 100 + * (including). + */ + unsigned base; + + enum replay_gain_mode mode; + + struct replay_gain_info info; + + /** + * The current volume, between 0 and a value that may or may not exceed + * #PCM_VOLUME_1. + * + * If the default value of true is used for replaygain_limit, the + * application of the volume to the signal will never cause clipping. + * + * On the other hand, if the user has set replaygain_limit to false, + * the chance of clipping is explicitly preferred if that's required to + * maintain a consistent audio level. Whether clipping will actually + * occur depends on what value the user is using for replaygain_preamp. + */ + unsigned volume; + + struct audio_format audio_format; + + struct pcm_buffer buffer; +}; + +static inline GQuark +replay_gain_quark(void) +{ + return g_quark_from_static_string("replay_gain"); +} + +/** + * Recalculates the new volume after a property was changed. + */ +static void +replay_gain_filter_update(struct replay_gain_filter *filter) +{ + if (filter->mode != REPLAY_GAIN_OFF) { + float scale = replay_gain_tuple_scale(&filter->info.tuples[filter->mode], + replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit); + g_debug("scale=%f\n", (double)scale); + + filter->volume = pcm_float_to_volume(scale); + } else + filter->volume = PCM_VOLUME_1; + + if (filter->mixer != NULL) { + /* update the hardware mixer volume */ + + unsigned volume = (filter->volume * filter->base) / PCM_VOLUME_1; + if (volume > 100) + volume = 100; + + GError *error = NULL; + if (!mixer_set_volume(filter->mixer, volume, &error)) { + g_warning("Failed to update hardware mixer: %s", + error->message); + g_error_free(error); + } + } +} + +static struct filter * +replay_gain_filter_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = g_new(struct replay_gain_filter, 1); + + filter_init(&filter->filter, &replay_gain_filter_plugin); + filter->mixer = NULL; + + filter->mode = replay_gain_get_real_mode(); + replay_gain_info_init(&filter->info); + filter->volume = PCM_VOLUME_1; + + return &filter->filter; +} + +static void +replay_gain_filter_finish(struct filter *filter) +{ + g_free(filter); +} + +static const struct audio_format * +replay_gain_filter_open(struct filter *_filter, + struct audio_format *audio_format, + G_GNUC_UNUSED GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + audio_format->reverse_endian = false; + + filter->audio_format = *audio_format; + pcm_buffer_init(&filter->buffer); + + return &filter->audio_format; +} + +static void +replay_gain_filter_close(struct filter *_filter) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + pcm_buffer_deinit(&filter->buffer); +} + +static const void * +replay_gain_filter_filter(struct filter *_filter, + const void *src, size_t src_size, + size_t *dest_size_r, GError **error_r) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + bool success; + void *dest; + enum replay_gain_mode rg_mode; + + /* check if the mode has been changed since the last call */ + rg_mode = replay_gain_get_real_mode(); + + if (filter->mode != rg_mode) { + g_debug("replay gain mode has changed %d->%d\n", filter->mode, rg_mode); + filter->mode = rg_mode; + replay_gain_filter_update(filter); + } + + *dest_size_r = src_size; + + if (filter->volume == PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + dest = pcm_buffer_get(&filter->buffer, 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, replay_gain_quark(), 0, + "pcm_volume() has failed"); + return NULL; + } + + return dest; +} + +const struct filter_plugin replay_gain_filter_plugin = { + .name = "replay_gain", + .init = replay_gain_filter_init, + .finish = replay_gain_filter_finish, + .open = replay_gain_filter_open, + .close = replay_gain_filter_close, + .filter = replay_gain_filter_filter, +}; + +void +replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, + unsigned base) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + assert(mixer == NULL || (base > 0 && base <= 100)); + + filter->mixer = mixer; + filter->base = base; + + replay_gain_filter_update(filter); +} + +void +replay_gain_filter_set_info(struct filter *_filter, + const struct replay_gain_info *info) +{ + struct replay_gain_filter *filter = + (struct replay_gain_filter *)_filter; + + if (info != NULL) { + filter->info = *info; + replay_gain_info_complete(&filter->info); + } else + replay_gain_info_init(&filter->info); + + replay_gain_filter_update(filter); +} diff --git a/src/filter/replay_gain_filter_plugin.h b/src/filter/replay_gain_filter_plugin.h new file mode 100644 index 000000000..348b4f50c --- /dev/null +++ b/src/filter/replay_gain_filter_plugin.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2010 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 REPLAY_GAIN_FILTER_PLUGIN_H +#define REPLAY_GAIN_FILTER_PLUGIN_H + +#include "replay_gain_info.h" + +struct filter; +struct mixer; + +/** + * Enables or disables the hardware mixer for applying replay gain. + * + * @param mixer the hardware mixer, or NULL to fall back to software + * volume + * @param base the base volume level for scale=1.0, between 1 and 100 + * (including). + */ +void +replay_gain_filter_set_mixer(struct filter *_filter, struct mixer *mixer, + unsigned base); + +/** + * Sets a new #replay_gain_info at the beginning of a new song. + * + * @param info the new #replay_gain_info value, or NULL if no replay + * gain data is available for the current song + */ +void +replay_gain_filter_set_info(struct filter *filter, + const struct replay_gain_info *info); + +#endif diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c new file mode 100644 index 000000000..6b9aa2a2f --- /dev/null +++ b/src/filter/route_filter_plugin.c @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2003-2010 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 || + (unsigned)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..42311ca5e --- /dev/null +++ b/src/filter/volume_filter_plugin.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2010 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; + + *dest_size_r = src_size; + + if (filter->volume >= PCM_VOLUME_1) + /* optimized special case: 100% volume = no-op */ + return src; + + dest = pcm_buffer_get(&filter->buffer, 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..ad3b2c6f1 100644 --- a/src/normalize.h +++ b/src/filter/volume_filter_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..90de199b7 --- /dev/null +++ b/src/filter_config.c @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2003-2010 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..9ed4d204b --- /dev/null +++ b/src/filter_config.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 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..8dd6da491 --- /dev/null +++ b/src/filter_internal.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2010 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..492d703ac --- /dev/null +++ b/src/filter_plugin.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2003-2010 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..ac6b34522 --- /dev/null +++ b/src/filter_plugin.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2010 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..150043cc5 --- /dev/null +++ b/src/filter_registry.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2010 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, + &replay_gain_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..551a7afa1 --- /dev/null +++ b/src/filter_registry.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 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; +extern const struct filter_plugin replay_gain_filter_plugin; + +const struct filter_plugin * +filter_plugin_by_name(const char *name); + +#endif @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/glib_compat.h b/src/glib_compat.h new file mode 100644 index 000000000..4d0e7040d --- /dev/null +++ b/src/glib_compat.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2003-2010 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 */ + +#if !GLIB_CHECK_VERSION(2,16,0) + +#include <string.h> + +static inline 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 + +#endif diff --git a/src/icy_metadata.c b/src/icy_metadata.c index 69aa89092..6a79121cf 100644 --- a/src/icy_metadata.c +++ b/src/icy_metadata.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_metadata.h b/src/icy_metadata.h index f8eac4e91..4a51b4cf0 100644 --- a/src/icy_metadata.h +++ b/src/icy_metadata.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/icy_server.c b/src/icy_server.c index 50b10c6ca..62a2c67af 100644 --- a/src/icy_server.c +++ b/src/icy_server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/icy_server.h b/src/icy_server.h index b48014c29..3ce4ab635 100644 --- a/src/icy_server.h +++ b/src/icy_server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/idle.c b/src/idle.c index 11b57376d..eccb62322 100644 --- a/src/idle.c +++ b/src/idle.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..7caeb4a8c 100644 --- a/src/idle.h +++ b/src/idle.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..5391a1715 --- /dev/null +++ b/src/inotify_queue.c @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2003-2010 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..2e43d2f25 --- /dev/null +++ b/src/inotify_queue.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2003-2010 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..3a986cbad --- /dev/null +++ b/src/inotify_source.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2003-2010 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 "mpd_error.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) + MPD_ERROR("buffer full"); + + nbytes = read(source->fd, dest, length); + if (nbytes < 0) + MPD_ERROR("failed to read from inotify: %s", + g_strerror(errno)); + if (nbytes == 0) + MPD_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..e78b92c0f --- /dev/null +++ b/src/inotify_source.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2010 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..8d9657961 --- /dev/null +++ b/src/inotify_update.c @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2003-2010 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 unsigned inotify_max_depth; +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); + + if (directory->parent == NULL) { + g_warning("music directory was removed - " + "cannot continue to watch it"); + return; + } + + 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, unsigned depth) +{ + GError *error = NULL; + DIR *dir; + struct dirent *ent; + + assert(directory != NULL); + assert(depth <= inotify_max_depth); + assert(path_fs != NULL); + + ++depth; + + if (depth > inotify_max_depth) + return; + + 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); + ret = stat(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, depth); + g_free(child_path_fs); + } + + closedir(dir); +} + +G_GNUC_PURE +static unsigned +watch_directory_depth(const struct watch_directory *d) +{ + assert(d != NULL); + + unsigned depth = 0; + while ((d = d->parent) != NULL) + ++depth; + + return depth; +} + +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, + watch_directory_depth(directory)); + g_free(path_fs); + } + + if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0 || + /* at the maximum depth, we watch out for newly created + directories */ + (watch_directory_depth(directory) == inotify_max_depth && + (mask & (IN_CREATE|IN_ISDIR)) == (IN_CREATE|IN_ISDIR))) { + /* a file was changed, or a directory 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(unsigned max_depth) +{ + 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_max_depth = max_depth; + + 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, 0); + + 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..92b4e0cc6 --- /dev/null +++ b/src/inotify_update.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 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(unsigned max_depth); + +void +mpd_inotify_finish(void); + +#else /* !HAVE_INOTIFY_INIT */ + +static inline void +mpd_inotify_init(G_GNUC_UNUSED unsigned max_depth) +{ +} + +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..97e4836ff 100644 --- a/src/input/archive_input_plugin.c +++ b/src/input/archive_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -32,23 +33,23 @@ * parent_stream so tar plugin fetches file data from gzip * plugin and gzip fetches file from disk */ -static bool -input_archive_open(struct input_stream *is, const char *pathname) +static struct input_stream * +input_archive_open(const char *pathname, GError **error_r) { const struct archive_plugin *arplug; struct archive_file *file; char *archive, *filename, *suffix, *pname; - bool opened; + struct input_stream *is; - if (pathname[0] != '/') - return false; + if (!g_path_is_absolute(pathname)) + return NULL; pname = g_strdup(pathname); // archive_lookup will modify pname when true is returned if (!archive_lookup(pname, &archive, &filename, &suffix)) { g_debug("not an archive, lookup %s failed\n", pname); g_free(pname); - return false; + return NULL; } //check which archive plugin to use (by ext) @@ -56,22 +57,19 @@ input_archive_open(struct input_stream *is, const char *pathname) if (!arplug) { g_warning("can't handle archive %s\n",archive); g_free(pname); - return false; + return NULL; } - file = arplug->open(archive); + file = archive_file_open(arplug, archive, error_r); + if (file == NULL) + return NULL; //setup fileops - opened = arplug->open_stream(file, is, filename); - - if (!opened) { - g_warning("open inarchive file %s failed\n\n",filename); - arplug->close(file); - } else { - is->ready = true; - } + is = archive_file_open_stream(file, filename, error_r); + archive_file_close(file); g_free(pname); - return opened; + + return is; } const struct input_plugin input_plugin_archive = { diff --git a/src/input/archive_input_plugin.h b/src/input/archive_input_plugin.h index 482392a01..20568cfbe 100644 --- a/src/input/archive_input_plugin.h +++ b/src/input/archive_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c index 3893aef1c..ae645bddf 100644 --- a/src/input/curl_input_plugin.c +++ b/src/input/curl_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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> @@ -56,6 +57,8 @@ struct buffer { }; struct input_curl { + struct input_stream base; + /* some buffers which were passed to libcurl, which we have too free */ char *url, *range; @@ -96,8 +99,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 +154,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. @@ -176,10 +181,8 @@ input_curl_easy_free(struct input_curl *c) * Frees this stream (but not the input_stream struct itself). */ static void -input_curl_free(struct input_stream *is) +input_curl_free(struct input_curl *c) { - struct input_curl *c = is->data; - if (c->tag != NULL) tag_free(c->tag); g_free(c->meta_name); @@ -192,13 +195,14 @@ input_curl_free(struct input_stream *is) g_queue_free(c->buffers); g_free(c->url); + input_stream_deinit(&c->base); g_free(c); } static struct tag * input_curl_tag(struct input_stream *is) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; struct tag *tag = c->tag; c->tag = NULL; @@ -206,9 +210,8 @@ 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_curl *c, GError **error_r) { - struct input_curl *c = is->data; CURLMsg *msg; int msgs_in_queue; @@ -216,11 +219,12 @@ input_curl_multi_info_read(struct input_stream *is) &msgs_in_queue)) != NULL) { if (msg->msg == CURLMSG_DONE) { c->eof = true; - is->ready = true; + c->base.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; @@ -254,8 +258,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; } @@ -279,15 +284,17 @@ 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; + struct input_curl *c = (struct input_curl *)is; CURLMcode mcode = CURLM_CALL_MULTI_PERFORM; while (!c->eof && g_queue_is_empty(c->buffers)) { @@ -297,7 +304,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; @@ -305,14 +312,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(c, error_r); if (!bret) return false; } @@ -404,16 +412,17 @@ 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; + struct input_curl *c = (struct input_curl *)is; bool success; size_t nbytes = 0; char *dest = ptr; @@ -421,7 +430,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; @@ -439,7 +448,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; } @@ -447,21 +456,23 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size) static void input_curl_close(struct input_stream *is) { - input_curl_free(is); + struct input_curl *c = (struct input_curl *)is; + + input_curl_free(c); } static bool input_curl_eof(G_GNUC_UNUSED struct input_stream *is) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; return c->eof && g_queue_is_empty(c->buffers); } 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; + struct input_curl *c = (struct input_curl *)is; CURLMcode mcode; int running_handles; bool ret; @@ -472,7 +483,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); @@ -480,14 +492,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(c, error_r); if (!ret) return -1; @@ -498,8 +511,7 @@ input_curl_buffer(struct input_stream *is) static size_t input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) { - struct input_stream *is = stream; - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)stream; const char *header = ptr, *end, *value; char name[64]; @@ -528,7 +540,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) if (g_ascii_strcasecmp(name, "accept-ranges") == 0) { /* a stream with icy-metadata is not seekable */ if (!icy_defined(&c->icy_metadata)) - is->seekable = true; + c->base.seekable = true; } else if (g_ascii_strcasecmp(name, "content-length") == 0) { char buffer[64]; @@ -538,10 +550,10 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) memcpy(buffer, value, end - value); buffer[end - value] = 0; - is->size = is->offset + g_ascii_strtoull(buffer, NULL, 10); + c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10); } else if (g_ascii_strcasecmp(name, "content-type") == 0) { - g_free(is->mime); - is->mime = g_strndup(value, end - value); + g_free(c->base.mime); + c->base.mime = g_strndup(value, end - value); } else if (g_ascii_strcasecmp(name, "icy-name") == 0 || g_ascii_strcasecmp(name, "ice-name") == 0 || g_ascii_strcasecmp(name, "x-audiocast-name") == 0) { @@ -552,7 +564,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; @@ -572,7 +584,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) /* a stream with icy-metadata is not seekable */ - is->seekable = false; + c->base.seekable = false; } } @@ -583,8 +595,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream) static size_t input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) { - struct input_stream *is = stream; - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)stream; struct buffer *buffer; size *= nmemb; @@ -598,15 +609,14 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream) g_queue_push_tail(c->buffers, buffer); c->buffered = true; - is->ready = true; + c->base.ready = true; return size; } static bool -input_curl_easy_init(struct input_stream *is) +input_curl_easy_init(struct input_curl *c, GError **error_r) { - struct input_curl *c = is->data; CURLcode code; CURLMcode mcode; @@ -614,22 +624,27 @@ 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); curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, input_curl_headerfunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is); + curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, input_curl_writefunction); - curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, is); + curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); @@ -650,8 +665,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, @@ -664,9 +683,9 @@ input_curl_easy_init(struct input_stream *is) void input_curl_reinit(struct input_stream *is) { - struct input_curl *c = is->data; + struct input_curl *c = (struct input_curl *)is; - assert(is->plugin == &input_plugin_curl); + assert(c->base.plugin == &input_plugin_curl); assert(c->easy != NULL); curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, is); @@ -674,7 +693,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; @@ -684,8 +703,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; } @@ -693,9 +713,10 @@ 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; + struct input_curl *c = (struct input_curl *)is; bool ret; assert(is->ready); @@ -741,7 +762,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); @@ -767,7 +788,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(c, error_r); if (!ret) return false; @@ -778,59 +799,58 @@ 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(c, error_r); } -static bool -input_curl_open(struct input_stream *is, const char *url) +static struct input_stream * +input_curl_open(const char *url, GError **error_r) { struct input_curl *c; bool ret; if (strncmp(url, "http://", 7) != 0) - return false; + return NULL; c = g_new0(struct input_curl, 1); + input_stream_init(&c->base, &input_plugin_curl, url); + c->url = g_strdup(url); c->buffers = g_queue_new(); - is->plugin = &input_plugin_curl; - is->data = c; - c->multi = curl_multi_init(); if (c->multi == NULL) { - g_warning("curl_multi_init() failed\n"); - - input_curl_free(is); - return false; + g_set_error(error_r, curl_quark(), 0, + "curl_multi_init() failed"); + input_curl_free(c); + return NULL; } icy_clear(&c->icy_metadata); c->tag = NULL; - ret = input_curl_easy_init(is); + ret = input_curl_easy_init(c, error_r); if (!ret) { - input_curl_free(is); - return false; + input_curl_free(c); + return NULL; } - ret = input_curl_send_request(c); + ret = input_curl_send_request(c, error_r); if (!ret) { - input_curl_free(is); - return false; + input_curl_free(c); + return NULL; } - ret = input_curl_multi_info_read(is); + ret = input_curl_multi_info_read(c, error_r); if (!ret) { - input_curl_free(is); - return false; + input_curl_free(c); + return NULL; } - return true; + return &c->base; } const struct input_plugin input_plugin_curl = { diff --git a/src/input/curl_input_plugin.h b/src/input/curl_input_plugin.h index 63ac0dc23..be7db4e26 100644 --- a/src/input/curl_input_plugin.h +++ b/src/input/curl_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/ffmpeg_input_plugin.c b/src/input/ffmpeg_input_plugin.c new file mode 100644 index 000000000..0a6be29bc --- /dev/null +++ b/src/input/ffmpeg_input_plugin.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2003-2010 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/ffmpeg_input_plugin.h" +#include "input_plugin.h" + +#ifdef OLD_FFMPEG_INCLUDES +#include <avio.h> +#include <avformat.h> +#else +#include <libavformat/avio.h> +#include <libavformat/avformat.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "input_ffmpeg" + +struct input_ffmpeg { + struct input_stream base; + + URLContext *h; + + bool eof; +}; + +static inline GQuark +ffmpeg_quark(void) +{ + return g_quark_from_static_string("ffmpeg"); +} + +static bool +input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + av_register_all(); + +#if LIBAVFORMAT_VERSION_MAJOR >= 52 + /* disable this plugin if there's no registered protocol */ + if (av_protocol_next(NULL) == NULL) { + g_set_error(error_r, ffmpeg_quark(), 0, + "No protocol"); + return false; + } +#endif + + return true; +} + +static struct input_stream * +input_ffmpeg_open(const char *uri, GError **error_r) +{ + struct input_ffmpeg *i; + + if (!g_str_has_prefix(uri, "gopher://") && + !g_str_has_prefix(uri, "rtp://") && + !g_str_has_prefix(uri, "rtsp://") && + !g_str_has_prefix(uri, "rtmp://") && + !g_str_has_prefix(uri, "rtmpt://") && + !g_str_has_prefix(uri, "rtmps://")) + return NULL; + + i = g_new(struct input_ffmpeg, 1); + input_stream_init(&i->base, &input_plugin_ffmpeg, uri); + + int ret = url_open(&i->h, uri, URL_RDONLY); + if (ret != 0) { + g_free(i); + g_set_error(error_r, ffmpeg_quark(), ret, + "libavformat failed to open the URI"); + return NULL; + } + + i->eof = false; + + i->base.ready = true; + i->base.seekable = !i->h->is_streamed; + i->base.size = url_filesize(i->h); + + /* hack to make MPD select the "ffmpeg" decoder plugin - since + avio.h doesn't tell us the MIME type of the resource, we + can't select a decoder plugin, but the "ffmpeg" plugin is + quite good at auto-detection */ + i->base.mime = g_strdup("audio/x-mpd-ffmpeg"); + + return &i->base; +} + +static size_t +input_ffmpeg_read(struct input_stream *is, void *ptr, size_t size, + GError **error_r) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + + int ret = url_read(i->h, ptr, size); + if (ret <= 0) { + if (ret < 0) + g_set_error(error_r, ffmpeg_quark(), 0, + "url_read() failed"); + + i->eof = true; + return false; + } + + is->offset += ret; + return (size_t)ret; +} + +static void +input_ffmpeg_close(struct input_stream *is) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + + url_close(i->h); + input_stream_deinit(&i->base); + g_free(i); +} + +static bool +input_ffmpeg_eof(struct input_stream *is) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + + return i->eof; +} + +static bool +input_ffmpeg_seek(struct input_stream *is, goffset offset, int whence, + G_GNUC_UNUSED GError **error_r) +{ + struct input_ffmpeg *i = (struct input_ffmpeg *)is; + int64_t ret = url_seek(i->h, offset, whence); + + if (ret >= 0) { + i->eof = false; + return true; + } else { + g_set_error(error_r, ffmpeg_quark(), 0, "url_seek() failed"); + return false; + } +} + +const struct input_plugin input_plugin_ffmpeg = { + .name = "ffmpeg", + .init = input_ffmpeg_init, + .open = input_ffmpeg_open, + .close = input_ffmpeg_close, + .read = input_ffmpeg_read, + .eof = input_ffmpeg_eof, + .seek = input_ffmpeg_seek, +}; diff --git a/src/input/ffmpeg_input_plugin.h b/src/input/ffmpeg_input_plugin.h new file mode 100644 index 000000000..ff87064be --- /dev/null +++ b/src/input/ffmpeg_input_plugin.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2010 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_FFMPEG_INPUT_PLUGIN_H +#define MPD_FFMPEG_INPUT_PLUGIN_H + +/** + * An input plugin based on libavformat's "avio" library. + */ +extern const struct input_plugin input_plugin_ffmpeg; + +#endif diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c index bda1777ac..3646c656e 100644 --- a/src/input/file_input_plugin.c +++ b/src/input/file_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 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 "open.h" #include <sys/stat.h> -#include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> @@ -30,60 +32,79 @@ #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "input_file" -static bool -input_file_open(struct input_stream *is, const char *filename) +struct file_input_stream { + struct input_stream base; + + int fd; +}; + +static inline GQuark +file_quark(void) +{ + return g_quark_from_static_string("file"); +} + +static struct input_stream * +input_file_open(const char *filename, GError **error_r) { int fd, ret; struct stat st; + struct file_input_stream *fis; - if (filename[0] != '/') + if (!g_path_is_absolute(filename)) return false; - fd = open(filename, O_RDONLY); + fd = open_cloexec(filename, O_RDONLY|O_BINARY, 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; } - is->seekable = true; - 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; } - is->size = st.st_size; - #ifdef POSIX_FADV_SEQUENTIAL - posix_fadvise(fd, (off_t)0, is->size, POSIX_FADV_SEQUENTIAL); + posix_fadvise(fd, (off_t)0, st.st_size, POSIX_FADV_SEQUENTIAL); #endif - is->plugin = &input_plugin_file; - is->data = GINT_TO_POINTER(fd); - is->ready = true; + fis = g_new(struct file_input_stream, 1); + input_stream_init(&fis->base, &input_plugin_file, filename); - return true; + fis->base.size = st.st_size; + fis->base.seekable = true; + fis->base.ready = true; + + fis->fd = fd; + + return &fis->base; } 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); + struct file_input_stream *fis = (struct file_input_stream *)is; - offset = lseek(fd, offset, whence); + offset = (goffset)lseek(fis->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 +113,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); + struct file_input_stream *fis = (struct file_input_stream *)is; ssize_t nbytes; - nbytes = read(fd, ptr, size); + nbytes = read(fis->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; } @@ -112,9 +133,11 @@ input_file_read(struct input_stream *is, void *ptr, size_t size) static void input_file_close(struct input_stream *is) { - int fd = GPOINTER_TO_INT(is->data); + struct file_input_stream *fis = (struct file_input_stream *)is; - close(fd); + close(fis->fd); + input_stream_deinit(&fis->base); + g_free(fis); } static bool diff --git a/src/input/file_input_plugin.h b/src/input/file_input_plugin.h index d7610f5d7..40340e8bd 100644 --- a/src/input/file_input_plugin.h +++ b/src/input/file_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify 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 25e3129d9..834d111b8 100644 --- a/src/input/mms_input_plugin.c +++ b/src/input/mms_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -30,13 +31,21 @@ #define G_LOG_DOMAIN "input_mms" struct input_mms { + struct input_stream base; + mmsx_t *mms; bool eof; }; -static bool -input_mms_open(struct input_stream *is, const char *url) +static inline GQuark +mms_quark(void) +{ + return g_quark_from_static_string("mms"); +} + +static struct input_stream * +input_mms_open(const char *url, GError **error_r) { struct input_mms *m; @@ -44,39 +53,42 @@ input_mms_open(struct input_stream *is, const char *url) !g_str_has_prefix(url, "mmsh://") && !g_str_has_prefix(url, "mmst://") && !g_str_has_prefix(url, "mmsu://")) - return false; + return NULL; m = g_new(struct input_mms, 1); + input_stream_init(&m->base, &input_plugin_mms, url); + m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024); if (m->mms == NULL) { g_free(m); - g_warning("mmsx_connect() failed"); - return false; + g_set_error(error_r, mms_quark(), 0, "mmsx_connect() failed"); + return NULL; } m->eof = false; /* XX is this correct? at least this selects the ffmpeg decoder, which seems to work fine*/ - is->mime = g_strdup("audio/x-ms-wma"); + m->base.mime = g_strdup("audio/x-ms-wma"); + + m->base.ready = true; - is->plugin = &input_plugin_mms; - is->data = m; - is->ready = true; - return true; + return &m->base; } 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; + struct input_mms *m = (struct input_mms *)is; int ret; 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; @@ -91,29 +103,25 @@ input_mms_read(struct input_stream *is, void *ptr, size_t size) static void input_mms_close(struct input_stream *is) { - struct input_mms *m = is->data; + struct input_mms *m = (struct input_mms *)is; mmsx_close(m->mms); + input_stream_deinit(&m->base); g_free(m); } static bool input_mms_eof(struct input_stream *is) { - struct input_mms *m = is->data; + struct input_mms *m = (struct input_mms *)is; return m->eof; } -static int -input_mms_buffer(G_GNUC_UNUSED struct input_stream *is) -{ - 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; } @@ -122,7 +130,6 @@ const struct input_plugin input_plugin_mms = { .name = "mms", .open = input_mms_open, .close = input_mms_close, - .buffer = input_mms_buffer, .read = input_mms_read, .eof = input_mms_eof, .seek = input_mms_seek, diff --git a/src/input/mms_input_plugin.h b/src/input/mms_input_plugin.h index 3417278c2..2e10cfbb9 100644 --- a/src/input/mms_input_plugin.h +++ b/src/input/mms_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/input/rewind_input_plugin.c b/src/input/rewind_input_plugin.c index 9e57e6999..6325a978e 100644 --- a/src/input/rewind_input_plugin.c +++ b/src/input/rewind_input_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,6 @@ #include "config.h" #include "input/rewind_input_plugin.h" #include "input/curl_input_plugin.h" -#ifdef ENABLE_MMS -#include "input/mms_input_plugin.h" -#endif #include "input_plugin.h" #include "tag.h" @@ -35,7 +32,9 @@ #define G_LOG_DOMAIN "input_rewind" struct input_rewind { - struct input_stream input; + struct input_stream base; + + struct input_stream *input; /** * The read position within the buffer. Undefined as long as @@ -64,11 +63,9 @@ struct input_rewind { * contain more data for the next read operation? */ static bool -reading_from_buffer(const struct input_stream *is) +reading_from_buffer(const struct input_rewind *r) { - const struct input_rewind *r = is->data; - - return r->tail > 0 && is->offset < r->input.offset; + return r->tail > 0 && r->base.offset < r->input->offset; } /** @@ -78,17 +75,16 @@ reading_from_buffer(const struct input_stream *is) * attributes. */ static void -copy_attributes(struct input_stream *dest) +copy_attributes(struct input_rewind *r) { - const struct input_rewind *r = dest->data; - const struct input_stream *src = &r->input; + struct input_stream *dest = &r->base; + const struct input_stream *src = r->input; assert(dest != src); assert(src->mime == NULL || dest->mime != src->mime); dest->ready = src->ready; dest->seekable = src->seekable; - dest->error = src->error; dest->size = src->size; dest->offset = src->offset; @@ -101,43 +97,45 @@ copy_attributes(struct input_stream *dest) static void input_rewind_close(struct input_stream *is) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - input_stream_close(&r->input); + input_stream_close(r->input); + input_stream_deinit(&r->base); g_free(r); } static struct tag * input_rewind_tag(struct input_stream *is) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - return input_stream_tag(&r->input); + return input_stream_tag(r->input); } 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; + struct input_rewind *r = (struct input_rewind *)is; - int ret = input_stream_buffer(&r->input); - if (ret < 0 || !reading_from_buffer(is)) - copy_attributes(is); + int ret = input_stream_buffer(r->input, error_r); + if (ret < 0 || !reading_from_buffer(r)) + copy_attributes(r); return ret; } 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; + struct input_rewind *r = (struct input_rewind *)is; - if (reading_from_buffer(is)) { + if (reading_from_buffer(r)) { /* buffered read */ assert(r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input.offset); + assert(r->tail == (size_t)r->input->offset); if (size > r->tail - r->head) size = r->tail - r->head; @@ -150,9 +148,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) { @@ -161,44 +159,46 @@ input_rewind_read(struct input_stream *is, void *ptr, size_t size) memcpy(r->buffer + r->tail, ptr, nbytes); r->tail += nbytes; - assert(r->tail == (size_t)r->input.offset); + assert(r->tail == (size_t)r->input->offset); } - copy_attributes(is); + copy_attributes(r); return nbytes; } } static bool -input_rewind_eof(G_GNUC_UNUSED struct input_stream *is) +input_rewind_eof(struct input_stream *is) { - struct input_rewind *r = is->data; + struct input_rewind *r = (struct input_rewind *)is; - return !reading_from_buffer(is) && input_stream_eof(&r->input); + return !reading_from_buffer(r) && input_stream_eof(r->input); } 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; + struct input_rewind *r = (struct input_rewind *)is; 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) || + assert(!reading_from_buffer(r) || r->head == (size_t)is->offset); - assert(r->tail == (size_t)r->input.offset); + assert(r->tail == (size_t)r->input->offset); r->head = (size_t)offset; is->offset = offset; return true; } else { - bool success = input_stream_seek(&r->input, offset, whence); - copy_attributes(is); + bool success = input_stream_seek(r->input, offset, whence, + error_r); + copy_attributes(r); /* disable the buffer, because r->input has left the buffered range now */ @@ -217,7 +217,7 @@ static const struct input_plugin rewind_input_plugin = { .seek = input_rewind_seek, }; -void +struct input_stream * input_rewind_open(struct input_stream *is) { struct input_rewind *c; @@ -225,26 +225,14 @@ input_rewind_open(struct input_stream *is) assert(is != NULL); assert(is->offset == 0); - if (is->plugin != &input_plugin_curl -#ifdef ENABLE_MMS - && is->plugin != &input_plugin_mms -#endif - ) - /* due to limitations in the input_plugin API, we only - (explicitly) support the CURL input plugin */ - return; + if (is->seekable) + /* seekable resources don't need this plugin */ + return is; c = g_new(struct input_rewind, 1); + input_stream_init(&c->base, &rewind_input_plugin, is->uri); c->tail = 0; + c->input = is; - /* move the CURL input stream to c->input */ - c->input = *is; - if (is->plugin == &input_plugin_curl) - input_curl_reinit(&c->input); - - /* convert the existing input_stream pointer to a "rewind" - input stream */ - is->plugin = &rewind_input_plugin; - is->data = c; - is->mime = g_strdup(c->input.mime); + return &c->base; } diff --git a/src/input/rewind_input_plugin.h b/src/input/rewind_input_plugin.h index 33fedf4e1..23d25d94d 100644 --- a/src/input/rewind_input_plugin.h +++ b/src/input/rewind_input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,23 +27,11 @@ #ifndef MPD_INPUT_REWIND_H #define MPD_INPUT_REWIND_H -#include "config.h" +#include "check.h" struct input_stream; -#ifdef HAVE_CURL - -void +struct input_stream * input_rewind_open(struct input_stream *is); -#else - -static inline void -input_rewind_open(struct input_stream *is) -{ - (void)is; -} - -#endif - #endif diff --git a/src/input_init.c b/src/input_init.c new file mode 100644 index 000000000..1438c3e52 --- /dev/null +++ b/src/input_init.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2003-2010 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..eded15fa9 --- /dev/null +++ b/src/input_init.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2010 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..10be48dbb 100644 --- a/src/input_plugin.h +++ b/src/input_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,16 @@ struct input_plugin { */ void (*finish)(void); - bool (*open)(struct input_stream *is, const char *url); + struct input_stream *(*open)(const char *uri, 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..0b9b47d10 --- /dev/null +++ b/src/input_registry.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2010 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 HAVE_FFMPEG +#include "input/ffmpeg_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 HAVE_FFMPEG + &input_plugin_ffmpeg, +#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..e85d6be8e --- /dev/null +++ b/src/input_registry.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2003-2010 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..e769adb92 100644 --- a/src/input_stream.c +++ b/src/input_stream.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,137 +17,64 @@ * 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; - } + return g_quark_from_static_string("input"); } -void input_stream_global_finish(void) +struct input_stream * +input_stream_open(const char *url, GError **error_r) { - for (unsigned i = 0; i < num_input_plugins; ++i) - if (input_plugins_enabled[i] && - input_plugins[i]->finish != NULL) - input_plugins[i]->finish(); -} + GError *error = NULL; -bool -input_stream_open(struct input_stream *is, const char *url) -{ - 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) { + assert(error_r == NULL || *error_r == NULL); + + for (unsigned i = 0; input_plugins[i] != NULL; ++i) { const struct input_plugin *plugin = input_plugins[i]; + struct input_stream *is; - if (input_plugins_enabled[i] && plugin->open(is, url)) { + if (!input_plugins_enabled[i]) + continue; + + is = plugin->open(url, &error); + if (is != NULL) { assert(is->plugin != NULL); assert(is->plugin->close != NULL); assert(is->plugin->read != NULL); assert(is->plugin->eof != NULL); assert(!is->seekable || is->plugin->seek != NULL); - input_rewind_open(is); + is = input_rewind_open(is); - return true; + return is; + } else if (error != NULL) { + g_propagate_error(error_r, error); + return NULL; } } + 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,19 +88,18 @@ 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) { is->plugin->close(is); - - g_free(is->mime); } bool input_stream_eof(struct input_stream *is) @@ -181,10 +107,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..056d008a7 100644 --- a/src/input_stream.h +++ b/src/input_stream.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 { /** @@ -33,9 +39,10 @@ struct input_stream { const struct input_plugin *plugin; /** - * an opaque pointer managed by the plugin + * The absolute URI which was used to open this stream. May + * be NULL if this is unknown. */ - void *data; + char *uri; /** * indicates whether the stream is ready for reading and @@ -49,19 +56,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 @@ -69,30 +71,37 @@ struct input_stream { char *mime; }; -/** - * 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); +static inline void +input_stream_init(struct input_stream *is, const struct input_plugin *plugin, + const char *uri) +{ + is->plugin = plugin; + is->uri = g_strdup(uri); + is->ready = false; + is->seekable = false; + is->size = -1; + is->offset = 0; + is->mime = NULL; +} + +static inline void +input_stream_deinit(struct input_stream *is) +{ + g_free(is->uri); + g_free(is->mime); +} /** * Opens a new input stream. You may not access it until the "ready" * flag is set. * - * @param is the input_stream object allocated by the caller - * @return true on success + * @return an #input_stream object on success, NULL on error */ -bool -input_stream_open(struct input_stream *is, const char *url); +struct input_stream * +input_stream_open(const char *uri, GError **error_r); /** - * Closes the input stream and free resources. This does not free the - * input_stream pointer itself, because it is assumed to be allocated - * by the caller. + * Close the input stream and free resources. */ void input_stream_close(struct input_stream *is); @@ -106,7 +115,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 +140,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 +152,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 d6cade855..da2e79909 100644 --- a/src/listen.c +++ b/src/listen.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,337 +17,51 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "listen.h" -#include "socket_util.h" +#include "server_socket.h" #include "client.h" #include "conf.h" -#include "utils.h" -#include "config.h" +#include "glib_compat.h" -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> #include <string.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> #include <assert.h> -#ifdef WIN32 -#include <ws2tcpip.h> -#include <winsock.h> -#else -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <netdb.h> -#endif - #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "listen" #define DEFAULT_PORT 6600 -struct listen_socket { - struct listen_socket *next; - - int fd; - - guint source_id; -}; - -static struct listen_socket *listen_sockets; +static struct server_socket *listen_socket; int listen_port; -static GQuark -listen_quark(void) -{ - return g_quark_from_static_string("listen"); -} - -static gboolean -listen_in_event(GIOChannel *source, GIOCondition condition, gpointer data); - -static bool -listen_add_address(int pf, const struct sockaddr *addrp, socklen_t addrlen, - GError **error) -{ - char *address_string; - int fd; - struct listen_socket *ls; - GIOChannel *channel; - - address_string = sockaddr_to_string(addrp, addrlen, NULL); - if (address_string != NULL) { - g_debug("binding to socket address %s", address_string); - g_free(address_string); - } - - fd = socket_bind_listen(pf, SOCK_STREAM, 0, addrp, addrlen, 5, error); - if (fd < 0) - return false; - - ls = g_new(struct listen_socket, 1); - ls->fd = fd; - - channel = g_io_channel_unix_new(fd); - ls->source_id = g_io_add_watch(channel, G_IO_IN, - listen_in_event, GINT_TO_POINTER(fd)); - g_io_channel_unref(channel); - - ls->next = listen_sockets; - listen_sockets = ls; - - return true; -} - -#ifdef HAVE_TCP - -/** - * Add a listener on a port on all IPv4 interfaces. - * - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_port_ipv4(unsigned int port, GError **error) -{ - struct sockaddr_in sin; - const struct sockaddr *addrp = (const struct sockaddr *)&sin; - socklen_t addrlen = sizeof(sin); - - memset(&sin, 0, sizeof(sin)); - sin.sin_port = htons(port); - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - - return listen_add_address(PF_INET, addrp, addrlen, error); -} - -#ifdef HAVE_IPV6 -/** - * Add a listener on a port on all IPv6 interfaces. - * - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_port_ipv6(unsigned int port, GError **error) -{ - struct sockaddr_in6 sin; - const struct sockaddr *addrp = (const struct sockaddr *)&sin; - socklen_t addrlen = sizeof(sin); - - memset(&sin, 0, sizeof(sin)); - sin.sin6_port = htons(port); - sin.sin6_family = AF_INET6; - - return listen_add_address(PF_INET6, addrp, addrlen, error); -} -#endif /* HAVE_IPV6 */ - -#endif /* HAVE_TCP */ - -/** - * Add a listener on a port on all interfaces. - * - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_port(unsigned int port, GError **error) -{ -#ifdef HAVE_TCP - bool success; -#ifdef HAVE_IPV6 - bool success6; - GError *error2 = NULL; -#endif - - g_debug("binding to any address"); - -#ifdef HAVE_IPV6 - success6 = listen_add_port_ipv6(port, &error2); - if (!success6) { - if (error2->domain != listen_quark() || - (error2->code != EAFNOSUPPORT && error2->code != EINVAL && - error2->code != EPROTONOSUPPORT)) { - g_propagate_error(error, error2); - return false; - } - - /* although MPD was compiled with IPv6 support, this - host does not have it - ignore this error */ - g_error_free(error2); - } -#endif - - success = listen_add_port_ipv4(port, error); - if (!success) { -#ifdef HAVE_IPV6 - if (success6) - /* non-critical: IPv6 listener is - already set up */ - g_clear_error(error); - else -#endif - return false; - } - - return true; -#else /* HAVE_TCP */ - (void)port; - - g_set_error(error, listen_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -/** - * Resolves a host name, and adds listeners on all addresses in the - * result set. - * - * @param hostname the host name to be resolved - * @param port the TCP port - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_host(const char *hostname, unsigned port, GError **error_r) -{ -#ifdef HAVE_TCP - struct addrinfo hints, *ai, *i; - char service[20]; - int ret; - bool success; - - g_debug("binding to address for %s", hostname); - - memset(&hints, 0, sizeof(hints)); - hints.ai_flags = AI_PASSIVE; -#ifdef AI_ADDRCONFIG - hints.ai_flags |= AI_ADDRCONFIG; -#endif - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - g_snprintf(service, sizeof(service), "%u", port); - - ret = getaddrinfo(hostname, service, &hints, &ai); - if (ret != 0) { - g_set_error(error_r, listen_quark(), ret, - "Failed to look up host \"%s\": %s", - hostname, gai_strerror(ret)); - return false; - } - - for (i = ai; i != NULL; i = i->ai_next) { - GError *error = NULL; - - success = listen_add_address(i->ai_family, i->ai_addr, - i->ai_addrlen, &error); - if (!success) { - if (i == ai) { - /* first bind has failed: fatal - error */ - g_propagate_error(error_r, error); - return false; - } else { - char *address_string = - sockaddr_to_string(i->ai_addr, - i->ai_addrlen, - NULL); - if (address_string == NULL) - address_string = g_strdup("[unknown]"); - - g_warning("bind to %s failed: %s " - "(continuing anyway, because at " - "least one address is bound)", - address_string, error->message); - g_free(address_string); - g_error_free(error); - } - } - } - - freeaddrinfo(ai); - - return true; -#else /* HAVE_TCP */ - - (void)hostname; - (void)port; - - g_set_error(error_r, listen_quark(), 0, - "TCP support is disabled"); - return false; -#endif /* HAVE_TCP */ -} - -#ifdef HAVE_UN -/** - * Add a listener on a Unix domain socket. - * - * @param path the absolute socket path - * @param error location to store the error occuring, or NULL to ignore errors - * @return true on success - */ -static bool -listen_add_path(const char *path, GError **error) +static void +listen_callback(int fd, const struct sockaddr *address, + size_t address_length, int uid, G_GNUC_UNUSED void *ctx) { - size_t path_length; - struct sockaddr_un s_un; - const struct sockaddr *addrp = (const struct sockaddr *)&s_un; - socklen_t addrlen = sizeof(s_un); - bool success; - - path_length = strlen(path); - if (path_length >= sizeof(s_un.sun_path)) { - g_set_error(error, listen_quark(), 0, - "unix socket path is too long"); - return false; - } - - unlink(path); - - s_un.sun_family = AF_UNIX; - memcpy(s_un.sun_path, path, path_length + 1); - - success = listen_add_address(PF_UNIX, addrp, addrlen, error); - if (!success) - return false; - - /* allow everybody to connect */ - chmod(path, 0666); - - return true; + client_new(fd, address, address_length, uid); } -#endif /* HAVE_UN */ static bool listen_add_config_param(unsigned int port, const struct config_param *param, - GError **error) + GError **error_r) { assert(param != NULL); if (0 == strcmp(param->value, "any")) { - return listen_add_port(port, error); -#ifdef HAVE_UN + return server_socket_add_port(listen_socket, port, error_r); } else if (param->value[0] == '/') { - return listen_add_path(param->value, error); -#endif /* HAVE_UN */ + return server_socket_add_path(listen_socket, param->value, + error_r); } else { - return listen_add_host(param->value, port, error); + return server_socket_add_host(listen_socket, param->value, + port, error_r); } } -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 = @@ -355,16 +69,20 @@ void listen_global_init(void) bool success; GError *error = NULL; + listen_socket = server_socket_new(listen_callback, NULL); + if (param != NULL) { /* "bind_to_address" is configured, create listeners for all values */ 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); @@ -373,69 +91,27 @@ void listen_global_init(void) /* no "bind_to_address" configured, bind the configured port on all interfaces */ - success = listen_add_port(port, &error); - if (!success) - g_error("Failed to listen on *:%d: %s", - port, error->message); + success = server_socket_add_port(listen_socket, port, error_r); + if (!success) { + g_propagate_prefixed_error(error_r, error, + "Failed to listen on *:%d: ", + port); + return false; + } } + if (!server_socket_open(listen_socket, error_r)) + return false; + listen_port = port; + return true; } void listen_global_finish(void) { g_debug("listen_global_finish called"); - while (listen_sockets != NULL) { - struct listen_socket *ls = listen_sockets; - listen_sockets = ls->next; + assert(listen_socket != NULL); - g_source_remove(ls->source_id); - close(ls->fd); - g_free(ls); - } -} - -static int get_remote_uid(int fd) -{ -#ifdef HAVE_STRUCT_UCRED - struct ucred cred; - socklen_t len = sizeof (cred); - - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) - return 0; - - return cred.uid; -#else -#ifdef HAVE_GETPEEREID - uid_t euid; - gid_t egid; - - if (getpeereid(fd, &euid, &egid) == 0) - return euid; -#endif - return -1; -#endif -} - -static gboolean -listen_in_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) -{ - int listen_fd = GPOINTER_TO_INT(data), fd; - struct sockaddr_storage sa; - socklen_t sa_length = sizeof(sa); - - fd = accept(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) { - g_warning("Problems accept()'ing"); - } - - return true; + server_socket_free(listen_socket); } diff --git a/src/listen.h b/src/listen.h index 63253fc53..449b5ebae 100644 --- a/src/listen.h +++ b/src/listen.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 7b4721fa9..e27858a0e 100644 --- a/src/locate.c +++ b/src/locate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" diff --git a/src/locate.h b/src/locate.h index d0bdfa136..0283f551b 100644 --- a/src/locate.h +++ b/src/locate.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 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 "mpd_error.h" #include <assert.h> #include <sys/types.h> @@ -59,9 +61,9 @@ static void redirect_logs(int fd) { assert(fd >= 0); if (dup2(fd, STDOUT_FILENO) < 0) - g_error("problems dup2 stdout : %s\n", strerror(errno)); + MPD_ERROR("problems dup2 stdout : %s\n", strerror(errno)); if (dup2(fd, STDERR_FILENO) < 0) - g_error("problems dup2 stderr : %s\n", strerror(errno)); + MPD_ERROR("problems dup2 stderr : %s\n", strerror(errno)); } static const char *log_date(void) @@ -128,7 +130,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 @@ -137,8 +139,8 @@ log_init_file(const char *path, unsigned line) out_filename = path; out_fd = open_log_file(); if (out_fd < 0) - g_error("problem opening log file \"%s\" (config line %u) for " - "writing\n", path, line); + MPD_ERROR("problem opening log file \"%s\" (config line %u) " + "for writing\n", path, line); g_log_set_default_handler(file_log_func, NULL); } @@ -215,8 +217,8 @@ parse_log_level(const char *value, unsigned line) else if (0 == strcmp(value, "verbose")) return G_LOG_LEVEL_DEBUG; else { - g_error("unknown log level \"%s\" at line %u\n", - value, line); + MPD_ERROR("unknown log level \"%s\" at line %u\n", + value, line); return G_LOG_LEVEL_MESSAGE; } } @@ -251,8 +253,8 @@ void log_init(bool verbose, bool use_stdout) available) */ log_init_syslog(); #else - g_error("config parameter \"%s\" not found\n", - CONF_LOG_FILE); + MPD_ERROR("config parameter \"%s\" not found\n", + CONF_LOG_FILE); #endif #ifdef HAVE_SYSLOG } else if (strcmp(param->value, "syslog") == 0) { @@ -271,7 +273,12 @@ void setup_log_output(bool use_stdout) { fflush(NULL); if (!use_stdout) { - if (out_filename != NULL) { +#ifndef WIN32 + if (out_filename == NULL) + out_fd = open("/dev/null", O_WRONLY); +#endif + + if (out_fd >= 0) { redirect_logs(out_fd); close(out_fd); } @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,18 +32,23 @@ * 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://", "mmst://", "mmsu://", #endif +#ifdef HAVE_FFMPEG + "gopher://", + "rtp://", + "rtsp://", + "rtmp://", + "rtmpt://", + "rtmps://", +#endif NULL }; @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/main.c b/src/main.c index 5035a4836..a500e2934 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -42,19 +42,23 @@ #include "volume.h" #include "log.h" #include "permission.h" -#include "replay_gain.h" +#include "replay_gain_config.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" +#include "mpd_error.h" + +#ifdef ENABLE_INOTIFY +#include "inotify_update.h" +#endif #ifdef ENABLE_SQLITE #include "sticker.h" @@ -88,7 +92,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 +127,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; @@ -111,23 +142,15 @@ openDB(const Options *options) } if (path == NULL) - g_error(CONF_DB_FILE " setting missing"); + MPD_ERROR(CONF_DB_FILE " setting missing"); 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,29 +164,51 @@ 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) + MPD_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; retval = WSAStartup(MAKEWORD(2, 2), &sockinfo); if(retval != 0) { - g_error("Attempt to open Winsock2 failed; error code %d\n", + MPD_ERROR("Attempt to open Winsock2 failed; error code %d\n", retval); } if (LOBYTE(sockinfo.wVersion) != 2) { - g_error("We use Winsock2 but your version is either too new or " - "old; please install Winsock 2.x\n"); + MPD_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. @@ -182,8 +227,8 @@ initialize_decoder_and_player(void) if (param != NULL) { buffer_size = strtol(param->value, &test, 10); if (*test != '\0' || buffer_size <= 0) - g_error("buffer size \"%s\" is not a positive integer, " - "line %i\n", param->value, param->line); + MPD_ERROR("buffer size \"%s\" is not a positive integer, " + "line %i\n", param->value, param->line); } else buffer_size = DEFAULT_BUFFER_SIZE; @@ -192,15 +237,15 @@ initialize_decoder_and_player(void) buffered_chunks = buffer_size / CHUNK_SIZE; if (buffered_chunks >= 1 << 15) - g_error("buffer size \"%li\" is too big\n", (long)buffer_size); + MPD_ERROR("buffer size \"%li\" is too big\n", (long)buffer_size); param = config_get_param(CONF_BUFFER_BEFORE_PLAY); if (param != NULL) { perc = strtod(param->value, &test); if (*test != '%' || perc < 0 || perc > 100) { - g_error("buffered before play \"%s\" is not a positive " - "percentage and less than 100 percent, line %i", - param->value, param->line); + MPD_ERROR("buffered before play \"%s\" is not a positive " + "percentage and less than 100 percent, line %i", + param->value, param->line); } } else perc = DEFAULT_BUFFER_BEFORE_PLAY; @@ -210,7 +255,6 @@ initialize_decoder_and_player(void) buffered_before_play = buffered_chunks; pc_init(buffered_chunks, buffered_before_play); - dc_init(); } /** @@ -226,11 +270,31 @@ idle_event_emitted(void) client_manager_idle_add(flags); } +/** + * event_pipe callback function for PIPE_EVENT_SHUTDOWN + */ +static void +shutdown_event_emitted(void) +{ + g_main_loop_quit(main_loop); +} + int main(int argc, char *argv[]) { - Options options; +#ifdef WIN32 + return win32_main(argc, argv); +#else + return mpd_main(argc, argv); +#endif +} + +int mpd_main(int argc, char *argv[]) +{ + struct options options; clock_t start; bool create_db; + GError *error = NULL; + bool success; daemonize_close_stdin(); @@ -239,45 +303,52 @@ 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); + event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_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 +356,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 +367,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,30 +387,56 @@ 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"); + MPD_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(config_get_unsigned(CONF_AUTO_UPDATE_DEPTH, + G_MAXUINT)); +#else + if (success) + g_warning("inotify: auto_update was disabled. enable during compilation phase"); +#endif - /* run the main loop */ + config_global_check(); + /* enable all audio outputs (if not already done by + playlist_state_restore() */ + pc_update_audio(); + +#ifdef WIN32 + win32_app_started(); +#endif + + /* run the main loop */ g_main_loop_run(main_loop); +#ifdef WIN32 + win32_app_stopping(); +#endif + /* cleanup */ 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 +447,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..9b9cba018 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,6 +26,47 @@ extern GThread *main_task; extern GMainLoop *main_loop; -extern struct notify main_notify; +extern GCond *main_cond; + +/** + * A entry point for application. + * On non-Windows platforms this is called directly from main() + * On Windows platform this is called from win32_main() + * after doing some initialization. + */ +int mpd_main(int argc, char *argv[]); + +#ifdef WIN32 + +/** + * If program is run as windows service performs nessesary initialization + * and then calls mpd_main() with specified arguments. + * If program is run as a regular application calls mpd_main() immediately. + */ +int +win32_main(int argc, char *argv[]); + +/** + * When running as a service reports to service control manager + * that our service is started. + * When running as a console application enables console handler that will + * trigger PIPE_EVENT_SHUTDOWN when user closes console window + * or presses Ctrl+C. + * This function should be called just before entering main loop. + */ +void +win32_app_started(void); + +/** + * When running as a service reports to service control manager + * that our service is about to stop. + * When running as a console application enables console handler that will + * catch all shutdown requests and ignore them. + * This function should be called just after leaving main loop. + */ +void +win32_app_stopping(void); + +#endif #endif diff --git a/src/main_win32.c b/src/main_win32.c new file mode 100644 index 000000000..543d8ba81 --- /dev/null +++ b/src/main_win32.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2003-2010 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 "main.h" + +#ifdef WIN32 + +#include "mpd_error.h" +#include "event_pipe.h" + +#include <glib.h> + +#define WINVER 0x0501 +#include <windows.h> + +static int service_argc; +static char **service_argv; +static char service_name[] = ""; +static BOOL ignore_console_events; +static SERVICE_STATUS_HANDLE service_handle; + +static void WINAPI +service_main(DWORD argc, CHAR *argv[]); + +static SERVICE_TABLE_ENTRY service_registry[] = { + {service_name, service_main}, + {NULL, NULL} +}; + +static void +service_notify_status(DWORD status_code) +{ + SERVICE_STATUS current_status; + + current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING + ? 0 + : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + + current_status.dwCurrentState = status_code; + current_status.dwWin32ExitCode = NO_ERROR; + current_status.dwCheckPoint = 0; + current_status.dwWaitHint = 1000; + + SetServiceStatus(service_handle, ¤t_status); +} + +static DWORD WINAPI +service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, + G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context) +{ + switch (control) { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + event_pipe_emit(PIPE_EVENT_SHUTDOWN); + return NO_ERROR; + default: + return NO_ERROR; + } +} + +static void WINAPI +service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_handle = + RegisterServiceCtrlHandlerEx(service_name, + service_dispatcher, NULL); + + if (service_handle == 0) { + error_code = GetLastError(); + error_message = g_win32_error_message(error_code); + MPD_ERROR("RegisterServiceCtrlHandlerEx() failed: %s", + error_message); + } + + service_notify_status(SERVICE_START_PENDING); + mpd_main(service_argc, service_argv); + service_notify_status(SERVICE_STOPPED); +} + +static BOOL WINAPI +console_handler(DWORD event) +{ + switch (event) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + if (!ignore_console_events) + event_pipe_emit(PIPE_EVENT_SHUTDOWN); + return TRUE; + default: + return FALSE; + } +} + +int win32_main(int argc, char *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_argc = argc; + service_argv = argv; + + if (StartServiceCtrlDispatcher(service_registry)) + return 0; /* run as service successefully */ + + error_code = GetLastError(); + if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { + /* running as console app */ + SetConsoleTitle("Music Player Daemon"); + ignore_console_events = TRUE; + SetConsoleCtrlHandler(console_handler, TRUE); + return mpd_main(argc, argv); + } + + error_message = g_win32_error_message(error_code); + MPD_ERROR("StartServiceCtrlDispatcher() failed: %s", error_message); +} + +void win32_app_started() +{ + if (service_handle != 0) + service_notify_status(SERVICE_RUNNING); + else + ignore_console_events = FALSE; +} + +void win32_app_stopping() +{ + if (service_handle != 0) + service_notify_status(SERVICE_STOP_PENDING); + else + ignore_console_events = TRUE; +} + +#endif diff --git a/src/mapper.c b/src/mapper.c index 5518cb79e..108de9531 100644 --- a/src/mapper.c +++ b/src/mapper.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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) @@ -122,6 +93,16 @@ mapper_has_music_directory(void) return music_dir != NULL; } +const char * +map_to_relative_path(const char *path_utf8) +{ + return music_dir != NULL && + memcmp(path_utf8, music_dir, music_dir_length) == 0 && + G_IS_DIR_SEPARATOR(path_utf8[music_dir_length]) + ? path_utf8 + music_dir_length + 1 + : path_utf8; +} + char * map_uri_fs(const char *uri) { @@ -189,9 +170,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 +180,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..9f84f96fe 100644 --- a/src/mapper.h +++ b/src/mapper.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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); @@ -42,6 +42,14 @@ bool mapper_has_music_directory(void); /** + * If the specified absolute path points inside the music directory, + * this function converts it to a relative path. If not, it returns + * the unmodified string pointer. + */ +const char * +map_to_relative_path(const char *path_utf8); + +/** * Determines the absolute file system path of a relative URI. This * is basically done by converting the URI to the file system charset * and prepending the music directory. diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer_plugin.c index 52e553cc5..38f36cb8f 100644 --- a/src/mixer/alsa_mixer.c +++ b/src/mixer/alsa_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..418068ac2 100644 --- a/src/mixer/oss_mixer.c +++ b/src/mixer/oss_mixer_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..2be0b8266 --- /dev/null +++ b/src/mixer/pulse_mixer_plugin.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2003-2010 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..be199f688 --- /dev/null +++ b/src/mixer/pulse_mixer_plugin.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 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..93802e977 --- /dev/null +++ b/src/mixer/software_mixer_plugin.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2010 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..3bd07ac62 --- /dev/null +++ b/src/mixer/software_mixer_plugin.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2003-2010 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/winmm_mixer_plugin.c b/src/mixer/winmm_mixer_plugin.c new file mode 100644 index 000000000..e1e8a7a45 --- /dev/null +++ b/src/mixer/winmm_mixer_plugin.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2003-2010 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_api.h" +#include "output_api.h" +#include "output/winmm_output_plugin.h" + +#include <assert.h> +#include <math.h> +#include <windows.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "winmm_mixer" + +struct winmm_mixer { + struct mixer base; + struct winmm_output *output; +}; + +static inline GQuark +winmm_mixer_quark(void) +{ + return g_quark_from_static_string("winmm_mixer"); +} + +static inline int +winmm_volume_decode(DWORD volume) +{ + return lround((volume & 0xFFFF) / 655.35); +} + +static inline DWORD +winmm_volume_encode(int volume) +{ + int value = lround(volume * 655.35); + return MAKELONG(value, value); +} + +static struct mixer * +winmm_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error_r) +{ + assert(ao != NULL); + + struct winmm_mixer *wm = g_new(struct winmm_mixer, 1); + mixer_init(&wm->base, &winmm_mixer_plugin); + wm->output = (struct winmm_output *) ao; + + return &wm->base; +} + +static void +winmm_mixer_finish(struct mixer *data) +{ + g_free(data); +} + +static int +winmm_mixer_get_volume(struct mixer *mixer, GError **error_r) +{ + struct winmm_mixer *wm = (struct winmm_mixer *) mixer; + DWORD volume; + HWAVEOUT handle = winmm_output_get_handle(wm->output); + MMRESULT result = waveOutGetVolume(handle, &volume); + + if (result != MMSYSERR_NOERROR) { + g_set_error(error_r, 0, winmm_mixer_quark(), + "Failed to get winmm volume"); + return -1; + } + + return winmm_volume_decode(volume); +} + +static bool +winmm_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r) +{ + struct winmm_mixer *wm = (struct winmm_mixer *) mixer; + DWORD value = winmm_volume_encode(volume); + HWAVEOUT handle = winmm_output_get_handle(wm->output); + MMRESULT result = waveOutSetVolume(handle, value); + + if (result != MMSYSERR_NOERROR) { + g_set_error(error_r, 0, winmm_mixer_quark(), + "Failed to set winmm volume"); + return false; + } + + return true; +} + +const struct mixer_plugin winmm_mixer_plugin = { + .init = winmm_mixer_init, + .finish = winmm_mixer_finish, + .get_volume = winmm_mixer_get_volume, + .set_volume = winmm_mixer_set_volume, +}; diff --git a/src/mixer_all.c b/src/mixer_all.c index 252cb61ab..ffe610b91 100644 --- a/src/mixer_all.c +++ b/src/mixer_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..cece23292 100644 --- a/src/mixer_all.h +++ b/src/mixer_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..4c8959fb8 100644 --- a/src/mixer_api.c +++ b/src/mixer_api.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_api.h b/src/mixer_api.h index fe27f5119..26c001703 100644 --- a/src/mixer_api.h +++ b/src/mixer_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/mixer_control.c b/src/mixer_control.c index e19b82d65..458b3abc1 100644 --- a/src/mixer_control.c +++ b/src/mixer_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..1f48e8ca5 100644 --- a/src/mixer_control.h +++ b/src/mixer_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..a472c8807 100644 --- a/src/mixer_list.h +++ b/src/mixer_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,8 +25,10 @@ #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; +extern const struct mixer_plugin winmm_mixer_plugin; #endif diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h index 2b9b440e5..0915a03f3 100644 --- a/src/mixer_plugin.h +++ b/src/mixer_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..4f347dd94 --- /dev/null +++ b/src/mixer_type.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 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..fd1c5576c --- /dev/null +++ b/src/mixer_type.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2003-2010 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/mpd_error.h b/src/mpd_error.h new file mode 100644 index 000000000..47618d03c --- /dev/null +++ b/src/mpd_error.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 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_ERROR_H +#define MPD_ERROR_H + +#include <stdlib.h> + +/* This macro is used as an intermediate step to a proper error handling + * using GError in mpd. It is used for unrecoverable error conditions + * and exits immediately. The long-term goal is to replace this macro by + * proper error handling. */ + +#define MPD_ERROR(...) \ + do { \ + g_critical(__VA_ARGS__); \ + exit(EXIT_FAILURE); \ + } while(0) + +#endif diff --git a/src/notify.c b/src/notify.c index 3b45c22b4..d148a4bfc 100644 --- a/src/notify.c +++ b/src/notify.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/notify.h b/src/notify.h index 0655bc6b7..0c657f2fb 100644 --- a/src/notify.h +++ b/src/notify.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/open.h b/src/open.h new file mode 100644 index 000000000..e39c64a97 --- /dev/null +++ b/src/open.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2003-2010 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 + * + * Portability macros for opening files. + */ + +#ifndef MPD_OPEN_H +#define MPD_OPEN_H + +#include <fcntl.h> + +/* On Windows, files are opened in "text" mode by default, and the C + library will mangle data being read/written; this must be switched + off by specifying the proprietary "O_BINARY" flag. That sucks! */ +#ifndef O_BINARY +#ifdef _O_BINARY +#define O_BINARY _O_BINARY +#else +#define O_BINARY 0 +#endif +#endif + +#endif diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 818c83ca2..9177fabe4 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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; }; /** @@ -172,15 +183,148 @@ alsa_test_default_device(void) } static snd_pcm_format_t -get_bitformat(const struct audio_format *af) +get_bitformat(enum sample_format sample_format) +{ + switch (sample_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_S24: + return G_BYTE_ORDER == G_BIG_ENDIAN + ? SND_PCM_FORMAT_S24_3BE + : SND_PCM_FORMAT_S24_3LE; + + case SAMPLE_FORMAT_S32: + return SND_PCM_FORMAT_S32; + + default: + 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_S24_3BE: + return SND_PCM_FORMAT_S24_3LE; + + case SND_PCM_FORMAT_S24_3LE: + return SND_PCM_FORMAT_S24_3BE; + + case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE; + default: return SND_PCM_FORMAT_UNKNOWN; + } +} + +/** + * Attempts to configure the specified sample format. + */ +static int +alsa_output_try_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + snd_pcm_format_t alsa_format = get_bitformat(sample_format); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); + if (err == 0) + audio_format->format = sample_format; + + return err; +} + +/** + * Attempts to configure the specified sample format with reversed + * host byte order. + */ +static int +alsa_output_try_reverse(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + snd_pcm_format_t alsa_format = + byteswap_bitformat(get_bitformat(sample_format)); + if (alsa_format == SND_PCM_FORMAT_UNKNOWN) + return -EINVAL; + + int err = snd_pcm_hw_params_set_format(pcm, hwparams, alsa_format); + if (err == 0) { + audio_format->format = sample_format; + audio_format->reverse_endian = true; + } + + return err; +} + +/** + * Attempts to configure the specified sample format, and tries the + * reversed host byte order if was not supported. + */ +static int +alsa_output_try_format_both(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format, + enum sample_format sample_format) +{ + int err = alsa_output_try_format(pcm, hwparams, audio_format, + sample_format); + if (err == -EINVAL) + err = alsa_output_try_reverse(pcm, hwparams, audio_format, + sample_format); + + return err; +} + +/** + * Configure a sample format, and probe other formats if that fails. + */ +static int +alsa_output_setup_format(snd_pcm_t *pcm, snd_pcm_hw_params_t *hwparams, + struct audio_format *audio_format) { - 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; + /* try the input format first */ + + int err = alsa_output_try_format_both(pcm, hwparams, audio_format, + audio_format->format); + if (err != -EINVAL) + return err; + + /* if unsupported by the hardware, try other formats */ + + static const enum sample_format probe_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S24, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED, + }; + + for (unsigned i = 0; probe_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + if (probe_formats[i] == audio_format->format) + continue; + + err = alsa_output_try_format_both(pcm, hwparams, audio_format, + probe_formats[i]); + if (err != -EINVAL) + return err; } - return SND_PCM_FORMAT_UNKNOWN; + + return -EINVAL; } /** @@ -189,7 +333,6 @@ get_bitformat(const struct audio_format *af) */ static bool alsa_setup(struct alsa_data *ad, struct audio_format *audio_format, - snd_pcm_format_t bitformat, GError **error) { snd_pcm_hw_params_t *hwparams; @@ -208,7 +351,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) @@ -235,31 +377,12 @@ configure_hw: ad->writei = snd_pcm_writei; } - err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat); - if (err == -EINVAL && (audio_format->bits == 24 || - audio_format->bits == 16)) { - /* 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 == -EINVAL && audio_format->bits != 16) { - /* 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; - } - } - + err = alsa_output_setup_format(ad->pcm, hwparams, audio_format); 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; } @@ -285,6 +408,26 @@ configure_hw: } audio_format->sample_rate = sample_rate; + snd_pcm_uframes_t buffer_size_min, buffer_size_max; + snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); + snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); + unsigned buffer_time_min, buffer_time_max; + snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0); + snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0); + g_debug("buffer: size=%u..%u time=%u..%u", + (unsigned)buffer_size_min, (unsigned)buffer_size_max, + buffer_time_min, buffer_time_max); + + snd_pcm_uframes_t period_size_min, period_size_max; + snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0); + snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, 0); + unsigned period_time_min, period_time_max; + snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0); + snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0); + g_debug("period: size=%u..%u time=%u..%u", + (unsigned)period_size_min, (unsigned)period_size_max, + period_time_min, period_time_max); + if (ad->buffer_time > 0) { buffer_time = ad->buffer_time; cmd = "snd_pcm_hw_params_set_buffer_time_near"; @@ -365,6 +508,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: @@ -378,19 +524,9 @@ static bool alsa_open(void *data, struct audio_format *audio_format, GError **error) { struct alsa_data *ad = data; - snd_pcm_format_t bitformat; int err; bool success; - bitformat = get_bitformat(audio_format); - if (bitformat == SND_PCM_FORMAT_UNKNOWN) { - /* sample format is not supported by this plugin - - fall back to 16 bit samples */ - - audio_format->bits = 16; - bitformat = SND_PCM_FORMAT_S16; - } - err = snd_pcm_open(&ad->pcm, alsa_device(ad), SND_PCM_STREAM_PLAYBACK, ad->mode); if (err < 0) { @@ -400,7 +536,7 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error) return false; } - success = alsa_setup(ad, audio_format, bitformat, error); + success = alsa_setup(ad, audio_format, error); if (!success) { snd_pcm_close(ad->pcm); return false; @@ -431,6 +567,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 +585,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 +633,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 +645,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 +667,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..d5c95018c 100644 --- a/src/output/ao_plugin.c +++ b/src/output/ao_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/ffado_output_plugin.c b/src/output/ffado_output_plugin.c new file mode 100644 index 000000000..723698ed0 --- /dev/null +++ b/src/output/ffado_output_plugin.c @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/* + * Warning: this plugin was not tested successfully. I just couldn't + * keep libffado2 from crashing. Use at your own risk. + * + * For details, see my Debian bug reports: + * + * http://bugs.debian.org/601657 + * http://bugs.debian.org/601659 + * http://bugs.debian.org/601663 + * + */ + +#include "config.h" +#include "output_api.h" +#include "timer.h" + +#include <glib.h> +#include <assert.h> + +#include <libffado/ffado.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "ffado" + +enum { + MAX_STREAMS = 8, +}; + +struct mpd_ffado_stream { + /** libffado's stream number */ + int number; + + float *buffer; +}; + +struct mpd_ffado_device { + char *device_name; + int verbose; + unsigned period_size, nb_buffers; + + ffado_device_t *dev; + + /** + * The current sample position inside the stream buffers. New + * samples get appended at this position on all streams at the + * same time. When the buffers are full + * (buffer_position==period_size), + * ffado_streaming_transfer_playback_buffers() gets called to + * hand them over to libffado. + */ + unsigned buffer_position; + + /** + * The number of streams which are really used by MPD. + */ + int num_streams; + struct mpd_ffado_stream streams[MAX_STREAMS]; +}; + +static inline GQuark +ffado_output_quark(void) +{ + return g_quark_from_static_string("ffado_output"); +} + +static void * +ffado_init(G_GNUC_UNUSED const struct audio_format *audio_format, + const struct config_param *param, + GError **error_r) +{ + g_debug("using libffado version %s, API=%d", + ffado_get_version(), ffado_get_api_version()); + + struct mpd_ffado_device *fd = g_new(struct mpd_ffado_device, 1); + fd->device_name = config_dup_block_string(param, "device", NULL); + fd->verbose = config_get_block_unsigned(param, "verbose", 0); + + fd->period_size = config_get_block_unsigned(param, "period_size", + 1024); + if (fd->period_size == 0 || fd->period_size > 1024 * 1024) { + g_set_error(error_r, ffado_output_quark(), 0, + "invalid period_size setting"); + return false; + } + + fd->nb_buffers = config_get_block_unsigned(param, "nb_buffers", 3); + if (fd->nb_buffers == 0 || fd->nb_buffers > 1024) { + g_set_error(error_r, ffado_output_quark(), 0, + "invalid nb_buffers setting"); + return false; + } + + return fd; +} + +static void +ffado_finish(void *data) +{ + struct mpd_ffado_device *fd = data; + + g_free(fd->device_name); + g_free(fd); +} + +static bool +ffado_configure_stream(ffado_device_t *dev, struct mpd_ffado_stream *stream, + GError **error_r) +{ + char *buffer = (char *)stream->buffer; + if (ffado_streaming_set_playback_stream_buffer(dev, stream->number, + buffer) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "failed to configure stream buffer"); + return false; + } + + if (ffado_streaming_playback_stream_onoff(dev, stream->number, + 1) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "failed to disable stream"); + return false; + } + + return true; +} + +static bool +ffado_configure(struct mpd_ffado_device *fd, struct audio_format *audio_format, + GError **error_r) +{ + assert(fd != NULL); + assert(fd->dev != NULL); + assert(audio_format->channels <= MAX_STREAMS); + + if (ffado_streaming_set_audio_datatype(fd->dev, + ffado_audio_datatype_float) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_set_audio_datatype() failed"); + return false; + } + + int num_streams = ffado_streaming_get_nb_playback_streams(fd->dev); + if (num_streams < 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_get_nb_playback_streams() failed"); + return false; + } + + g_debug("there are %d playback streams", num_streams); + + fd->num_streams = 0; + for (int i = 0; i < num_streams; ++i) { + char name[256]; + ffado_streaming_get_playback_stream_name(fd->dev, i, name, + sizeof(name) - 1); + + ffado_streaming_stream_type type = + ffado_streaming_get_playback_stream_type(fd->dev, i); + if (type != ffado_stream_type_audio) { + g_debug("stream %d name='%s': not an audio stream", + i, name); + continue; + } + + if (fd->num_streams >= audio_format->channels) { + g_debug("stream %d name='%s': ignoring", + i, name); + continue; + } + + g_debug("stream %d name='%s'", i, name); + + struct mpd_ffado_stream *stream = + &fd->streams[fd->num_streams++]; + + stream->number = i; + + /* allocated buffer is zeroed = silence */ + stream->buffer = g_new0(float, fd->period_size); + + if (!ffado_configure_stream(fd->dev, stream, error_r)) + return false; + } + + if (!audio_valid_channel_count(fd->num_streams)) { + g_set_error(error_r, ffado_output_quark(), 0, + "invalid channel count from libffado: %u", + audio_format->channels); + return false; + } + + g_debug("configured %d audio streams", fd->num_streams); + + if (ffado_streaming_prepare(fd->dev) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_prepare() failed"); + return false; + } + + if (ffado_streaming_start(fd->dev) != 0) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_start() failed"); + return false; + } + + audio_format->channels = fd->num_streams; + return true; +} + +static bool +ffado_open(void *data, struct audio_format *audio_format, GError **error_r) +{ + struct mpd_ffado_device *fd = data; + + /* will be converted to floating point, choose best input + format */ + audio_format->format = SAMPLE_FORMAT_S24_P32; + + ffado_device_info_t device_info; + memset(&device_info, 0, sizeof(device_info)); + if (fd->device_name != NULL) { + device_info.nb_device_spec_strings = 1; + device_info.device_spec_strings = &fd->device_name; + } + + ffado_options_t options; + memset(&options, 0, sizeof(options)); + options.sample_rate = audio_format->sample_rate; + options.period_size = fd->period_size; + options.nb_buffers = fd->nb_buffers; + options.verbose = fd->verbose; + + fd->dev = ffado_streaming_init(device_info, options); + if (fd->dev == NULL) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_init() failed"); + return false; + } + + if (!ffado_configure(fd, audio_format, error_r)) { + ffado_streaming_finish(fd->dev); + + for (int i = 0; i < fd->num_streams; ++i) { + struct mpd_ffado_stream *stream = &fd->streams[i]; + g_free(stream->buffer); + } + + return false; + } + + fd->buffer_position = 0; + + return true; +} + +static void +ffado_close(void *data) +{ + struct mpd_ffado_device *fd = data; + + ffado_streaming_stop(fd->dev); + ffado_streaming_finish(fd->dev); + + for (int i = 0; i < fd->num_streams; ++i) { + struct mpd_ffado_stream *stream = &fd->streams[i]; + g_free(stream->buffer); + } +} + +static size_t +ffado_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct mpd_ffado_device *fd = data; + + /* wait for prefious buffer to finish (if it was full) */ + + if (fd->buffer_position >= fd->period_size) { + switch (ffado_streaming_wait(fd->dev)) { + case ffado_wait_ok: + case ffado_wait_xrun: + break; + + default: + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_wait() failed"); + return 0; + } + + fd->buffer_position = 0; + } + + /* copy samples to stream buffers, non-interleaved */ + + const int32_t *p = chunk; + unsigned num_frames = size / sizeof(*p) / fd->num_streams; + if (num_frames > fd->period_size - fd->buffer_position) + num_frames = fd->period_size - fd->buffer_position; + + for (unsigned i = num_frames; i > 0; --i) { + for (int stream = 0; stream < fd->num_streams; ++stream) + fd->streams[stream].buffer[fd->buffer_position] = + *p++ / (float)(1 << 23); + ++fd->buffer_position; + } + + /* if buffer full, transfer to device */ + + if (fd->buffer_position >= fd->period_size && + /* libffado documentation says this function returns -1 on + error, but that is a lie - it returns a boolean value, + and "false" means error */ + !ffado_streaming_transfer_playback_buffers(fd->dev)) { + g_set_error(error_r, ffado_output_quark(), 0, + "ffado_streaming_transfer_playback_buffers() failed"); + return 0; + } + + return num_frames * sizeof(*p) * fd->num_streams; +} + +const struct audio_output_plugin ffado_output_plugin = { + .name = "ffado", + .init = ffado_init, + .finish = ffado_finish, + .open = ffado_open, + .close = ffado_close, + .play = ffado_play, +}; diff --git a/src/output/fifo_plugin.c b/src/output/fifo_output_plugin.c index 76bbe8cfa..f4217ec4d 100644 --- a/src/output/fifo_plugin.c +++ b/src/output/fifo_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,15 +17,17 @@ * 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 "open.h" #include <glib.h> #include <sys/types.h> #include <sys/stat.h> -#include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.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|O_BINARY, 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|O_BINARY, 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..6bd095838 100644 --- a/src/output/httpd_client.c +++ b/src/output/httpd_client.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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> @@ -280,11 +282,12 @@ httpd_client_send_response(struct httpd_client *client) } else { gchar *metadata_header; - metadata_header = icy_server_metadata_header("Add config information here!", /* TODO */ - "Add config information here!", /* TODO */ - "Add config information here!", /* TODO */ - client->httpd->content_type, - client->metaint); + metadata_header = icy_server_metadata_header( + client->httpd->name, + client->httpd->genre, + client->httpd->website, + client->httpd->content_type, + client->metaint); g_strlcpy(buffer, metadata_header, sizeof(buffer)); @@ -482,11 +485,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_client.h b/src/output/httpd_client.h index 4a2912f80..7ebd0bbc0 100644 --- a/src/output/httpd_client.h +++ b/src/output/httpd_client.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h index 2257e27a2..fee72c07f 100644 --- a/src/output/httpd_internal.h +++ b/src/output/httpd_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,30 +29,34 @@ #include <glib.h> -#include <sys/socket.h> +#include <stdbool.h> struct httpd_client; struct httpd_output { /** - * The configured encoder plugin. + * True if the audio output is open and accepts client + * connections. */ - struct encoder *encoder; + bool open; /** - * The MIME type produced by the #encoder. + * The configured encoder plugin. */ - const char *content_type; + struct encoder *encoder; /** - * The configured address of the listener socket. + * Number of bytes which were fed into the encoder, without + * ever receiving new output. This is used to estimate + * whether MPD should manually flush the encoder, to avoid + * buffer underruns in the client. */ - struct sockaddr_storage address; + size_t unflushed_input; /** - * The size of #address. + * The MIME type produced by the #encoder. */ - socklen_t address_size; + const char *content_type; /** * This mutex protects the listener socket and the client @@ -69,12 +73,7 @@ struct httpd_output { /** * The listener socket. */ - int fd; - - /** - * A GLib main loop source id for the listener socket. - */ - guint source_id; + struct server_socket *server_socket; /** * The header page, which is sent to every client on connect. @@ -87,6 +86,19 @@ struct httpd_output { struct page *metadata; /** + * The configured name. + */ + char const *name; + /** + * The configured genre. + */ + char const *genre; + /** + * The configured website address. + */ + char const *website; + + /** * A linked list containing all clients which are currently * connected. */ @@ -97,6 +109,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 026e8d9d8..72994018a 100644 --- a/src/output/httpd_output_plugin.c +++ b/src/output/httpd_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,15 +26,19 @@ #include "socket_util.h" #include "page.h" #include "icy_server.h" +#include "fd_util.h" +#include "server_socket.h" #include <assert.h> #include <sys/types.h> -#include <netinet/in.h> -#include <netdb.h> #include <unistd.h> #include <errno.h> +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "httpd_output" @@ -46,18 +51,49 @@ httpd_output_quark(void) return g_quark_from_static_string("httpd_output"); } +static void +httpd_listen_in_event(int fd, const struct sockaddr *address, + size_t address_length, int uid, void *ctx); + +static bool +httpd_output_bind(struct httpd_output *httpd, GError **error_r) +{ + httpd->open = false; + + g_mutex_lock(httpd->mutex); + bool success = server_socket_open(httpd->server_socket, error_r); + g_mutex_unlock(httpd->mutex); + + return success; +} + +static void +httpd_output_unbind(struct httpd_output *httpd) +{ + assert(!httpd->open); + + g_mutex_lock(httpd->mutex); + server_socket_close(httpd->server_socket); + g_mutex_unlock(httpd->mutex); +} + static void * httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, const struct config_param *param, GError **error) { struct httpd_output *httpd = g_new(struct httpd_output, 1); - const char *encoder_name; + const char *encoder_name, *bind_to_address; const struct encoder_plugin *encoder_plugin; guint port; - struct sockaddr_in *sin; /* read configuration */ + httpd->name = + config_get_block_string(param, "name", "Set name in config"); + httpd->genre = + config_get_block_string(param, "genre", "Set genre in config"); + httpd->website = + config_get_block_string(param, "website", "Set website in config"); port = config_get_block_unsigned(param, "port", 8000); @@ -69,21 +105,21 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, return NULL; } - if (strcmp(encoder_name, "vorbis") == 0) - httpd->content_type = "audio/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); + + /* set up bind_to_address */ - /* initialize listen address */ + httpd->server_socket = server_socket_new(httpd_listen_in_event, httpd); - sin = (struct sockaddr_in *)&httpd->address; - memset(sin, 0, sizeof(sin)); - sin->sin_port = htons(port); - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = INADDR_ANY; - httpd->address_size = sizeof(*sin); + bind_to_address = + config_get_block_string(param, "bind_to_address", NULL); + bool success = bind_to_address != NULL && + strcmp(bind_to_address, "any") != 0 + ? server_socket_add_host(httpd->server_socket, bind_to_address, + port, error) + : server_socket_add_port(httpd->server_socket, port, error); + if (!success) + return NULL; /* initialize metadata */ httpd->metadata = NULL; @@ -94,6 +130,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; @@ -108,6 +150,7 @@ httpd_output_finish(void *data) page_unref(httpd->metadata); encoder_finish(httpd->encoder); + server_socket_free(httpd->server_socket); g_mutex_free(httpd->mutex); g_free(httpd); } @@ -124,36 +167,64 @@ 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) httpd_client_send_metadata(client, httpd->metadata); } -static gboolean -httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - gpointer data) +static void +httpd_listen_in_event(int fd, const struct sockaddr *address, + size_t address_length, G_GNUC_UNUSED int uid, void *ctx) { - struct httpd_output *httpd = data; - int fd; - struct sockaddr_storage sa; - socklen_t sa_length = sizeof(sa); - - g_mutex_lock(httpd->mutex); + struct httpd_output *httpd = ctx; /* 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) +#ifdef HAVE_LIBWRAP + if (address->sa_family != AF_UNIX) { + char *hostaddr = sockaddr_to_string(address, address_length, NULL); + const char *progname = g_get_prgname(); + + struct request_info req; + request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0); + + fromhost(&req); + + if (!hosts_access(&req)) { + /* tcp wrappers says no */ + g_warning("libwrap refused connection (libwrap=%s) from %s", + progname, hostaddr); + g_free(hostaddr); + close(fd); + g_mutex_unlock(httpd->mutex); + return; + } + + g_free(hostaddr); + } +#else + (void)address; + (void)address_length; +#endif /* HAVE_WRAP */ + + g_mutex_lock(httpd->mutex); + + 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); - - return true; } /** @@ -165,12 +236,22 @@ httpd_output_read_page(struct httpd_output *httpd) { size_t size = 0, nbytes; + if (httpd->unflushed_input >= 65536) { + /* we have fed a lot of input into the encoder, but it + didn't give anything back yet - flush now to avoid + buffer underruns */ + encoder_flush(httpd->encoder, NULL); + httpd->unflushed_input = 0; + } + do { nbytes = encoder_read(httpd->encoder, httpd->buffer + size, sizeof(httpd->buffer) - size); if (nbytes == 0) break; + httpd->unflushed_input = 0; + size += nbytes; } while (size < sizeof(httpd->buffer)); @@ -195,41 +276,41 @@ httpd_output_encoder_open(struct httpd_output *httpd, bytes of encoder output after opening it, because it has to be sent to every new client */ httpd->header = httpd_output_read_page(httpd); + + httpd->unflushed_input = 0; + return true; } 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); if (!success) { - g_source_remove(httpd->source_id); - close(httpd->fd); g_mutex_unlock(httpd->mutex); return false; } @@ -237,8 +318,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 +341,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 +353,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 +364,7 @@ httpd_output_remove_client(struct httpd_output *httpd, assert(client != NULL); httpd->clients = g_list_remove(httpd->clients, client); + httpd->clients_cnt--; } void @@ -291,6 +375,16 @@ httpd_output_send_header(struct httpd_output *httpd, httpd_client_send(client, httpd->header); } +static unsigned +httpd_output_delay(void *data) +{ + struct httpd_output *httpd = data; + + return httpd->timer->started + ? timer_delay(httpd->timer) + : 0; +} + static void httpd_client_check_queue(gpointer data, G_GNUC_UNUSED gpointer user_data) { @@ -352,6 +446,8 @@ httpd_output_encode_and_play(struct httpd_output *httpd, if (!success) return false; + httpd->unflushed_input += size; + httpd_output_encoder_to_clients(httpd); return true; @@ -378,13 +474,29 @@ httpd_output_play(void *data, const void *chunk, size_t size, GError **error) if (!httpd->timer->started) timer_start(httpd->timer); - else - timer_sync(httpd->timer); timer_add(httpd->timer, size); return size; } +static bool +httpd_output_pause(void *data) +{ + struct httpd_output *httpd = data; + + g_mutex_lock(httpd->mutex); + bool has_clients = httpd->clients != NULL; + g_mutex_unlock(httpd->mutex); + + if (has_clients) { + static const char silence[1020]; + return httpd_output_play(data, silence, sizeof(silence), NULL); + } else { + g_usleep(100000); + return true; + } +} + static void httpd_send_metadata(gpointer data, gpointer user_data) { @@ -433,9 +545,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,9 +579,13 @@ 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, + .delay = httpd_output_delay, .send_tag = httpd_output_tag, .play = httpd_output_play, + .pause = httpd_output_pause, .cancel = httpd_output_cancel, }; diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c new file mode 100644 index 000000000..110ee5f26 --- /dev/null +++ b/src/output/jack_output_plugin.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) 2003-2010 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"); +} + +/** + * Determine the number of frames guaranteed to be available on all + * channels. + */ +static jack_nframes_t +mpd_jack_available(const struct jack_data *jd) +{ + size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); + + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); + if (current < min) + min = current; + } + + assert(min % sample_size == 0); + + return min / sample_size; +} + +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; + + if (nframes <= 0) + return 0; + + if (jd->pause) { + /* empty the ring buffers */ + + const jack_nframes_t available = mpd_jack_available(jd); + for (unsigned i = 0; i < jd->audio_format.channels; ++i) + jack_ringbuffer_read_advance(jd->ringbuffer[i], + available * sample_size); + + /* 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; + } + + jack_nframes_t available = mpd_jack_available(jd); + if (available > nframes) + available = nframes; + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + out = jack_port_get_buffer(jd->ports[i], nframes); + jack_ringbuffer_read(jd->ringbuffer[i], + (char *)out, available * sample_size); + + for (jack_nframes_t f = available; f < nframes; ++f) + /* ringbuffer underrun, fill with silence */ + out[f] = 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..20587f5c5 100644 --- a/src/output/mvp_plugin.c +++ b/src/output/mvp_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..89abbd91f 100644 --- a/src/output/null_plugin.c +++ b/src/output/null_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..767b3eb17 --- /dev/null +++ b/src/output/openal_plugin.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2003-2010 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..bb4164990 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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> @@ -28,6 +30,7 @@ #include <errno.h> #include <stdlib.h> #include <unistd.h> +#include <assert.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "oss" @@ -38,33 +41,15 @@ # include <sys/soundcard.h> #endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ -#if G_BYTE_ORDER == G_BIG_ENDIAN -# define AFMT_S16_MPD AFMT_S16_BE -#else -# define AFMT_S16_MPD AFMT_S16_LE -#endif - struct oss_data { int fd; const char *device; - struct audio_format audio_format; - int bitFormat; - int *supported[3]; - unsigned num_supported[3]; - int *unsupported[3]; - unsigned num_unsupported[3]; -}; - -enum oss_support { - OSS_SUPPORTED = 1, - OSS_UNSUPPORTED = 0, - OSS_UNKNOWN = -1, -}; -enum oss_param { - OSS_RATE = 0, - OSS_CHANNELS = 1, - OSS_BITS = 2, + /** + * The current input audio format. This is needed to reopen + * the device after cancel(). + */ + struct audio_format audio_format; }; /** @@ -76,188 +61,6 @@ oss_output_quark(void) return g_quark_from_static_string("oss_output"); } -static enum oss_param -oss_param_from_ioctl(unsigned param) -{ - enum oss_param idx = OSS_RATE; - - switch (param) { - case SNDCTL_DSP_SPEED: - idx = OSS_RATE; - break; - case SNDCTL_DSP_CHANNELS: - idx = OSS_CHANNELS; - break; - case SNDCTL_DSP_SAMPLESIZE: - idx = OSS_BITS; - break; - } - - return idx; -} - -static bool -oss_find_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_supported[idx]; i++) - if (od->supported[idx][i] == val) - return true; - - return false; -} - -static bool -oss_can_convert(int idx, int val) -{ - switch (idx) { - case OSS_BITS: - if (val != 16) - return false; - break; - case OSS_CHANNELS: - if (val != 2) - return false; - break; - } - - return true; -} - -static int -oss_get_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - int ret = -1; - int least = val; - int diff; - - for (unsigned i = 0; i < od->num_supported[idx]; i++) { - diff = od->supported[idx][i] - val; - if (diff < 0) - diff = -diff; - if (diff < least) { - if (!oss_can_convert(idx, od->supported[idx][i])) - continue; - - least = diff; - ret = od->supported[idx][i]; - } - } - - return ret; -} - -static bool -oss_find_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_unsupported[idx]; i++) { - if (od->unsupported[idx][i] == val) - return true; - } - - return false; -} - -static void -oss_add_supported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - od->num_supported[idx]++; - od->supported[idx] = g_realloc(od->supported[idx], - od->num_supported[idx] * sizeof(int)); - od->supported[idx][od->num_supported[idx] - 1] = val; -} - -static void -oss_add_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - enum oss_param idx = oss_param_from_ioctl(param); - - od->num_unsupported[idx]++; - od->unsupported[idx] = g_realloc(od->unsupported[idx], - od->num_unsupported[idx] * - sizeof(int)); - od->unsupported[idx][od->num_unsupported[idx] - 1] = val; -} - -static void -oss_remove_supported_param(struct oss_data *od, unsigned param, int val) -{ - unsigned j = 0; - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_supported[idx] - 1; i++) { - if (od->supported[idx][i] == val) - j = 1; - od->supported[idx][i] = od->supported[idx][i + j]; - } - - od->num_supported[idx]--; - od->supported[idx] = g_realloc(od->supported[idx], - od->num_supported[idx] * sizeof(int)); -} - -static void -oss_remove_unsupported_param(struct oss_data *od, unsigned param, int val) -{ - unsigned j = 0; - enum oss_param idx = oss_param_from_ioctl(param); - - for (unsigned i = 0; i < od->num_unsupported[idx] - 1; i++) { - if (od->unsupported[idx][i] == val) - j = 1; - od->unsupported[idx][i] = od->unsupported[idx][i + j]; - } - - od->num_unsupported[idx]--; - od->unsupported[idx] = g_realloc(od->unsupported[idx], - od->num_unsupported[idx] * - sizeof(int)); -} - -static enum oss_support -oss_param_is_supported(struct oss_data *od, unsigned param, int val) -{ - if (oss_find_supported_param(od, param, val)) - return OSS_SUPPORTED; - if (oss_find_unsupported_param(od, param, val)) - return OSS_UNSUPPORTED; - return OSS_UNKNOWN; -} - -static void -oss_set_supported(struct oss_data *od, unsigned param, int val) -{ - enum oss_support supported = oss_param_is_supported(od, param, val); - - if (supported == OSS_SUPPORTED) - return; - - if (supported == OSS_UNSUPPORTED) - oss_remove_unsupported_param(od, param, val); - - oss_add_supported_param(od, param, val); -} - -static void -oss_set_unsupported(struct oss_data *od, unsigned param, int val) -{ - enum oss_support supported = oss_param_is_supported(od, param, val); - - if (supported == OSS_UNSUPPORTED) - return; - - if (supported == OSS_SUPPORTED) - oss_remove_supported_param(od, param, val); - - oss_add_unsupported_param(od, param, val); -} - static struct oss_data * oss_data_new(void) { @@ -266,38 +69,12 @@ oss_data_new(void) ret->device = NULL; ret->fd = -1; - ret->supported[OSS_RATE] = NULL; - ret->supported[OSS_CHANNELS] = NULL; - ret->supported[OSS_BITS] = NULL; - ret->unsupported[OSS_RATE] = NULL; - ret->unsupported[OSS_CHANNELS] = NULL; - ret->unsupported[OSS_BITS] = NULL; - - ret->num_supported[OSS_RATE] = 0; - ret->num_supported[OSS_CHANNELS] = 0; - ret->num_supported[OSS_BITS] = 0; - ret->num_unsupported[OSS_RATE] = 0; - ret->num_unsupported[OSS_CHANNELS] = 0; - ret->num_unsupported[OSS_BITS] = 0; - - oss_set_supported(ret, SNDCTL_DSP_SPEED, 48000); - oss_set_supported(ret, SNDCTL_DSP_SPEED, 44100); - oss_set_supported(ret, SNDCTL_DSP_CHANNELS, 2); - oss_set_supported(ret, SNDCTL_DSP_SAMPLESIZE, 16); - return ret; } static void oss_data_free(struct oss_data *od) { - g_free(od->supported[OSS_RATE]); - g_free(od->supported[OSS_CHANNELS]); - g_free(od->supported[OSS_BITS]); - g_free(od->unsupported[OSS_RATE]); - g_free(od->unsupported[OSS_CHANNELS]); - g_free(od->unsupported[OSS_BITS]); - g_free(od); } @@ -343,7 +120,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; } @@ -419,113 +198,386 @@ oss_output_finish(void *data) oss_data_free(od); } -static int -oss_set_param(struct oss_data *od, unsigned param, int *value) +static void +oss_close(struct oss_data *od) { - int val = *value; - int copy; - enum oss_support supported = oss_param_is_supported(od, param, val); - - do { - if (supported == OSS_UNSUPPORTED) { - val = oss_get_supported_param(od, param, val); - if (copy < 0) - return -1; - } - copy = val; - if (ioctl(od->fd, param, ©)) { - oss_set_unsupported(od, param, val); - supported = OSS_UNSUPPORTED; - } else { - if (supported == OSS_UNKNOWN) { - oss_set_supported(od, param, val); - supported = OSS_SUPPORTED; - } - val = copy; - } - } while (supported == OSS_UNSUPPORTED); + if (od->fd >= 0) + close(od->fd); + od->fd = -1; +} + +/** + * A tri-state type for oss_try_ioctl(). + */ +enum oss_setup_result { + SUCCESS, + ERROR, + UNSUPPORTED, +}; + +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl_r(int fd, unsigned long request, int *value_r, + const char *msg, GError **error_r) +{ + assert(fd >= 0); + assert(value_r != NULL); + assert(msg != NULL); + assert(error_r == NULL || *error_r == NULL); - *value = val; + int ret = ioctl(fd, request, value_r); + if (ret >= 0) + return SUCCESS; - return 0; + if (errno == EINVAL) + return UNSUPPORTED; + + g_set_error(error_r, oss_output_quark(), errno, + "%s: %s", msg, g_strerror(errno)); + return ERROR; } -static void -oss_close(struct oss_data *od) +/** + * Invoke an ioctl on the OSS file descriptor. On success, SUCCESS is + * returned. If the parameter is not supported, UNSUPPORTED is + * returned. Any other failure returns ERROR and allocates a GError. + */ +static enum oss_setup_result +oss_try_ioctl(int fd, unsigned long request, int value, + const char *msg, GError **error_r) { - if (od->fd >= 0) - while (close(od->fd) && errno == EINTR) ; - od->fd = -1; + return oss_try_ioctl_r(fd, request, &value, msg, error_r); } /** - * Sets up the OSS device which was opened before. + * Set up the channel number, and attempts to find alternatives if the + * specified number is not supported. */ static bool -oss_setup(struct oss_data *od, GError **error) +oss_setup_channels(int fd, struct audio_format *audio_format, GError **error_r) { - int tmp; - - tmp = od->audio_format.channels; - if (oss_set_param(od, SNDCTL_DSP_CHANNELS, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u channels: %s", - od->device, od->audio_format.channels, - strerror(errno)); + const char *const msg = "Failed to set channel count"; + int channels = audio_format->channels; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: return false; + + case UNSUPPORTED: + break; } - od->audio_format.channels = tmp; - tmp = od->audio_format.sample_rate; - if (oss_set_param(od, SNDCTL_DSP_SPEED, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u Hz audio: %s", - od->device, od->audio_format.sample_rate, - strerror(errno)); - return false; + for (unsigned i = 1; i < 2; ++i) { + if (i == audio_format->channels) + /* don't try that again */ + continue; + + channels = i; + result = oss_try_ioctl_r(fd, SNDCTL_DSP_CHANNELS, &channels, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_channel_count(channels)) + break; + + audio_format->channels = channels; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } } - od->audio_format.sample_rate = tmp; - switch (od->audio_format.bits) { - case 8: - tmp = AFMT_S8; - break; - case 16: - tmp = AFMT_S16_MPD; + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Set up the sample rate, and attempts to find alternatives if the + * specified sample rate is not supported. + */ +static bool +oss_setup_sample_rate(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample rate"; + int sample_rate = audio_format->sample_rate; + enum oss_setup_result result = + oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: break; + } + + static const int sample_rates[] = { 48000, 44100, 0 }; + for (unsigned i = 0; sample_rates[i] != 0; ++i) { + sample_rate = sample_rates[i]; + if (sample_rate == (int)audio_format->sample_rate) + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SPEED, &sample_rate, + msg, error_r); + switch (result) { + case SUCCESS: + if (!audio_valid_sample_rate(sample_rate)) + break; + + audio_format->sample_rate = sample_rate; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; +} + +/** + * Convert a MPD sample format to its OSS counterpart. Returns + * AFMT_QUERY if there is no direct counterpart. + */ +static int +sample_format_to_oss(enum sample_format format) +{ + switch (format) { + case SAMPLE_FORMAT_UNDEFINED: + return AFMT_QUERY; + + case SAMPLE_FORMAT_S8: + return AFMT_S8; + + case SAMPLE_FORMAT_S16: + return AFMT_S16_NE; + + case SAMPLE_FORMAT_S24: +#ifdef AFMT_S24_PACKED + return AFMT_S24_PACKED; +#else + return AFMT_QUERY; +#endif + + case SAMPLE_FORMAT_S24_P32: +#ifdef AFMT_S24_NE + return AFMT_S24_NE; +#else + return AFMT_QUERY; +#endif + + case SAMPLE_FORMAT_S32: +#ifdef AFMT_S32_NE + return AFMT_S32_NE; +#else + return AFMT_QUERY; +#endif + } + + return AFMT_QUERY; +} + +/** + * Convert an OSS sample format to its MPD counterpart. Returns + * SAMPLE_FORMAT_UNDEFINED if there is no direct counterpart. + */ +static enum sample_format +sample_format_from_oss(int format) +{ + switch (format) { + case AFMT_S8: + return SAMPLE_FORMAT_S8; + + case AFMT_S16_NE: + return SAMPLE_FORMAT_S16; + +#ifdef AFMT_S24_PACKED + case AFMT_S24_PACKED: + return SAMPLE_FORMAT_S24; +#endif + +#ifdef AFMT_S24_NE + case AFMT_S24_NE: + return SAMPLE_FORMAT_S24_P32; +#endif + +#ifdef AFMT_S32_NE + case AFMT_S32_NE: + return SAMPLE_FORMAT_S32; +#endif default: - /* not supported by OSS - fall back to 16 bit */ - od->audio_format.bits = 16; - tmp = AFMT_S16_MPD; - break; + return SAMPLE_FORMAT_UNDEFINED; } +} - if (oss_set_param(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) { - g_set_error(error, oss_output_quark(), errno, - "OSS device \"%s\" does not support %u bit audio: %s", - od->device, tmp, strerror(errno)); +/** + * Set up the sample format, and attempts to find alternatives if the + * specified format is not supported. + */ +static bool +oss_setup_sample_format(int fd, struct audio_format *audio_format, + GError **error_r) +{ + const char *const msg = "Failed to set sample format"; + int oss_format = sample_format_to_oss(audio_format->format); + enum oss_setup_result result = oss_format != AFMT_QUERY + ? oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r) + : UNSUPPORTED; + enum sample_format mpd_format; + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + return true; + + case ERROR: return false; + + case UNSUPPORTED: + break; } - return true; + /* the requested sample format is not available - probe for + other formats supported by MPD */ + + static const enum sample_format sample_formats[] = { + SAMPLE_FORMAT_S24_P32, + SAMPLE_FORMAT_S32, + SAMPLE_FORMAT_S24, + SAMPLE_FORMAT_S16, + SAMPLE_FORMAT_S8, + SAMPLE_FORMAT_UNDEFINED /* sentinel */ + }; + + for (unsigned i = 0; sample_formats[i] != SAMPLE_FORMAT_UNDEFINED; ++i) { + mpd_format = sample_formats[i]; + if (mpd_format == audio_format->format) + /* don't try that again */ + continue; + + oss_format = sample_format_to_oss(mpd_format); + if (oss_format == AFMT_QUERY) + /* not supported by this OSS version */ + continue; + + result = oss_try_ioctl_r(fd, SNDCTL_DSP_SAMPLESIZE, + &oss_format, msg, error_r); + switch (result) { + case SUCCESS: + mpd_format = sample_format_from_oss(oss_format); + if (mpd_format == SAMPLE_FORMAT_UNDEFINED) + break; + + audio_format->format = mpd_format; + return true; + + case ERROR: + return false; + + case UNSUPPORTED: + break; + } + } + + g_set_error(error_r, oss_output_quark(), EINVAL, "%s", msg); + return false; } +/** + * Sets up the OSS device which was opened before. + */ static bool -oss_open(struct oss_data *od, GError **error) +oss_setup(struct oss_data *od, struct audio_format *audio_format, + GError **error_r) { - bool success; + return oss_setup_channels(od->fd, audio_format, error_r) && + oss_setup_sample_rate(od->fd, audio_format, error_r) && + oss_setup_sample_format(od->fd, audio_format, error_r); +} - if ((od->fd = open(od->device, O_WRONLY)) < 0) { - g_set_error(error, oss_output_quark(), errno, +/** + * Reopen the device with the saved audio_format, without any probing. + */ +static bool +oss_reopen(struct oss_data *od, GError **error_r) +{ + assert(od->fd < 0); + + od->fd = open_cloexec(od->device, O_WRONLY, 0); + if (od->fd < 0) { + g_set_error(error_r, oss_output_quark(), errno, "Error opening OSS device \"%s\": %s", od->device, strerror(errno)); return false; } - success = oss_setup(od, error); - if (!success) { + enum oss_setup_result result; + + const char *const msg1 = "Failed to set channel count"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_CHANNELS, + od->audio_format.channels, msg1, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg1); + return false; + } + + const char *const msg2 = "Failed to set sample rate"; + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SPEED, + od->audio_format.sample_rate, msg2, error_r); + if (result != SUCCESS) { + oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg2); + return false; + } + + const char *const msg3 = "Failed to set sample format"; + assert(sample_format_to_oss(od->audio_format.format) != AFMT_QUERY); + result = oss_try_ioctl(od->fd, SNDCTL_DSP_SAMPLESIZE, + sample_format_to_oss(od->audio_format.format), + msg3, error_r); + if (result != SUCCESS) { oss_close(od); + if (result == UNSUPPORTED) + g_set_error(error_r, oss_output_quark(), EINVAL, + "%s", msg3); return false; } @@ -535,18 +587,23 @@ oss_open(struct oss_data *od, GError **error) static bool oss_output_open(void *data, struct audio_format *audio_format, GError **error) { - bool ret; struct oss_data *od = data; - od->audio_format = *audio_format; - - ret = oss_open(od, error); - if (!ret) + 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)); return false; + } - *audio_format = od->audio_format; + if (!oss_setup(od, audio_format, error)) { + oss_close(od); + return false; + } - return ret; + od->audio_format = *audio_format; + return true; } static void @@ -575,7 +632,7 @@ oss_output_play(void *data, const void *chunk, size_t size, GError **error) ssize_t ret; /* reopen the device since it was closed by dropBufferedAudio */ - if (od->fd < 0 && !oss_open(od, error)) + if (od->fd < 0 && !oss_reopen(od, error)) return 0; while (true) { @@ -601,5 +658,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..17d138d35 100644 --- a/src/output/osx_plugin.c +++ b/src/output/osx_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..1d1aec7b1 100644 --- a/src/output/pipe_output_plugin.c +++ b/src/output/pipe_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..d29fbd705 --- /dev/null +++ b/src/output/pulse_output_plugin.c @@ -0,0 +1,825 @@ +/* + * Copyright (C) 2003-2010 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); + po->context = NULL; + 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); + po->mainloop = NULL; + + 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); + po->mainloop = NULL; + 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..06e3aec43 --- /dev/null +++ b/src/output/pulse_output_plugin.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2010 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..c01d927c4 --- /dev/null +++ b/src/output/recorder_output_plugin.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2003-2010 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 "open.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.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|O_BINARY, + 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 dbc56f337..baaeccf92 100644 --- a/src/output/shout_plugin.c +++ b/src/output/shout_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,11 @@ * 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 "mpd_error.h" #include <shout/shout.h> #include <glib.h> @@ -100,8 +102,8 @@ static void free_shout_data(struct shout_data *sd) #define check_block_param(name) { \ block_param = config_get_block_param(param, name); \ if (!block_param) { \ - g_error("no \"%s\" defined for shout device defined at line " \ - "%i\n", name, param->line); \ + MPD_ERROR("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ } \ } @@ -126,6 +128,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 +200,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) { @@ -335,7 +342,6 @@ write_page(struct shout_data *sd, GError **error) if (sd->buf.len == 0) return true; - shout_sync(sd->shout_conn); err = shout_send(sd->shout_conn, sd->buf.data, sd->buf.len); if (!handle_shout_error(sd, err, error)) return false; @@ -434,6 +440,18 @@ my_shout_open_device(void *data, struct audio_format *audio_format, return true; } +static unsigned +my_shout_delay(void *data) +{ + struct shout_data *sd = (struct shout_data *)data; + + int delay = shout_delay(sd->shout_conn); + if (delay < 0) + delay = 0; + + return delay; +} + static size_t my_shout_play(void *data, const void *chunk, size_t size, GError **error) { @@ -448,15 +466,8 @@ my_shout_play(void *data, const void *chunk, size_t size, GError **error) static bool my_shout_pause(void *data) { - struct shout_data *sd = (struct shout_data *)data; static const char silence[1020]; - if (shout_delay(sd->shout_conn) > 500) { - /* cap the latency for unpause */ - g_usleep(500000); - return true; - } - return my_shout_play(data, silence, sizeof(silence), NULL); } @@ -471,10 +482,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; @@ -533,6 +544,7 @@ const struct audio_output_plugin shoutPlugin = { .init = my_shout_init_driver, .finish = my_shout_finish_driver, .open = my_shout_open_device, + .delay = my_shout_delay, .play = my_shout_play, .pause = my_shout_pause, .cancel = my_shout_drop_buffered_audio, diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c index 5febf0afc..deb3298a5 100644 --- a/src/output/solaris_output_plugin.c +++ b/src/output/solaris_output_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/winmm_output_plugin.c b/src/output/winmm_output_plugin.c new file mode 100644 index 000000000..b9687874d --- /dev/null +++ b/src/output/winmm_output_plugin.c @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2003-2010 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 "pcm_buffer.h" +#include "mixer_list.h" +#include "winmm_output_plugin.h" + +#include <stdlib.h> +#include <string.h> +#include <windows.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "winmm_output" + +struct winmm_buffer { + struct pcm_buffer buffer; + + WAVEHDR hdr; +}; + +struct winmm_output { + UINT device_id; + HWAVEOUT handle; + + /** + * This event is triggered by Windows when a buffer is + * finished. + */ + HANDLE event; + + struct winmm_buffer buffers[8]; + unsigned next_buffer; +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +winmm_output_quark(void) +{ + return g_quark_from_static_string("winmm_output"); +} + +HWAVEOUT +winmm_output_get_handle(struct winmm_output* output) +{ + return output->handle; +} + +static bool +winmm_output_test_default_device(void) +{ + return waveOutGetNumDevs() > 0; +} + +static UINT +get_device_id(const char *device_name) +{ + /* if device is not specified use wave mapper */ + if (device_name == NULL) + return WAVE_MAPPER; + + /* check for device id */ + char *endptr; + UINT id = strtoul(device_name, &endptr, 0); + if (endptr > device_name && *endptr == 0) + return id; + + /* check for device name */ + for (UINT i = 0; i < waveOutGetNumDevs(); i++) { + WAVEOUTCAPS caps; + MMRESULT result = waveOutGetDevCaps(i, &caps, sizeof(caps)); + if (result != MMSYSERR_NOERROR) + continue; + /* szPname is only 32 chars long, so it is often truncated. + Use partial match to work around this. */ + if (strstr(device_name, caps.szPname) == device_name) + return i; + } + + /* fallback to wave mapper */ + return WAVE_MAPPER; +} + +static void * +winmm_output_init(G_GNUC_UNUSED const struct audio_format *audio_format, + G_GNUC_UNUSED const struct config_param *param, + G_GNUC_UNUSED GError **error) +{ + struct winmm_output *wo = g_new(struct winmm_output, 1); + const char *device = config_get_block_string(param, "device", NULL); + wo->device_id = get_device_id(device); + return wo; +} + +static void +winmm_output_finish(void *data) +{ + struct winmm_output *wo = data; + + g_free(wo); +} + +static bool +winmm_output_open(void *data, struct audio_format *audio_format, + GError **error_r) +{ + struct winmm_output *wo = data; + + wo->event = CreateEvent(NULL, false, false, NULL); + if (wo->event == NULL) { + g_set_error(error_r, winmm_output_quark(), 0, + "CreateEvent() failed"); + return false; + } + + switch (audio_format->format) { + case SAMPLE_FORMAT_S8: + case SAMPLE_FORMAT_S16: + break; + + case SAMPLE_FORMAT_S24: + case SAMPLE_FORMAT_S24_P32: + case SAMPLE_FORMAT_S32: + case SAMPLE_FORMAT_UNDEFINED: + /* we havn't tested formats other than S16 */ + audio_format->format = SAMPLE_FORMAT_S16; + break; + } + + if (audio_format->channels > 2) + /* same here: more than stereo was not tested */ + audio_format->channels = 2; + + WAVEFORMATEX format; + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = audio_format->channels; + format.nSamplesPerSec = audio_format->sample_rate; + format.nBlockAlign = audio_format_frame_size(audio_format); + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.wBitsPerSample = audio_format_sample_size(audio_format) * 8; + format.cbSize = 0; + + MMRESULT result = waveOutOpen(&wo->handle, wo->device_id, &format, + (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); + if (result != MMSYSERR_NOERROR) { + CloseHandle(wo->event); + g_set_error(error_r, winmm_output_quark(), result, + "waveOutOpen() failed"); + return false; + } + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { + pcm_buffer_init(&wo->buffers[i].buffer); + memset(&wo->buffers[i].hdr, 0, sizeof(wo->buffers[i].hdr)); + } + + wo->next_buffer = 0; + + return true; +} + +static void +winmm_output_close(void *data) +{ + struct winmm_output *wo = data; + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) + pcm_buffer_deinit(&wo->buffers[i].buffer); + + waveOutClose(wo->handle); + + CloseHandle(wo->event); +} + +/** + * Copy data into a buffer, and prepare the wave header. + */ +static bool +winmm_set_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, + const void *data, size_t size, + GError **error_r) +{ + void *dest = pcm_buffer_get(&buffer->buffer, size); + if (dest == NULL) { + g_set_error(error_r, winmm_output_quark(), 0, + "Out of memory"); + return false; + } + + memcpy(dest, data, size); + + memset(&buffer->hdr, 0, sizeof(buffer->hdr)); + buffer->hdr.lpData = dest; + buffer->hdr.dwBufferLength = size; + + MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + if (result != MMSYSERR_NOERROR) { + g_set_error(error_r, winmm_output_quark(), result, + "waveOutPrepareHeader() failed"); + return false; + } + + return true; +} + +/** + * Wait until the buffer is finished. + */ +static bool +winmm_drain_buffer(struct winmm_output *wo, struct winmm_buffer *buffer, + GError **error_r) +{ + if ((buffer->hdr.dwFlags & WHDR_DONE) == WHDR_DONE) + /* already finished */ + return true; + + while (true) { + MMRESULT result = waveOutUnprepareHeader(wo->handle, + &buffer->hdr, + sizeof(buffer->hdr)); + if (result == MMSYSERR_NOERROR) + return true; + else if (result != WAVERR_STILLPLAYING) { + g_set_error(error_r, winmm_output_quark(), result, + "waveOutUnprepareHeader() failed"); + return false; + } + + /* wait some more */ + WaitForSingleObject(wo->event, INFINITE); + } +} + +static size_t +winmm_output_play(void *data, const void *chunk, size_t size, GError **error_r) +{ + struct winmm_output *wo = data; + + /* get the next buffer from the ring and prepare it */ + struct winmm_buffer *buffer = &wo->buffers[wo->next_buffer]; + if (!winmm_drain_buffer(wo, buffer, error_r) || + !winmm_set_buffer(wo, buffer, chunk, size, error_r)) + return 0; + + /* enqueue the buffer */ + MMRESULT result = waveOutWrite(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + if (result != MMSYSERR_NOERROR) { + waveOutUnprepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + g_set_error(error_r, winmm_output_quark(), result, + "waveOutWrite() failed"); + return 0; + } + + /* mark our buffer as "used" */ + wo->next_buffer = (wo->next_buffer + 1) % + G_N_ELEMENTS(wo->buffers); + + return size; +} + +static bool +winmm_drain_all_buffers(struct winmm_output *wo, GError **error_r) +{ + for (unsigned i = wo->next_buffer; i < G_N_ELEMENTS(wo->buffers); ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) + return false; + + for (unsigned i = 0; i < wo->next_buffer; ++i) + if (!winmm_drain_buffer(wo, &wo->buffers[i], error_r)) + return false; + + return true; +} + +static void +winmm_stop(struct winmm_output *wo) +{ + waveOutReset(wo->handle); + + for (unsigned i = 0; i < G_N_ELEMENTS(wo->buffers); ++i) { + struct winmm_buffer *buffer = &wo->buffers[i]; + waveOutUnprepareHeader(wo->handle, &buffer->hdr, + sizeof(buffer->hdr)); + } +} + +static void +winmm_output_drain(void *data) +{ + struct winmm_output *wo = data; + + if (!winmm_drain_all_buffers(wo, NULL)) + winmm_stop(wo); +} + +static void +winmm_output_cancel(void *data) +{ + struct winmm_output *wo = data; + + winmm_stop(wo); +} + +const struct audio_output_plugin winmm_output_plugin = { + .name = "winmm", + .test_default_device = winmm_output_test_default_device, + .init = winmm_output_init, + .finish = winmm_output_finish, + .open = winmm_output_open, + .close = winmm_output_close, + .play = winmm_output_play, + .drain = winmm_output_drain, + .cancel = winmm_output_cancel, + .mixer_plugin = &winmm_mixer_plugin, +}; diff --git a/src/output/winmm_output_plugin.h b/src/output/winmm_output_plugin.h new file mode 100644 index 000000000..39507275a --- /dev/null +++ b/src/output/winmm_output_plugin.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2010 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_WINMM_OUTPUT_PLUGIN_H +#define MPD_WINMM_OUTPUT_PLUGIN_H + +#include <windows.h> + +struct winmm_output; + +HWAVEOUT winmm_output_get_handle(struct winmm_output*); + +#endif diff --git a/src/output_all.c b/src/output_all.c index 86e78e13e..19c0f0166 100644 --- a/src/output_all.c +++ b/src/output_all.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -25,6 +26,7 @@ #include "pipe.h" #include "buffer.h" #include "player_control.h" +#include "mpd_error.h" #ifndef NDEBUG #include "chunk.h" @@ -52,6 +54,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; @@ -116,17 +123,17 @@ audio_output_all_init(void) if (!audio_output_init(output, param, &error)) { if (param != NULL) - g_error("line %i: %s", - param->line, error->message); + MPD_ERROR("line %i: %s", + param->line, error->message); else - g_error("%s", error->message); + MPD_ERROR("%s", error->message); } /* require output names to be unique: */ for (j = 0; j < i; j++) { if (!strcmp(output->name, audio_outputs[j].name)) { - g_error("output devices with identical " - "names: %s\n", output->name); + MPD_ERROR("output devices with identical " + "names: %s\n", output->name); } } } @@ -138,6 +145,7 @@ audio_output_all_finish(void) unsigned int i; for (i = 0; i < num_audio_outputs; i++) { + audio_output_disable(&audio_outputs[i]); audio_output_finish(&audio_outputs[i]); } @@ -148,6 +156,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 +183,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 +205,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 +295,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 +330,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 +443,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 +475,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 +496,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 +517,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 +526,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 +556,43 @@ audio_output_all_close(void) g_music_buffer = NULL; audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_release(void) +{ + unsigned int i; + + for (i = 0; i < num_audio_outputs; ++i) + audio_output_release(&audio_outputs[i]); + + if (g_mp != NULL) { + assert(g_music_buffer != NULL); + + music_pipe_clear(g_mp, g_music_buffer); + music_pipe_free(g_mp); + g_mp = NULL; + } + + g_music_buffer = NULL; + + audio_format_clear(&input_audio_format); + + audio_output_all_elapsed_time = -1.0; +} + +void +audio_output_all_song_border(void) +{ + /* clear the elapsed_time pointer at the beginning of a new + song */ + audio_output_all_elapsed_time = 0.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..a579bf5f1 100644 --- a/src/output_all.h +++ b/src/output_all.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 @@ -85,6 +92,13 @@ void audio_output_all_close(void); /** + * Closes all audio outputs. Outputs with the "always_on" flag are + * put into pause mode. + */ +void +audio_output_all_release(void); + +/** * Enqueue a #music_chunk object for playing, i.e. pushes it to a * #music_pipe. * @@ -123,9 +137,29 @@ 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); +/** + * Indicate that a new song will begin now. + */ +void +audio_output_all_song_border(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_api.h b/src/output_api.h index c31893bc6..8e002dd48 100644 --- a/src/output_api.h +++ b/src/output_api.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_command.c b/src/output_command.c index 5da176dde..825884e8e 100644 --- a/src/output_command.c +++ b/src/output_command.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_command.h b/src/output_command.h index d92ff5ec8..fab015c3f 100644 --- a/src/output_command.h +++ b/src/output_command.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_control.c b/src/output_control.c index ecd226821..161404f78 100644 --- a/src/output_control.c +++ b/src/output_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,29 +51,55 @@ 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); } -/** - * Like ao_command(), but assumes the object is locked by the caller. - */ -static void -ao_command_locked(struct audio_output *ao, enum audio_output_command cmd) +static void ao_command_async(struct audio_output *ao, + enum audio_output_command cmd) { assert(ao->command == AO_COMMAND_NONE); ao->command = cmd; + 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_unlock(ao->mutex); - ao_command_wait(ao); g_mutex_lock(ao->mutex); + ao_command(ao, AO_COMMAND_ENABLE); + g_mutex_unlock(ao->mutex); } -static void ao_command_async(struct audio_output *ao, - enum audio_output_command cmd) +void +audio_output_disable(struct audio_output *ao) { - assert(ao->command == AO_COMMAND_NONE); - ao->command = cmd; - notify_signal(&ao->notify); + 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 void @@ -94,9 +124,13 @@ audio_output_open(struct audio_output *ao, if (ao->open && audio_format_equals(audio_format, &ao->in_audio_format)) { - assert(ao->pipe == mp); + assert(ao->pipe == mp || + (ao->always_on && ao->pause)); if (ao->pause) { + ao->chunk = NULL; + ao->pipe = mp; + /* unpause with the CANCEL command; this is a hack, but suits well for forcing the thread to leave the ao_pause() thread, and we need @@ -104,7 +138,11 @@ audio_output_open(struct audio_output *ao, /* we're not using audio_output_cancel() here, because that function is asynchronous */ - ao_command_locked(ao, AO_COMMAND_CANCEL); + 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; @@ -113,33 +151,49 @@ 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_locked(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_locked(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) +{ + assert(ao != NULL); + + 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, @@ -149,28 +203,29 @@ audio_output_update(struct audio_output *ao, g_mutex_lock(ao->mutex); - if (ao->enabled) { + if (ao->enabled && ao->really_enabled) { if (ao->fail_timer == NULL || g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) { - bool ret = audio_output_open(ao, audio_format, mp); + bool success = audio_output_open(ao, audio_format, mp); g_mutex_unlock(ao->mutex); - return ret; + return success; } } else if (audio_output_is_open(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); - notify_signal(&ao->notify); + if (audio_output_is_open(ao)) + g_cond_signal(ao->cond); + + g_mutex_unlock(ao->mutex); } void audio_output_pause(struct audio_output *ao) @@ -181,29 +236,36 @@ 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_cancel(struct audio_output *ao) +void +audio_output_drain_async(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_DRAIN); + g_mutex_unlock(ao->mutex); } -static void -audio_output_close_locked(struct audio_output *ao) +void audio_output_cancel(struct audio_output *ao) { - assert(ao != NULL); - assert(!ao->open || ao->fail_timer == NULL); - - if (ao->mixer != NULL) - mixer_auto_close(ao->mixer); + g_mutex_lock(ao->mutex); + if (audio_output_is_open(ao)) + ao_command_async(ao, AO_COMMAND_CANCEL); + g_mutex_unlock(ao->mutex); +} - if (ao->open) - ao_command_locked(ao, AO_COMMAND_CLOSE); - else if (ao->fail_timer != NULL) { - g_timer_destroy(ao->fail_timer); - ao->fail_timer = NULL; - } +void +audio_output_release(struct audio_output *ao) +{ + if (ao->always_on) + audio_output_pause(ao); + else + audio_output_close(ao); } void audio_output_close(struct audio_output *ao) @@ -223,7 +285,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); } @@ -232,6 +296,16 @@ 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); + + if (ao->replay_gain_filter != NULL) + filter_free(ao->replay_gain_filter); + + if (ao->other_replay_gain_filter != NULL) + filter_free(ao->other_replay_gain_filter); + + filter_free(ao->filter); + + pcm_buffer_deinit(&ao->cross_fade_buffer); } diff --git a/src/output_control.h b/src/output_control.h index ce3abe3f6..7f4f4a53c 100644 --- a/src/output_control.h +++ b/src/output_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,8 +67,20 @@ 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); + +/** + * Closes the audio output, but if the "always_on" flag is set, put it + * into pause mode instead. + */ +void +audio_output_release(struct audio_output *ao); + void audio_output_finish(struct audio_output *ao); #endif diff --git a/src/output_init.c b/src/output_init.c index 927424324..f4700dfb2 100644 --- a/src/output_init.c +++ b/src/output_init.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,21 +17,34 @@ * 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 "filter/replay_gain_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 +69,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 +179,115 @@ 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->always_on = config_get_block_bool(param, "always_on", false); 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); + pcm_buffer_init(&ao->cross_fade_buffer); - ao->config_audio_format = format != NULL; - if (ao->config_audio_format) { - bool ret; + /* set up the filter chain */ - ret = audio_format_parse(&ao->out_audio_format, format, - error); - if (!ret) - return false; + ao->filter = filter_chain_new(); + assert(ao->filter != NULL); + + /* create the replay_gain filter */ + + const char *replay_gain_handler = + config_get_block_string(param, "replay_gain_handler", + "software"); + + if (strcmp(replay_gain_handler, "none") != 0) { + ao->replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->replay_gain_filter != NULL); + + ao->replay_gain_serial = 0; + + ao->other_replay_gain_filter = filter_new(&replay_gain_filter_plugin, + param, NULL); + assert(ao->other_replay_gain_filter != NULL); + + ao->other_replay_gain_serial = 0; + } else { + ao->replay_gain_filter = NULL; + ao->other_replay_gain_filter = NULL; + } + + /* create the normalization filter (if configured) */ + + 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); + } + + /* use the hardware mixer for replay gain? */ + + if (strcmp(replay_gain_handler, "mixer") == 0) { + if (ao->mixer != NULL) + replay_gain_filter_set_mixer(ao->replay_gain_filter, + ao->mixer, 100); + else + g_warning("No such mixer for output '%s'", ao->name); + } else if (strcmp(replay_gain_handler, "software") != 0 && + ao->replay_gain_filter != NULL) { + g_set_error(error_r, audio_output_quark(), 0, + "Invalid \"replay_gain_handler\" value"); + return false; + } + + /* 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 b863c9ecc..18d431352 100644 --- a/src/output_internal.h +++ b/src/output_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,16 +21,33 @@ #define MPD_OUTPUT_INTERNAL_H #include "audio_format.h" -#include "pcm_convert.h" -#include "notify.h" +#include "pcm_buffer.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,10 +77,10 @@ struct audio_output { struct mixer *mixer; /** - * This flag is true, when the audio_format of this device is - * configured in mpd.conf. + * Shall this output always play something (i.e. silence), + * even when playback is stopped? */ - bool config_audio_format; + bool always_on; /** * Has the user enabled this device? @@ -71,6 +88,12 @@ struct audio_output { bool enabled; /** + * Is this device actually enabled, i.e. the "enable" method + * has succeeded? + */ + bool really_enabled; + + /** * Is the device (already) open and functional? * * This attribute may only be modified by the output thread. @@ -94,6 +117,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 +135,55 @@ struct audio_output { */ struct audio_format out_audio_format; - struct pcm_convert_state convert_state; + /** + * The buffer used to allocate the cross-fading result. + */ + struct pcm_buffer cross_fade_buffer; /** - * The thread handle, or NULL if the output thread isn't - * running. + * The filter object of this audio output. This is an + * instance of chain_filter_plugin. */ - GThread *thread; + struct filter *filter; /** - * Notify object for the thread. + * The replay_gain_filter_plugin instance of this audio + * output. */ - struct notify notify; + struct filter *replay_gain_filter; + + /** + * The serial number of the last replay gain info. 0 means no + * replay gain info was available. + */ + unsigned replay_gain_serial; + + /** + * The replay_gain_filter_plugin instance of this audio + * output, to be applied to the second chunk during + * cross-fading. + */ + struct filter *other_replay_gain_filter; + + /** + * The serial number of the last replay gain info by the + * "other" chunk during cross-fading. + */ + unsigned other_replay_gain_serial; + + /** + * 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. + */ + struct filter *convert_filter; + + /** + * The thread handle, or NULL if the output thread isn't + * running. + */ + GThread *thread; /** * The next command to be performed by the output thread. @@ -137,6 +202,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..8238f581b 100644 --- a/src/output_list.c +++ b/src/output_list.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,16 @@ 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; +extern const struct audio_output_plugin winmm_output_plugin; +extern const struct audio_output_plugin ffado_output_plugin; const struct audio_output_plugin *audio_output_plugins[] = { #ifdef HAVE_SHOUT @@ -55,6 +59,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 +69,26 @@ 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 +#ifdef ENABLE_WINMM_OUTPUT + &winmm_output_plugin, +#endif +#ifdef ENABLE_FFADO_OUTPUT + &ffado_output_plugin, +#endif NULL }; diff --git a/src/output_list.h b/src/output_list.h index b6f82163c..d72bc224b 100644 --- a/src/output_list.h +++ b/src/output_list.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_plugin.h b/src/output_plugin.h index 13dba0d0b..36e17ed1b 100644 --- a/src/output_plugin.h +++ b/src/output_plugin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 @@ -83,6 +101,16 @@ struct audio_output_plugin { void (*close)(void *data); /** + * Returns a positive number if the output thread shall delay + * the next call to play() or pause(). This should be + * implemented instead of doing a sleep inside the plugin, + * because this allows MPD to listen to commands meanwhile. + * + * @return the number of milliseconds to wait + */ + unsigned (*delay)(void *data); + + /** * Display metadata for the next chunk. Optional method, * because not all devices can display metadata. */ @@ -99,6 +127,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 +183,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) @@ -163,6 +212,14 @@ ao_plugin_close(const struct audio_output_plugin *plugin, void *data) plugin->close(data); } +static inline unsigned +ao_plugin_delay(const struct audio_output_plugin *plugin, void *data) +{ + return plugin->delay != NULL + ? plugin->delay(data) + : 0; +} + static inline void ao_plugin_send_tag(const struct audio_output_plugin *plugin, void *data, const struct tag *tag) @@ -180,6 +237,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..7a747ad2f 100644 --- a/src/output_print.c +++ b/src/output_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "output_print.h" #include "output_internal.h" #include "output_all.h" diff --git a/src/output_print.h b/src/output_print.h index aec6f0f87..5ad7e34c7 100644 --- a/src/output_print.h +++ b/src/output_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/output_state.c b/src/output_state.c index c7e6c8579..e1187b951 100644 --- a/src/output_state.c +++ b/src/output_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..962ccd97a 100644 --- a/src/output_state.h +++ b/src/output_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 035cf99c1..380956fac 100644 --- a/src/output_thread.c +++ b/src/output_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,18 @@ * 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 "pcm_mix.h" +#include "filter_plugin.h" +#include "filter/convert_filter_plugin.h" +#include "filter/replay_gain_filter_plugin.h" +#include "mpd_error.h" #include <glib.h> @@ -37,71 +43,423 @@ 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 const struct audio_format * +ao_filter_open(struct audio_output *ao, + struct audio_format *audio_format, + GError **error_r) +{ + /* the replay_gain filter cannot fail here */ + if (ao->replay_gain_filter != NULL) + filter_open(ao->replay_gain_filter, audio_format, error_r); + if (ao->other_replay_gain_filter != NULL) + filter_open(ao->other_replay_gain_filter, audio_format, + error_r); + + const struct audio_format *af + = filter_open(ao->filter, audio_format, error_r); + if (af == NULL) { + if (ao->replay_gain_filter != NULL) + filter_close(ao->replay_gain_filter); + if (ao->other_replay_gain_filter != NULL) + filter_close(ao->other_replay_gain_filter); + } + + return af; +} + +static void +ao_filter_close(struct audio_output *ao) +{ + if (ao->replay_gain_filter != NULL) + filter_close(ao->replay_gain_filter); + if (ao->other_replay_gain_filter != NULL) + filter_close(ao->other_replay_gain_filter); + + filter_close(ao->filter); +} + +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->pipe != NULL); + assert(ao->chunk == NULL); + + if (ao->fail_timer != NULL) { + /* this can only happen when this + output thread fails while + audio_output_open() is run in the + player thread */ + g_timer_destroy(ao->fail_timer); + ao->fail_timer = 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 = ao_filter_open(ao, &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); + + ao_filter_close(ao); + 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); + ao_filter_close(ao); + + g_mutex_lock(ao->mutex); g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name); } -static bool -ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +static void +ao_reopen_filter(struct audio_output *ao) { - const char *data = chunk->data; - size_t size = chunk->length; + const struct audio_format *filter_audio_format; GError *error = NULL; + ao_filter_close(ao); + filter_audio_format = ao_filter_open(ao, &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); +} + +/** + * Wait until the output's delay reaches zero. + * + * @return true if playback should be continued, false if a command + * was issued + */ +static bool +ao_wait(struct audio_output *ao) +{ + while (true) { + unsigned delay = ao_plugin_delay(ao->plugin, ao->data); + if (delay == 0) + return true; + + GTimeVal tv; + g_get_current_time(&tv); + g_time_val_add(&tv, delay * 1000); + g_cond_timed_wait(ao->cond, ao->mutex, &tv); + + if (ao->command != AO_COMMAND_NONE) + return false; + } +} + +static const char * +ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk, + struct filter *replay_gain_filter, + unsigned *replay_gain_serial_p, + size_t *length_r) +{ + assert(chunk != 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) + const char *data = chunk->data; + size_t length = chunk->length; + + (void)ao; + + assert(length % audio_format_frame_size(&ao->in_audio_format) == 0); + + if (length > 0 && replay_gain_filter != NULL) { + if (chunk->replay_gain_serial != *replay_gain_serial_p) { + replay_gain_filter_set_info(replay_gain_filter, + chunk->replay_gain_serial != 0 + ? &chunk->replay_gain_info + : NULL); + *replay_gain_serial_p = chunk->replay_gain_serial; + } + + GError *error = NULL; + data = filter_filter(replay_gain_filter, data, length, + &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + } + + *length_r = length; + return data; +} + +static const char * +ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk, + size_t *length_r) +{ + GError *error = NULL; + + size_t length; + const char *data = ao_chunk_data(ao, chunk, ao->replay_gain_filter, + &ao->replay_gain_serial, &length); + if (data == NULL) + return NULL; + + if (length == 0) { + /* empty chunk, nothing to do */ + *length_r = 0; + return data; + } + + /* cross-fade */ + + if (chunk->other != NULL) { + size_t other_length; + const char *other_data = + ao_chunk_data(ao, chunk->other, + ao->other_replay_gain_filter, + &ao->other_replay_gain_serial, + &other_length); + if (other_data == NULL) + return NULL; + + if (other_length == 0) { + *length_r = 0; + return data; + } + + /* if the "other" chunk is longer, then that trailer + is used as-is, without mixing; it is part of the + "next" song being faded in, and if there's a rest, + it means cross-fading ends here */ + + if (length > other_length) + length = other_length; + + char *dest = pcm_buffer_get(&ao->cross_fade_buffer, + other_length); + memcpy(dest, other_data, other_length); + pcm_mix(dest, data, length, &ao->in_audio_format, + 1.0 - chunk->mix_ratio); + + data = dest; + length = other_length; + } + + /* apply filter chain */ + + data = filter_filter(ao->filter, data, length, &length, &error); + if (data == NULL) { + g_warning("\"%s\" [%s] failed to filter: %s", + ao->name, ao->plugin->name, error->message); + g_error_free(error); + return NULL; + } + + *length_r = length; + return data; +} + +static bool +ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk) +{ + GError *error = NULL; + + assert(ao != NULL); + assert(ao->filter != 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; + size_t size; + const char *data = ao_filter_chunk(ao, chunk, &size); + if (data == NULL) { + ao_close(ao, false); - 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; + /* 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; + if (!ao_wait(ao)) + break; + + 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 */ @@ -124,32 +482,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; @@ -160,23 +531,35 @@ 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 { + if (!ao_wait(ao)) + break; + + 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); @@ -187,64 +570,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->pipe != NULL); - assert(ao->chunk == NULL); - - if (ao->fail_timer != NULL) { - /* this can only happen when this - output thread fails while - audio_output_open() is run in the - player thread */ - g_timer_destroy(ao->fail_timer); - ao->fail_timer = 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; @@ -252,11 +602,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; @@ -277,38 +623,46 @@ 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) ao_plugin_cancel(ao->plugin, ao->data); - - /* we must clear the notification now, because - the notify_wait() call below must wait - until audio_output_all_cancel() has cleared - the pipe; if another notification happens - to be still pending, we get a race - condition with a crash or an assertion - failure */ - notify_clear(&ao->notify); - ao_command_finished(ao); /* 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); } } @@ -319,5 +673,5 @@ void audio_output_thread_start(struct audio_output *ao) assert(ao->command == AO_COMMAND_NONE); if (!(ao->thread = g_thread_create(audio_output_task, ao, true, &e))) - g_error("Failed to spawn output task: %s\n", e->message); + MPD_ERROR("Failed to spawn output task: %s\n", e->message); } diff --git a/src/output_thread.h b/src/output_thread.h index a79c3b250..1ee0856f2 100644 --- a/src/output_thread.h +++ b/src/output_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/page.c b/src/page.c index 5ea03cd02..59369cb34 100644 --- a/src/page.c +++ b/src/page.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/page.h b/src/page.h index a150e3123..652c4ad6e 100644 --- a/src/page.h +++ b/src/page.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/path.c b/src/path.c index fc73ee7c9..5e39c1636 100644 --- a/src/path.c +++ b/src/path.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "path.h" #include "conf.h" +#include "mpd_error.h" #include <glib.h> @@ -63,7 +65,7 @@ path_set_fs_charset(const char *charset) /* convert a space to ensure that the charset is valid */ test = g_convert(" ", 1, charset, "UTF-8", NULL, NULL, NULL); if (test == NULL) - g_error("invalid filesystem charset: %s", charset); + MPD_ERROR("invalid filesystem charset: %s", charset); g_free(test); g_free(fs_charset); diff --git a/src/path.h b/src/path.h index be845d9b1..512cd13ea 100644 --- a/src/path.h +++ b/src/path.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_buffer.h b/src/pcm_buffer.h index b143bd98f..73959ea03 100644 --- a/src/pcm_buffer.h +++ b/src/pcm_buffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -65,8 +65,8 @@ pcm_buffer_get(struct pcm_buffer *buffer, size_t size) /* free the old buffer */ g_free(buffer->buffer); - /* allocate a new buffer; align at 64kB boundaries */ - buffer->size = (size | 0xffff) + 1; + /* allocate a new buffer; align at 8 kB boundaries */ + buffer->size = ((size - 1) | 0x1fff) + 1; buffer->buffer = g_malloc(buffer->size); } diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c new file mode 100644 index 000000000..967c574cb --- /dev/null +++ b/src/pcm_byteswap.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003-2010 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..005e75ded --- /dev/null +++ b/src/pcm_byteswap.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2003-2010 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..34e72ca4e 100644 --- a/src/pcm_channels.c +++ b/src/pcm_channels.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..a23cbd364 100644 --- a/src/pcm_channels.h +++ b/src/pcm_channels.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..5fe89b53a 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,9 +17,12 @@ * 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 "pcm_pack.h" #include "audio_format.h" #include <assert.h> @@ -38,7 +41,9 @@ void pcm_convert_init(struct pcm_convert_state *state) pcm_dither_24_init(&state->dither); pcm_buffer_init(&state->format_buffer); + pcm_buffer_init(&state->pack_buffer); pcm_buffer_init(&state->channels_buffer); + pcm_buffer_init(&state->byteswap_buffer); } void pcm_convert_deinit(struct pcm_convert_state *state) @@ -46,42 +51,62 @@ void pcm_convert_deinit(struct pcm_convert_state *state) pcm_resample_deinit(&state->resample); pcm_buffer_deinit(&state->format_buffer); + pcm_buffer_deinit(&state->pack_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,71 +116,146 @@ 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; } +/** + * Convert to 24 bit packed samples (aka S24_3LE / S24_3BE). + */ +static const void * +pcm_convert_24_packed(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, + GError **error_r) +{ + assert(dest_format->format == SAMPLE_FORMAT_S24); + + /* use the normal 24 bit conversion first */ + + struct audio_format audio_format; + audio_format_init(&audio_format, dest_format->sample_rate, + SAMPLE_FORMAT_S24_P32, dest_format->channels); + + const int32_t *buffer; + size_t buffer_size; + + buffer = pcm_convert_24(state, src_format, src_buffer, src_size, + &audio_format, &buffer_size, error_r); + if (buffer == NULL) + return NULL; + + /* now convert to packed 24 bit */ + + unsigned num_samples = buffer_size / 4; + size_t dest_size = num_samples * 3; + + uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size); + pcm_pack_24(dest, buffer, num_samples, dest_format->reverse_endian); + + *dest_size_r = dest_size; + return dest; +} + 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 +266,38 @@ 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 SAMPLE_FORMAT_S24: + return pcm_convert_24_packed(state, + src_format, src, src_size, + 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..01ba2c787 100644 --- a/src/pcm_convert.h +++ b/src/pcm_convert.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -39,10 +39,22 @@ struct pcm_convert_state { /** the buffer for converting the sample format */ struct pcm_buffer format_buffer; + /** the buffer for converting to/from packed samples */ + struct pcm_buffer pack_buffer; + /** 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 +75,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..03388f0e0 100644 --- a/src/pcm_dither.c +++ b/src/pcm_dither.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_dither.h b/src/pcm_dither.h index a5c0c3bae..dafae957f 100644 --- a/src/pcm_dither.h +++ b/src/pcm_dither.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_format.c b/src/pcm_format.c index 0e686e17c..3fd76a987 100644 --- a/src/pcm_format.c +++ b/src/pcm_format.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +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> +#include "pcm_pack.h" static void pcm_convert_8_to_16(int16_t *out, const int8_t *in, @@ -49,16 +49,29 @@ pcm_convert_32_to_16(struct pcm_dither *dither, pcm_dither_32_to_16(dither, out, in, num_samples); } +static int32_t * +pcm_convert_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src, + unsigned num_samples) +{ + int32_t *dest = pcm_buffer_get(buffer, num_samples * 4); + pcm_unpack_24(dest, src, num_samples, false); + return dest; +} + 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; + int32_t *dest32; + + switch (src_format) { + case SAMPLE_FORMAT_UNDEFINED: + break; - switch (bits) { - case 8: + 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 +81,24 @@ 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: + /* convert to S24_P32 first */ + num_samples = src_size / 3; + + dest32 = pcm_convert_24_to_24p32(buffer, src, num_samples); + dest = (int16_t *)dest32; + + /* convert to 16 bit in-place */ + *dest_size_r = num_samples * sizeof(*dest); + pcm_convert_24_to_16(dither, dest, dest32, + num_samples); + return dest; + + 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 +108,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 +119,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 +154,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 +173,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 +182,17 @@ pcm_convert_to_24(struct pcm_buffer *buffer, num_samples); return dest; - case 24: + case SAMPLE_FORMAT_S24: + num_samples = src_size / 3; + *dest_size_r = num_samples * sizeof(*dest); + + return pcm_convert_24_to_24p32(buffer, src, num_samples); + + 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 +202,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 +237,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 +256,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 +265,18 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; - case 24: + case SAMPLE_FORMAT_S24: + /* convert to S24_P32 first */ + num_samples = src_size / 3; + + dest = pcm_convert_24_to_24p32(buffer, src, num_samples); + + /* convert to 32 bit in-place */ + *dest_size_r = num_samples * sizeof(*dest); + pcm_convert_24_to_32(dest, dest, num_samples); + return dest; + + 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 +285,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..3e96fc65f 100644 --- a/src/pcm_format.h +++ b/src/pcm_format.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..3145c07be 100644 --- a/src/pcm_mix.c +++ b/src/pcm_mix.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,12 @@ * 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" #include "audio_format.h" +#include "mpd_error.h" #include <glib.h> @@ -30,8 +32,8 @@ #define G_LOG_DOMAIN "pcm" static void -pcm_add_8(int8_t *buffer1, const int8_t *buffer2, - unsigned num_samples, int volume1, int volume2) +pcm_add_vol_8(int8_t *buffer1, const int8_t *buffer2, + unsigned num_samples, int volume1, int volume2) { while (num_samples > 0) { int32_t sample1 = *buffer1; @@ -47,8 +49,8 @@ pcm_add_8(int8_t *buffer1, const int8_t *buffer2, } static void -pcm_add_16(int16_t *buffer1, const int16_t *buffer2, - unsigned num_samples, int volume1, int volume2) +pcm_add_vol_16(int16_t *buffer1, const int16_t *buffer2, + unsigned num_samples, int volume1, int volume2) { while (num_samples > 0) { int32_t sample1 = *buffer1; @@ -64,8 +66,8 @@ pcm_add_16(int16_t *buffer1, const int16_t *buffer2, } static void -pcm_add_24(int32_t *buffer1, const int32_t *buffer2, - unsigned num_samples, unsigned volume1, unsigned volume2) +pcm_add_vol_24(int32_t *buffer1, const int32_t *buffer2, + unsigned num_samples, unsigned volume1, unsigned volume2) { while (num_samples > 0) { int64_t sample1 = *buffer1; @@ -81,29 +83,134 @@ pcm_add_24(int32_t *buffer1, const int32_t *buffer2, } static void +pcm_add_vol_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_vol(void *buffer1, const void *buffer2, size_t size, + int vol1, int vol2, + const struct audio_format *format) +{ + switch (format->format) { + case SAMPLE_FORMAT_S8: + pcm_add_vol_8((int8_t *)buffer1, (const int8_t *)buffer2, + size, vol1, vol2); + break; + + case SAMPLE_FORMAT_S16: + pcm_add_vol_16((int16_t *)buffer1, (const int16_t *)buffer2, + size / 2, vol1, vol2); + break; + + case SAMPLE_FORMAT_S24_P32: + pcm_add_vol_24((int32_t *)buffer1, (const int32_t *)buffer2, + size / 4, vol1, vol2); + break; + + case SAMPLE_FORMAT_S32: + pcm_add_vol_32((int32_t *)buffer1, (const int32_t *)buffer2, + size / 4, vol1, vol2); + break; + + default: + MPD_ERROR("format %s not supported by pcm_add_vol", + sample_format_to_string(format->format)); + } +} + +static void +pcm_add_8(int8_t *buffer1, const int8_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int32_t sample1 = *buffer1; + int32_t sample2 = *buffer2++; + + sample1 += sample2; + + *buffer1++ = pcm_range(sample1, 8); + --num_samples; + } +} + +static void +pcm_add_16(int16_t *buffer1, const int16_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int32_t sample1 = *buffer1; + int32_t sample2 = *buffer2++; + + sample1 += sample2; + + *buffer1++ = pcm_range(sample1, 16); + --num_samples; + } +} + +static void +pcm_add_24(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int64_t sample1 = *buffer1; + int64_t sample2 = *buffer2++; + + sample1 += sample2; + + *buffer1++ = pcm_range(sample1, 24); + --num_samples; + } +} + +static void +pcm_add_32(int32_t *buffer1, const int32_t *buffer2, unsigned num_samples) +{ + while (num_samples > 0) { + int64_t sample1 = *buffer1; + int64_t sample2 = *buffer2++; + + sample1 += sample2; + + *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: - pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, - size, vol1, vol2); + switch (format->format) { + case SAMPLE_FORMAT_S8: + pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2, size); break; - case 16: - pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, - size / 2, vol1, vol2); + case SAMPLE_FORMAT_S16: + pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2, size / 2); break; - case 24: - pcm_add_24((int32_t*)buffer1, - (const int32_t*)buffer2, - size / 4, vol1, vol2); + case SAMPLE_FORMAT_S24_P32: + pcm_add_24((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); + break; + + case SAMPLE_FORMAT_S32: + pcm_add_32((int32_t *)buffer1, (const int32_t *)buffer2, size / 4); break; default: - g_error("%u bits not supported by pcm_add!\n", format->bits); + MPD_ERROR("format %s not supported by pcm_add", + sample_format_to_string(format->format)); } } @@ -112,11 +219,20 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size, const struct audio_format *format, float portion1) { int vol1; - float s = sin(M_PI_2 * portion1); + float s; + + /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN + * to signal mixing rather than fading */ + if (isnan(portion1)) { + pcm_add(buffer1, buffer2, size, format); + return; + } + + s = sin(M_PI_2 * portion1); s *= s; vol1 = s * PCM_VOLUME_1 + 0.5; vol1 = vol1 > PCM_VOLUME_1 ? PCM_VOLUME_1 : (vol1 < 0 ? 0 : vol1); - pcm_add(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); + pcm_add_vol(buffer1, buffer2, size, vol1, PCM_VOLUME_1 - vol1, format); } diff --git a/src/pcm_mix.h b/src/pcm_mix.h index 6605585bd..086d5501e 100644 --- a/src/pcm_mix.h +++ b/src/pcm_mix.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -35,7 +35,8 @@ struct audio_format; * @param size the size of both buffers in bytes * @param format the audio format of both buffers * @param portion1 a number between 0.0 and 1.0 specifying the portion - * of the first buffer in the mix; portion2 = (1.0 - portion1) + * of the first buffer in the mix; portion2 = (1.0 - portion1). The value + * NaN is used by the MixRamp code to specify that simple addition is required. */ void pcm_mix(void *buffer1, const void *buffer2, size_t size, diff --git a/src/pcm_pack.c b/src/pcm_pack.c new file mode 100644 index 000000000..9af0ab1ed --- /dev/null +++ b/src/pcm_pack.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2010 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 "pcm_pack.h" + +#include <glib.h> + +static void +pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian) +{ + const uint8_t *src = (const uint8_t *)src0; + + if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + ++src; + + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; +} + +void +pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, + bool reverse_endian) +{ + /* duplicate loop to help the compiler's optimizer (constant + parameter to the pack_sample() inline function) */ + + if (G_LIKELY(!reverse_endian)) { + while (num_samples-- > 0) { + pack_sample(dest, src++, false); + dest += 3; + } + } else { + while (num_samples-- > 0) { + pack_sample(dest, src++, true); + dest += 3; + } + } +} + +static void +unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian) +{ + uint8_t *dest = (uint8_t *)dest0; + + if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + /* extend the sign bit to the most fourth byte */ + *dest++ = *src & 0x80 ? 0xff : 0x00; + + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src; + + if ((G_BYTE_ORDER == G_LITTLE_ENDIAN) != reverse_endian) + /* extend the sign bit to the most fourth byte */ + *dest++ = *src & 0x80 ? 0xff : 0x00; +} + +void +pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples, + bool reverse_endian) +{ + /* duplicate loop to help the compiler's optimizer (constant + parameter to the unpack_sample() inline function) */ + + if (G_LIKELY(!reverse_endian)) { + while (num_samples-- > 0) { + unpack_sample(dest++, src, false); + src += 3; + } + } else { + while (num_samples-- > 0) { + unpack_sample(dest++, src, true); + src += 3; + } + } +} diff --git a/src/pcm_pack.h b/src/pcm_pack.h new file mode 100644 index 000000000..3c99eaa35 --- /dev/null +++ b/src/pcm_pack.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2010 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 + * + * Library for working with packed 24 bit samples. + */ + +#ifndef PCM_PACK_H +#define PCM_PACK_H + +#include <stdbool.h> +#include <stdint.h> + +/** + * Converts padded 24 bit samples (4 bytes per sample) to packed 24 + * bit samples (3 bytes per sample). + * + * This function can be used to convert a buffer in-place. + * + * @param dest the destination buffer (array of triples) + * @param src the source buffer + * @param num_samples the number of samples to convert + * @param reverse_endian is src and dest in non-host byte order? + */ +void +pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, + bool reverse_endian); + +/** + * Converts packed 24 bit samples (3 bytes per sample) to padded 24 + * bit samples (4 bytes per sample). + * + * @param dest the destination buffer + * @param src the source buffer (array of triples) + * @param num_samples the number of samples to convert + * @param reverse_endian is src and dest in non-host byte order? + */ +void +pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples, + bool reverse_endian); + +#endif diff --git a/src/pcm_prng.h b/src/pcm_prng.h index ea5983588..186ed9d0e 100644 --- a/src/pcm_prng.h +++ b/src/pcm_prng.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/pcm_resample.c b/src/pcm_resample.c index d1360d02a..4a7578e09 100644 --- a/src/pcm_resample.c +++ b/src/pcm_resample.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..24d17ff9b 100644 --- a/src/pcm_resample.h +++ b/src/pcm_resample.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..0c75d8ba4 100644 --- a/src/pcm_resample_fallback.c +++ b/src/pcm_resample_fallback.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..26acc809d 100644 --- a/src/pcm_resample_internal.h +++ b/src/pcm_resample_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..99ca53da4 100644 --- a/src/pcm_resample_libsamplerate.c +++ b/src/pcm_resample_libsamplerate.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..15f9e1b10 100644 --- a/src/pcm_utils.h +++ b/src/pcm_utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..240c779d8 100644 --- a/src/pcm_volume.c +++ b/src/pcm_volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/pcm_volume.h b/src/pcm_volume.h index 5cff35cb8..eb61e9526 100644 --- a/src/pcm_volume.h +++ b/src/pcm_volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/permission.c b/src/permission.c index 7df4e27fc..17b443e0a 100644 --- a/src/permission.c +++ b/src/permission.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "permission.h" #include "conf.h" +#include "mpd_error.h" #include <glib.h> @@ -58,7 +60,7 @@ static unsigned parsePermissions(const char *string) } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { permission |= PERMISSION_ADMIN; } else { - g_error("unknown permission \"%s\"", temp); + MPD_ERROR("unknown permission \"%s\"", temp); } } @@ -89,7 +91,7 @@ void initPermissions(void) strchr(param->value, PERMISSION_PASSWORD_CHAR); if (separator == NULL) - g_error("\"%c\" not found in password string " + MPD_ERROR("\"%c\" not found in password string " "\"%s\", line %i", PERMISSION_PASSWORD_CHAR, param->value, param->line); @@ -111,7 +113,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..9b3a60a66 100644 --- a/src/permission.h +++ b/src/permission.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..7e4b0d081 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/pipe.h b/src/pipe.h index 2825b320a..7eabb6e54 100644 --- a/src/pipe.h +++ b/src/pipe.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/player_control.c b/src/player_control.c index ac4b006dd..a190bbd8b 100644 --- a/src/player_control.c +++ b/src/player_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -28,24 +30,41 @@ #include <assert.h> #include <stdio.h> +#include <math.h> struct player_control pc; +static void +pc_enqueue_song_locked(struct song *song); + 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; + pc.mixramp_db = 0; + pc.mixramp_delay_seconds = nanf(""); } 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 +76,48 @@ 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); + player_lock(); + if (pc.state != PLAYER_STATE_STOP) - player_command(PLAYER_COMMAND_STOP); + player_command_locked(PLAYER_COMMAND_STOP); - pc.next_song = song; - player_command(PLAYER_COMMAND_PLAY); + assert(pc.next_song == NULL); + + pc_enqueue_song_locked(song); + + assert(pc.next_song == NULL); + + player_unlock(); idle_add(IDLE_PLAYER); } @@ -85,16 +125,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,57 +155,86 @@ void playerKill(void) idle_add(IDLE_PLAYER); } -void playerPause(void) +void +pc_pause(void) +{ + player_lock(); + + if (pc.state != PLAYER_STATE_STOP) { + player_command_locked(PLAYER_COMMAND_PAUSE); + idle_add(IDLE_PLAYER); + } + + player_unlock(); +} + +static void +pc_pause_locked(void) { if (pc.state != PLAYER_STATE_STOP) { - player_command(PLAYER_COMMAND_PAUSE); + player_command_locked(PLAYER_COMMAND_PAUSE); idle_add(IDLE_PLAYER); } } -void playerSetPause(int pause_flag) +void +pc_set_pause(bool pause_flag) { + player_lock(); + switch (pc.state) { case PLAYER_STATE_STOP: break; case PLAYER_STATE_PLAY: if (pause_flag) - playerPause(); + pc_pause_locked(); break; + case PLAYER_STATE_PAUSE: if (!pause_flag) - playerPause(); + pc_pause_locked(); break; } -} -int getPlayerElapsedTime(void) -{ - return (int)(pc.elapsed_time + 0.5); + player_unlock(); } -unsigned long getPlayerBitRate(void) +void +pc_get_status(struct player_status *status) { - return pc.bit_rate; -} + player_lock(); + player_command_locked(PLAYER_COMMAND_REFRESH); -int getPlayerTotalTime(void) -{ - return (int)(pc.total_time + 0.5); + status->state = pc.state; + + 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; + player_lock(); + pc.error = PLAYER_ERROR_NOERROR; + pc.errored_song = NULL; + player_unlock(); } -enum player_error getPlayerError(void) +enum player_error +pc_get_error(void) { return pc.error; } @@ -166,58 +245,63 @@ 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) +static void +pc_enqueue_song_locked(struct song *song) { assert(song != NULL); assert(pc.next_song == NULL); pc.next_song = song; - player_command(PLAYER_COMMAND_QUEUE); + player_command_locked(PLAYER_COMMAND_QUEUE); +} + +void +pc_enqueue_song(struct song *song) +{ + assert(song != NULL); + + player_lock(); + pc_enqueue_song_locked(song); + player_unlock(); } bool @@ -228,9 +312,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 +325,52 @@ 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 (cross_fade_seconds < 0) + cross_fade_seconds = 0; + pc.cross_fade_seconds = cross_fade_seconds; + + idle_add(IDLE_OPTIONS); +} + +float +pc_get_mixramp_db(void) +{ + return pc.mixramp_db; +} + +void +pc_set_mixramp_db(float mixramp_db) { - if (crossFadeInSeconds < 0) - crossFadeInSeconds = 0; - pc.cross_fade_seconds = crossFadeInSeconds; + pc.mixramp_db = mixramp_db; idle_add(IDLE_OPTIONS); } -void setPlayerSoftwareVolume(int volume) +float +pc_get_mixramp_delay(void) { - if (volume > PCM_VOLUME_1) - volume = PCM_VOLUME_1; - else if (volume < 0) - volume = 0; + return pc.mixramp_delay_seconds; +} - pc.software_volume = volume; +void +pc_set_mixramp_delay(float mixramp_delay_seconds) +{ + pc.mixramp_delay_seconds = mixramp_delay_seconds; + + idle_add(IDLE_OPTIONS); } -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..76c47609a 100644 --- a/src/player_control.h +++ b/src/player_control.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,29 @@ 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; + float mixramp_db; + float mixramp_delay_seconds; double total_play_time; }; @@ -92,6 +123,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 +192,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 +246,25 @@ queueSong(struct song *song); bool pc_seek(struct song *song, float seek_time); -void setPlayerCrossFade(float crossFadeInSeconds); +void +pc_set_cross_fade(float cross_fade_seconds); + +float +pc_get_cross_fade(void); -float getPlayerCrossFade(void); +void +pc_set_mixramp_db(float mixramp_db); -void setPlayerSoftwareVolume(int volume); +float +pc_get_mixramp_db(void); -double getPlayerTotalPlayTime(void); +void +pc_set_mixramp_delay(float mixramp_delay_seconds); -static inline const struct audio_format * -player_get_audio_format(void) -{ - return &pc.audio_format; -} +float +pc_get_mixramp_delay(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 d428484c7..2d8822eb0 100644 --- a/src/player_thread.c +++ b/src/player_thread.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -33,6 +34,7 @@ #include "idle.h" #include "main.h" #include "buffer.h" +#include "mpd_error.h" #include <glib.h> @@ -46,6 +48,8 @@ enum xfade_state { }; struct player { + struct decoder_control *dc; + struct music_pipe *pipe; /** @@ -102,20 +106,46 @@ 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); } /** @@ -130,38 +160,41 @@ player_dc_at_current_song(const struct player *player) assert(player != NULL); assert(player->pipe != NULL); - return dc.pipe == player->pipe; + return player->dc->pipe == player->pipe; } /** - * Has the decoder already begun decoding the next song? - * - * Note: this function does not check if the decoder is already - * finished. + * 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 player_dc_at_next_song(const struct player *player) { - return dc.pipe != NULL && !player_dc_at_current_song(player); + return player->dc->pipe != NULL && !player_dc_at_current_song(player); } /** * 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; - if (dc.pipe != NULL) { + dc_stop(dc); + + 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; } } @@ -169,35 +202,48 @@ player_dc_stop(struct player *player) * 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; + + assert(player->queued || pc.command == PLAYER_COMMAND_SEEK); + assert(pc.next_song != NULL); + + player->queued = false; - if (decoder_has_failed()) { - assert(dc.next_song == NULL || dc.next_song->url != NULL); - pc.errored_song = dc.next_song; + 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); @@ -205,54 +251,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; } @@ -261,7 +339,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; } @@ -271,6 +350,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) @@ -294,6 +375,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); @@ -307,15 +389,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 */ @@ -326,7 +412,7 @@ static bool player_seek_decoder(struct player *player) music_pipe_clear(player->pipe, player_buffer); /* re-start the decoder */ - dc_start_async(pc.next_song, player->pipe); + player_dc_start(player, player->pipe); ret = player_wait_for_decoder(player); if (!ret) { /* decoder failure */ @@ -339,7 +425,7 @@ static bool player_seek_decoder(struct player *player) but it is the same song file; exchange the pipe */ music_pipe_clear(player->pipe, player_buffer); music_pipe_free(player->pipe); - player->pipe = dc.pipe; + player->pipe = dc->pipe; } pc.next_song = NULL; @@ -365,14 +451,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; @@ -385,53 +472,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(!player_dc_at_next_song(player)); 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: @@ -443,80 +550,91 @@ static void player_process_command(struct player *player) return; } - if (player_dc_at_next_song(player)) + if (player_dc_at_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; } @@ -529,6 +647,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; @@ -544,7 +663,7 @@ play_next_chunk(struct player *player) <= 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 @@ -558,6 +677,7 @@ play_next_chunk(struct player *player) if (other_chunk != NULL) { chunk = music_pipe_shift(player->pipe); assert(chunk != NULL); + assert(chunk->other == NULL); /* don't send the tags of the new song (which is being faded in) yet; postpone it until @@ -567,21 +687,30 @@ play_next_chunk(struct player *player) other_chunk->tag); other_chunk->tag = NULL; - cross_fade_apply(chunk, other_chunk, - &dc.out_audio_format, - cross_fade_position, - player->cross_fade_chunks); - music_buffer_return(player_buffer, other_chunk); + if (isnan(pc.mixramp_delay_seconds)) { + chunk->mix_ratio = ((float)cross_fade_position) + / player->cross_fade_chunks; + } else { + chunk->mix_ratio = nan(""); + } + + chunk->other = 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; } @@ -603,27 +732,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; } @@ -633,15 +769,25 @@ 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; + + audio_output_all_song_border(); if (!player_wait_for_decoder(player)) return false; @@ -654,53 +800,59 @@ 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, .cross_fade_tag = NULL, - .size_to_time = 0.0, + .elapsed_time = 0.0, }; + player_unlock(); + player.pipe = music_pipe_new(); - dc.buffer = player_buffer; - dc_start(&pc.notify, pc.next_song, player.pipe); + 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 && @@ -709,7 +861,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 */ @@ -720,10 +876,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; } @@ -731,31 +896,35 @@ 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(!player_dc_at_next_song(&player)); - dc.pipe = NULL; + assert(dc->pipe == NULL || dc->pipe == player.pipe); - player.queued = false; - dc_start_async(pc.next_song, music_pipe_new()); + player_dc_start(&player, music_pipe_new()); } if (player_dc_at_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, + pc.mixramp_db, + pc.mixramp_delay_seconds, + dc->replay_gain_db, + dc->replay_gain_prev_db, + dc->mixramp_start, + dc->mixramp_prev_end, + &dc->out_audio_format, &player.play_audio_format, music_buffer_size(player_buffer) - pc.buffered_before_play); @@ -768,9 +937,13 @@ static void do_play(void) player.xfade = XFADE_DISABLED; } - if (player.paused) - notify_wait(&pc.notify); - else if (!music_pipe_empty(player.pipe)) { + if (player.paused) { + player_lock(); + + if (pc.command == PLAYER_COMMAND_NONE) + player_wait(); + continue; + } else if (!music_pipe_empty(player.pipe)) { /* at least one music chunk is ready - send it to the audio output */ @@ -787,12 +960,16 @@ static void do_play(void) 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_empty(player.pipe)) + if (music_pipe_empty(player.pipe)) { + /* 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 @@ -800,11 +977,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); @@ -815,38 +989,61 @@ static void do_play(void) if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); + 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: - audio_output_all_close(); - player_command_finished(); + player_unlock(); + + audio_output_all_release(); + + player_lock(); + player_command_finished_locked(); #ifndef NDEBUG /* in the DEBUG build, check for leaked @@ -858,25 +1055,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) @@ -887,5 +1098,5 @@ void player_create(void) pc.thread = g_thread_create(player_task, NULL, true, &e); if (pc.thread == NULL) - g_error("Failed to spawn player task: %s", e->message); + MPD_ERROR("Failed to spawn player task: %s", e->message); } diff --git a/src/player_thread.h b/src/player_thread.h index 51ad28fcc..e645b1d09 100644 --- a/src/player_thread.h +++ b/src/player_thread.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist.c b/src/playlist.c index 660dd6a83..4a1e54814 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,28 +89,34 @@ 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. + * Called if the player thread has started playing the "queued" song. */ -static void syncPlaylistWithQueue(struct playlist *playlist) +static void +playlist_song_started(struct playlist *playlist) { - if (pc.next_song == NULL && playlist->queued != -1) { - /* queued song has started: copy queued to current, - and notify the clients */ + assert(pc.next_song == NULL); + assert(playlist->queued >= -1); - int current = playlist->current; - playlist->current = playlist->queued; - playlist->queued = -1; + /* queued song has started: copy queued to current, + and notify the clients */ - if(playlist->queue.consume) - deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current)); + int current = playlist->current; + playlist->current = playlist->queued; + playlist->queued = -1; - idle_add(IDLE_PLAYER); + /* Pause if we are in single mode. */ + if(playlist->queue.single && !playlist->queue.repeat) { + pc_set_pause(true); } + + if(playlist->queue.consume) + playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); + + idle_add(IDLE_PLAYER); } const struct song * @@ -179,7 +183,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; @@ -193,38 +197,45 @@ 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) + player_lock(); + enum player_state pc_state = pc_get_state(); + const struct song *pc_next_song = pc.next_song; + player_unlock(); + + if (pc_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); + if (pc_next_song == NULL && playlist->queued != -1) + playlist_song_started(playlist); /* make sure the queued song is always set (if possible) */ - if (pc.next_song == NULL) + if (pc.next_song == NULL && playlist->queued < 0) playlist_update_queued_song(playlist, NULL); } } @@ -234,14 +245,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 @@ -252,37 +263,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; @@ -297,7 +309,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 */ @@ -307,7 +320,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; @@ -322,7 +336,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; @@ -331,7 +346,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; @@ -366,14 +382,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, @@ -382,19 +399,15 @@ 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) { - if (playlist->queue.single == 1) - { - if (playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, - playlist->current); - else - return -1; - } - if (playlist->current + 1 < (int)queue_length(&playlist->queue)) + if (playlist->queue.single == 1 && playlist->queue.repeat == 1) + return queue_order_to_position(&playlist->queue, + playlist->current); + else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) return queue_order_to_position(&playlist->queue, playlist->current + 1); else if (playlist->queue.repeat == 1) @@ -405,19 +418,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..3ba90ff91 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..39513e710 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2003-2010 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..7ce91aa41 --- /dev/null +++ b/src/playlist/asx_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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..b22712bc7 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2003-2010 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) + + track_get_index(track, 1) + - track_get_zero_pre(track)) * 1000) / 75; + + /* append pregap of the next track to the end of this one */ + track = cd_get_track(playlist->cd, playlist->next); + if (track != NULL) + song->end_ms = ((track_get_start(track) + + track_get_index(track, 1) + - track_get_zero_pre(track)) * 1000) / 75; + else + song->end_ms = 0; + + 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..c89ec55c5 --- /dev/null +++ b/src/playlist/cue_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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..9a04aa066 --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.c @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2003-2010 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..fa726c5f6 --- /dev/null +++ b/src/playlist/extm3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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/flac_playlist_plugin.c b/src/playlist/flac_playlist_plugin.c new file mode 100644 index 000000000..9d66fb331 --- /dev/null +++ b/src/playlist/flac_playlist_plugin.c @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2003-2010 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/flac_playlist_plugin.h" +#include "playlist_plugin.h" +#include "tag.h" +#include "song.h" +#include "decoder/flac_metadata.h" + +#include <FLAC/metadata.h> + +#include <glib.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "flac" + +#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7 + +struct flac_playlist { + struct playlist_provider base; + + char *uri; + + FLAC__StreamMetadata *cuesheet; + FLAC__StreamMetadata streaminfo; + + unsigned next_track; +}; + +static struct playlist_provider * +flac_playlist_open_uri(const char *uri) +{ + if (!g_path_is_absolute(uri)) + /* only local files supported */ + return NULL; + + FLAC__StreamMetadata *cuesheet; + if (!FLAC__metadata_get_cuesheet(uri, &cuesheet)) + return NULL; + + struct flac_playlist *playlist = g_new(struct flac_playlist, 1); + playlist_provider_init(&playlist->base, &flac_playlist_plugin); + + if (!FLAC__metadata_get_streaminfo(uri, &playlist->streaminfo)) { + FLAC__metadata_object_delete(playlist->cuesheet); + g_free(playlist); + return NULL; + } + + playlist->uri = g_strdup(uri); + playlist->cuesheet = cuesheet; + playlist->next_track = 0; + + return &playlist->base; +} + +static void +flac_playlist_close(struct playlist_provider *_playlist) +{ + struct flac_playlist *playlist = (struct flac_playlist *)_playlist; + + g_free(playlist->uri); + FLAC__metadata_object_delete(playlist->cuesheet); + g_free(playlist); +} + +static struct song * +flac_playlist_read(struct playlist_provider *_playlist) +{ + struct flac_playlist *playlist = (struct flac_playlist *)_playlist; + const FLAC__StreamMetadata_CueSheet *cs = + &playlist->cuesheet->data.cue_sheet; + + /* find the next audio track */ + + while (playlist->next_track < cs->num_tracks && + (cs->tracks[playlist->next_track].number > cs->num_tracks || + cs->tracks[playlist->next_track].type != 0)) + ++playlist->next_track; + + if (playlist->next_track >= cs->num_tracks) + return NULL; + + FLAC__uint64 start = cs->tracks[playlist->next_track].offset; + ++playlist->next_track; + FLAC__uint64 end = playlist->next_track < cs->num_tracks + ? cs->tracks[playlist->next_track].offset + : playlist->streaminfo.data.stream_info.total_samples; + + struct song *song = song_file_new(playlist->uri, NULL); + song->start_ms = start * 1000 / + playlist->streaminfo.data.stream_info.sample_rate; + song->end_ms = end * 1000 / + playlist->streaminfo.data.stream_info.sample_rate; + + char track[16]; + g_snprintf(track, sizeof(track), "%u", playlist->next_track); + song->tag = flac_tag_load(playlist->uri, track); + if (song->tag == NULL) + song->tag = tag_new(); + + song->tag->time = end > start + ? ((end - start - 1 + + playlist->streaminfo.data.stream_info.sample_rate) / + playlist->streaminfo.data.stream_info.sample_rate) + : 0; + + tag_clear_items_by_type(song->tag, TAG_TRACK); + tag_add_item(song->tag, TAG_TRACK, track); + + return song; +} + +static const char *const flac_playlist_suffixes[] = { + "flac", + NULL +}; + +static const char *const flac_playlist_mime_types[] = { + "application/flac", + "application/x-flac", + "audio/flac", + "audio/x-flac", + NULL +}; + +const struct playlist_plugin flac_playlist_plugin = { + .name = "flac", + + .open_uri = flac_playlist_open_uri, + .close = flac_playlist_close, + .read = flac_playlist_read, + + .suffixes = flac_playlist_suffixes, + .mime_types = flac_playlist_mime_types, +}; + +#else /* FLAC_API_VERSION_CURRENT <= 7 */ + +static bool +flac_playlist_init(G_GNUC_UNUSED const struct config_param *param) +{ + /* this libFLAC version does not support embedded CUE sheets; + disable this plugin */ + return false; +} + +const struct playlist_plugin flac_playlist_plugin = { + .name = "flac", + .init = flac_playlist_init, +}; + +#endif diff --git a/src/playlist/flac_playlist_plugin.h b/src/playlist/flac_playlist_plugin.h new file mode 100644 index 000000000..7b141264f --- /dev/null +++ b/src/playlist/flac_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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_FLAC_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_FLAC_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin flac_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..afb3979d9 --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2003-2010 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; + int ret; + char buffer[4096]; + size_t length = 0, nbytes; + + input_stream = input_stream_open(url, &error); + if (input_stream == NULL) { + 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; + + /* 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); + + playlist->is = input_stream_open(p, &error); + g_free(p); + + if (playlist->is == NULL) { + 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..363377c21 --- /dev/null +++ b/src/playlist/lastfm_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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..221c27277 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2003-2010 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..98dcc4729 --- /dev/null +++ b/src/playlist/m3u_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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..2a36f12f5 --- /dev/null +++ b/src/playlist/pls_playlist_plugin.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2003-2010 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..c3bcf3f05 --- /dev/null +++ b/src/playlist/pls_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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/rss_playlist_plugin.c b/src/playlist/rss_playlist_plugin.c new file mode 100644 index 000000000..b5787bb68 --- /dev/null +++ b/src/playlist/rss_playlist_plugin.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2003-2010 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/rss_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 "rss" + +/** + * This is the state object for the GLib XML parser. + */ +struct rss_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, ITEM, + } state; + + /** + * The current tag within the "entry" element. This is only + * valid if state==ITEM. 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 +rss_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 rss_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + parser->state = ITEM; + parser->song = song_remote_new("rss:"); + parser->tag = TAG_NUM_OF_ITEM_TYPES; + } + + break; + + case ITEM: + if (g_ascii_strcasecmp(element_name, "enclosure") == 0) { + const gchar *href = get_attribute(attribute_names, + attribute_values, + "url"); + 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, "title") == 0) + parser->tag = TAG_TITLE; + else if (g_ascii_strcasecmp(element_name, "itunes:author") == 0) + parser->tag = TAG_ARTIST; + + break; + } +} + +static void +rss_end_element(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct rss_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ITEM: + if (g_ascii_strcasecmp(element_name, "item") == 0) { + if (strcmp(parser->song->uri, "rss:") != 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 +rss_text(G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *text, gsize text_len, + gpointer user_data, G_GNUC_UNUSED GError **error) +{ + struct rss_parser *parser = user_data; + + switch (parser->state) { + case ROOT: + break; + + case ITEM: + 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 rss_parser = { + .start_element = rss_start_element, + .end_element = rss_end_element, + .text = rss_text, +}; + +static void +song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) +{ + struct song *song = data; + + song_free(song); +} + +static void +rss_parser_destroy(gpointer data) +{ + struct rss_parser *parser = data; + + if (parser->state >= ITEM) + song_free(parser->song); + + g_slist_foreach(parser->songs, song_free_callback, NULL); + g_slist_free(parser->songs); +} + +/* + * The playlist object + * + */ + +struct rss_playlist { + struct playlist_provider base; + + GSList *songs; +}; + +static struct playlist_provider * +rss_open_stream(struct input_stream *is) +{ + struct rss_parser parser = { + .songs = NULL, + .state = ROOT, + }; + struct rss_playlist *playlist; + GMarkupParseContext *context; + char buffer[1024]; + size_t nbytes; + bool success; + GError *error = NULL; + + /* parse the RSS XML file */ + + context = g_markup_parse_context_new(&rss_parser, + G_MARKUP_TREAT_CDATA_AS_TEXT, + &parser, rss_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 #rss_playlist object from the parsed song list */ + + playlist = g_new(struct rss_playlist, 1); + playlist_provider_init(&playlist->base, &rss_playlist_plugin); + playlist->songs = g_slist_reverse(parser.songs); + parser.songs = NULL; + + g_markup_parse_context_free(context); + + return &playlist->base; +} + +static void +rss_close(struct playlist_provider *_playlist) +{ + struct rss_playlist *playlist = (struct rss_playlist *)_playlist; + + g_slist_foreach(playlist->songs, song_free_callback, NULL); + g_slist_free(playlist->songs); + g_free(playlist); +} + +static struct song * +rss_read(struct playlist_provider *_playlist) +{ + struct rss_playlist *playlist = (struct rss_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 rss_suffixes[] = { + "rss", + NULL +}; + +static const char *const rss_mime_types[] = { + "application/rss+xml", + "text/xml", + NULL +}; + +const struct playlist_plugin rss_playlist_plugin = { + .name = "rss", + + .open_stream = rss_open_stream, + .close = rss_close, + .read = rss_read, + + .suffixes = rss_suffixes, + .mime_types = rss_mime_types, +}; diff --git a/src/playlist/rss_playlist_plugin.h b/src/playlist/rss_playlist_plugin.h new file mode 100644 index 000000000..d8992f2e5 --- /dev/null +++ b/src/playlist/rss_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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_RSS_PLAYLIST_PLUGIN_H +#define MPD_PLAYLIST_RSS_PLAYLIST_PLUGIN_H + +extern const struct playlist_plugin rss_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..50f6bd1e7 --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2003-2010 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..ea832207d --- /dev/null +++ b/src/playlist/xspf_playlist_plugin.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2010 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_any.c b/src/playlist_any.c new file mode 100644 index 000000000..39e21b178 --- /dev/null +++ b/src/playlist_any.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2010 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_any.h" +#include "playlist_list.h" +#include "playlist_mapper.h" +#include "uri.h" +#include "input_stream.h" + +#include <assert.h> + +static struct playlist_provider * +playlist_open_remote(const char *uri, struct input_stream **is_r) +{ + assert(uri_has_scheme(uri)); + + struct playlist_provider *playlist = playlist_list_open_uri(uri); + if (playlist != NULL) { + *is_r = NULL; + return playlist; + } + + GError *error = NULL; + struct input_stream *is = input_stream_open(uri, &error); + if (is == NULL) { + if (error != NULL) { + g_warning("Failed to open %s: %s", + uri, error->message); + g_error_free(error); + } + + return NULL; + } + + playlist = playlist_list_open_stream(is, uri); + if (playlist == NULL) { + input_stream_close(is); + return NULL; + } + + *is_r = is; + return playlist; +} + +struct playlist_provider * +playlist_open_any(const char *uri, struct input_stream **is_r) +{ + return uri_has_scheme(uri) + ? playlist_open_remote(uri, is_r) + : playlist_mapper_open(uri, is_r); +} diff --git a/src/playlist_any.h b/src/playlist_any.h new file mode 100644 index 000000000..6fed97d15 --- /dev/null +++ b/src/playlist_any.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 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_ANY_H +#define MPD_PLAYLIST_ANY_H + +#include <stdbool.h> + +struct playlist_provider; +struct input_stream; + +/** + * Opens a playlist from the specified URI, which can be either an + * absolute remote URI (with a scheme) or a relative path to the + * music orplaylist directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_open_any(const char *uri, struct input_stream **is_r); + +#endif diff --git a/src/playlist_control.c b/src/playlist_control.c index 4c156f0f5..ce9bc8442 100644 --- a/src/playlist_control.c +++ b/src/playlist_control.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,6 +22,7 @@ * */ +#include "config.h" #include "playlist_internal.h" #include "player_control.h" #include "idle.h" @@ -31,15 +32,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; @@ -47,7 +40,7 @@ void stopPlaylist(struct playlist *playlist) assert(playlist->current >= 0); g_debug("stop"); - playerWait(); + pc_stop(); playlist->queued = -1; playlist->playing = false; @@ -69,11 +62,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 */ @@ -84,7 +77,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; } @@ -116,28 +109,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; @@ -155,12 +148,8 @@ nextSongInPlaylist(struct playlist *playlist) next_order = queue_next_order(&playlist->queue, playlist->current); if (next_order < 0) { - /* cancel single */ - playlist->queue.single = false; - idle_add(IDLE_OPTIONS); - /* no song after this one: stop playback */ - stopPlaylist(playlist); + playlist_stop(playlist); /* reset "current song" */ playlist->current = -1; @@ -177,50 +166,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; @@ -236,7 +217,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; @@ -244,7 +225,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; } @@ -262,11 +243,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_database.c b/src/playlist_database.c new file mode 100644 index 000000000..0a8a6f139 --- /dev/null +++ b/src/playlist_database.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2003-2010 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_database.h" +#include "playlist_vector.h" +#include "text_file.h" + +#include <string.h> +#include <stdlib.h> + +static GQuark +playlist_database_quark(void) +{ + return g_quark_from_static_string("playlist_database"); +} + +void +playlist_vector_save(FILE *fp, const struct playlist_vector *pv) +{ + for (const struct playlist_metadata *pm = pv->head; + pm != NULL; pm = pm->next) + fprintf(fp, PLAYLIST_META_BEGIN "%s\n" + "mtime: %li\n" + "playlist_end\n", + pm->name, (long)pm->mtime); +} + +bool +playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, + GString *buffer, GError **error_r) +{ + struct playlist_metadata pm = { + .mtime = 0, + }; + char *line, *colon; + const char *value; + + while ((line = read_text_line(fp, buffer)) != NULL && + strcmp(line, "playlist_end") != 0) { + colon = strchr(line, ':'); + if (colon == NULL || colon == line) { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + + *colon++ = 0; + value = g_strchug(colon); + + if (strcmp(line, "mtime") == 0) + pm.mtime = strtol(value, NULL, 10); + else { + g_set_error(error_r, playlist_database_quark(), 0, + "unknown line in db: %s", line); + return false; + } + } + + playlist_vector_update_or_add(pv, name, pm.mtime); + return true; +} diff --git a/src/playlist_database.h b/src/playlist_database.h new file mode 100644 index 000000000..7e114abdd --- /dev/null +++ b/src/playlist_database.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2003-2010 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_DATABASE_H +#define MPD_PLAYLIST_DATABASE_H + +#include "check.h" + +#include <stdbool.h> +#include <stdio.h> +#include <glib.h> + +#define PLAYLIST_META_BEGIN "playlist_begin: " + +struct playlist_vector; + +void +playlist_vector_save(FILE *fp, const struct playlist_vector *pv); + +bool +playlist_metadata_load(FILE *fp, struct playlist_vector *pv, const char *name, + GString *buffer, GError **error_r); + +#endif diff --git a/src/playlist_edit.c b/src/playlist_edit.c index b83dc0933..c54b72750 100644 --- a/src/playlist_edit.c +++ b/src/playlist_edit.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..2833b62ed 100644 --- a/src/playlist_global.c +++ b/src/playlist_global.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..9d205188f 100644 --- a/src/playlist_internal.h +++ b/src/playlist_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..019654bfc --- /dev/null +++ b/src/playlist_list.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2003-2010 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/rss_playlist_plugin.h" +#include "playlist/cue_playlist_plugin.h" +#include "playlist/flac_playlist_plugin.h" +#include "input_stream.h" +#include "uri.h" +#include "utils.h" +#include "conf.h" +#include "glib_compat.h" +#include "mpd_error.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, + &rss_playlist_plugin, +#ifdef ENABLE_LASTFM + &lastfm_playlist_plugin, +#endif +#ifdef HAVE_CUE + &cue_playlist_plugin, +#endif +#ifdef HAVE_FLAC + &flac_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) + MPD_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]); +} + +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_mime2(struct input_stream *is, const char *mime) +{ + struct playlist_provider *playlist; + + assert(is != NULL); + assert(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, 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_mime(struct input_stream *is) +{ + assert(is->mime != NULL); + + const char *semicolon = strchr(is->mime, ';'); + if (semicolon == NULL) + return playlist_list_open_stream_mime2(is, is->mime); + + if (semicolon == is->mime) + return NULL; + + /* probe only the portion before the semicolon*/ + char *mime = g_strndup(is->mime, semicolon - is->mime); + struct playlist_provider *playlist = + playlist_list_open_stream_mime2(is, mime); + g_free(mime); + return playlist; +} + +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; + + GError *error = 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; + } + } + + 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; +} + +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(const char *path_fs, struct input_stream **is_r) +{ + GError *error = NULL; + const char *suffix; + struct input_stream *is; + struct playlist_provider *playlist; + + assert(path_fs != NULL); + + suffix = uri_get_suffix(path_fs); + if (suffix == NULL || !playlist_suffix_supported(suffix)) + return NULL; + + is = input_stream_open(path_fs, &error); + if (is == NULL) { + 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) + *is_r = is; + else + input_stream_close(is); + + return playlist; +} diff --git a/src/playlist_list.h b/src/playlist_list.h new file mode 100644 index 000000000..3710589a2 --- /dev/null +++ b/src/playlist_list.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2010 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 + +#include <stdbool.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); + +/** + * Determines if there is a playlist plugin which can handle the + * specified file name suffix. + */ +bool +playlist_suffix_supported(const char *suffix); + +/** + * Opens a playlist from a local file. + * + * @param path_fs the path of the playlist file + * @param is_r on success, an input_stream object is returned here, + * which must be closed after the playlist_provider object is freed + * @return a playlist, or NULL on error + */ +struct playlist_provider * +playlist_list_open_path(const char *path_fs, struct input_stream **is_r); + +#endif diff --git a/src/playlist_mapper.c b/src/playlist_mapper.c new file mode 100644 index 000000000..99b322073 --- /dev/null +++ b/src/playlist_mapper.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2003-2010 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_mapper.h" +#include "playlist_list.h" +#include "stored_playlist.h" +#include "mapper.h" +#include "uri.h" + +#include <assert.h> + +static struct playlist_provider * +playlist_open_path(const char *path_fs, struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + playlist = playlist_list_open_uri(path_fs); + if (playlist != NULL) + *is_r = NULL; + else + playlist = playlist_list_open_path(path_fs, is_r); + + return playlist; +} + +/** + * Load a playlist from the configured playlist directory. + */ +static struct playlist_provider * +playlist_open_in_playlist_dir(const char *uri, struct input_stream **is_r) +{ + char *path_fs; + + assert(spl_valid_name(uri)); + + const char *playlist_directory_fs = map_spl_path(); + if (playlist_directory_fs == NULL) + return NULL; + + path_fs = g_build_filename(playlist_directory_fs, uri, NULL); + + struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + g_free(path_fs); + + return playlist; +} + +/** + * Load a playlist from the configured music directory. + */ +static struct playlist_provider * +playlist_open_in_music_dir(const char *uri, struct input_stream **is_r) +{ + char *path_fs; + + assert(uri_safe_local(uri)); + + path_fs = map_uri_fs(uri); + if (path_fs == NULL) + return NULL; + + struct playlist_provider *playlist = playlist_open_path(path_fs, is_r); + g_free(path_fs); + + return playlist; +} + +struct playlist_provider * +playlist_mapper_open(const char *uri, struct input_stream **is_r) +{ + struct playlist_provider *playlist; + + if (spl_valid_name(uri)) { + playlist = playlist_open_in_playlist_dir(uri, is_r); + if (playlist != NULL) + return playlist; + } + + if (uri_safe_local(uri)) { + playlist = playlist_open_in_music_dir(uri, is_r); + if (playlist != NULL) + return playlist; + } + + return NULL; +} diff --git a/src/playlist_mapper.h b/src/playlist_mapper.h new file mode 100644 index 000000000..b98af1b13 --- /dev/null +++ b/src/playlist_mapper.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2003-2010 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_MAPPER_H +#define MPD_PLAYLIST_MAPPER_H + +struct input_stream; + +/** + * Opens a playlist from an URI relative to the playlist or music + * directory. + * + * @param is_r on success, an input_stream object may be returned + * here, which must be closed after the playlist_provider object is + * freed + */ +struct playlist_provider * +playlist_mapper_open(const char *uri, struct input_stream **is_r); + +#endif diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h new file mode 100644 index 000000000..3d840573e --- /dev/null +++ b/src/playlist_plugin.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2010 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..89ab2e5ab 100644 --- a/src/playlist_print.c +++ b/src/playlist_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,13 +17,19 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "playlist_print.h" +#include "playlist_list.h" +#include "playlist_plugin.h" +#include "playlist_any.h" +#include "playlist_song.h" #include "queue_print.h" #include "stored_playlist.h" #include "song_print.h" #include "song.h" #include "database.h" #include "client.h" +#include "input_stream.h" void playlist_print_uris(struct client *client, const struct playlist *playlist) @@ -69,7 +75,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; @@ -138,3 +144,41 @@ spl_print(struct client *client, const char *name_utf8, bool detail) spl_free(list); return true; } + +static void +playlist_provider_print(struct client *client, const char *uri, + struct playlist_provider *playlist, bool detail) +{ + struct song *song; + char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; + + while ((song = playlist_plugin_read(playlist)) != NULL) { + song = playlist_check_translate_song(song, base_uri); + if (song == NULL) + continue; + + if (detail) + song_print_info(client, song); + else + song_print_uri(client, song); + } + + g_free(base_uri); +} + +bool +playlist_file_print(struct client *client, const char *uri, bool detail) +{ + struct input_stream *is; + struct playlist_provider *playlist = playlist_open_any(uri, &is); + if (playlist == NULL) + return false; + + playlist_provider_print(client, uri, playlist, detail); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return true; +} diff --git a/src/playlist_print.h b/src/playlist_print.h index 0cfe80776..b3a0446ed 100644 --- a/src/playlist_print.h +++ b/src/playlist_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -101,4 +101,15 @@ playlist_print_changes_position(struct client *client, bool spl_print(struct client *client, const char *name_utf8, bool detail); +/** + * Send the playlist file to the client. + * + * @param client the client which requested the playlist + * @param uri the URI of the playlist file in UTF-8 encoding + * @param detail true if all details should be printed + * @return true on success, false if the playlist does not exist + */ +bool +playlist_file_print(struct client *client, const char *uri, bool detail); + #endif diff --git a/src/playlist_queue.c b/src/playlist_queue.c new file mode 100644 index 000000000..635e23a28 --- /dev/null +++ b/src/playlist_queue.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2010 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_plugin.h" +#include "playlist_any.h" +#include "playlist_song.h" +#include "song.h" +#include "input_stream.h" + +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 = playlist_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; +} + +enum playlist_result +playlist_open_into_queue(const char *uri, struct playlist *dest) +{ + struct input_stream *is; + struct playlist_provider *playlist = playlist_open_any(uri, &is); + if (playlist == NULL) + return PLAYLIST_RESULT_NO_SUCH_LIST; + + enum playlist_result result = + playlist_load_into_queue(uri, playlist, dest); + playlist_plugin_close(playlist); + + if (is != NULL) + input_stream_close(is); + + return result; +} diff --git a/src/playlist_queue.h b/src/playlist_queue.h new file mode 100644 index 000000000..530d4b4be --- /dev/null +++ b/src/playlist_queue.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2003-2010 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..8ddc93ec9 100644 --- a/src/playlist_save.c +++ b/src/playlist_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_save.h b/src/playlist_save.h index 8669ca025..a0131cf7f 100644 --- a/src/playlist_save.h +++ b/src/playlist_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/playlist_song.c b/src/playlist_song.c new file mode 100644 index 000000000..20fac59d8 --- /dev/null +++ b/src/playlist_song.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003-2010 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_song.h" +#include "database.h" +#include "mapper.h" +#include "song.h" +#include "uri.h" +#include "ls.h" +#include "tag.h" + +#include <assert.h> +#include <string.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); + g_free(path_fs); + + 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; +} + +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri) +{ + struct song *dest; + + if (song_in_database(song)) + /* already ok */ + return song; + + char *uri = song->uri; + + if (uri_has_scheme(uri)) { + if (uri_supported_scheme(uri)) + /* valid remote song */ + return song; + else { + /* unsupported remote song */ + song_free(song); + return NULL; + } + } + + if (g_path_is_absolute(uri)) { + /* XXX fs_charset vs utf8? */ + char *prefix = base_uri != NULL + ? map_uri_fs(base_uri) + : map_directory_fs(db_get_root()); + + if (prefix == NULL || !g_str_has_prefix(uri, prefix) || + uri[strlen(prefix)] != '/') { + /* local files must be relative to the music + directory */ + g_free(prefix); + song_free(song); + return NULL; + } + + uri += strlen(prefix) + 1; + g_free(prefix); + } + + if (base_uri != NULL) + uri = g_build_filename(base_uri, uri, NULL); + else + uri = g_strdup(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; +} diff --git a/src/playlist_song.h b/src/playlist_song.h new file mode 100644 index 000000000..5a2e4c2b0 --- /dev/null +++ b/src/playlist_song.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2003-2010 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_SONG_H +#define MPD_PLAYLIST_SONG_H + +/** + * 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. + */ +struct song * +playlist_check_translate_song(struct song *song, const char *base_uri); + +#endif diff --git a/src/playlist_state.c b/src/playlist_state.c index af0f7982b..bb9897e01 100644 --- a/src/playlist_state.c +++ b/src/playlist_state.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,11 +22,13 @@ * */ +#include "config.h" #include "playlist_state.h" #include "playlist.h" #include "player_control.h" #include "queue_save.h" #include "path.h" +#include "text_file.h" #include <string.h> #include <stdlib.h> @@ -39,6 +41,8 @@ #define PLAYLIST_STATE_FILE_CURRENT "current: " #define PLAYLIST_STATE_FILE_TIME "time: " #define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " +#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " #define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" #define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" @@ -51,57 +55,65 @@ void playlist_state_save(FILE *fp, const struct playlist *playlist) { - fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE); + struct player_status player_status; + + pc_get_status(&player_status); + + fputs(PLAYLIST_STATE_FILE_STATE, fp); if (playlist->playing) { - switch (getPlayerState()) { + switch (player_status.state) { case PLAYER_STATE_PAUSE: - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE); + fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); break; default: - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY); + fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); } - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT, + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", queue_order_to_position(&playlist->queue, playlist->current)); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME, - getPlayerElapsedTime()); - } else - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP); - - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, - playlist->queue.random); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, - playlist->queue.repeat); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_SINGLE, - playlist->queue.single); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME, + fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", + (int)player_status.elapsed_time); + } else { + fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); + + if (playlist->current >= 0) + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + queue_order_to_position(&playlist->queue, + playlist->current)); + } + + fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); + fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat); + fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single); + fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist->queue.consume); - fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE, - (int)(getPlayerCrossFade())); - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN); + fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)(pc_get_cross_fade())); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc_get_mixramp_db()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc_get_mixramp_delay()); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); queue_save(fp, &playlist->queue); - fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); } static void -playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer) +playlist_state_load(FILE *fp, GString *buffer, struct playlist *playlist) { - int song; - - if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { + const char *line = read_text_line(fp, buffer); + if (line == NULL) { g_warning("No playlist in state file"); return; } - while (!g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - g_strchomp(buffer); - - song = queue_load_song(&playlist->queue, buffer); + while (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(fp, buffer, line, &playlist->queue); - if (!fgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { - g_warning("'%s' not found in state file", - PLAYLIST_STATE_FILE_PLAYLIST_END); + line = read_text_line(fp, buffer); + if (line == NULL) { + g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); break; } } @@ -109,87 +121,116 @@ 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, GString *buffer, + struct playlist *playlist) { int current = -1; int seek_time = 0; int state = PLAYER_STATE_STOP; - char buffer[PLAYLIST_BUFFER_SIZE]; bool random_mode = false; - while (fgets(buffer, sizeof(buffer), fp)) { - g_strchomp(buffer); + if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE)) + return false; - 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)) { + 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 ((line = read_text_line(fp, buffer)) != NULL) { + if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_TIME)) { seek_time = - atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)])); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) { + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_REPEAT)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + (&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), "1") == 0) { - setPlaylistRepeatStatus(playlist, true); + playlist_set_repeat(playlist, true); } else - setPlaylistRepeatStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) { + playlist_set_repeat(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_SINGLE)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + (&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), "1") == 0) { - setPlaylistSingleStatus(playlist, true); + playlist_set_single(playlist, true); } else - setPlaylistSingleStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) { + playlist_set_single(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CONSUME)) { if (strcmp - (&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + (&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), "1") == 0) { - setPlaylistConsumeStatus(playlist, true); + playlist_set_consume(playlist, true); } else - setPlaylistConsumeStatus(playlist, false); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) { - setPlayerCrossFade(atoi - (& - (buffer - [strlen - (PLAYLIST_STATE_FILE_CROSSFADE)]))); - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) { + playlist_set_consume(playlist, false); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc_set_cross_fade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc_set_mixramp_db(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + pc_set_mixramp_delay(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY))); + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_RANDOM)) { random_mode = - strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM), + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), "1") == 0; - } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CURRENT)) { - current = atoi(&(buffer + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_CURRENT)) { + current = atoi(&(line [strlen (PLAYLIST_STATE_FILE_CURRENT)])); - } else if (g_str_has_prefix(buffer, + } else if (g_str_has_prefix(line, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - if (state == PLAYER_STATE_STOP) - current = -1; - playlist_state_load(fp, playlist, buffer); + playlist_state_load(fp, buffer, playlist); } } - 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..8ca3657f2 100644 --- a/src/playlist_state.h +++ b/src/playlist_state.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,8 @@ #ifndef PLAYLIST_STATE_H #define PLAYLIST_STATE_H +#include <glib.h> +#include <stdbool.h> #include <stdio.h> struct playlist; @@ -32,7 +34,17 @@ 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, GString *buffer, + 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/playlist_vector.c b/src/playlist_vector.c new file mode 100644 index 000000000..7c1765a98 --- /dev/null +++ b/src/playlist_vector.c @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2003-2010 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_vector.h" + +#include <assert.h> +#include <string.h> +#include <glib.h> + +static struct playlist_metadata * +playlist_metadata_new(const char *name, time_t mtime) +{ + assert(name != NULL); + + struct playlist_metadata *pm = g_slice_new(struct playlist_metadata); + pm->name = g_strdup(name); + pm->mtime = mtime; + return pm; +} + +static void +playlist_metadata_free(struct playlist_metadata *pm) +{ + assert(pm != NULL); + assert(pm->name != NULL); + + g_free(pm->name); + g_slice_free(struct playlist_metadata, pm); +} + +void +playlist_vector_deinit(struct playlist_vector *pv) +{ + assert(pv != NULL); + + while (pv->head != NULL) { + struct playlist_metadata *pm = pv->head; + pv->head = pm->next; + playlist_metadata_free(pm); + } +} + +static struct playlist_metadata ** +playlist_vector_find_p(struct playlist_vector *pv, const char *name) +{ + assert(pv != NULL); + assert(name != NULL); + + struct playlist_metadata **pmp = &pv->head; + + for (;;) { + struct playlist_metadata *pm = *pmp; + if (pm == NULL) + return NULL; + + if (strcmp(pm->name, name) == 0) + return pmp; + + pmp = &pm->next; + } +} + +struct playlist_metadata * +playlist_vector_find(struct playlist_vector *pv, const char *name) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + return pmp != NULL ? *pmp : NULL; +} + +void +playlist_vector_add(struct playlist_vector *pv, + const char *name, time_t mtime) +{ + struct playlist_metadata *pm = playlist_metadata_new(name, mtime); + pm->next = pv->head; + pv->head = pm; +} + +bool +playlist_vector_update_or_add(struct playlist_vector *pv, + const char *name, time_t mtime) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + if (pmp != NULL) { + struct playlist_metadata *pm = *pmp; + if (mtime == pm->mtime) + return false; + + pm->mtime = mtime; + } else + playlist_vector_add(pv, name, mtime); + + return true; +} + +bool +playlist_vector_remove(struct playlist_vector *pv, const char *name) +{ + struct playlist_metadata **pmp = playlist_vector_find_p(pv, name); + if (pmp == NULL) + return false; + + struct playlist_metadata *pm = *pmp; + *pmp = pm->next; + + playlist_metadata_free(pm); + return true; +} diff --git a/src/playlist_vector.h b/src/playlist_vector.h new file mode 100644 index 000000000..566de5f00 --- /dev/null +++ b/src/playlist_vector.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2003-2010 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_VECTOR_H +#define MPD_PLAYLIST_VECTOR_H + +#include <stdbool.h> +#include <stddef.h> +#include <sys/time.h> + +/** + * A directory entry pointing to a playlist file. + */ +struct playlist_metadata { + struct playlist_metadata *next; + + /** + * The UTF-8 encoded name of the playlist file. + */ + char *name; + + time_t mtime; +}; + +struct playlist_vector { + struct playlist_metadata *head; +}; + +static inline void +playlist_vector_init(struct playlist_vector *pv) +{ + pv->head = NULL; +} + +void +playlist_vector_deinit(struct playlist_vector *pv); + +struct playlist_metadata * +playlist_vector_find(struct playlist_vector *pv, const char *name); + +void +playlist_vector_add(struct playlist_vector *pv, + const char *name, time_t mtime); + +/** + * @return true if the vector or one of its items was modified + */ +bool +playlist_vector_update_or_add(struct playlist_vector *pv, + const char *name, time_t mtime); + +bool +playlist_vector_remove(struct playlist_vector *pv, const char *name); + +#endif /* SONGVEC_H */ diff --git a/src/poison.h b/src/poison.h index 5919c3cbe..9c7052c91 100644 --- a/src/poison.h +++ b/src/poison.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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 16891d0aa..dd0b48cb5 100644 --- a/src/queue.c +++ b/src/queue.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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; } @@ -43,14 +44,9 @@ queue_next_order(const struct queue *queue, unsigned order) { assert(order < queue->length); - if (queue->single) - { - if (queue->repeat && !queue->consume) - return order; - else - return -1; - } - if (order + 1 < queue->length) + if (queue->single && queue->repeat && !queue->consume) + return order; + else if (order + 1 < queue->length) return order + 1; else if (queue->repeat && (order > 0 || !queue->consume)) /* restart at first song */ @@ -111,7 +107,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 +128,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 +139,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 +159,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 +199,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 +239,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 +267,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 +287,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 +303,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..05eeafa22 100644 --- a/src/queue.h +++ b/src/queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..53ddfb689 100644 --- a/src/queue_print.c +++ b/src/queue_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,12 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue_print.h" #include "queue.h" #include "song.h" #include "song_print.h" #include "locate.h" #include "client.h" +#include "mapper.h" /** * Send detailed information about a range of songs in the queue to a @@ -60,11 +62,8 @@ queue_print_uris(struct client *client, const struct queue *queue, assert(end <= queue_length(queue)); for (unsigned i = start; i < end; ++i) { - const struct song *song = queue_get(queue, i); - char *uri = song_get_uri(song); - - client_printf(client, "%i:%s\n", i, uri); - g_free(uri); + client_printf(client, "%i:", i); + song_print_uri(client, queue_get(queue, i)); } } diff --git a/src/queue_print.h b/src/queue_print.h index 02cbc8b76..d754a9673 100644 --- a/src/queue_print.h +++ b/src/queue_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/queue_save.c b/src/queue_save.c index 9a5a0e30f..afe04ca2d 100644 --- a/src/queue_save.c +++ b/src/queue_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,63 +17,90 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "queue_save.h" #include "queue.h" #include "song.h" #include "uri.h" #include "database.h" +#include "song_save.h" #include <stdlib.h> -void -queue_save(FILE *fp, const struct queue *queue) +static void +queue_save_database_song(FILE *fp, int idx, const struct song *song) { - for (unsigned i = 0; i < queue_length(queue); i++) { - const struct song *song = queue_get(queue, i); - char *uri = song_get_uri(song); + char *uri = song_get_uri(song); - fprintf(fp, "%i:%s\n", i, uri); - g_free(uri); - } + fprintf(fp, "%i:%s\n", idx, uri); + g_free(uri); } -static struct song * -get_song(const char *uri) +static void +queue_save_full_song(FILE *fp, const struct song *song) { - struct song *song; + song_save(fp, song); +} - song = db_get_song(uri); - if (song != NULL) - return song; +static void +queue_save_song(FILE *fp, int idx, const struct song *song) +{ + if (song_in_database(song)) + queue_save_database_song(fp, idx, song); + else + queue_save_full_song(fp, song); +} - if (uri_has_scheme(uri)) - return song_remote_new(uri); +void +queue_save(FILE *fp, const struct queue *queue) +{ + for (unsigned i = 0; i < queue_length(queue); i++) + queue_save_song(fp, i, queue_get(queue, i)); +} - return NULL; +static struct song * +get_song(const char *uri) +{ + return uri_has_scheme(uri) + ? song_remote_new(uri) + : db_get_song(uri); } -int -queue_load_song(struct queue *queue, const char *line) +void +queue_load_song(FILE *fp, GString *buffer, const char *line, + struct queue *queue) { - long ret; - char *endptr; struct song *song; if (queue_is_full(queue)) - return -1; + return; - ret = strtol(line, &endptr, 10); - if (ret < 0 || *endptr != ':' || endptr[1] == 0) { - g_warning("Malformed playlist line in state file"); - return -1; - } + if (g_str_has_prefix(line, SONG_BEGIN)) { + const char *uri = line + sizeof(SONG_BEGIN) - 1; + if (!uri_has_scheme(uri) && !g_path_is_absolute(uri)) + return; - line = endptr + 1; + GError *error = NULL; + song = song_load(fp, NULL, uri, buffer, &error); + if (song == NULL) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + } else { + char *endptr; + long ret = strtol(line, &endptr, 10); + if (ret < 0 || *endptr != ':' || endptr[1] == 0) { + g_warning("Malformed playlist line in state file"); + return; + } - song = get_song(line); - if (song == NULL) - return -1; + line = endptr + 1; + + song = get_song(line); + if (song == NULL) + return; + } queue_append(queue, song); - return ret; } diff --git a/src/queue_save.h b/src/queue_save.h index 07209b8d0..287683390 100644 --- a/src/queue_save.h +++ b/src/queue_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #ifndef QUEUE_SAVE_H #define QUEUE_SAVE_H +#include <glib.h> #include <stdio.h> struct queue; @@ -33,10 +34,10 @@ void queue_save(FILE *fp, const struct queue *queue); /** - * Loads one song from the state file line and returns its number. - * Returns -1 on failure. + * Loads one song from the state file and appends it to the queue. */ -int -queue_load_song(struct queue *queue, const char *line); +void +queue_load_song(FILE *fp, GString *buffer, const char *line, + struct queue *queue); #endif diff --git a/src/refcount.h b/src/refcount.h new file mode 100644 index 000000000..87a2715a4 --- /dev/null +++ b/src/refcount.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2010 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. + */ + +/** \file + * + * A very simple reference counting library. + */ + +#ifndef MPD_REFCOUNT_H +#define MPD_REFCOUNT_H + +#include <glib.h> + +struct refcount { + gint n; +}; + +static inline void +refcount_init(struct refcount *r) +{ + r->n = 1; +} + +static inline void +refcount_inc(struct refcount *r) +{ + g_atomic_int_inc(&r->n); +} + +/** + * @return true if the number of references has been dropped to 0 + */ +static inline bool +refcount_dec(struct refcount *r) +{ + return g_atomic_int_dec_and_test(&r->n); +} + +#endif diff --git a/src/replay_gain.c b/src/replay_gain.c deleted file mode 100644 index bcb501e54..000000000 --- a/src/replay_gain.c +++ /dev/null @@ -1,134 +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. - */ -/* - * (c)2004 replayGain code by AliasMrJones - */ - -#include "replay_gain.h" -#include "conf.h" -#include "audio_format.h" -#include "pcm_volume.h" - -#include <glib.h> -#include <stdlib.h> -#include <string.h> -#include <math.h> - -static const char *const replay_gain_mode_names[] = { - [REPLAY_GAIN_ALBUM] = "album", - [REPLAY_GAIN_TRACK] = "track", -}; - -enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; - -static float replay_gain_preamp = 1.0; - -void replay_gain_global_init(void) -{ - const struct config_param *param = config_get_param(CONF_REPLAYGAIN); - - if (!param) - return; - - if (strcmp(param->value, "track") == 0) { - replay_gain_mode = REPLAY_GAIN_TRACK; - } else if (strcmp(param->value, "album") == 0) { - replay_gain_mode = REPLAY_GAIN_ALBUM; - } else { - g_error("replaygain value \"%s\" at line %i is invalid\n", - param->value, param->line); - } - - param = config_get_param(CONF_REPLAYGAIN_PREAMP); - - if (param) { - char *test; - float f = strtod(param->value, &test); - - if (*test != '\0') { - g_error("Replaygain preamp \"%s\" is not a number at " - "line %i\n", param->value, param->line); - } - - if (f < -15 || f > 15) { - g_error("Replaygain preamp \"%s\" is not between -15 and" - "15 at line %i\n", param->value, param->line); - } - - replay_gain_preamp = pow(10, f / 20.0); - } -} - -static float calc_replay_gain_scale(float gain, float peak) -{ - float scale; - - if (gain == 0.0) - return (1); - scale = pow(10.0, gain / 20.0); - scale *= replay_gain_preamp; - if (scale > 15.0) - scale = 15.0; - - if (scale * peak > 1.0) { - scale = 1.0 / peak; - } - return (scale); -} - -struct replay_gain_info *replay_gain_info_new(void) -{ - struct replay_gain_info *ret = g_new(struct replay_gain_info, 1); - - for (unsigned i = 0; i < G_N_ELEMENTS(ret->tuples); ++i) { - ret->tuples[i].gain = 0.0; - ret->tuples[i].peak = 0.0; - } - - /* set to -1 so that we know in replay_gain_apply to compute the scale */ - ret->scale = -1.0; - - return ret; -} - -void replay_gain_info_free(struct replay_gain_info *info) -{ - g_free(info); -} - -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) - return; - - 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); - - info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak); - } - - pcm_volume(buffer, size, format, pcm_float_to_volume(info->scale)); -} diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c new file mode 100644 index 000000000..bbfe127a7 --- /dev/null +++ b/src/replay_gain_config.c @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2003-2010 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 "replay_gain_config.h" +#include "playlist.h" +#include "conf.h" +#include "idle.h" +#include "mpd_error.h" + +#include <glib.h> + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +static const char *const replay_gain_mode_names[] = { + [REPLAY_GAIN_ALBUM] = "album", + [REPLAY_GAIN_TRACK] = "track", +}; + +enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF; + +const bool DEFAULT_REPLAYGAIN_LIMIT = true; + +float replay_gain_preamp = 1.0; +float replay_gain_missing_preamp = 1.0; +bool replay_gain_limit; + +const char * +replay_gain_get_mode_string(void) +{ + switch (replay_gain_mode) { + case REPLAY_GAIN_AUTO: + return "auto"; + + case REPLAY_GAIN_OFF: + return "off"; + + 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(p, "off") == 0) + replay_gain_mode = REPLAY_GAIN_OFF; + else if (strcmp(p, "track") == 0) + replay_gain_mode = REPLAY_GAIN_TRACK; + else if (strcmp(p, "album") == 0) + replay_gain_mode = REPLAY_GAIN_ALBUM; + else if (strcmp(p, "auto") == 0) + replay_gain_mode = REPLAY_GAIN_AUTO; + 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)) { + MPD_ERROR("replaygain value \"%s\" at line %i is invalid\n", + param->value, param->line); + } + + param = config_get_param(CONF_REPLAYGAIN_PREAMP); + + if (param) { + char *test; + float f = strtod(param->value, &test); + + if (*test != '\0') { + MPD_ERROR("Replaygain preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + MPD_ERROR("Replaygain preamp \"%s\" is not between -15 and" + "15 at line %i\n", param->value, param->line); + } + + 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') { + MPD_ERROR("Replaygain missing preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + MPD_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); + } + + replay_gain_limit = config_get_bool(CONF_REPLAYGAIN_LIMIT, DEFAULT_REPLAYGAIN_LIMIT); +} + +enum replay_gain_mode replay_gain_get_real_mode(void) +{ + enum replay_gain_mode rgm; + + rgm = replay_gain_mode; + + if (rgm == REPLAY_GAIN_AUTO) + rgm = g_playlist.queue.random ? REPLAY_GAIN_TRACK : REPLAY_GAIN_ALBUM; + + return rgm; +} diff --git a/src/replay_gain.h b/src/replay_gain_config.h index aa48f3f14..8fb77a5f6 100644 --- a/src/replay_gain.h +++ b/src/replay_gain_config.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -16,44 +16,40 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* - * (c)2004 replayGain code by AliasMrJones - */ -#ifndef MPD_REPLAY_GAIN_H -#define MPD_REPLAY_GAIN_H +#ifndef MPD_REPLAY_GAIN_CONFIG_H +#define MPD_REPLAY_GAIN_CONFIG_H -enum replay_gain_mode { - REPLAY_GAIN_OFF = -1, - REPLAY_GAIN_ALBUM, - REPLAY_GAIN_TRACK, -}; +#include "check.h" +#include "replay_gain_info.h" -struct audio_format; +#include <stdbool.h> extern enum replay_gain_mode replay_gain_mode; - -struct replay_gain_tuple { - float gain; - float peak; -}; - -struct replay_gain_info { - struct replay_gain_tuple tuples[2]; - - /* used internally by mpd, to mess with it */ - float scale; -}; - -struct replay_gain_info * -replay_gain_info_new(void); - -void replay_gain_info_free(struct replay_gain_info *info); +extern float replay_gain_preamp; +extern float replay_gain_missing_preamp; +extern bool replay_gain_limit; void replay_gain_global_init(void); -void -replay_gain_apply(struct replay_gain_info *info, char *buffer, int bufferSize, - const struct audio_format *format); +/** + * 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); + +/** + * Returns the "real" mode according to the "auto" setting" + */ +enum replay_gain_mode +replay_gain_get_real_mode(void); #endif diff --git a/src/replay_gain_info.c b/src/replay_gain_info.c new file mode 100644 index 000000000..3b4ab4577 --- /dev/null +++ b/src/replay_gain_info.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2010 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 "replay_gain_info.h" + +float +replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit) +{ + float scale; + + if (replay_gain_tuple_defined(tuple)) { + scale = pow(10.0, tuple->gain / 20.0); + scale *= preamp; + if (scale > 15.0) + scale = 15.0; + + if (peak_limit && scale * tuple->peak > 1.0) + scale = 1.0 / tuple->peak; + } else + scale = missing_preamp; + + return scale; +} + +void +replay_gain_info_complete(struct replay_gain_info *info) +{ + if (!replay_gain_tuple_defined(&info->tuples[REPLAY_GAIN_ALBUM])) + info->tuples[REPLAY_GAIN_ALBUM] = + info->tuples[REPLAY_GAIN_TRACK]; +} diff --git a/src/replay_gain_info.h b/src/replay_gain_info.h new file mode 100644 index 000000000..83b46df84 --- /dev/null +++ b/src/replay_gain_info.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2010 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_REPLAY_GAIN_INFO_H +#define MPD_REPLAY_GAIN_INFO_H + +#include "check.h" + +#include <stdbool.h> +#include <math.h> + +enum replay_gain_mode { + REPLAY_GAIN_AUTO = -2, + REPLAY_GAIN_OFF, + REPLAY_GAIN_ALBUM, + REPLAY_GAIN_TRACK, +}; + +struct replay_gain_tuple { + float gain; + float peak; +}; + +struct replay_gain_info { + struct replay_gain_tuple tuples[2]; +}; + +static inline void +replay_gain_tuple_init(struct replay_gain_tuple *tuple) +{ + tuple->gain = INFINITY; + tuple->peak = 0.0; +} + +static inline void +replay_gain_info_init(struct replay_gain_info *info) +{ + replay_gain_tuple_init(&info->tuples[REPLAY_GAIN_ALBUM]); + replay_gain_tuple_init(&info->tuples[REPLAY_GAIN_TRACK]); +} + +static inline bool +replay_gain_tuple_defined(const struct replay_gain_tuple *tuple) +{ + return !isinf(tuple->gain); +} + +float +replay_gain_tuple_scale(const struct replay_gain_tuple *tuple, float preamp, float missing_preamp, bool peak_limit); + +/** + * Attempt to auto-complete missing data. In particular, if album + * information is missing, track gain is used. + */ +void +replay_gain_info_complete(struct replay_gain_info *info); + +#endif diff --git a/src/riff.c b/src/riff.c index a8ea9dd42..2e8648ff6 100644 --- a/src/riff.c +++ b/src/riff.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/riff.h b/src/riff.h index 470985105..bfcb69a7d 100644 --- a/src/riff.h +++ b/src/riff.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/server_socket.c b/src/server_socket.c new file mode 100644 index 000000000..bb7a6f097 --- /dev/null +++ b/src/server_socket.c @@ -0,0 +1,444 @@ +/* + * Copyright (C) 2003-2010 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 "server_socket.h" +#include "socket_util.h" +#include "fd_util.h" +#include "glib_compat.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> + +#ifdef WIN32 +#define WINVER 0x0501 +#include <ws2tcpip.h> +#include <winsock.h> +#else +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <netdb.h> +#endif + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "listen" + +#define DEFAULT_PORT 6600 + +struct one_socket { + struct one_socket *next; + struct server_socket *parent; + + unsigned serial; + + int fd; + guint source_id; + + char *path; + + size_t address_length; + struct sockaddr address; +}; + +struct server_socket { + server_socket_callback_t callback; + void *callback_ctx; + + struct one_socket *sockets, **sockets_tail_r; + unsigned next_serial; +}; + +static GQuark +server_socket_quark(void) +{ + return g_quark_from_static_string("server_socket"); +} + +struct server_socket * +server_socket_new(server_socket_callback_t callback, void *callback_ctx) +{ + struct server_socket *ss = g_new(struct server_socket, 1); + ss->callback = callback; + ss->callback_ctx = callback_ctx; + ss->sockets = NULL; + ss->sockets_tail_r = &ss->sockets; + ss->next_serial = 1; + return ss; +} + +void +server_socket_free(struct server_socket *ss) +{ + server_socket_close(ss); + + while (ss->sockets != NULL) { + struct one_socket *s = ss->sockets; + ss->sockets = s->next; + + assert(s->fd < 0); + + g_free(s->path); + g_free(s); + } + + g_free(ss); +} + +/** + * Wraper for sockaddr_to_string() which never fails. + */ +static char * +one_socket_to_string(const struct one_socket *s) +{ + char *p = sockaddr_to_string(&s->address, s->address_length, NULL); + if (p == NULL) + p = g_strdup("[unknown]"); + return p; +} + +static int +get_remote_uid(int fd) +{ +#ifdef HAVE_STRUCT_UCRED + struct ucred cred; + socklen_t len = sizeof (cred); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0) + return 0; + + return cred.uid; +#else +#ifdef HAVE_GETPEEREID + uid_t euid; + gid_t egid; + + if (getpeereid(fd, &euid, &egid) == 0) + return euid; +#else + (void)fd; +#endif + return -1; +#endif +} + +static gboolean +server_socket_in_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + gpointer data) +{ + struct one_socket *s = data; + + struct sockaddr_storage address; + size_t address_length = sizeof(address); + int fd = accept_cloexec_nonblock(s->fd, (struct sockaddr*)&address, + &address_length); + if (fd >= 0) + s->parent->callback(fd, (const struct sockaddr*)&address, + address_length, get_remote_uid(fd), + s->parent->callback_ctx); + else + g_warning("accept() failed: %s", g_strerror(errno)); + + return true; +} + +bool +server_socket_open(struct server_socket *ss, GError **error_r) +{ + struct one_socket *good = NULL, *bad = NULL; + GError *last_error = NULL; + + for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { + assert(s->serial > 0); + assert(good == NULL || s->serial >= good->serial); + assert(s->fd < 0); + + if (bad != NULL && s->serial != bad->serial) { + server_socket_close(ss); + g_propagate_error(error_r, last_error); + return false; + } + + GError *error = NULL; + s->fd = socket_bind_listen(s->address.sa_family, SOCK_STREAM, 0, + &s->address, s->address_length, 5, + &error); + if (s->fd < 0) { + if (good != NULL && good->serial == s->serial) { + char *address_string = one_socket_to_string(s); + char *good_string = one_socket_to_string(good); + g_warning("bind to '%s' failed: %s " + "(continuing anyway, because " + "binding to '%s' succeeded)", + address_string, error->message, + good_string); + g_free(address_string); + g_free(good_string); + g_error_free(error); + } else if (bad == NULL) { + bad = s; + + char *address_string = one_socket_to_string(s); + g_propagate_prefixed_error(&last_error, error, + "Failed to bind to '%s': ", + address_string); + g_free(address_string); + } else + g_error_free(error); + continue; + } + + /* allow everybody to connect */ + + if (s->path != NULL) + chmod(s->path, 0666); + + /* register in the GLib main loop */ + + GIOChannel *channel = g_io_channel_unix_new(s->fd); + s->source_id = g_io_add_watch(channel, G_IO_IN, + server_socket_in_event, s); + g_io_channel_unref(channel); + + /* mark this socket as "good", and clear previous + errors */ + + good = s; + + if (bad != NULL) { + bad = NULL; + g_error_free(last_error); + last_error = NULL; + } + } + + if (bad != NULL) { + server_socket_close(ss); + g_propagate_error(error_r, last_error); + return false; + } + + return true; +} + +void +server_socket_close(struct server_socket *ss) +{ + for (struct one_socket *s = ss->sockets; s != NULL; s = s->next) { + if (s->fd < 0) + continue; + + g_source_remove(s->source_id); + close(s->fd); + s->fd = -1; + } +} + +static struct one_socket * +one_socket_new(unsigned serial, const struct sockaddr *address, + size_t address_length) +{ + assert(address != NULL); + assert(address_length > 0); + + struct one_socket *s = g_malloc(sizeof(*s) - sizeof(s->address) + + address_length); + s->next = NULL; + s->serial = serial; + s->fd = -1; + s->path = NULL; + s->address_length = address_length; + memcpy(&s->address, address, address_length); + + return s; +} + +static struct one_socket * +server_socket_add_address(struct server_socket *ss, + const struct sockaddr *address, + size_t address_length) +{ + assert(ss != NULL); + assert(ss->sockets_tail_r != NULL); + assert(*ss->sockets_tail_r == NULL); + + struct one_socket *s = one_socket_new(ss->next_serial, + address, address_length); + s->parent = ss; + *ss->sockets_tail_r = s; + ss->sockets_tail_r = &s->next; + + return s; +} + +#ifdef HAVE_TCP + +/** + * Add a listener on a port on all IPv4 interfaces. + * + * @param port the TCP port + */ +static void +server_socket_add_port_ipv4(struct server_socket *ss, unsigned port) +{ + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + + server_socket_add_address(ss, (const struct sockaddr *)&sin, + sizeof(sin)); +} + +#ifdef HAVE_IPV6 +/** + * Add a listener on a port on all IPv6 interfaces. + * + * @param port the TCP port + */ +static void +server_socket_add_port_ipv6(struct server_socket *ss, unsigned port) +{ + struct sockaddr_in6 sin; + memset(&sin, 0, sizeof(sin)); + sin.sin6_port = htons(port); + sin.sin6_family = AF_INET6; + + server_socket_add_address(ss, (const struct sockaddr *)&sin, + sizeof(sin)); +} +#endif /* HAVE_IPV6 */ + +#endif /* HAVE_TCP */ + +bool +server_socket_add_port(struct server_socket *ss, unsigned port, + GError **error_r) +{ +#ifdef HAVE_TCP + if (port == 0 || port > 0xffff) { + g_set_error(error_r, server_socket_quark(), 0, + "Invalid TCP port"); + return false; + } + +#ifdef HAVE_IPV6 + server_socket_add_port_ipv6(ss, port); +#endif + server_socket_add_port_ipv4(ss, port); + + ++ss->next_serial; + + return true; +#else /* HAVE_TCP */ + (void)ss; + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +server_socket_add_host(struct server_socket *ss, const char *hostname, + unsigned port, GError **error_r) +{ +#ifdef HAVE_TCP + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + char service[20]; + g_snprintf(service, sizeof(service), "%u", port); + + struct addrinfo *ai; + int ret = getaddrinfo(hostname, service, &hints, &ai); + if (ret != 0) { + g_set_error(error_r, server_socket_quark(), ret, + "Failed to look up host \"%s\": %s", + hostname, gai_strerror(ret)); + return false; + } + + for (const struct addrinfo *i = ai; i != NULL; i = i->ai_next) + server_socket_add_address(ss, i->ai_addr, i->ai_addrlen); + + freeaddrinfo(ai); + + ++ss->next_serial; + + return true; +#else /* HAVE_TCP */ + (void)ss; + (void)hostname; + (void)port; + + g_set_error(error_r, server_socket_quark(), 0, + "TCP support is disabled"); + return false; +#endif /* HAVE_TCP */ +} + +bool +server_socket_add_path(struct server_socket *ss, const char *path, + GError **error_r) +{ +#ifdef HAVE_UN + struct sockaddr_un s_un; + + size_t path_length = strlen(path); + if (path_length >= sizeof(s_un.sun_path)) { + g_set_error(error_r, server_socket_quark(), 0, + "UNIX socket path is too long"); + return false; + } + + unlink(path); + + s_un.sun_family = AF_UNIX; + memcpy(s_un.sun_path, path, path_length + 1); + + struct one_socket *s = + server_socket_add_address(ss, (const struct sockaddr *)&s_un, + sizeof(s_un)); + s->path = g_strdup(path); + + return true; +#else /* !HAVE_UN */ + (void)ss; + (void)path; + + g_set_error(error_r, server_socket_quark(), 0, + "UNIX domain socket support is disabled"); + return false; +#endif /* !HAVE_UN */ +} + diff --git a/src/server_socket.h b/src/server_socket.h new file mode 100644 index 000000000..ae0ce0c8d --- /dev/null +++ b/src/server_socket.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2010 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_SERVER_SOCKET_H +#define MPD_SERVER_SOCKET_H + +#include <stdbool.h> + +#include <glib.h> + +struct sockaddr; + +typedef void (*server_socket_callback_t)(int fd, + const struct sockaddr *address, + size_t address_length, int uid, + void *ctx); + +struct server_socket * +server_socket_new(server_socket_callback_t callback, void *callback_ctx); + +void +server_socket_free(struct server_socket *ss); + +bool +server_socket_open(struct server_socket *ss, GError **error_r); + +void +server_socket_close(struct server_socket *ss); + +/** + * Add a listener on a port on all interfaces. + * + * @param port the TCP port + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success + */ +bool +server_socket_add_port(struct server_socket *ss, unsigned port, + GError **error_r); + +/** + * Resolves a host name, and adds listeners on all addresses in the + * result set. + * + * @param hostname the host name to be resolved + * @param port the TCP port + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success + */ +bool +server_socket_add_host(struct server_socket *ss, const char *hostname, + unsigned port, GError **error_r); + +/** + * Add a listener on a Unix domain socket. + * + * @param path the absolute socket path + * @param error_r location to store the error occuring, or NULL to + * ignore errors + * @return true on success + */ +bool +server_socket_add_path(struct server_socket *ss, const char *path, + GError **error_r); + +#endif diff --git a/src/sig_handlers.c b/src/sig_handlers.c index e70e1a159..8aa85cf88 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "sig_handlers.h" #ifndef WIN32 @@ -24,6 +25,7 @@ #include "log.h" #include "main.h" #include "event_pipe.h" +#include "mpd_error.h" #include <glib.h> @@ -45,7 +47,7 @@ static void x_sigaction(int signum, const struct sigaction *act) { if (sigaction(signum, act, NULL) < 0) - g_error("sigaction() failed: %s", strerror(errno)); + MPD_ERROR("sigaction() failed: %s", strerror(errno)); } static void @@ -65,7 +67,7 @@ void initSigHandlers(void) sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; - while (sigaction(SIGPIPE, &sa, NULL) < 0 && errno == EINTR) ; + x_sigaction(SIGPIPE, &sa); sa.sa_handler = exit_signal_handler; x_sigaction(SIGINT, &sa); diff --git a/src/sig_handlers.h b/src/sig_handlers.h index efc7f797c..a578cd243 100644 --- a/src/sig_handlers.h +++ b/src/sig_handlers.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/socket_util.c b/src/socket_util.c index da4e414b6..0909765ba 100644 --- a/src/socket_util.c +++ b/src/socket_util.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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> @@ -27,6 +28,7 @@ #include <sys/socket.h> #include <netdb.h> #else /* G_OS_WIN32 */ +#define WINVER 0x0501 #include <ws2tcpip.h> #include <winsock.h> #endif /* G_OS_WIN32 */ @@ -102,15 +104,21 @@ 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)); return -1; } +#ifdef WIN32 + const char *optval = (const char *)&reuse; +#else + const void *optval = &reuse; +#endif + ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, - &reuse, sizeof(reuse)); + optval, sizeof(reuse)); if (ret < 0) { g_set_error(error, listen_quark(), errno, "setsockopt() failed: %s", g_strerror(errno)); diff --git a/src/socket_util.h b/src/socket_util.h index dc129df40..7ef081362 100644 --- a/src/socket_util.h +++ b/src/socket_util.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song.c b/src/song.c index 76c25f44f..13fd476b9 100644 --- a/src/song.c +++ b/src/song.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..26a1dc806 100644 --- a/src/song.h +++ b/src/song.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..16239e03b 100644 --- a/src/song_print.c +++ b/src/song_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -24,46 +25,87 @@ #include "tag_print.h" #include "client.h" #include "uri.h" +#include "mapper.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); + client_printf(client, "%s%s\n", SONG_FILE, + map_to_relative_path(uri)); g_free(allocated); } } -int +void song_print_info(struct client *client, struct song *song) { - song_print_url(client, song); + song_print_uri(client, song); + + 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 if (song->start_ms > 0) + 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), +#ifdef G_OS_WIN32 + "%Y-%m-%dT%H:%M:%SZ", +#else + "%FT%TZ", +#endif + tm2); + client_printf(client, "Last-Modified: %s\n", + timestamp); + } + } if (song->tag) tag_print(client, song->tag); - - return 0; } static int song_print_info_x(struct song *song, void *data) { struct client *client = data; - return song_print_info(client, song); + song_print_info(client, song); + + return 0; } -int songvec_print(struct client *client, const struct songvec *sv) +void +songvec_print(struct client *client, const struct songvec *sv) { - return songvec_for_each(sv, song_print_info_x, client); + songvec_for_each(sv, song_print_info_x, client); } diff --git a/src/song_print.h b/src/song_print.h index 291fd81c8..cb83f4711 100644 --- a/src/song_print.h +++ b/src/song_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,12 +24,13 @@ struct client; struct song; struct songvec; -int +void song_print_info(struct client *client, struct song *song); -int songvec_print(struct client *client, const struct songvec *sv); +void +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..a1a573298 100644 --- a/src/song_save.c +++ b/src/song_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,136 +32,107 @@ #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 -song_save(struct song *song, void *data) +void +song_save(FILE *fp, const struct song *song) { - FILE *fp = data; - - fprintf(fp, SONG_KEY "%s\n", song->url); + fprintf(fp, SONG_BEGIN "%s\n", song->uri); - song_save_url(fp, song); + if (song->end_ms > 0) + fprintf(fp, "Range: %u-%u\n", song->start_ms, song->end_ms); + else if (song->start_ms > 0) + fprintf(fp, "Range: %u-\n", song->start_ms); if (song->tag != NULL) tag_save(fp, song->tag); - fprintf(fp, SONG_MTIME "%li\n", (long)song->mtime); - - return 0; + fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime); + fprintf(fp, SONG_END "\n"); } -void songvec_save(FILE *fp, struct songvec *sv) +static int +song_save_callback(struct song *song, void *data) { - fprintf(fp, "%s\n", SONG_BEGIN); - songvec_for_each(sv, song_save, fp); - fprintf(fp, "%s\n", SONG_END); + FILE *fp = data; + song_save(fp, song); + return 0; } -static void -insertSongIntoList(struct songvec *sv, struct song *newsong) +void songvec_save(FILE *fp, const struct songvec *sv) { - 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); - } + songvec_for_each(sv, song_save_callback, fp); } -static char * -matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType) +struct song * +song_load(FILE *fp, struct directory *parent, const char *uri, + GString *buffer, GError **error_r) { - int i; - - for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { - size_t len = strlen(tag_item_names[i]); + struct song *song = parent != NULL + ? song_file_new(uri, parent) + : song_remote_new(uri); + 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 NULL; } - } - 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 (strcmp(line, "Range") == 0) { + char *endptr; + + song->start_ms = strtoul(value, &endptr, 10); + if (*endptr == '-') + song->end_ms = strtoul(endptr + 1, NULL, 10); + } 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 NULL; } - 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..03285015a 100644 --- a/src/song_save.h +++ b/src/song_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,14 +20,32 @@ #ifndef MPD_SONG_SAVE_H #define MPD_SONG_SAVE_H +#include <glib.h> + #include <stdio.h> +#define SONG_BEGIN "song_begin: " + +struct song; struct songvec; struct directory; -void songvec_save(FILE *fp, struct songvec *sv); +void +song_save(FILE *fp, const struct song *song); + +void +songvec_save(FILE *fp, const 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..c3c64c8d1 100644 --- a/src/song_sticker.c +++ b/src/song_sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_sticker.h b/src/song_sticker.h index 9652052e0..6318ccf48 100644 --- a/src/song_sticker.h +++ b/src/song_sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/song_update.c b/src/song_update.c new file mode 100644 index 000000000..b418b600e --- /dev/null +++ b/src/song_update.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2003-2010 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 "input_stream.h" + +#include <glib.h> + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdio.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; + struct input_stream *is = NULL; + + 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 { + /* load file tag */ + song->tag = decoder_plugin_tag_dup(plugin, path_fs); + if (song->tag != NULL) + break; + + /* fall back to stream tag */ + if (plugin->stream_tag != NULL) { + /* open the input_stream (if not already + open) */ + if (is == NULL) + is = input_stream_open(path_fs, NULL); + + /* now try the stream_tag() method */ + if (is != NULL) { + song->tag = decoder_plugin_stream_tag(plugin, + is); + if (song->tag != NULL) + break; + + input_stream_seek(is, 0, SEEK_SET, NULL); + } + } + + plugin = decoder_plugin_from_suffix(suffix, plugin); + } while (plugin != NULL); + + if (is != NULL) + input_stream_close(is); + + 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..38bcbac88 100644 --- a/src/songvec.c +++ b/src/songvec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..8a50b974b 100644 --- a/src/songvec.h +++ b/src/songvec.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..55af25d5c 100644 --- a/src/state_file.c +++ b/src/state_file.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,10 +17,14 @@ * 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 "text_file.h" +#include "glib_compat.h" #include <glib.h> #include <assert.h> @@ -30,28 +34,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 +62,26 @@ 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); + + fclose(fp); - 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; + 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,25 @@ 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); + + GString *buffer = g_string_sized_new(1024); + const char *line; + while ((line = read_text_line(fp, buffer)) != NULL) { + success = read_sw_volume_state(line) || + audio_output_state_read(line) || + playlist_state_restore(line, fp, buffer, &g_playlist); + if (!success) + g_warning("Unrecognized line in state file: %s", line); } - while(fclose(fp) && errno == EINTR) /* nothing */; + fclose(fp); + + 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); + + + g_string_free(buffer, true); } /** @@ -97,6 +117,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 +139,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/state_file.h b/src/state_file.h index d1e53d005..ec01fcbed 100644 --- a/src/state_file.h +++ b/src/state_file.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/stats.c b/src/stats.c index 01f6761f3..673d531ec 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/stats.h b/src/stats.h index ee1a3d5d6..fbb2e4a46 100644 --- a/src/stats.h +++ b/src/stats.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/sticker.c b/src/sticker.c index cded09fca..c59cdd078 100644 --- a/src/sticker.c +++ b/src/sticker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..6cc0ebcee 100644 --- a/src/sticker.h +++ b/src/sticker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..b158c8af3 100644 --- a/src/sticker_print.c +++ b/src/sticker_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/sticker_print.h b/src/sticker_print.h index 25f0deae2..ac542709c 100644 --- a/src/sticker_print.h +++ b/src/sticker_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/stored_playlist.c b/src/stored_playlist.c index 5ed7182f6..cd2818522 100644 --- a/src/stored_playlist.c +++ b/src/stored_playlist.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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" @@ -159,7 +160,7 @@ spl_save(GPtrArray *list, const char *utf8path) if (path_fs == NULL) return PLAYLIST_RESULT_BAD_NAME; - while (!(file = fopen(path_fs, "w")) && errno == EINTR); + file = fopen(path_fs, "w"); g_free(path_fs); if (file == NULL) return PLAYLIST_RESULT_ERRNO; @@ -169,7 +170,7 @@ spl_save(GPtrArray *list, const char *utf8path) playlist_print_uri(file, uri); } - while (fclose(file) != 0 && errno == EINTR); + fclose(file); return PLAYLIST_RESULT_SUCCESS; } @@ -188,7 +189,7 @@ spl_load(const char *utf8path) if (path_fs == NULL) return NULL; - while (!(file = fopen(path_fs, "r")) && errno == EINTR); + file = fopen(path_fs, "r"); g_free(path_fs); if (file == NULL) return NULL; @@ -226,7 +227,7 @@ spl_load(const char *utf8path) break; } - while (fclose(file) && errno == EINTR); + fclose(file); return list; } @@ -312,12 +313,12 @@ spl_clear(const char *utf8path) if (path_fs == NULL) return PLAYLIST_RESULT_BAD_NAME; - while (!(file = fopen(path_fs, "w")) && errno == EINTR); + file = fopen(path_fs, "w"); g_free(path_fs); if (file == NULL) return PLAYLIST_RESULT_ERRNO; - while (fclose(file) != 0 && errno == EINTR); + fclose(file); idle_add(IDLE_STORED_PLAYLIST); return PLAYLIST_RESULT_SUCCESS; @@ -392,26 +393,26 @@ spl_append_song(const char *utf8path, struct song *song) if (path_fs == NULL) return PLAYLIST_RESULT_BAD_NAME; - while (!(file = fopen(path_fs, "a")) && errno == EINTR); + file = fopen(path_fs, "a"); g_free(path_fs); if (file == NULL) return PLAYLIST_RESULT_ERRNO; if (fstat(fileno(file), &st) < 0) { int save_errno = errno; - while (fclose(file) != 0 && errno == EINTR); + fclose(file); errno = save_errno; return PLAYLIST_RESULT_ERRNO; } if (st.st_size / (MPD_PATH_MAX + 1) >= (off_t)playlist_max_length) { - while (fclose(file) != 0 && errno == EINTR); + fclose(file); return PLAYLIST_RESULT_TOO_LARGE; } playlist_print_song(file, song); - while (fclose(file) != 0 && errno == EINTR); + fclose(file); idle_add(IDLE_STORED_PLAYLIST); return PLAYLIST_RESULT_SUCCESS; diff --git a/src/stored_playlist.h b/src/stored_playlist.h index e78ce293b..3afdbb0f0 100644 --- a/src/stored_playlist.h +++ b/src/stored_playlist.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/strset.c b/src/strset.c index 474dd6642..e071fbc98 100644 --- a/src/strset.c +++ b/src/strset.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "strset.h" #include <assert.h> diff --git a/src/strset.h b/src/strset.h index 7b0c38c1a..9a7aa45e5 100644 --- a/src/strset.h +++ b/src/strset.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,11 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag.h" #include "tag_internal.h" #include "tag_pool.h" #include "conf.h" #include "song.h" +#include "mpd_error.h" #include <glib.h> #include <assert.h> @@ -42,18 +44,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", @@ -111,7 +115,7 @@ void tag_lib_init(void) /* 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) @@ -135,8 +139,8 @@ void tag_lib_init(void) type = tag_name_parse_i(c); if (type == TAG_NUM_OF_ITEM_TYPES) - g_error("error parsing metadata item \"%s\"", - c); + MPD_ERROR("error parsing metadata item \"%s\"", + c); ignore_tag_items[type] = false; @@ -169,7 +173,7 @@ static void tag_delete_item(struct tag *tag, unsigned idx) if (tag->num_items - idx > 0) { memmove(tag->items + idx, tag->items + idx + 1, - tag->num_items - idx); + (tag->num_items - idx) * sizeof(tag->items[0])); } if (tag->num_items > 0) { @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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, diff --git a/src/tag_ape.c b/src/tag_ape.c index babefcda3..4841b3138 100644 --- a/src/tag_ape.c +++ b/src/tag_ape.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "tag_ape.h" #include "tag.h" #include "tag_table.h" @@ -27,8 +28,8 @@ #include <stdio.h> static const char *const ape_tag_names[TAG_NUM_OF_ITEM_TYPES] = { - [TAG_ITEM_ALBUM_ARTIST] = "album artist", - [TAG_ITEM_DATE] = "year" + [TAG_ALBUM_ARTIST] = "album artist", + [TAG_DATE] = "year", }; static enum tag_type @@ -82,7 +83,7 @@ tag_ape_load(const char *file) unsigned char reserved[8]; } footer; - fp = fopen(file, "r"); + fp = fopen(file, "rb"); if (!fp) return NULL; diff --git a/src/tag_ape.h b/src/tag_ape.h index dd06d27cf..150659685 100644 --- a/src/tag_ape.h +++ b/src/tag_ape.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_id3.c b/src/tag_id3.c index a33ebc00b..c1302ca86 100644 --- a/src/tag_id3.c +++ b/src/tag_id3.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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; @@ -504,18 +495,18 @@ struct tag *tag_id3_load(const char *file) struct id3_tag *tag; FILE *stream; - stream = fopen(file, "r"); + stream = fopen(file, "rb"); if (!stream) { g_debug("tag_id3_load: Failed to open file: '%s', %s", file, strerror(errno)); 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..43f9678b4 100644 --- a/src/tag_id3.h +++ b/src/tag_id3.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_internal.h b/src/tag_internal.h index 4c3ef41be..9d76efed1 100644 --- a/src/tag_internal.h +++ b/src/tag_internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_pool.c b/src/tag_pool.c index 6aef12941..6ad1e1f2d 100644 --- a/src/tag_pool.c +++ b/src/tag_pool.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_pool.h b/src/tag_pool.h index 991a32a7e..289d6fe5f 100644 --- a/src/tag_pool.h +++ b/src/tag_pool.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_print.c b/src/tag_print.c index dddbbbe67..493fa89b5 100644 --- a/src/tag_print.c +++ b/src/tag_print.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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_print.h b/src/tag_print.h index 24ffbc914..e16e2c441 100644 --- a/src/tag_print.h +++ b/src/tag_print.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/tag_rva2.c b/src/tag_rva2.c new file mode 100644 index 000000000..35f12118f --- /dev/null +++ b/src/tag_rva2.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003-2010 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 "tag_rva2.h" +#include "replay_gain_info.h" + +#include <stdint.h> +#include <glib.h> +#include <id3tag.h> + +enum rva2_channel { + CHANNEL_OTHER = 0x00, + CHANNEL_MASTER_VOLUME = 0x01, + CHANNEL_FRONT_RIGHT = 0x02, + CHANNEL_FRONT_LEFT = 0x03, + CHANNEL_BACK_RIGHT = 0x04, + CHANNEL_BACK_LEFT = 0x05, + CHANNEL_FRONT_CENTRE = 0x06, + CHANNEL_BACK_CENTRE = 0x07, + CHANNEL_SUBWOOFER = 0x08 +}; + +struct rva2_data { + uint8_t type; + uint8_t volume_adjustment[2]; + uint8_t peak_bits; +}; + +static inline id3_length_t +rva2_peak_bytes(const struct rva2_data *data) +{ + return (data->peak_bits + 7) / 8; +} + +static inline int +rva2_fixed_volume_adjustment(const struct rva2_data *data) +{ + signed int voladj_fixed; + voladj_fixed = (data->volume_adjustment[0] << 8) | + data->volume_adjustment[1]; + voladj_fixed |= -(voladj_fixed & 0x8000); + return voladj_fixed; +} + +static inline float +rva2_float_volume_adjustment(const struct rva2_data *data) +{ + /* + * "The volume adjustment is encoded as a fixed point decibel + * value, 16 bit signed integer representing (adjustment*512), + * giving +/- 64 dB with a precision of 0.001953125 dB." + */ + + return (float)rva2_fixed_volume_adjustment(data) / (float)512; +} + +static inline bool +rva2_apply_data(struct replay_gain_info *replay_gain_info, + const struct rva2_data *data) +{ + if (data->type != CHANNEL_MASTER_VOLUME) + return false; + + float volume_adjustment = rva2_float_volume_adjustment(data); + + replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain = volume_adjustment; + replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain = volume_adjustment; + + return true; +} + +bool +tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info) +{ + struct id3_frame const * frame; + + id3_latin1_t const *id; + id3_byte_t const *data; + id3_length_t length; + + /* relative volume adjustment information */ + + frame = id3_tag_findframe(tag, "RVA2", 0); + if (frame == NULL) + return false; + + id = id3_field_getlatin1(id3_frame_field(frame, 0)); + data = id3_field_getbinarydata(id3_frame_field(frame, 1), + &length); + + if (id == NULL || data == NULL) + return false; + + /* + * "The 'identification' string is used to identify the + * situation and/or device where this adjustment should apply. + * The following is then repeated for every channel + * + * Type of channel $xx + * Volume adjustment $xx xx + * Bits representing peak $xx + * Peak volume $xx (xx ...)" + */ + + while (length >= 4) { + const struct rva2_data *d = (const struct rva2_data *)data; + unsigned int peak_bytes = rva2_peak_bytes(d); + if (4 + peak_bytes > length) + break; + + if (rva2_apply_data(replay_gain_info, d)) + return true; + + data += 4 + peak_bytes; + length -= 4 + peak_bytes; + } + + return false; +} diff --git a/src/tag_rva2.h b/src/tag_rva2.h new file mode 100644 index 000000000..a92c97912 --- /dev/null +++ b/src/tag_rva2.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 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_TAG_RVA2_H +#define MPD_TAG_RVA2_H + +#include "check.h" + +#include <stdbool.h> + +struct id3_tag; +struct replay_gain_info; + +/** + * Parse the RVA2 tag, and fill the #replay_gain_info struct. This is + * used by decoder plugins with ID3 support. + * + * @return true on success + */ +bool +tag_rva2_parse(struct id3_tag *tag, struct replay_gain_info *replay_gain_info); + +#endif diff --git a/src/tag_save.c b/src/tag_save.c index fac948b9f..9b90d1b92 100644 --- a/src/tag_save.c +++ b/src/tag_save.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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/tag_save.h b/src/tag_save.h index 687c35beb..2e8924c20 100644 --- a/src/tag_save.h +++ b/src/tag_save.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/text_file.c b/src/text_file.c new file mode 100644 index 000000000..355217aba --- /dev/null +++ b/src/text_file.c @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2003-2010 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); + } + + /* remove the newline characters */ + if (buffer->str[length - 1] == '\n') + --length; + if (buffer->str[length - 1] == '\r') + --length; + + g_string_set_size(buffer, length); + return buffer->str; +} diff --git a/src/text_file.h b/src/text_file.h new file mode 100644 index 000000000..d016f8f7a --- /dev/null +++ b/src/text_file.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2010 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..29fb6dce6 --- /dev/null +++ b/src/text_input_stream.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2003-2010 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..a1fda065d --- /dev/null +++ b/src/text_input_stream.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2003-2010 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..e125b1009 100644 --- a/src/timer.c +++ b/src/timer.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "timer.h" #include "audio_format.h" @@ -70,6 +71,19 @@ void timer_add(Timer *timer, int size) timer->time += ((uint64_t)size * 1000000) / timer->rate; } +unsigned +timer_delay(const Timer *timer) +{ + int64_t delay = (timer->time - now()) / 1000; + if (delay < 0) + return 0; + + if (delay > G_MAXINT) + delay = G_MAXINT; + + return delay / 1000; +} + void timer_sync(Timer *timer) { int64_t sleep_duration; diff --git a/src/timer.h b/src/timer.h index 7225fb5ee..bbd895b31 100644 --- a/src/timer.h +++ b/src/timer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -40,6 +40,12 @@ void timer_reset(Timer *timer); void timer_add(Timer *timer, int size); +/** + * Returns the number of milliseconds to sleep to get back to sync. + */ +unsigned +timer_delay(const Timer *timer); + void timer_sync(Timer *timer); #endif diff --git a/src/tokenizer.c b/src/tokenizer.c new file mode 100644 index 000000000..2b9e05070 --- /dev/null +++ b/src/tokenizer.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2003-2010 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..61ff398a4 --- /dev/null +++ b/src/tokenizer.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2003-2010 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..d57fb114d 100644 --- a/src/update.c +++ b/src/update.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,45 +17,22 @@ * 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 "mpd_error.h" #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 +42,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,703 +57,33 @@ 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) +static void * update_task(void *_path) { - 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); + const char *path = _path; - 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; - } + if (path != NULL && *path != 0) + g_debug("starting: %s", path); 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; + g_debug("starting"); - 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); + modified = update_walk(path, discard); - 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 (modified || !db_exists()) + db_save(); - if (stat_directory_child(parent, name, &st) == 0) - updateInDirectory(parent, name, &st); + if (path != NULL && *path != 0) + g_debug("finished: %s", path); 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); - } - + g_debug("finished"); g_free(_path); - if (modified || !db_exists()) - db_save(); - progress = UPDATE_PROGRESS_DONE; event_pipe_emit(PIPE_EVENT_UPDATE); return NULL; } -static void spawn_update_task(char *path) +static void +spawn_update_task(const char *path) { GError *e = NULL; @@ -802,15 +91,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))) - g_error("Failed to spawn update task: %s", e->message); + + update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e); + if (update_thr == NULL) + MPD_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 +110,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 +131,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 +159,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..3f8a6f6a4 100644 --- a/src/update.h +++ b/src/update.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..65744f0d6 --- /dev/null +++ b/src/update_internal.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2003-2010 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..d7b2d4e5f --- /dev/null +++ b/src/update_queue.c @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2010 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..f7c2342a2 --- /dev/null +++ b/src/update_remove.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2003-2010 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..95d5854a8 --- /dev/null +++ b/src/update_walk.c @@ -0,0 +1,909 @@ +/* + * Copyright (C) 2003-2010 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 "playlist_list.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; + } + + playlist_vector_remove(&parent->playlists, name); +} + +/* 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 bool +directory_child_is_regular(const struct directory *directory, + const char *name_utf8) +{ + char *path_fs = map_directory_child_fs(directory, name_utf8); + if (path_fs == NULL) + return false; + + struct stat st; + bool is_regular = stat(path_fs, &st) == 0 && S_ISREG(st.st_mode); + g_free(path_fs); + + return is_regular; +} + +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); + + for (const struct playlist_metadata *pm = directory->playlists.head; + pm != NULL;) { + const struct playlist_metadata *next = pm->next; + + if (!directory_child_is_regular(directory, pm->name)) + playlist_vector_remove(&directory->playlists, pm->name); + + pm = next; + } +} + +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; +} + +#ifndef G_OS_WIN32 +static int +statDirectory(struct directory *dir) +{ + struct stat st; + + if (stat_directory(dir, &st) < 0) + return -1; + + directory_set_stat(dir, &st); + + return 0; +} +#endif + +static int +inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device) +{ +#ifndef G_OS_WIN32 + 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; + } +#else + (void)parent; + (void)inode; + (void)device; +#endif + + 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; +} + +/** + * Checks if the given permissions on the mapped file are given. + */ +static bool +directory_child_access(const struct directory *directory, + const char *name, int mode) +{ +#ifdef WIN32 + /* access() is useless on WIN32 */ + (void)directory; + (void)name; + return true; +#else + char *path = map_directory_child_fs(directory, name); + if (path == NULL) + /* something went wrong, but that isn't a permission + problem */ + return true; + + bool success = access(path, mode) == 0 || errno != EACCES; + g_free(path); + return success; +#endif +} + +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 (!directory_child_access(directory, name, R_OK)) { + g_warning("no read permissions on %s/%s", + directory_get_path(directory), name); + if (song != NULL) + delete_song(directory, song); + return; + } + + 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 + + } else if (playlist_suffix_supported(suffix)) { + if (playlist_vector_update_or_add(&directory->playlists, name, + st->st_mtime)) + modified = true; + } +} + +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; +} @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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,51 @@ 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; + + unsigned dots = 0; + while (*p == '.') { + ++p; + ++dots; + } + + if (dots <= 2 && (*p == 0 || *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 * @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..53494cc5d 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..629056637 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..d7b72dd56 100644 --- a/src/volume.c +++ b/src/volume.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..db266fec9 100644 --- a/src/volume.h +++ b/src/volume.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..518a7a481 100644 --- a/src/zeroconf-avahi.c +++ b/src/zeroconf-avahi.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,8 +17,10 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf-internal.h" #include "listen.h" +#include "mpd_error.h" #include <glib.h> @@ -217,7 +219,7 @@ void init_avahi(const char *serviceName) g_debug("Initializing interface"); if (!avahi_is_valid_service_name(serviceName)) - g_error("Invalid zeroconf_name \"%s\"", serviceName); + MPD_ERROR("Invalid zeroconf_name \"%s\"", serviceName); avahiName = avahi_strdup(serviceName); diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c index 4e06319e7..84f777c50 100644 --- a/src/zeroconf-bonjour.c +++ b/src/zeroconf-bonjour.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "config.h" #include "zeroconf-internal.h" #include "listen.h" @@ -62,7 +63,7 @@ void init_zeroconf_osx(const char *serviceName) DNSServiceErrorType error = DNSServiceRegister(&dnsReference, 0, 0, serviceName, SERVICE_TYPE, NULL, NULL, - htons(listen_port), 0, + g_htons(listen_port), 0, NULL, dnsRegisterCallback, NULL); diff --git a/src/zeroconf-internal.h b/src/zeroconf-internal.h index f5aacebd8..7cb962431 100644 --- a/src/zeroconf-internal.h +++ b/src/zeroconf-internal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify diff --git a/src/zeroconf.c b/src/zeroconf.c index 42e995c45..7b00789b6 100644 --- a/src/zeroconf.c +++ b/src/zeroconf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -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..23354f87d 100644 --- a/src/zeroconf.h +++ b/src/zeroconf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 The Music Player Daemon Project + * Copyright (C) 2003-2010 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ #ifndef MPD_ZEROCONF_H #define MPD_ZEROCONF_H -#include "config.h" +#include "check.h" #ifdef HAVE_ZEROCONF |