aboutsummaryrefslogtreecommitdiffstats
path: root/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/decoder/_flac_common.c11
-rw-r--r--src/decoder/audiofile_plugin.c8
-rw-r--r--src/decoder/faad_plugin.c6
-rw-r--r--src/decoder/ffmpeg_plugin.c49
-rw-r--r--src/decoder/flac_plugin.c21
-rw-r--r--src/decoder/mad_plugin.c18
-rw-r--r--src/decoder/mikmod_plugin.c6
-rw-r--r--src/decoder/modplug_plugin.c8
-rw-r--r--src/decoder/mp4ff_plugin.c22
-rw-r--r--src/decoder/mpcdec_plugin.c4
-rw-r--r--src/decoder/mpg123_decoder_plugin.c211
-rw-r--r--src/decoder/sidplay_plugin.cxx280
-rw-r--r--src/decoder/sndfile_decoder_plugin.c244
-rwxr-xr-x[-rw-r--r--]src/decoder/vorbis_plugin.c12
-rw-r--r--src/decoder/wavpack_plugin.c28
-rw-r--r--src/decoder_api.c40
-rw-r--r--src/decoder_api.h100
-rw-r--r--src/decoder_control.c52
-rw-r--r--src/decoder_control.h97
-rw-r--r--src/decoder_internal.c27
-rw-r--r--src/decoder_list.c8
-rw-r--r--src/decoder_thread.c50
22 files changed, 1122 insertions, 180 deletions
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index e096750f3..d8802a6a3 100644
--- a/src/decoder/_flac_common.c
+++ b/src/decoder/_flac_common.c
@@ -170,11 +170,11 @@ flac_parse_comment(struct tag *tag, const char *char_tnum,
assert(tag != NULL);
if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
- TAG_ITEM_TRACK, char_tnum) ||
+ TAG_TRACK, char_tnum) ||
flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
- TAG_ITEM_DISC, char_tnum) ||
+ TAG_DISC, char_tnum) ||
flac_copy_comment(tag, entry, "album artist",
- TAG_ITEM_ALBUM_ARTIST, char_tnum))
+ TAG_ALBUM_ARTIST, char_tnum))
return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
@@ -201,9 +201,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
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;
+ audio_format_init(&data->audio_format, si->sample_rate,
+ si->bits_per_sample, si->channels);
data->total_time = ((float)si->total_samples) / (si->sample_rate);
break;
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_plugin.c
index f66d90dc1..b4959f6c2 100644
--- a/src/decoder/audiofile_plugin.c
+++ b/src/decoder/audiofile_plugin.c
@@ -136,11 +136,9 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
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);
+
+ audio_format_init(&audio_format, afGetRate(af_fp, AF_DEFAULT_TRACK),
+ bits, afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK));
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_plugin.c
index 7b2806a4c..516f741c7 100644
--- a/src/decoder/faad_plugin.c
+++ b/src/decoder/faad_plugin.c
@@ -266,11 +266,7 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
decoder_buffer_consume(buffer, nbytes);
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
+ audio_format_init(audio_format, sample_rate, 16, channels);
return true;
}
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index 27b0c2507..be9de3bf2 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -267,6 +267,7 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
struct audio_format audio_format;
enum decoder_command cmd;
int total_time;
+ uint8_t bits;
total_time = 0;
@@ -275,13 +276,13 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
}
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
- audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
+ 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;
+ bits = (uint8_t) 16;
#endif
- audio_format.sample_rate = (unsigned int)codec_context->sample_rate;
- audio_format.channels = codec_context->channels;
+ audio_format_init(&audio_format, codec_context->sample_rate, bits,
+ codec_context->channels);
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
@@ -342,8 +343,9 @@ static bool
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
enum tag_type type, const char *name)
{
- AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0);
- if (mt != NULL)
+ AVMetadataTag *mt = NULL;
+
+ while ((mt = av_metadata_get(m, name, mt, 0)) != NULL)
tag_add_item(tag, type, mt->value);
return mt != NULL;
}
@@ -352,44 +354,45 @@ ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
{
struct tag *tag = (struct tag *) ctx->tag;
- const AVFormatContext *f = ctx->format_context;
+ AVFormatContext *f = ctx->format_context;
tag->time = 0;
if (f->duration != (int64_t)AV_NOPTS_VALUE)
tag->time = f->duration / AV_TIME_BASE;
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
- if (!ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author"))
- ffmpeg_copy_metadata(tag, f->metadata,
- TAG_ITEM_ARTIST, "artist");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
+ av_metadata_conv(f, NULL, f->iformat->metadata_conv);
+
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_TITLE, "title");
+ if (!ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "author"))
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "artist");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM, "album");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_COMMENT, "comment");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_GENRE, "genre");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_TRACK, "track");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "year");
#else
if (f->author[0])
- tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
+ tag_add_item(tag, TAG_ARTIST, f->author);
if (f->title[0])
- tag_add_item(tag, TAG_ITEM_TITLE, f->title);
+ tag_add_item(tag, TAG_TITLE, f->title);
if (f->album[0])
- tag_add_item(tag, TAG_ITEM_ALBUM, f->album);
+ tag_add_item(tag, TAG_ALBUM, f->album);
if (f->track > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->track);
- tag_add_item(tag, TAG_ITEM_TRACK, buffer);
+ tag_add_item(tag, TAG_TRACK, buffer);
}
if (f->comment[0])
- tag_add_item(tag, TAG_ITEM_COMMENT, f->comment);
+ tag_add_item(tag, TAG_COMMENT, f->comment);
if (f->genre[0])
- tag_add_item(tag, TAG_ITEM_GENRE, f->genre);
+ tag_add_item(tag, TAG_GENRE, f->genre);
if (f->year > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->year);
- tag_add_item(tag, TAG_ITEM_DATE, buffer);
+ tag_add_item(tag, TAG_DATE, buffer);
}
#endif
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c
index 1d5d48d09..9692ba49f 100644
--- a/src/decoder/flac_plugin.c
+++ b/src/decoder/flac_plugin.c
@@ -300,6 +300,8 @@ flac_cue_tag_load(const char *file)
FLAC__uint64 track_time = 0;
#ifdef HAVE_CUE /* libcue */
FLAC__StreamMetadata* vc;
+ char* cs_filename;
+ FILE* cs_file;
#endif /* libcue */
FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
FLAC__StreamMetadata* cs;
@@ -329,16 +331,25 @@ flac_cue_tag_load(const char *file)
FLAC__metadata_object_delete(vc);
}
+
+ if (tag == NULL) {
+ cs_filename = g_strconcat(file, ".cue", NULL);
+
+ cs_file = fopen(cs_filename, "rt");
+ g_free(cs_filename);
+
+ if (cs_file != NULL) {
+ tag = cue_tag_file(cs_file, tnum);
+ fclose(cs_file);
+ }
+ }
#endif /* libcue */
if (tag == NULL)
tag = flac_tag_load(file, char_tnum);
- if (char_tnum != NULL)
- {
- tag_add_item( tag,
- TAG_ITEM_TRACK,
- char_tnum);
+ if (char_tnum != NULL) {
+ tag_add_item(tag, TAG_TRACK, char_tnum);
g_free(char_tnum);
}
diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c
index 1ef7183fa..27ddf655d 100644
--- a/src/decoder/mad_plugin.c
+++ b/src/decoder/mad_plugin.c
@@ -779,10 +779,10 @@ mp3_frame_duration(const struct mad_frame *frame)
MAD_UNITS_MILLISECONDS) / 1000.0;
}
-static off_t
+static goffset
mp3_this_frame_offset(const struct mp3_data *data)
{
- off_t offset = data->input_stream->offset;
+ goffset offset = data->input_stream->offset;
if (data->stream.this_frame != NULL)
offset -= data->stream.bufend - data->stream.this_frame;
@@ -792,7 +792,7 @@ mp3_this_frame_offset(const struct mp3_data *data)
return offset;
}
-static off_t
+static goffset
mp3_rest_including_this_frame(const struct mp3_data *data)
{
return data->input_stream->size - mp3_this_frame_offset(data);
@@ -804,7 +804,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);
@@ -1170,13 +1170,6 @@ 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)
{
@@ -1192,7 +1185,8 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
return;
}
- mp3_audio_format(&data, &audio_format);
+ audio_format_init(&audio_format, data.frame.header.samplerate, 24,
+ MAD_NCHANNELS(&data.frame.header));
decoder_initialized(decoder, &audio_format,
data.input_stream->seekable, data.total_time);
diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_plugin.c
index 065c34319..850232f34 100644
--- a/src/decoder/mikmod_plugin.c
+++ b/src/decoder/mikmod_plugin.c
@@ -175,9 +175,7 @@ mod_decode(struct decoder *decoder, const char *path)
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
+ audio_format_init(&audio_format, 44100, 16, 2);
secPerByte =
1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
@@ -222,7 +220,7 @@ static struct tag *modTagDup(const char *file)
title = g_strdup(Player_LoadTitle(path2));
g_free(path2);
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
return ret;
}
diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c
index f636f2fa6..301c80674 100644
--- a/src/decoder/modplug_plugin.c
+++ b/src/decoder/modplug_plugin.c
@@ -121,9 +121,7 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
+ audio_format_init(&audio_format, 44100, 16, 2);
sec_perbyte =
1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
@@ -186,11 +184,11 @@ static struct tag *mod_tagdup(const char *file)
return NULL;
}
ret = tag_new();
- ret->time = 0;
+ ret->time = ModPlug_GetLength(f) / 1000;
title = g_strdup(ModPlug_GetName(f));
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
g_free(title);
ModPlug_Unload(f);
diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index cf9382904..745d1e619 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -131,11 +131,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
}
*track_r = track;
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
+ audio_format_init(audio_format, sample_rate, 16, channels);
if (!audio_format_valid(audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
@@ -395,22 +391,22 @@ mp4_tag_dup(const char *file)
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
if (0 == g_ascii_strcasecmp("artist", item)) {
- tag_add_item(ret, TAG_ITEM_ARTIST, value);
+ tag_add_item(ret, TAG_ARTIST, value);
} else if (0 == g_ascii_strcasecmp("title", item)) {
- tag_add_item(ret, TAG_ITEM_TITLE, value);
+ tag_add_item(ret, TAG_TITLE, value);
} else if (0 == g_ascii_strcasecmp("album", item)) {
- tag_add_item(ret, TAG_ITEM_ALBUM, value);
+ tag_add_item(ret, TAG_ALBUM, value);
} else if (0 == g_ascii_strcasecmp("track", item)) {
- tag_add_item(ret, TAG_ITEM_TRACK, value);
+ tag_add_item(ret, TAG_TRACK, value);
} else if (0 == g_ascii_strcasecmp("disc", item)) {
/* Is that the correct id? */
- tag_add_item(ret, TAG_ITEM_DISC, value);
+ tag_add_item(ret, TAG_DISC, value);
} else if (0 == g_ascii_strcasecmp("genre", item)) {
- tag_add_item(ret, TAG_ITEM_GENRE, value);
+ tag_add_item(ret, TAG_GENRE, value);
} else if (0 == g_ascii_strcasecmp("date", item)) {
- tag_add_item(ret, TAG_ITEM_DATE, value);
+ tag_add_item(ret, TAG_DATE, value);
} else if (0 == g_ascii_strcasecmp("writer", item)) {
- tag_add_item(ret, TAG_ITEM_COMPOSER, value);
+ tag_add_item(ret, TAG_COMPOSER, value);
}
free(item);
diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_plugin.c
index 26349f93a..a684da104 100644
--- a/src/decoder/mpcdec_plugin.c
+++ b/src/decoder/mpcdec_plugin.c
@@ -193,9 +193,7 @@ 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;
+ audio_format_init(&audio_format, info.sample_freq, 24, info.channels);
if (!audio_format_valid(&audio_format)) {
#ifndef MPC_IS_OLD_API
diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c
new file mode 100644
index 000000000..20d9c4a54
--- /dev/null
+++ b/src/decoder/mpg123_decoder_plugin.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "decoder_api.h"
+
+#include <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)
+{
+ 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;
+ }
+
+ audio_format_init(audio_format, rate, 16, channels);
+ if (!audio_format_valid(audio_format)) {
+ g_warning("invalid audio format");
+ 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, position;
+ 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;
+
+ position = mpg123_tell(handle);
+
+ /* 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,
+ (float)position /
+ (float)audio_format.sample_rate,
+ 0, NULL);
+
+ /* seeking not yet implemented */
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ /* cleanup */
+
+ mpg123_delete(handle);
+}
+
+static struct tag *
+mpd_mpg123_tag_dup(const char *path_fs)
+{
+ struct audio_format audio_format;
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples;
+ struct tag *tag;
+
+ handle = mpg123_new(NULL, &error);
+ if (handle == NULL) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return NULL;
+ }
+
+ if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ num_samples = mpg123_length(handle);
+ if (num_samples <= 0) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ tag = tag_new();
+
+ tag->time = num_samples / audio_format.sample_rate;
+
+ /* ID3 tag support not yet implemented */
+
+ mpg123_delete(handle);
+ return tag;
+}
+
+static const char *const mpg123_suffixes[] = {
+ "mp3",
+ NULL
+};
+
+const struct decoder_plugin mpg123_decoder_plugin = {
+ .name = "mpg123",
+ .init = mpd_mpg123_init,
+ .finish = mpd_mpg123_finish,
+ .file_decode = mpd_mpg123_file_decode,
+ /* streaming not yet implemented */
+ .tag_dup = mpd_mpg123_tag_dup,
+ .suffixes = mpg123_suffixes,
+};
diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx
index c62e6b4b6..079b178fb 100644
--- a/src/decoder/sidplay_plugin.cxx
+++ b/src/decoder/sidplay_plugin.cxx
@@ -21,14 +21,180 @@ extern "C" {
#include "../decoder_api.h"
}
+#include <errno.h>
+#include <stdlib.h>
#include <glib.h>
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
+#include <sidplay/utils/SidTuneMod.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "sidplay"
+#define SUBTUNE_PREFIX "tune_"
+
+static GPatternSpec *path_with_subtune;
+static const char *songlength_file;
+static GKeyFile *songlength_database;
+
+static bool all_files_are_containers;
+static unsigned default_songlength;
+
+static bool filter_setting;
+
+static GKeyFile *
+sidplay_load_songlength_db(const char *path)
+{
+ GError *error = NULL;
+ gchar *data;
+ gsize size;
+
+ if (!g_file_get_contents(path, &data, &size, &error)) {
+ g_warning("unable to read songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ /* replace any ; comment characters with # */
+ for (gsize i = 0; i < size; i++)
+ if (data[i] == ';')
+ data[i] = '#';
+
+ GKeyFile *db = g_key_file_new();
+ bool success = g_key_file_load_from_data(db, data, size,
+ G_KEY_FILE_NONE, &error);
+ g_free(data);
+ if (!success) {
+ g_warning("unable to parse songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ g_key_file_free(db);
+ return NULL;
+ }
+
+ g_key_file_set_list_separator(db, ' ');
+ return db;
+}
+
+static bool
+sidplay_init(const struct config_param *param)
+{
+ /* read the songlengths database file */
+ songlength_file=config_get_block_string(param,
+ "songlength_database", NULL);
+ if (songlength_file != NULL)
+ songlength_database = sidplay_load_songlength_db(songlength_file);
+
+ default_songlength=config_get_block_unsigned(param,
+ "default_songlength", 0);
+
+ all_files_are_containers=config_get_block_bool(param,
+ "all_files_are_containers", true);
+
+ path_with_subtune=g_pattern_spec_new(
+ "*/" SUBTUNE_PREFIX "???.sid");
+
+ filter_setting=config_get_block_bool(param, "filter", true);
+
+ return true;
+}
+
+void
+sidplay_finish()
+{
+ g_pattern_spec_free(path_with_subtune);
+
+ if(songlength_database)
+ g_key_file_free(songlength_database);
+}
+
+/**
+ * returns the file path stripped of any /tune_xxx.sid subtune
+ * suffix
+ */
+static char *
+get_container_name(const char *path_fs)
+{
+ char *path_container=g_strdup(path_fs);
+
+ if(!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, NULL))
+ return path_container;
+
+ char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if(ptr) *ptr='\0';
+
+ return path_container;
+}
+
+/**
+ * returns tune number from file.sid/tune_xxx.sid style path or 1 if
+ * no subtune is appended
+ */
+static int
+get_song_num(const char *path_fs)
+{
+ if(g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, NULL)) {
+ char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ if(!sub) return 1;
+
+ sub+=strlen("/" SUBTUNE_PREFIX);
+ int song_num=strtol(sub, NULL, 10);
+
+ if (errno == EINVAL)
+ return 1;
+ else
+ return song_num;
+ } else
+ return 1;
+}
+
+/* get the song length in seconds */
+static int
+get_song_length(const char *path_fs)
+{
+ if (songlength_database == NULL)
+ return -1;
+
+ gchar *sid_file=get_container_name(path_fs);
+ SidTuneMod tune(sid_file);
+ g_free(sid_file);
+ if(!tune) {
+ g_warning("failed to load file for calculating md5 sum");
+ return -1;
+ }
+ char md5sum[SIDTUNE_MD5_LENGTH+1];
+ tune.createMD5(md5sum);
+
+ int song_num=get_song_num(path_fs);
+
+ gsize num_items;
+ gchar **values=g_key_file_get_string_list(songlength_database,
+ "Database", md5sum, &num_items, NULL);
+ if(!values || song_num>num_items) {
+ g_strfreev(values);
+ return -1;
+ }
+
+ int minutes=strtol(values[song_num-1], NULL, 10);
+ if(errno==EINVAL) minutes=0;
+
+ int seconds;
+ char *ptr=strchr(values[song_num-1], ':');
+ if(ptr) {
+ seconds=strtol(ptr+1, NULL, 10);
+ if(errno==EINVAL) seconds=0;
+ } else
+ seconds=0;
+
+ g_strfreev(values);
+
+ return (minutes*60)+seconds;
+}
+
static void
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
{
@@ -36,13 +202,19 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* load the tune */
- SidTune tune(path_fs, NULL, true);
+ char *path_container=get_container_name(path_fs);
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune) {
g_warning("failed to load file");
return;
}
- tune.selectSong(1);
+ int song_num=get_song_num(path_fs);
+ tune.selectSong(song_num);
+
+ int song_len=get_song_length(path_fs);
+ if(song_len==-1) song_len=default_songlength;
/* initialize the player */
@@ -67,7 +239,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
return;
}
- builder.filter(false);
+ builder.filter(filter_setting);
if (!builder) {
g_warning("ReSIDBuilder.filter() failed");
return;
@@ -103,14 +275,14 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* initialize the MPD decoder */
struct audio_format audio_format;
- audio_format.sample_rate = 48000;
- audio_format.bits = 16;
- audio_format.channels = 2;
+ audio_format_init(&audio_format, 48000, 16, 2);
- decoder_initialized(decoder, &audio_format, false, -1);
+ decoder_initialized(decoder, &audio_format, true, (float)song_len);
/* .. and play */
+ float data_time=0;
+ int timebase=player.timebase();
enum decoder_command cmd;
do {
char buffer[4096];
@@ -121,29 +293,105 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
break;
cmd = decoder_data(decoder, NULL, buffer, nbytes,
- 0, 0, NULL);
- } while (cmd == DECODE_COMMAND_NONE);
+ data_time, 0, NULL);
+
+ data_time=player.time()/timebase;
+
+ if(cmd==DECODE_COMMAND_SEEK) {
+ int target_time=decoder_seek_where(decoder);
+
+ /* 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()/timebase;
+ }
+
+ decoder_command_finished(decoder);
+ }
+
+ if(song_len && data_time>=(float)song_len)
+ break;
+
+ } while (cmd != DECODE_COMMAND_STOP);
}
static struct tag *
sidplay_tag_dup(const char *path_fs)
{
- SidTune tune(path_fs, NULL, true);
+ int song_num=get_song_num(path_fs);
+ char *path_container=get_container_name(path_fs);
+
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune)
return NULL;
const SidTuneInfo &info = tune.getInfo();
struct tag *tag = tag_new();
+ /* title */
+ const char *title;
if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]);
-
+ title=info.infoString[0];
+ else
+ title="";
+
+ if(info.songs>1) {
+ char *tag_title=g_strdup_printf("%s (%d/%d)",
+ title, song_num, info.songs);
+ tag_add_item(tag, TAG_TITLE, tag_title);
+ g_free(tag_title);
+ } else
+ tag_add_item(tag, TAG_TITLE, title);
+
+ /* artist */
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]);
+ tag_add_item(tag, TAG_ARTIST, info.infoString[1]);
+
+ /* track */
+ char *track=g_strdup_printf("%d", song_num);
+ tag_add_item(tag, TAG_TRACK, track);
+ g_free(track);
+
+ /* time */
+ int song_len=get_song_length(path_fs);
+ if(song_len!=-1) tag->time=song_len;
return tag;
}
+static char *
+sidplay_container_scan(const char *path_fs, const unsigned int tnum)
+{
+ SidTune tune(path_fs, NULL, true);
+ if (!tune)
+ return NULL;
+
+ const SidTuneInfo &info=tune.getInfo();
+
+ /* Don't treat sids containing a single tune
+ as containers */
+ if(!all_files_are_containers && info.songs<2)
+ return NULL;
+
+ /* Construct container/tune path names, eg.
+ Delta.sid/tune_001.sid */
+ if(tnum<=info.songs) {
+ char *subtune= g_strdup_printf(
+ SUBTUNE_PREFIX "%03u.sid", tnum);
+ return subtune;
+ } else
+ return NULL;
+}
+
static const char *const sidplay_suffixes[] = {
"sid",
NULL
@@ -152,12 +400,12 @@ static const char *const sidplay_suffixes[] = {
extern const struct decoder_plugin sidplay_decoder_plugin;
const struct decoder_plugin sidplay_decoder_plugin = {
"sidplay",
- NULL, /* init() */
- NULL, /* finish() */
+ sidplay_init,
+ sidplay_finish,
NULL, /* stream_decode() */
sidplay_file_decode,
sidplay_tag_dup,
- NULL, /* container_scan */
+ sidplay_container_scan,
sidplay_suffixes,
NULL, /* mime_types */
};
diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c
new file mode 100644
index 000000000..59993ef89
--- /dev/null
+++ b/src/decoder/sndfile_decoder_plugin.c
@@ -0,0 +1,244 @@
+/*
+ * 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 "decoder_api.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);
+ 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;
+ size_t nbytes;
+
+ nbytes = input_stream_read(is, ptr, count);
+ if (nbytes == 0 && is->error != 0)
+ 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)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ struct audio_format audio_format;
+ size_t frame_size;
+ sf_count_t read_frames, num_frames, position = 0;
+ 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. */
+ audio_format_init(&audio_format, info.samplerate, 32, info.channels);
+
+ if (!audio_format_valid(&audio_format)) {
+ g_warning("invalid audio format");
+ 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,
+ frame_to_time(position, &audio_format),
+ 0, NULL);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ sf_count_t c =
+ time_to_frame(decoder_seek_where(decoder),
+ &audio_format);
+ c = sf_seek(sf, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ sf_close(sf);
+}
+
+static struct tag *
+sndfile_tag_dup(const char *path_fs)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ struct tag *tag;
+ const char *p;
+
+ info.format = 0;
+
+ sf = sf_open(path_fs, SFM_READ, &info);
+ if (sf == NULL)
+ return NULL;
+
+ if (!audio_valid_sample_rate(info.samplerate)) {
+ sf_close(sf);
+ g_warning("Invalid sample rate in %s\n", path_fs);
+ return NULL;
+ }
+
+ tag = tag_new();
+ tag->time = info.frames / info.samplerate;
+
+ p = sf_get_string(sf, SF_STR_TITLE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_TITLE, p);
+
+ p = sf_get_string(sf, SF_STR_ARTIST);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ARTIST, p);
+
+ p = sf_get_string(sf, SF_STR_DATE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_DATE, p);
+
+ sf_close(sf);
+
+ return tag;
+}
+
+static const char *const sndfile_suffixes[] = {
+ "wav", "aiff", "aif", /* Microsoft / SGI / Apple */
+ "au", "snd", /* Sun / DEC / NeXT */
+ "paf", /* Paris Audio File */
+ "iff", "svx", /* Commodore Amiga IFF / SVX */
+ "sf", /* IRCAM */
+ "voc", /* Creative */
+ "w64", /* Soundforge */
+ "pvf", /* Portable Voice Format */
+ "xi", /* Fasttracker */
+ "htk", /* HMM Tool Kit */
+ "caf", /* Apple */
+ "sd2", /* Sound Designer II */
+
+ /* libsndfile also supports FLAC and Ogg Vorbis, but only by
+ linking with libFLAC and libvorbis - we can do better, we
+ have native plugins for these libraries */
+
+ NULL
+};
+
+static const char *const sndfile_mime_types[] = {
+ "audio/x-wav",
+ "audio/x-aiff",
+
+ /* what are the MIME types of the other supported formats? */
+
+ NULL
+};
+
+const struct decoder_plugin sndfile_decoder_plugin = {
+ .name = "sndfile",
+ .stream_decode = sndfile_stream_decode,
+ .tag_dup = sndfile_tag_dup,
+ .suffixes = sndfile_suffixes,
+ .mime_types = sndfile_mime_types,
+};
diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c
index d4f81e91f..2ac9c3bcb 100644..100755
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_plugin.c
@@ -24,6 +24,7 @@
#include "uri.h"
#ifndef HAVE_TREMOR
+#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>
#else
#include <tremor/ivorbisfile.h>
@@ -176,11 +177,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)
@@ -324,8 +325,7 @@ vorbis_stream_decode(struct decoder *decoder,
vorbis_info *vi = ov_info(&vf, -1);
struct replay_gain_info *new_rgi;
- audio_format.channels = vi->channels;
- audio_format.sample_rate = vi->rate;
+ audio_format_init(&audio_format, vi->rate, 16, vi->channels);
if (!audio_format_valid(&audio_format)) {
g_warning("Invalid audio format: %u:%u:%u\n",
@@ -378,7 +378,7 @@ vorbis_tag_dup(const char *file)
FILE *fp;
OggVorbis_File vf;
- fp = fopen(file, "r");
+ fp = fopen(file, "rb");
if (!fp) {
return NULL;
}
diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c
index 821536fb5..3e7c0cef7 100644
--- a/src/decoder/wavpack_plugin.c
+++ b/src/decoder/wavpack_plugin.c
@@ -41,17 +41,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. */
@@ -145,9 +145,9 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
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);
+ audio_format_init(&audio_format, WavpackGetSampleRate(wpc),
+ WavpackGetBitsPerSample(wpc),
+ WavpackGetReducedChannels(wpc));
/* round bitwidth to 8-bit units */
audio_format.bits = (audio_format.bits + 7) & (~7);
diff --git a/src/decoder_api.c b/src/decoder_api.c
index 2ece3bb98..4cff9916c 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -57,7 +57,10 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
dc.seekable = seekable;
dc.total_time = total_time;
+ decoder_lock();
dc.state = DECODE_STATE_DECODE;
+ decoder_unlock();
+
notify_signal(&pc.notify);
g_debug("audio_format=%u:%u:%u, seekable=%s",
@@ -88,6 +91,8 @@ enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
{
+ decoder_lock();
+
assert(dc.command != DECODE_COMMAND_NONE);
assert(dc.command != DECODE_COMMAND_SEEK ||
dc.seek_error || decoder->seeking);
@@ -105,6 +110,8 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
}
dc.command = DECODE_COMMAND_NONE;
+ decoder_unlock();
+
notify_signal(&pc.notify);
}
@@ -225,21 +232,24 @@ decoder_data(struct decoder *decoder,
struct replay_gain_info *replay_gain_info)
{
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);
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK ||
+ decoder_lock();
+ cmd = dc.command;
+ decoder_unlock();
+
+ 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;
@@ -259,14 +269,15 @@ decoder_data(struct decoder *decoder,
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.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) {
@@ -301,8 +312,7 @@ decoder_data(struct decoder *decoder,
/* apply replay gain or normalization */
- if (replay_gain_info != NULL &&
- replay_gain_mode != REPLAY_GAIN_OFF)
+ if (replay_gain_mode != REPLAY_GAIN_OFF)
replay_gain_apply(replay_gain_info, dest, nbytes,
&dc.out_audio_format);
else if (normalizationEnabled)
diff --git a/src/decoder_api.h b/src/decoder_api.h
index 37090d8d0..2ecd98ce7 100644
--- a/src/decoder_api.h
+++ b/src/decoder_api.h
@@ -17,15 +17,16 @@
* 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 "decoder_command.h"
#include "decoder_plugin.h"
#include "input_stream.h"
@@ -36,56 +37,97 @@
#include <stdbool.h>
-
/**
* Notify the player thread that it has finished initialization and
* that it has read the song's meta data.
+ *
+ * @param decoder the decoder object
+ * @param audio_format the audio format which is going to be sent to
+ * decoder_data()
+ * @param seekable true if the song is seekable
+ * @param total_time the total number of seconds in this song; -1 if unknown
*/
-void decoder_initialized(struct decoder * decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time);
+void
+decoder_initialized(struct decoder *decoder,
+ const struct audio_format *audio_format,
+ bool seekable, float total_time);
/**
* Returns the URI of the current song in UTF-8 encoding.
*
- * The return value is allocated on the heap, and must be freed by the
- * caller.
+ * @param decoder the decoder object
+ * @return an allocated string which must be freed with g_free()
*/
-char *decoder_get_uri(struct decoder *decoder);
+char *
+decoder_get_uri(struct decoder *decoder);
-enum decoder_command decoder_get_command(struct decoder * decoder);
+/**
+ * Determines the pending decoder command.
+ *
+ * @param decoder the decoder object
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
+ */
+enum decoder_command
+decoder_get_command(struct decoder *decoder);
/**
* Called by the decoder when it has performed the requested command
* (dc->command). This function resets dc->command and wakes up the
* player thread.
+ *
+ * @param decoder the decoder object
*/
-void decoder_command_finished(struct decoder * decoder);
+void
+decoder_command_finished(struct decoder *decoder);
-double decoder_seek_where(struct decoder * decoder);
+/**
+ * Call this when you have received the DECODE_COMMAND_SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the week
+ */
+double
+decoder_seek_where(struct decoder *decoder);
-void decoder_seek_error(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);
/**
- * 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).
+ * 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 *inStream,
- void *buffer, size_t length);
+size_t
+decoder_read(struct decoder *decoder, struct input_stream *is,
+ void *buffer, size_t length);
/**
* 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,
+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);
@@ -93,8 +135,12 @@ decoder_data(struct decoder *decoder,
* This function is called by the decoder plugin when it has
* successfully decoded a tag.
*
+ * @param decoder the decoder object
* @param is an input stream which is buffering while we are waiting
* for the player
+ * @param tag the tag to send
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
enum decoder_command
decoder_tag(struct decoder *decoder, struct input_stream *is,
diff --git a/src/decoder_control.c b/src/decoder_control.c
index 44bb63e15..3b993431c 100644
--- a/src/decoder_control.c
+++ b/src/decoder_control.c
@@ -18,6 +18,7 @@
*/
#include "decoder_control.h"
+#include "notify.h"
#include <assert.h>
@@ -25,36 +26,63 @@ struct decoder_control dc;
void dc_init(void)
{
- notify_init(&dc.notify);
+ dc.mutex = g_mutex_new();
+ dc.cond = g_cond_new();
+
dc.state = DECODE_STATE_STOP;
dc.command = DECODE_COMMAND_NONE;
}
void dc_deinit(void)
{
- notify_deinit(&dc.notify);
+ g_cond_free(dc.cond);
+ g_mutex_free(dc.mutex);
}
-void
-dc_command_wait(struct notify *notify)
+static void
+dc_command_wait_locked(struct notify *notify)
{
while (dc.command != DECODE_COMMAND_NONE) {
- notify_signal(&dc.notify);
+ decoder_signal();
+ decoder_unlock();
+
notify_wait(notify);
+
+ decoder_lock();
}
}
+void
+dc_command_wait(struct notify *notify)
+{
+ decoder_lock();
+ dc_command_wait_locked(notify);
+ decoder_unlock();
+}
+
static void
-dc_command(struct notify *notify, enum decoder_command cmd)
+dc_command_locked(struct notify *notify, enum decoder_command cmd)
{
dc.command = cmd;
- dc_command_wait(notify);
+ dc_command_wait_locked(notify);
+}
+
+static void
+dc_command(struct notify *notify, enum decoder_command cmd)
+{
+ decoder_lock();
+ dc_command_locked(notify, cmd);
+ decoder_unlock();
}
static void dc_command_async(enum decoder_command cmd)
{
+ decoder_lock();
+
dc.command = cmd;
- notify_signal(&dc.notify);
+ decoder_signal();
+
+ decoder_unlock();
}
void
@@ -80,15 +108,19 @@ dc_start_async(struct song *song)
void
dc_stop(struct notify *notify)
{
+ decoder_lock();
+
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(notify, DECODE_COMMAND_STOP);
if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR)
- dc_command(notify, DECODE_COMMAND_STOP);
+ dc_command_locked(notify, DECODE_COMMAND_STOP);
+
+ decoder_unlock();
}
bool
diff --git a/src/decoder_control.h b/src/decoder_control.h
index 703ea256c..7e861f970 100644
--- a/src/decoder_control.h
+++ b/src/decoder_control.h
@@ -22,13 +22,16 @@
#include "decoder_command.h"
#include "audio_format.h"
-#include "notify.h"
+
+#include <glib.h>
#include <assert.h>
#define DECODE_TYPE_FILE 0
#define DECODE_TYPE_URL 1
+struct notify;
+
enum decoder_state {
DECODE_STATE_STOP = 0,
DECODE_STATE_START,
@@ -48,14 +51,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;
@@ -80,6 +94,46 @@ void dc_init(void);
void dc_deinit(void);
+/**
+ * Locks the #decoder_control object.
+ */
+static inline void
+decoder_lock(void)
+{
+ g_mutex_lock(dc.mutex);
+}
+
+/**
+ * Unlocks the #decoder_control object.
+ */
+static inline void
+decoder_unlock(void)
+{
+ g_mutex_unlock(dc.mutex);
+}
+
+/**
+ * Waits for a signal on the #decoder_control object. This function
+ * is only valid in the decoder thread. The object must be locked
+ * prior to calling this function.
+ */
+static inline void
+decoder_wait(void)
+{
+ g_cond_wait(dc.cond, dc.mutex);
+}
+
+/**
+ * Signals the #decoder_control object. This function is only valid
+ * in the player thread. The object should be locked prior to calling
+ * this function.
+ */
+static inline void
+decoder_signal(void)
+{
+ g_cond_signal(dc.cond);
+}
+
static inline bool decoder_is_idle(void)
{
return (dc.state == DECODE_STATE_STOP ||
@@ -100,6 +154,39 @@ static inline bool decoder_has_failed(void)
return dc.state == DECODE_STATE_ERROR;
}
+static inline bool decoder_lock_is_idle(void)
+{
+ bool ret;
+
+ decoder_lock();
+ ret = decoder_is_idle();
+ decoder_unlock();
+
+ return ret;
+}
+
+static inline bool decoder_lock_is_starting(void)
+{
+ bool ret;
+
+ decoder_lock();
+ ret = decoder_is_starting();
+ decoder_unlock();
+
+ return ret;
+}
+
+static inline bool decoder_lock_has_failed(void)
+{
+ bool ret;
+
+ decoder_lock();
+ ret = decoder_has_failed();
+ decoder_unlock();
+
+ return ret;
+}
+
static inline struct song *
decoder_current_song(void)
{
diff --git a/src/decoder_internal.c b/src/decoder_internal.c
index 4a56fa5f3..1b064d0aa 100644
--- a/src/decoder_internal.c
+++ b/src/decoder_internal.c
@@ -28,6 +28,24 @@
#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 int
+decoder_input_buffer(struct input_stream *is)
+{
+ int ret;
+
+ decoder_unlock();
+ ret = input_stream_buffer(is) > 0;
+ decoder_lock();
+
+ return ret;
+}
+
+/**
* All chunks are full of decoded data; wait for the player to free
* one.
*/
@@ -38,9 +56,12 @@ need_chunks(struct input_stream *is, bool do_wait)
dc.command == DECODE_COMMAND_SEEK)
return dc.command;
- if ((is == NULL || input_stream_buffer(is) <= 0) && do_wait) {
- notify_wait(&dc.notify);
+ if ((is == NULL || decoder_input_buffer(is) <= 0) && do_wait) {
+ decoder_wait();
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
return dc.command;
}
@@ -63,7 +84,9 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
if (decoder->chunk != NULL)
return decoder->chunk;
+ decoder_lock();
cmd = need_chunks(is, true);
+ decoder_unlock();
} while (cmd == DECODE_COMMAND_NONE);
return NULL;
diff --git a/src/decoder_list.c b/src/decoder_list.c
index a42585e34..d30611e1b 100644
--- a/src/decoder_list.c
+++ b/src/decoder_list.c
@@ -28,9 +28,11 @@
#include <string.h>
extern const struct decoder_plugin mad_decoder_plugin;
+extern const struct decoder_plugin mpg123_decoder_plugin;
extern const struct decoder_plugin vorbis_decoder_plugin;
extern const struct decoder_plugin flac_decoder_plugin;
extern const struct decoder_plugin oggflac_decoder_plugin;
+extern const struct decoder_plugin sndfile_decoder_plugin;
extern const struct decoder_plugin audiofile_decoder_plugin;
extern const struct decoder_plugin mp4ff_decoder_plugin;
extern const struct decoder_plugin faad_decoder_plugin;
@@ -47,6 +49,9 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_MAD
&mad_decoder_plugin,
#endif
+#ifdef HAVE_MPG123
+ &mpg123_decoder_plugin,
+#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
@@ -56,6 +61,9 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
+#ifdef ENABLE_SNDFILE
+ &sndfile_decoder_plugin,
+#endif
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index d6ff058ec..5140f2ec3 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -49,11 +49,15 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
assert(input_stream->ready);
assert(dc.state == DECODE_STATE_START);
+ decoder_unlock();
+
/* rewind the stream, so each plugin gets a fresh start */
input_stream_seek(input_stream, 0, SEEK_SET);
decoder_plugin_stream_decode(plugin, decoder, input_stream);
+ decoder_lock();
+
assert(dc.state == DECODE_STATE_START ||
dc.state == DECODE_STATE_DECODE);
@@ -73,8 +77,12 @@ decoder_file_decode(const struct decoder_plugin *plugin,
assert(path[0] == '/');
assert(dc.state == DECODE_STATE_START);
+ decoder_unlock();
+
decoder_plugin_file_decode(plugin, decoder, path);
+ decoder_lock();
+
assert(dc.state == DECODE_STATE_START ||
dc.state == DECODE_STATE_DECODE);
@@ -103,28 +111,40 @@ static void decoder_run_song(const struct song *song, const char *uri)
dc.state = DECODE_STATE_START;
dc.command = DECODE_COMMAND_NONE;
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
/* wait for the input stream to become ready; its metadata
will be available then */
while (!input_stream.ready) {
if (dc.command == DECODE_COMMAND_STOP) {
+ decoder_unlock();
input_stream_close(&input_stream);
+ decoder_lock();
dc.state = DECODE_STATE_STOP;
return;
}
+ decoder_unlock();
ret = input_stream_buffer(&input_stream);
if (ret < 0) {
input_stream_close(&input_stream);
+ decoder_lock();
dc.state = DECODE_STATE_ERROR;
return;
}
+
+ decoder_lock();
}
if (dc.command == DECODE_COMMAND_STOP) {
+ decoder_unlock();
input_stream_close(&input_stream);
+ decoder_lock();
+
dc.state = DECODE_STATE_STOP;
return;
}
@@ -144,7 +164,7 @@ static void decoder_run_song(const struct song *song, const char *uri)
if (ret)
break;
- plugin = NULL;
+ assert(dc.state == DECODE_STATE_START);
}
/* if that fails, try suffix matching the URL: */
@@ -160,7 +180,6 @@ static void decoder_run_song(const struct song *song, const char *uri)
break;
assert(dc.state == DECODE_STATE_START);
- plugin = NULL;
}
}
/* fallback to mp3: */
@@ -179,7 +198,10 @@ static void decoder_run_song(const struct song *song, const char *uri)
const char *s = uri_get_suffix(uri);
while ((plugin = decoder_plugin_from_suffix(s, next++))) {
if (plugin->file_decode != NULL) {
+ decoder_unlock();
input_stream_close(&input_stream);
+ decoder_lock();
+
close_instream = false;
ret = decoder_file_decode(plugin,
&decoder, uri);
@@ -191,7 +213,13 @@ static void decoder_run_song(const struct song *song, const char *uri)
been closed before
decoder_file_decode() -
reopen it */
- if (input_stream_open(&input_stream, uri))
+ bool success;
+
+ decoder_unlock();
+ success = input_stream_open(&input_stream, uri);
+ decoder_lock();
+
+ if (success)
close_instream = true;
else
continue;
@@ -205,6 +233,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
}
}
+ decoder_unlock();
+
pcm_convert_deinit(&decoder.conv_state);
/* flush the last chunk */
@@ -223,6 +253,8 @@ static void decoder_run_song(const struct song *song, const char *uri)
if (decoder.decoder_tag != NULL)
tag_free(decoder.decoder_tag);
+ decoder_lock();
+
dc.state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
}
@@ -249,6 +281,8 @@ static void decoder_run(void)
static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
{
+ decoder_lock();
+
do {
assert(dc.state == DECODE_STATE_STOP ||
dc.state == DECODE_STATE_ERROR);
@@ -259,20 +293,28 @@ static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
decoder_run();
dc.command = DECODE_COMMAND_NONE;
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
break;
case DECODE_COMMAND_STOP:
dc.command = DECODE_COMMAND_NONE;
+
+ decoder_unlock();
notify_signal(&pc.notify);
+ decoder_lock();
break;
case DECODE_COMMAND_NONE:
- notify_wait(&dc.notify);
+ decoder_wait();
break;
}
} while (dc.command != DECODE_COMMAND_NONE || !dc.quit);
+ decoder_unlock();
+
return NULL;
}