aboutsummaryrefslogtreecommitdiffstats
path: root/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to 'src/decoder')
-rw-r--r--src/decoder/_flac_common.c429
-rw-r--r--src/decoder/_flac_common.h183
-rw-r--r--src/decoder/_ogg_common.c4
-rw-r--r--src/decoder/_ogg_common.h4
-rw-r--r--src/decoder/audiofile_decoder_plugin.c (renamed from src/decoder/audiofile_plugin.c)101
-rw-r--r--src/decoder/faad_decoder_plugin.c (renamed from src/decoder/faad_plugin.c)96
-rw-r--r--src/decoder/ffmpeg_decoder_plugin.c (renamed from src/decoder/ffmpeg_plugin.c)364
-rw-r--r--src/decoder/flac_compat.h114
-rw-r--r--src/decoder/flac_decoder_plugin.c497
-rw-r--r--src/decoder/flac_metadata.c289
-rw-r--r--src/decoder/flac_metadata.h55
-rw-r--r--src/decoder/flac_pcm.c109
-rw-r--r--src/decoder/flac_pcm.h33
-rw-r--r--src/decoder/flac_plugin.c918
-rw-r--r--src/decoder/fluidsynth_decoder_plugin.c (renamed from src/decoder/fluidsynth_plugin.c)13
-rw-r--r--src/decoder/mad_decoder_plugin.c (renamed from src/decoder/mad_plugin.c)198
-rw-r--r--src/decoder/mikmod_decoder_plugin.c (renamed from src/decoder/mikmod_plugin.c)153
-rw-r--r--src/decoder/modplug_decoder_plugin.c (renamed from src/decoder/modplug_plugin.c)67
-rw-r--r--src/decoder/mp4ff_decoder_plugin.c (renamed from src/decoder/mp4ff_plugin.c)66
-rw-r--r--src/decoder/mpcdec_decoder_plugin.c (renamed from src/decoder/mpcdec_plugin.c)93
-rw-r--r--src/decoder/mpg123_decoder_plugin.c209
-rw-r--r--src/decoder/oggflac_decoder_plugin.c (renamed from src/decoder/oggflac_plugin.c)84
-rw-r--r--src/decoder/sidplay_decoder_plugin.cxx417
-rw-r--r--src/decoder/sidplay_plugin.cxx163
-rw-r--r--src/decoder/sndfile_decoder_plugin.c251
-rw-r--r--src/decoder/vorbis_decoder_plugin.c (renamed from src/decoder/vorbis_plugin.c)191
-rw-r--r--src/decoder/wavpack_decoder_plugin.c (renamed from src/decoder/wavpack_plugin.c)188
-rw-r--r--src/decoder/wildmidi_decoder_plugin.c (renamed from src/decoder/wildmidi_plugin.c)12
28 files changed, 2940 insertions, 2361 deletions
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index 7c8fe9875..6f6d33f05 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,102 @@ 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;
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))
+ decoder_replay_gain(data->decoder, &rgi);
+ if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) {
+ g_debug("setting mixramp_tags");
+ decoder_mixramp(data->decoder, 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 +159,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 2a46601f4..ee87816de 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,37 @@
#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)
+{
+ g_logv(G_LOG_DOMAIN, level_ffmpeg_to_glib(level), fmt, vl);
+}
+
+#endif /* !OLD_FFMPEG_INCLUDES */
struct ffmpeg_stream {
/** hack - see url_to_struct() */
@@ -100,7 +119,7 @@ static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
if (whence == AVSEEK_SIZE)
return stream->input->size;
- ret = input_stream_seek(stream->input, pos, whence);
+ ret = input_stream_seek(stream->input, pos, whence, NULL);
if (!ret)
return -1;
@@ -124,6 +143,10 @@ static URLProtocol mpd_ffmpeg_fileops = {
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();
register_protocol(&mpd_ffmpeg_fileops);
return true;
@@ -160,78 +183,6 @@ append_uri_suffix(struct ffmpeg_stream *stream, const char *uri)
g_free(base);
}
-static bool
-ffmpeg_helper(const char *uri, struct input_stream *input,
- bool (*callback)(struct ffmpeg_context *ctx),
- struct ffmpeg_context *ctx)
-{
- AVFormatContext *format_context;
- AVCodecContext *codec_context;
- AVCodec *codec;
- int audio_stream;
- struct ffmpeg_stream stream = {
- .url = "mpd://X", /* only the mpd:// prefix matters */
- };
- bool ret;
-
- if (uri != NULL)
- append_uri_suffix(&stream, uri);
-
- stream.input = input;
- if (ctx && ctx->decoder) {
- stream.decoder = ctx->decoder; //are we in decoding loop ?
- } else {
- stream.decoder = NULL;
- }
-
- //ffmpeg works with ours "fileops" helper
- if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) {
- g_warning("Open failed\n");
- return false;
- }
-
- if (av_find_stream_info(format_context)<0) {
- g_warning("Couldn't find stream info\n");
- return false;
- }
-
- audio_stream = ffmpeg_find_audio_stream(format_context);
- if (audio_stream == -1) {
- g_warning("No audio stream inside\n");
- return false;
- }
-
- codec_context = format_context->streams[audio_stream]->codec;
- if (codec_context->codec_name[0] != 0)
- g_debug("codec '%s'", codec_context->codec_name);
-
- codec = avcodec_find_decoder(codec_context->codec_id);
-
- if (!codec) {
- g_warning("Unsupported audio codec\n");
- return false;
- }
-
- if (avcodec_open(codec_context, codec)<0) {
- g_warning("Could not open codec\n");
- 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_file(format_context);
-
- return ret;
-}
-
/**
* On some platforms, libavcodec wants the output buffer aligned to 16
* bytes (because it uses SSE/Altivec internally). This function
@@ -254,7 +205,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
const AVRational *time_base)
{
enum decoder_command cmd = DECODE_COMMAND_NONE;
- int position;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
int16_t *aligned_buffer;
size_t buffer_size;
@@ -262,6 +212,11 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
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;
@@ -286,65 +241,117 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
if (audio_size <= 0)
continue;
- position = packet->pts != (int64_t)AV_NOPTS_VALUE
- ? av_rescale_q(packet->pts, *time_base,
- (AVRational){1, 1})
- : 0;
-
cmd = decoder_data(decoder, is,
aligned_buffer, audio_size,
- position,
- codec_context->bit_rate / 1000, NULL);
+ codec_context->bit_rate / 1000);
}
return cmd;
}
-static bool
-ffmpeg_decode_internal(struct ffmpeg_context *ctx)
+static enum sample_format
+ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
{
- struct decoder *decoder = ctx->decoder;
- AVCodecContext *codec_context = ctx->codec_context;
- AVFormatContext *format_context = ctx->format_context;
- AVPacket packet;
- struct audio_format audio_format;
- enum decoder_command cmd;
- int total_time;
+#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
+ int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
- total_time = 0;
+ /* XXX implement & test other sample formats */
-#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
- audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
+ switch (bits) {
+ case 16:
+ return SAMPLE_FORMAT_S16;
+ }
+
+ return SAMPLE_FORMAT_UNDEFINED;
#else
/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
- audio_format.bits = (uint8_t) 16;
+ return SAMPLE_FORMAT_S16;
#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;
+}
+
+static void
+ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
+{
+ struct ffmpeg_stream stream = {
+ .url = "mpd://X", /* only the mpd:// prefix matters */
+ .decoder = decoder,
+ .input = input,
+ };
+ AVFormatContext *format_context;
+ AVCodecContext *codec_context;
+ AVCodec *codec;
+ int audio_stream;
+
+ if (input->uri != NULL)
+ append_uri_suffix(&stream, input->uri);
+
+ //ffmpeg works with ours "fileops" helper
+ if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) {
+ g_warning("Open failed\n");
+ return;
+ }
+
+ if (av_find_stream_info(format_context)<0) {
+ g_warning("Couldn't find stream info\n");
+ av_close_input_file(format_context);
+ return;
+ }
+
+ audio_stream = ffmpeg_find_audio_stream(format_context);
+ if (audio_stream == -1) {
+ g_warning("No audio stream inside\n");
+ av_close_input_file(format_context);
+ return;
+ }
+
+ codec_context = format_context->streams[audio_stream]->codec;
+ if (codec_context->codec_name[0] != 0)
+ g_debug("codec '%s'", codec_context->codec_name);
+
+ codec = avcodec_find_decoder(codec_context->codec_id);
+
+ if (!codec) {
+ g_warning("Unsupported audio codec\n");
+ av_close_input_file(format_context);
+ return;
+ }
+
+ if (avcodec_open(codec_context, codec)<0) {
+ g_warning("Could not open codec\n");
+ av_close_input_file(format_context);
+ 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;
+ GError *error = NULL;
+ struct audio_format audio_format;
+ 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_file(format_context);
+ return;
}
+ 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);
@@ -361,19 +368,8 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
}
} while (cmd != DECODE_COMMAND_STOP);
- return true;
-}
-
-static void
-ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
-{
- struct ffmpeg_context ctx;
-
- ctx.input = input;
- ctx.decoder = decoder;
-
- ffmpeg_helper(decoder_get_uri(decoder), input,
- ffmpeg_decode_internal, &ctx);
+ avcodec_close(codec_context);
+ av_close_input_file(format_context);
}
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
@@ -381,84 +377,92 @@ 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;
}
#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;
+ struct ffmpeg_stream stream = {
+ .url = "mpd://X", /* only the mpd:// prefix matters */
+ .decoder = NULL,
+ .input = is,
+ };
- tag->time = 0;
- if (f->duration != (int64_t)AV_NOPTS_VALUE)
- tag->time = f->duration / AV_TIME_BASE;
+ if (is->uri != NULL)
+ append_uri_suffix(&stream, is->uri);
+
+ AVFormatContext *f;
+ if (av_open_input_file(&f, stream.url, NULL, 0, NULL) != 0)
+ return NULL;
+
+ if (av_find_stream_info(f) < 0) {
+ av_close_input_file(f);
+ 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");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_TITLE, "title");
+#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "artist");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "date");
+#else
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "author");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "year");
+#endif
+ 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_ARTIST_SORT, "author-sort");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM_ARTIST, "album_artist");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM_ARTIST_SORT, "album_artist-sort");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_COMPOSER, "composer");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_PERFORMER, "performer");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_DISC, "disc");
#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, &input, ffmpeg_tag_internal, &ctx);
- if (!ret) {
- tag_free(ctx.tag);
- ctx.tag = NULL;
- }
- input_stream_close(&input);
+ av_close_input_file(f);
- return ctx.tag;
+ return tag;
}
/**
@@ -569,7 +573,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..68d15f6d4
--- /dev/null
+++ b/src/decoder/flac_metadata.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (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;
+ unsigned char tmp, *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];
+ tmp = p[len];
+ p[len] = '\0';
+ *str = strdup((char *)p);
+ p[len] = tmp;
+
+ 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/decoder/flac_pcm.h b/src/decoder/flac_pcm.h
new file mode 100644
index 000000000..bccfc645c
--- /dev/null
+++ b/src/decoder/flac_pcm.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 MPD_FLAC_PCM_H
+#define MPD_FLAC_PCM_H
+
+#include "audio_format.h"
+
+#include <FLAC/ordinals.h>
+
+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);
+
+#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/mad_plugin.c b/src/decoder/mad_decoder_plugin.c
index 7cc78a0d2..57221d878 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,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
-#include "../conf.h"
#include "config.h"
+#include "decoder_api.h"
+#include "conf.h"
#include "tag_id3.h"
+#include "audio_check.h"
#include <assert.h>
#include <unistd.h>
@@ -125,6 +126,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 +150,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 +167,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);
@@ -297,17 +300,17 @@ static int parse_rva2(struct id3_tag * tag, struct replay_gain_info * replay_gai
#endif
#ifdef HAVE_ID3TAG
-static struct replay_gain_info *
-parse_id3_replay_gain_info(struct id3_tag *tag)
+static bool
+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)
@@ -338,21 +341,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);
+ parse_rva2(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 = strdup(value);
+ found = true;
+ } else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
+ *mixramp_end = 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;
@@ -405,13 +442,17 @@ 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;
+ if (parse_id3_replay_gain_info(&rgi, id3_tag)) {
+ 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, mixramp_start, mixramp_end);
}
}
@@ -420,7 +461,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. */
@@ -448,8 +488,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;
@@ -471,7 +510,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);
@@ -779,10 +818,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 +831,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 +843,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);
@@ -819,8 +858,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;
@@ -834,8 +872,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;
@@ -878,14 +915,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;
@@ -913,33 +953,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);
@@ -998,8 +1034,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;
@@ -1024,9 +1059,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;
}
@@ -1038,8 +1071,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;
@@ -1080,7 +1112,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;
@@ -1094,7 +1126,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;
@@ -1111,9 +1143,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;
@@ -1142,8 +1172,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);
@@ -1170,29 +1199,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);
@@ -1202,24 +1236,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;
@@ -1233,7 +1263,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 065c34319..69212da59 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,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
#include <glib.h>
#include <mikmod.h>
+#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mikmod"
@@ -29,30 +31,34 @@
#define MIKMOD_FRAME_SIZE 4096
-static BOOL mod_mpd_Init(void)
+static BOOL
+mikmod_mpd_init(void)
{
return VC_Init();
}
-static void mod_mpd_Exit(void)
+static void
+mikmod_mpd_exit(void)
{
VC_Exit();
}
-static void mod_mpd_Update(void)
+static void
+mikmod_mpd_update(void)
{
}
-static BOOL mod_mpd_IsThere(void)
+static BOOL
+mikmod_mpd_is_present(void)
{
- return 1;
+ return true;
}
-static char drv_name[] = "MPD";
-static char drv_version[] = "MPD Output Driver v0.1";
+static char drv_name[] = PACKAGE_NAME;
+static char drv_version[] = VERSION;
#if (LIBMIKMOD_VERSION > 0x030106)
-static char drv_alias[] = "mpd";
+static char drv_alias[] = PACKAGE;
#endif
static MDRIVER drv_mpd = {
@@ -68,18 +74,18 @@ static MDRIVER drv_mpd = {
#endif
NULL, /* CommandLine */
#endif
- mod_mpd_IsThere,
+ mikmod_mpd_is_present,
VC_SampleLoad,
VC_SampleUnload,
VC_SampleSpace,
VC_SampleLength,
- mod_mpd_Init,
- mod_mpd_Exit,
+ mikmod_mpd_init,
+ mikmod_mpd_exit,
NULL,
VC_SetNumVoices,
VC_PlayStart,
VC_PlayStop,
- mod_mpd_Update,
+ mikmod_mpd_update,
NULL,
VC_VoiceSetVolume,
VC_VoiceGetVolume,
@@ -94,11 +100,19 @@ static MDRIVER drv_mpd = {
VC_VoiceRealVolume
};
+static unsigned mikmod_sample_rate;
+
static bool
-mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
+mikmod_decoder_init(const struct config_param *param)
{
static char params[] = "";
+ mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate",
+ 44100);
+ if (!audio_valid_sample_rate(mikmod_sample_rate))
+ g_error("Invalid sample rate in line %d: %u",
+ param->line, mikmod_sample_rate);
+
md_device = 0;
md_reverb = 0;
@@ -106,7 +120,7 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
MikMod_RegisterAllLoaders();
md_pansep = 64;
- md_mixfreq = 44100;
+ md_mixfreq = mikmod_sample_rate;
md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
DMODE_16BITS);
@@ -119,115 +133,82 @@ 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;
+ MODULE *handle;
char *title;
- path2 = g_strdup(file);
- moduleHandle = Player_Load(path2, 128, 0);
+ path2 = g_strdup(path_fs);
+ handle = Player_Load(path2, 128, 0);
g_free(path2);
- if (moduleHandle == NULL) {
- g_debug("Failed to open file: %s", file);
+ if (handle == NULL) {
+ g_debug("Failed to open file: %s", path_fs);
return NULL;
}
- Player_Free(moduleHandle);
+ Player_Free(handle);
ret = tag_new();
ret->time = 0;
- path2 = g_strdup(file);
+ path2 = g_strdup(path_fs);
title = g_strdup(Player_LoadTitle(path2));
g_free(path2);
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
return ret;
}
-static const char *const modSuffixes[] = {
+static const char *const mikmod_decoder_suffixes[] = {
"amf",
"dsm",
"far",
@@ -248,9 +229,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 cf9382904..74fa13339 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 <glib.h>
@@ -98,7 +99,7 @@ mp4_seek(void *user_data, uint64_t position)
{
struct mp4_context *ctx = user_data;
- return input_stream_seek(ctx->input_stream, position, SEEK_SET)
+ return input_stream_seek(ctx->input_stream, position, SEEK_SET, NULL)
? 0 : -1;
}
@@ -110,6 +111,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
int track;
uint32_t sample_rate;
unsigned char channels;
+ GError *error = NULL;
decoder = faacDecOpen();
@@ -130,22 +132,17 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
return NULL;
}
- *track_r = track;
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
-
- if (!audio_format_valid(audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format->sample_rate,
- audio_format->bits,
- audio_format->channels);
+ if (!audio_format_init_checked(audio_format, sample_rate,
+ SAMPLE_FORMAT_S16, channels,
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
faacDecClose(decoder);
return NULL;
}
+ *track_r = track;
+
return decoder;
}
@@ -265,7 +262,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
dur -= offset;
file_time += ((float)dur) / scale;
- if (seeking && file_time > seek_where)
+ if (seeking && file_time >= seek_where)
seek_position_found = true;
if (seeking && seek_position_found) {
@@ -331,7 +328,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
cmd = decoder_data(mpd_decoder, input_stream,
sample_buffer, sample_buffer_length,
- file_time, bit_rate, NULL);
+ bit_rate);
}
g_free(seek_table);
@@ -340,13 +337,12 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
}
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,
+ .input_stream = is,
};
mp4ff_callback_t callback = {
.read = mp4_read,
@@ -359,21 +355,13 @@ mp4_tag_dup(const char *file)
int32_t scale;
int i;
- if (!input_stream_open(&input_stream, file)) {
- g_warning("Failed to open file: %s", file);
- return NULL;
- }
-
mp4fh = mp4ff_open_read(&callback);
- if (!mp4fh) {
- input_stream_close(&input_stream);
+ if (mp4fh == NULL)
return NULL;
- }
track = mp4_get_aac_track(mp4fh, NULL, NULL, NULL);
if (track < 0) {
mp4ff_close(mp4fh);
- input_stream_close(&input_stream);
return NULL;
}
@@ -382,7 +370,6 @@ mp4_tag_dup(const char *file)
scale = mp4ff_time_scale(mp4fh, track);
if (scale < 0) {
mp4ff_close(mp4fh);
- input_stream_close(&input_stream);
tag_free(ret);
return NULL;
}
@@ -395,22 +382,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);
@@ -418,7 +405,6 @@ mp4_tag_dup(const char *file)
}
mp4ff_close(mp4fh);
- input_stream_close(&input_stream);
return ret;
}
@@ -429,7 +415,7 @@ static const char *const mp4_mime_types[] = { "audio/mp4", "audio/m4a", NULL };
const struct decoder_plugin mp4ff_decoder_plugin = {
.name = "mp4",
.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..a2eb21ae4
--- /dev/null
+++ b/src/decoder/sidplay_decoder_plugin.cxx
@@ -0,0 +1,417 @@
+/*
+ * Copyright (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;
+
+ /* 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.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_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
+ 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",
+ 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 0ff898647..25e91c142 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,13 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */
-
-#include "_ogg_common.h"
#include "config.h"
+#include "_ogg_common.h"
+#include "audio_check.h"
#include "uri.h"
#ifndef HAVE_TREMOR
+#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>
#else
#include <tremor/ivorbisfile.h>
@@ -55,17 +55,17 @@
#define OGG_DECODE_USE_BIGENDIAN 0
#endif
-typedef struct _OggCallbackData {
+struct vorbis_decoder_data {
struct decoder *decoder;
struct input_stream *input_stream;
bool seekable;
-} OggCallbackData;
+};
static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
{
+ struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata;
size_t ret;
- OggCallbackData *data = (OggCallbackData *) vdata;
ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb);
@@ -76,11 +76,11 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence)
{
- const OggCallbackData *data = (const OggCallbackData *) vdata;
+ struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata;
return data->seekable &&
decoder_get_command(data->decoder) != DECODE_COMMAND_STOP &&
- input_stream_seek(data->input_stream, offset, whence)
+ input_stream_seek(data->input_stream, offset, whence, NULL)
? 0 : -1;
}
@@ -92,12 +92,37 @@ static int ogg_close_cb(G_GNUC_UNUSED void *vdata)
static long ogg_tell_cb(void *vdata)
{
- const OggCallbackData *data = (const OggCallbackData *) vdata;
+ const struct vorbis_decoder_data *data =
+ (const struct vorbis_decoder_data *)vdata;
return (long)data->input_stream->offset;
}
static const char *
+vorbis_strerror(int code)
+{
+ switch (code) {
+ case OV_EREAD:
+ return "read error";
+
+ case OV_ENOTVORBIS:
+ return "not vorbis stream";
+
+ case OV_EVERSION:
+ return "vorbis version mismatch";
+
+ case OV_EBADHEADER:
+ return "invalid vorbis header";
+
+ case OV_EFAULT:
+ return "internal logic error";
+
+ default:
+ return "unknown error";
+ }
+}
+
+static const char *
vorbis_comment_value(const char *comment, const char *needle)
{
size_t len = strlen(needle);
@@ -109,14 +134,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 =
@@ -140,12 +164,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";
@@ -176,11 +195,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)
@@ -220,19 +239,10 @@ vorbis_send_comments(struct decoder *decoder, struct input_stream *is,
}
static bool
-oggvorbis_seekable(struct decoder *decoder)
+oggvorbis_seekable(const struct input_stream *is)
{
- char *uri;
- bool seekable;
-
- uri = decoder_get_uri(decoder);
- /* disable seeking on remote streams, because libvorbis seeks
- around like crazy, and due to being very expensive, this
- delays song playback my 10 or 20 seconds */
- seekable = !uri_has_scheme(uri);
- g_free(uri);
-
- return seekable;
+ return is->seekable &&
+ (is->uri == NULL || !uri_has_scheme(is->uri));
}
/* public */
@@ -240,19 +250,19 @@ static void
vorbis_stream_decode(struct decoder *decoder,
struct input_stream *input_stream)
{
+ GError *error = NULL;
OggVorbis_File vf;
ov_callbacks callbacks;
- OggCallbackData data;
+ struct vorbis_decoder_data data;
struct audio_format audio_format;
+ float total_time;
int current_section;
int prev_section = -1;
long ret;
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)
@@ -260,47 +270,44 @@ vorbis_stream_decode(struct decoder *decoder,
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(input_stream, 0, SEEK_SET);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
data.decoder = decoder;
data.input_stream = input_stream;
- data.seekable = input_stream->seekable && oggvorbis_seekable(decoder);
+ data.seekable = oggvorbis_seekable(input_stream);
callbacks.read_func = ogg_read_cb;
callbacks.seek_func = ogg_seek_cb;
callbacks.close_func = ogg_close_cb;
callbacks.tell_func = ogg_tell_cb;
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
- const char *error;
-
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
return;
- switch (ret) {
- case OV_EREAD:
- error = "read error";
- break;
- case OV_ENOTVORBIS:
- error = "not vorbis stream";
- break;
- case OV_EVERSION:
- error = "vorbis version mismatch";
- break;
- case OV_EBADHEADER:
- error = "invalid vorbis header";
- break;
- case OV_EFAULT:
- error = "internal logic error";
- break;
- default:
- error = "unknown error";
- break;
- }
+ g_warning("Error decoding Ogg Vorbis stream: %s",
+ vorbis_strerror(ret));
+ return;
+ }
+
+ 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, data.seekable, total_time);
do {
if (cmd == DECODE_COMMAND_SEEK) {
@@ -320,54 +327,40 @@ 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);
}
@@ -378,7 +371,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_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 8bad6943a..60696fb1a 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 *