aboutsummaryrefslogtreecommitdiffstats
path: root/src/decoder
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/decoder/_flac_common.c346
-rw-r--r--src/decoder/_flac_common.h178
-rw-r--r--src/decoder/_ogg_common.c2
-rw-r--r--src/decoder/_ogg_common.h2
-rw-r--r--src/decoder/audiofile_plugin.c97
-rw-r--r--src/decoder/faad_plugin.c69
-rw-r--r--src/decoder/ffmpeg_plugin.c90
-rw-r--r--src/decoder/flac_compat.h114
-rw-r--r--src/decoder/flac_metadata.c193
-rw-r--r--src/decoder/flac_metadata.h45
-rw-r--r--src/decoder/flac_pcm.c108
-rw-r--r--src/decoder/flac_pcm.h (renamed from src/normalize.c)36
-rw-r--r--src/decoder/flac_plugin.c548
-rw-r--r--src/decoder/fluidsynth_plugin.c11
-rw-r--r--src/decoder/mad_plugin.c42
-rw-r--r--src/decoder/mikmod_plugin.c152
-rw-r--r--src/decoder/modplug_plugin.c43
-rw-r--r--src/decoder/mp4ff_plugin.c47
-rw-r--r--src/decoder/mpcdec_plugin.c39
-rw-r--r--src/decoder/mpg123_decoder_plugin.c210
-rw-r--r--src/decoder/oggflac_plugin.c78
-rw-r--r--src/decoder/sidplay_plugin.cxx286
-rw-r--r--src/decoder/sndfile_decoder_plugin.c251
-rwxr-xr-x[-rw-r--r--]src/decoder/vorbis_plugin.c141
-rw-r--r--src/decoder/wavpack_plugin.c120
-rw-r--r--src/decoder/wildmidi_plugin.c7
-rw-r--r--src/decoder_api.c223
-rw-r--r--src/decoder_api.h112
-rw-r--r--src/decoder_buffer.c1
-rw-r--r--src/decoder_control.c119
-rw-r--r--src/decoder_control.h169
-rw-r--r--src/decoder_internal.c54
-rw-r--r--src/decoder_internal.h7
-rw-r--r--src/decoder_list.c83
-rw-r--r--src/decoder_list.h17
-rw-r--r--src/decoder_plugin.c47
-rw-r--r--src/decoder_plugin.h14
-rw-r--r--src/decoder_print.c54
-rw-r--r--src/decoder_print.h28
-rw-r--r--src/decoder_thread.c408
-rw-r--r--src/decoder_thread.h5
41 files changed, 3017 insertions, 1579 deletions
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index 7c8fe9875..4dd2b46ea 100644
--- a/src/decoder/_flac_common.c
+++ b/src/decoder/_flac_common.c
@@ -21,7 +21,11 @@
* Common data structures and functions used by FLAC and OggFLAC
*/
+#include "config.h"
#include "_flac_common.h"
+#include "flac_metadata.h"
+#include "flac_pcm.h"
+#include "audio_check.h"
#include <glib.h>
@@ -31,186 +35,96 @@ void
flac_data_init(struct flac_data *data, struct decoder * decoder,
struct input_stream *input_stream)
{
- data->time = 0;
+ pcm_buffer_init(&data->buffer);
+
+ data->have_stream_info = false;
+ data->first_frame = 0;
+ data->next_frame = 0;
+
data->position = 0;
- data->bit_rate = 0;
data->decoder = decoder;
data->input_stream = input_stream;
data->replay_gain_info = NULL;
data->tag = NULL;
}
-static void
-flac_find_float_comment(const FLAC__StreamMetadata *block,
- const char *cmnt, float *fl, bool *found_r)
-{
- int offset;
- size_t pos;
- int len;
- unsigned char tmp, *p;
-
- offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
- cmnt);
- if (offset < 0)
- return;
-
- pos = strlen(cmnt) + 1; /* 1 is for '=' */
- len = block->data.vorbis_comment.comments[offset].length - pos;
- if (len <= 0)
- return;
-
- p = &block->data.vorbis_comment.comments[offset].entry[pos];
- tmp = p[len];
- p[len] = '\0';
- *fl = (float)atof((char *)p);
- p[len] = tmp;
-
- *found_r = true;
-}
-
-static void
-flac_parse_replay_gain(const FLAC__StreamMetadata *block,
- struct flac_data *data)
+void
+flac_data_deinit(struct flac_data *data)
{
- bool found = false;
+ pcm_buffer_deinit(&data->buffer);
- if (data->replay_gain_info)
+ if (data->replay_gain_info != NULL)
replay_gain_info_free(data->replay_gain_info);
- data->replay_gain_info = replay_gain_info_new();
-
- flac_find_float_comment(block, "replaygain_album_gain",
- &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain,
- &found);
- flac_find_float_comment(block, "replaygain_album_peak",
- &data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak,
- &found);
- flac_find_float_comment(block, "replaygain_track_gain",
- &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain,
- &found);
- flac_find_float_comment(block, "replaygain_track_peak",
- &data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak,
- &found);
-
- if (!found) {
- replay_gain_info_free(data->replay_gain_info);
- data->replay_gain_info = NULL;
- }
+ if (data->tag != NULL)
+ tag_free(data->tag);
}
-/**
- * Checks if the specified name matches the entry's name, and if yes,
- * returns the comment value (not null-temrinated).
- */
-static const char *
-flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, const char *char_tnum, size_t *length_r)
+static enum sample_format
+flac_sample_format(const FLAC__StreamMetadata_StreamInfo *si)
{
- size_t name_length = strlen(name);
- size_t char_tnum_length = 0;
- const char *comment = (const char*)entry->entry;
+ switch (si->bits_per_sample) {
+ case 8:
+ return SAMPLE_FORMAT_S8;
- if (entry->length <= name_length ||
- g_ascii_strncasecmp(comment, name, name_length) != 0)
- return NULL;
+ case 16:
+ return SAMPLE_FORMAT_S16;
- if (char_tnum != NULL) {
- char_tnum_length = strlen(char_tnum);
- if (entry->length > name_length + char_tnum_length + 2 &&
- comment[name_length] == '[' &&
- g_ascii_strncasecmp(comment + name_length + 1,
- char_tnum, char_tnum_length) == 0 &&
- comment[name_length + char_tnum_length + 1] == ']')
- name_length = name_length + char_tnum_length + 2;
- else if (entry->length > name_length + char_tnum_length &&
- g_ascii_strncasecmp(comment + name_length,
- char_tnum, char_tnum_length) == 0)
- name_length = name_length + char_tnum_length;
- }
+ case 24:
+ return SAMPLE_FORMAT_S24_P32;
- if (comment[name_length] == '=') {
- *length_r = entry->length - name_length - 1;
- return comment + name_length + 1;
- }
+ case 32:
+ return SAMPLE_FORMAT_S32;
- return NULL;
+ default:
+ return SAMPLE_FORMAT_UNDEFINED;
+ }
}
-/**
- * Check if the comment's name equals the passed name, and if so, copy
- * the comment value into the tag.
- */
-static bool
-flac_copy_comment(struct tag *tag,
- const FLAC__StreamMetadata_VorbisComment_Entry *entry,
- const char *name, enum tag_type tag_type,
- const char *char_tnum)
+bool
+flac_data_get_audio_format(struct flac_data *data,
+ struct audio_format *audio_format)
{
- const char *value;
- size_t value_length;
+ GError *error = NULL;
- value = flac_comment_value(entry, name, char_tnum, &value_length);
- if (value != NULL) {
- tag_add_item_n(tag, tag_type, value, value_length);
- return true;
+ if (!data->have_stream_info) {
+ g_warning("no STREAMINFO packet found");
+ return false;
}
- return false;
-}
-
-/* tracknumber is used in VCs, MPD uses "track" ..., all the other
- * tag names match */
-static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
-static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
+ data->sample_format = flac_sample_format(&data->stream_info);
-static void
-flac_parse_comment(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata_VorbisComment_Entry *entry)
-{
- assert(tag != NULL);
-
- if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
- TAG_ITEM_TRACK, char_tnum) ||
- flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
- TAG_ITEM_DISC, char_tnum) ||
- flac_copy_comment(tag, entry, "album artist",
- TAG_ITEM_ALBUM_ARTIST, char_tnum))
- return;
+ if (!audio_format_init_checked(audio_format,
+ data->stream_info.sample_rate,
+ data->sample_format,
+ data->stream_info.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
- for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
- if (flac_copy_comment(tag, entry,
- tag_item_names[i], i, char_tnum))
- return;
-}
+ data->frame_size = audio_format_frame_size(audio_format);
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata *block)
-{
- FLAC__StreamMetadata_VorbisComment_Entry *comments =
- block->data.vorbis_comment.comments;
-
- for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i)
- flac_parse_comment(tag, char_tnum, comments++);
+ return true;
}
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data)
{
- const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info);
-
switch (block->type) {
case FLAC__METADATA_TYPE_STREAMINFO:
- data->audio_format.bits = (int8_t)si->bits_per_sample;
- data->audio_format.sample_rate = si->sample_rate;
- data->audio_format.channels = (int8_t)si->channels;
- data->total_time = ((float)si->total_samples) / (si->sample_rate);
+ data->stream_info = block->data.stream_info;
+ data->have_stream_info = true;
break;
+
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- flac_parse_replay_gain(block, data);
+ if (data->replay_gain_info)
+ replay_gain_info_free(data->replay_gain_info);
+ data->replay_gain_info = flac_parse_replay_gain(block);
if (data->tag != NULL)
- flac_vorbis_comments_to_tag(data->tag, NULL, block);
+ flac_vorbis_comments_to_tag(data->tag, NULL,
+ &block->data.vorbis_comment);
default:
break;
@@ -239,133 +153,43 @@ void flac_error_common_cb(const char *plugin,
}
}
-static void flac_convert_stereo16(int16_t *dest,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- for (; position < end; ++position) {
- *dest++ = buf[0][position];
- *dest++ = buf[1][position];
- }
-}
-
-static void
-flac_convert_16(int16_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
-
-/**
- * Note: this function also handles 24 bit files!
- */
-static void
-flac_convert_32(int32_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
-
-static void
-flac_convert_8(int8_t *dest,
- unsigned int num_channels,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
+FLAC__StreamDecoderWriteStatus
+flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
+ const FLAC__int32 *const buf[],
+ FLAC__uint64 nbytes)
{
- unsigned int c_chan;
-
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
+ enum decoder_command cmd;
+ size_t buffer_size = frame->header.blocksize * data->frame_size;
+ void *buffer;
+ unsigned bit_rate;
-static void flac_convert(unsigned char *dest,
- unsigned int num_channels,
- unsigned int bytes_per_sample,
- const FLAC__int32 * const buf[],
- unsigned int position, unsigned int end)
-{
- switch (bytes_per_sample) {
- case 2:
- if (num_channels == 2)
- flac_convert_stereo16((int16_t*)dest, buf,
- position, end);
- else
- flac_convert_16((int16_t*)dest, num_channels, buf,
- position, end);
- break;
+ buffer = pcm_buffer_get(&data->buffer, buffer_size);
- case 4:
- flac_convert_32((int32_t*)dest, num_channels, buf,
- position, end);
- break;
+ flac_convert(buffer, frame->header.channels,
+ data->sample_format, buf,
+ 0, frame->header.blocksize);
- case 1:
- flac_convert_8((int8_t*)dest, num_channels, buf,
- position, end);
+ if (nbytes > 0)
+ bit_rate = nbytes * 8 * frame->header.sample_rate /
+ (1000 * frame->header.blocksize);
+ else
+ bit_rate = 0;
+
+ cmd = decoder_data(data->decoder, data->input_stream,
+ buffer, buffer_size,
+ bit_rate,
+ data->replay_gain_info);
+ data->next_frame += frame->header.blocksize;
+ switch (cmd) {
+ case DECODE_COMMAND_NONE:
+ case DECODE_COMMAND_START:
break;
- }
-}
-
-FLAC__StreamDecoderWriteStatus
-flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
- const FLAC__int32 *const buf[])
-{
- unsigned int c_samp;
- const unsigned int num_channels = frame->header.channels;
- const unsigned int bytes_per_sample =
- audio_format_sample_size(&data->audio_format);
- const unsigned int bytes_per_channel =
- bytes_per_sample * frame->header.channels;
- const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel;
- unsigned int num_samples;
- enum decoder_command cmd;
- if (bytes_per_sample != 1 && bytes_per_sample != 2 &&
- bytes_per_sample != 4)
- /* exotic unsupported bit rate */
+ case DECODE_COMMAND_STOP:
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
- for (c_samp = 0; c_samp < frame->header.blocksize;
- c_samp += num_samples) {
- num_samples = frame->header.blocksize - c_samp;
- if (num_samples > max_samples)
- num_samples = max_samples;
-
- flac_convert(data->chunk,
- num_channels, bytes_per_sample, buf,
- c_samp, c_samp + num_samples);
-
- cmd = decoder_data(data->decoder, data->input_stream,
- data->chunk,
- num_samples * bytes_per_channel,
- data->time, data->bit_rate,
- data->replay_gain_info);
- switch (cmd) {
- case DECODE_COMMAND_NONE:
- case DECODE_COMMAND_START:
- break;
-
- case DECODE_COMMAND_STOP:
- return
- FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
-
- case DECODE_COMMAND_SEEK:
- return
- FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
- }
+ case DECODE_COMMAND_SEEK:
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
diff --git a/src/decoder/_flac_common.h b/src/decoder/_flac_common.h
index 68de7e969..2f328afa6 100644
--- a/src/decoder/_flac_common.h
+++ b/src/decoder/_flac_common.h
@@ -24,132 +24,51 @@
#ifndef MPD_FLAC_COMMON_H
#define MPD_FLAC_COMMON_H
-#include "../decoder_api.h"
-#include "config.h"
+#include "decoder_api.h"
+#include "pcm_buffer.h"
#include <glib.h>
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "flac"
-
-#include <FLAC/export.h>
-#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
-# include <FLAC/seekable_stream_decoder.h>
-# define flac_decoder FLAC__SeekableStreamDecoder
-# define flac_new() FLAC__seekable_stream_decoder_new()
-
-# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0)
-
-# define flac_get_decode_position(x,y) \
- FLAC__seekable_stream_decoder_get_decode_position(x,y)
-# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x)
-# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x)
-# define flac_process_metadata(x) \
- FLAC__seekable_stream_decoder_process_until_end_of_metadata(x)
-# define flac_seek_absolute(x,y) \
- FLAC__seekable_stream_decoder_seek_absolute(x,y)
-# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x)
-# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x)
-
-# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
-
-typedef unsigned flac_read_status_size_t;
-# define flac_read_status FLAC__SeekableStreamDecoderReadStatus
-# define flac_read_status_continue \
- FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
-# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
-# define flac_read_status_abort \
- FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
-
-# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus
-# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
-# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
-
-# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus
-# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
-# define flac_tell_status_error \
- FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
-# define flac_tell_status_unsupported \
- FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
-
-# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus
-# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
-# define flac_length_status_error \
- FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
-# define flac_length_status_unsupported \
- FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
-
-# ifdef HAVE_OGGFLAC
-# include <OggFLAC/seekable_stream_decoder.h>
-# endif
-#else /* FLAC_API_VERSION_CURRENT > 7 */
-
-/*
- * OggFLAC support is handled by our flac_plugin already, and
- * thus we *can* always have it if libFLAC was compiled with it
- */
-# include "_ogg_common.h"
-
-# include <FLAC/stream_decoder.h>
-# define flac_decoder FLAC__StreamDecoder
-# define flac_new() FLAC__stream_decoder_new()
-
-# define flac_init(a,b,c,d,e,f,g,h,i,j) \
- (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
- == FLAC__STREAM_DECODER_INIT_STATUS_OK)
-# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \
- (FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \
- == FLAC__STREAM_DECODER_INIT_STATUS_OK)
-
-# define flac_get_decode_position(x,y) \
- FLAC__stream_decoder_get_decode_position(x,y)
-# define flac_get_state(x) FLAC__stream_decoder_get_state(x)
-# define flac_process_single(x) FLAC__stream_decoder_process_single(x)
-# define flac_process_metadata(x) \
- FLAC__stream_decoder_process_until_end_of_metadata(x)
-# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y)
-# define flac_finish(x) FLAC__stream_decoder_finish(x)
-# define flac_delete(x) FLAC__stream_decoder_delete(x)
-
-# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM
-
-typedef size_t flac_read_status_size_t;
-# define flac_read_status FLAC__StreamDecoderReadStatus
-# define flac_read_status_continue \
- FLAC__STREAM_DECODER_READ_STATUS_CONTINUE
-# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM
-# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT
-
-# define flac_seek_status FLAC__StreamDecoderSeekStatus
-# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK
-# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR
-# define flac_seek_status_unsupported \
- FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED
-
-# define flac_tell_status FLAC__StreamDecoderTellStatus
-# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK
-# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR
-# define flac_tell_status_unsupported \
- FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED
-
-# define flac_length_status FLAC__StreamDecoderLengthStatus
-# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK
-# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR
-# define flac_length_status_unsupported \
- FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED
-
-#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-
+#include <FLAC/stream_decoder.h>
#include <FLAC/metadata.h>
-#define FLAC_CHUNK_SIZE 4080
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "flac"
struct flac_data {
- unsigned char chunk[FLAC_CHUNK_SIZE];
- float time;
- unsigned int bit_rate;
- struct audio_format audio_format;
- float total_time;
+ struct pcm_buffer buffer;
+
+ enum sample_format sample_format;
+
+ /**
+ * The size of one frame in the output buffer.
+ */
+ unsigned frame_size;
+
+ /**
+ * Is the #stream_info member valid?
+ */
+ bool have_stream_info;
+
+ /**
+ * A copy of the stream info object passed to the metadata
+ * callback. Once we drop support for libFLAC 1.1.2, we can
+ * remove this attribute, and use
+ * FLAC__stream_decoder_get_total_samples() etc.
+ */
+ FLAC__StreamMetadata_StreamInfo stream_info;
+
+ /**
+ * The number of the first frame in this song. This is only
+ * non-zero if playing sub songs from a CUE sheet.
+ */
+ FLAC__uint64 first_frame;
+
+ /**
+ * The number of the next frame which is going to be decoded.
+ */
+ FLAC__uint64 next_frame;
+
FLAC__uint64 position;
struct decoder *decoder;
struct input_stream *input_stream;
@@ -162,6 +81,20 @@ void
flac_data_init(struct flac_data *data, struct decoder * decoder,
struct input_stream *input_stream);
+void
+flac_data_deinit(struct flac_data *data);
+
+/**
+ * Obtains the audio format from the stream_info attribute, and copies
+ * it to the specified #audio_format object. This also updates the
+ * frame_size attribute.
+ *
+ * @return true on success, false the audio format is not supported
+ */
+bool
+flac_data_get_audio_format(struct flac_data *data,
+ struct audio_format *audio_format);
+
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
struct flac_data *data);
@@ -169,13 +102,10 @@ void flac_error_common_cb(const char *plugin,
FLAC__StreamDecoderErrorStatus status,
struct flac_data *data);
-void
-flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
- const FLAC__StreamMetadata *block);
-
FLAC__StreamDecoderWriteStatus
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
- const FLAC__int32 *const buf[]);
+ const FLAC__int32 *const buf[],
+ FLAC__uint64 nbytes);
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
diff --git a/src/decoder/_ogg_common.c b/src/decoder/_ogg_common.c
index 6c6553422..d838e0ff4 100644
--- a/src/decoder/_ogg_common.c
+++ b/src/decoder/_ogg_common.c
@@ -21,8 +21,8 @@
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
*/
+#include "config.h"
#include "_ogg_common.h"
-#include "../utils.h"
ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream)
{
diff --git a/src/decoder/_ogg_common.h b/src/decoder/_ogg_common.h
index e650c366d..eca5d40e0 100644
--- a/src/decoder/_ogg_common.h
+++ b/src/decoder/_ogg_common.h
@@ -24,7 +24,7 @@
#ifndef MPD_OGG_COMMON_H
#define MPD_OGG_COMMON_H
-#include "../decoder_api.h"
+#include "decoder_api.h"
typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type;
diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_plugin.c
index f66d90dc1..18cfdda5d 100644
--- a/src/decoder/audiofile_plugin.c
+++ b/src/decoder/audiofile_plugin.c
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#include <audiofile.h>
#include <af_vfs.h>
@@ -45,10 +47,20 @@ static int audiofile_get_duration(const char *file)
}
static ssize_t
-audiofile_file_read(AFvirtualfile *vfile, void *data, size_t nbytes)
+audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
{
struct input_stream *is = (struct input_stream *) vfile->closure;
- return input_stream_read(is, data, nbytes);
+ GError *error = NULL;
+ size_t nbytes;
+
+ nbytes = input_stream_read(is, data, length, &error);
+ if (nbytes == 0 && error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return -1;
+ }
+
+ return nbytes;
}
static long
@@ -78,7 +90,7 @@ audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative)
{
struct input_stream *is = (struct input_stream *) vfile->closure;
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
- if (input_stream_seek(is, offset, whence)) {
+ if (input_stream_seek(is, offset, whence, NULL)) {
return is->offset;
} else {
return -1;
@@ -99,17 +111,56 @@ setup_virtual_fops(struct input_stream *stream)
return vf;
}
+static enum sample_format
+audiofile_bits_to_sample_format(int bits)
+{
+ switch (bits) {
+ case 8:
+ return SAMPLE_FORMAT_S8;
+
+ case 16:
+ return SAMPLE_FORMAT_S16;
+
+ case 24:
+ return SAMPLE_FORMAT_S24_P32;
+
+ case 32:
+ return SAMPLE_FORMAT_S32;
+ }
+
+ return SAMPLE_FORMAT_UNDEFINED;
+}
+
+static enum sample_format
+audiofile_setup_sample_format(AFfilehandle af_fp)
+{
+ int fs, bits;
+
+ afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
+ if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
+ g_debug("input file has %d bit samples, converting to 16",
+ bits);
+ bits = 16;
+ }
+
+ afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
+ AF_SAMPFMT_TWOSCOMP, bits);
+ afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
+
+ return audiofile_bits_to_sample_format(bits);
+}
+
static void
audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
{
+ GError *error = NULL;
AFvirtualfile *vf;
int fs, frame_count;
AFfilehandle af_fp;
- int bits;
struct audio_format audio_format;
float total_time;
uint16_t bit_rate;
- int ret, current = 0;
+ int ret;
char chunk[CHUNK_SIZE];
enum decoder_command cmd;
@@ -126,26 +177,13 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
return;
}
- afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
- if (!audio_valid_sample_format(bits)) {
- g_debug("input file has %d bit samples, converting to 16",
- bits);
- bits = 16;
- }
-
- afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
- AF_SAMPFMT_TWOSCOMP, bits);
- afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
- audio_format.bits = (uint8_t)bits;
- audio_format.sample_rate =
- (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK);
- audio_format.channels =
- (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK);
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate, audio_format.bits,
- audio_format.channels);
+ if (!audio_format_init_checked(&audio_format,
+ afGetRate(af_fp, AF_DEFAULT_TRACK),
+ audiofile_setup_sample_format(af_fp),
+ afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
afCloseFile(af_fp);
return;
}
@@ -166,17 +204,14 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
if (ret <= 0)
break;
- current += ret;
cmd = decoder_data(decoder, NULL,
chunk, ret * fs,
- (float)current /
- (float)audio_format.sample_rate,
bit_rate, NULL);
if (cmd == DECODE_COMMAND_SEEK) {
- current = decoder_seek_where(decoder) *
+ AFframecount frame = decoder_seek_where(decoder) *
audio_format.sample_rate;
- afSeekFrame(af_fp, AF_DEFAULT_TRACK, current);
+ afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
decoder_command_finished(decoder);
cmd = DECODE_COMMAND_NONE;
diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_plugin.c
index 7b2806a4c..2272963b9 100644
--- a/src/decoder/faad_plugin.c
+++ b/src/decoder/faad_plugin.c
@@ -17,9 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
-#include "decoder_buffer.h"
#include "config.h"
+#include "decoder_api.h"
+#include "decoder_buffer.h"
+#include "audio_check.h"
#define AAC_MAX_CHANNELS 6
@@ -37,6 +38,15 @@ static const unsigned adts_sample_rates[] =
};
/**
+ * The GLib quark used for errors reported by this plugin.
+ */
+static inline GQuark
+faad_decoder_quark(void)
+{
+ return g_quark_from_static_string("faad");
+}
+
+/**
* Check whether the buffer head is an AAC frame, and return the frame
* length. Returns 0 if it is not a frame.
*/
@@ -195,7 +205,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
/* obtain the duration from the ADTS header */
float song_length = adts_song_duration(buffer);
- input_stream_seek(is, tagsize, SEEK_SET);
+ input_stream_seek(is, tagsize, SEEK_SET, NULL);
data = decoder_buffer_read(buffer, &length);
if (data != NULL)
@@ -232,7 +242,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
*/
static bool
faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
- struct audio_format *audio_format)
+ struct audio_format *audio_format, GError **error_r)
{
union {
/* deconst hack for libfaad */
@@ -247,32 +257,33 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
/* neaacdec.h declares all arguments as "unsigned long", but
internally expects uint32_t pointers. To avoid gcc
warnings, use this workaround. */
- unsigned long *sample_rate_r = (unsigned long *)(void *)&sample_rate;
+ unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
#else
- uint32_t *sample_rate_r = &sample_rate;
+ uint32_t *sample_rate_p = &sample_rate;
#endif
u.in = decoder_buffer_read(buffer, &length);
- if (u.in == NULL)
+ if (u.in == NULL) {
+ g_set_error(error_r, faad_decoder_quark(), 0,
+ "Empty file");
return false;
+ }
nbytes = faacDecInit(decoder, u.out,
#ifdef HAVE_FAAD_BUFLEN_FUNCS
length,
#endif
- sample_rate_r, &channels);
- if (nbytes < 0)
+ sample_rate_p, &channels);
+ if (nbytes < 0) {
+ g_set_error(error_r, faad_decoder_quark(), 0,
+ "Not an AAC stream");
return false;
+ }
decoder_buffer_consume(buffer, nbytes);
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
-
- return true;
+ return audio_format_init_checked(audio_format, sample_rate,
+ SAMPLE_FORMAT_S16, channels, error_r);
}
/**
@@ -319,7 +330,7 @@ faad_get_file_time_float(const char *file)
faacDecConfigurationPtr config;
struct input_stream is;
- if (!input_stream_open(&is, file))
+ if (!input_stream_open(&is, file, NULL))
return -1;
buffer = decoder_buffer_new(NULL, &is,
@@ -338,8 +349,8 @@ faad_get_file_time_float(const char *file)
decoder_buffer_fill(buffer);
- ret = faad_decoder_init(decoder, buffer, &audio_format);
- if (ret && audio_format_valid(&audio_format))
+ ret = faad_decoder_init(decoder, buffer, &audio_format, NULL);
+ if (ret)
length = 0;
faacDecClose(decoder);
@@ -371,7 +382,7 @@ faad_get_file_time(const char *file)
static void
faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
{
- float file_time;
+ GError *error = NULL;
float total_time = 0;
faacDecHandle decoder;
struct audio_format audio_format;
@@ -408,15 +419,10 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
/* initialize it */
- ret = faad_decoder_init(decoder, buffer, &audio_format);
+ ret = faad_decoder_init(decoder, buffer, &audio_format, &error);
if (!ret) {
- g_warning("Error not a AAC stream.\n");
- faacDecClose(decoder);
- return;
- }
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("invalid audio format\n");
+ g_warning("%s", error->message);
+ g_error_free(error);
faacDecClose(decoder);
return;
}
@@ -427,8 +433,6 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
/* the decoder loop */
- file_time = 0.0;
-
do {
size_t frame_size;
const void *decoded;
@@ -474,15 +478,12 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
bit_rate = frame_info.bytesconsumed * 8.0 *
frame_info.channels * audio_format.sample_rate /
frame_info.samples / 1000 + 0.5;
- file_time +=
- (float)(frame_info.samples) / frame_info.channels /
- audio_format.sample_rate;
}
/* send PCM samples to MPD */
cmd = decoder_data(mpd_decoder, is, decoded,
- (size_t)frame_info.samples * 2, file_time,
+ (size_t)frame_info.samples * 2,
bit_rate, NULL);
} while (cmd != DECODE_COMMAND_STOP);
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index d2bd642fd..6d856f293 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#include <glib.h>
@@ -100,7 +101,7 @@ static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
if (whence == AVSEEK_SIZE)
return stream->input->size;
- ret = input_stream_seek(stream->input, pos, whence);
+ ret = input_stream_seek(stream->input, pos, whence, NULL);
if (!ret)
return -1;
@@ -231,7 +232,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
const AVRational *time_base)
{
enum decoder_command cmd = DECODE_COMMAND_NONE;
- int position;
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
int16_t *aligned_buffer;
size_t buffer_size;
@@ -263,22 +263,42 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
if (audio_size <= 0)
continue;
- position = packet->pts != (int64_t)AV_NOPTS_VALUE
- ? av_rescale_q(packet->pts, *time_base,
- (AVRational){1, 1})
- : 0;
+ if (packet->pts != (int64_t)AV_NOPTS_VALUE)
+ decoder_timestamp(decoder,
+ av_rescale_q(packet->pts, *time_base,
+ (AVRational){1, 1}));
cmd = decoder_data(decoder, is,
aligned_buffer, audio_size,
- position,
codec_context->bit_rate / 1000, NULL);
}
return cmd;
}
+static enum sample_format
+ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
+{
+#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
+ int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
+
+ /* XXX implement & test other sample formats */
+
+ switch (bits) {
+ case 16:
+ return SAMPLE_FORMAT_S16;
+ }
+
+ return SAMPLE_FORMAT_UNDEFINED;
+#else
+ /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
+ return SAMPLE_FORMAT_S16;
+#endif
+}
+
static bool
ffmpeg_decode_internal(struct ffmpeg_context *ctx)
{
+ GError *error = NULL;
struct decoder *decoder = ctx->decoder;
AVCodecContext *codec_context = ctx->codec_context;
AVFormatContext *format_context = ctx->format_context;
@@ -289,19 +309,12 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
total_time = 0;
-#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
- audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
-#else
- /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
- audio_format.bits = (uint8_t) 16;
-#endif
- audio_format.sample_rate = (unsigned int)codec_context->sample_rate;
- audio_format.channels = codec_context->channels;
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate, audio_format.bits,
- audio_format.channels);
+ if (!audio_format_init_checked(&audio_format,
+ codec_context->sample_rate,
+ ffmpeg_sample_format(codec_context),
+ codec_context->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
return false;
}
@@ -357,8 +370,9 @@ static bool
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
enum tag_type type, const char *name)
{
- AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0);
- if (mt != NULL)
+ AVMetadataTag *mt = NULL;
+
+ while ((mt = av_metadata_get(m, name, mt, 0)) != NULL)
tag_add_item(tag, type, mt->value);
return mt != NULL;
}
@@ -376,35 +390,35 @@ static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
- ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_TITLE, "title");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ARTIST, "author");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_ALBUM, "album");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_COMMENT, "comment");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_GENRE, "genre");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_TRACK, "track");
+ ffmpeg_copy_metadata(tag, f->metadata, TAG_DATE, "year");
#else
if (f->author[0])
- tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
+ tag_add_item(tag, TAG_ARTIST, f->author);
if (f->title[0])
- tag_add_item(tag, TAG_ITEM_TITLE, f->title);
+ tag_add_item(tag, TAG_TITLE, f->title);
if (f->album[0])
- tag_add_item(tag, TAG_ITEM_ALBUM, f->album);
+ tag_add_item(tag, TAG_ALBUM, f->album);
if (f->track > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->track);
- tag_add_item(tag, TAG_ITEM_TRACK, buffer);
+ tag_add_item(tag, TAG_TRACK, buffer);
}
if (f->comment[0])
- tag_add_item(tag, TAG_ITEM_COMMENT, f->comment);
+ tag_add_item(tag, TAG_COMMENT, f->comment);
if (f->genre[0])
- tag_add_item(tag, TAG_ITEM_GENRE, f->genre);
+ tag_add_item(tag, TAG_GENRE, f->genre);
if (f->year > 0) {
char buffer[16];
snprintf(buffer, sizeof(buffer), "%d", f->year);
- tag_add_item(tag, TAG_ITEM_DATE, buffer);
+ tag_add_item(tag, TAG_DATE, buffer);
}
#endif
@@ -418,7 +432,7 @@ static struct tag *ffmpeg_tag(const char *file)
struct ffmpeg_context ctx;
bool ret;
- if (!input_stream_open(&input, file)) {
+ if (!input_stream_open(&input, file, NULL)) {
g_warning("failed to open %s\n", file);
return NULL;
}
diff --git a/src/decoder/flac_compat.h b/src/decoder/flac_compat.h
new file mode 100644
index 000000000..61d2c55e8
--- /dev/null
+++ b/src/decoder/flac_compat.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Common data structures and functions used by FLAC and OggFLAC
+ */
+
+#ifndef MPD_FLAC_COMPAT_H
+#define MPD_FLAC_COMPAT_H
+
+#include <FLAC/export.h>
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+# include <FLAC/seekable_stream_decoder.h>
+
+/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been
+ merged into the StreamDecoder. The following macros try to emulate
+ the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls
+ to the old SeekableStreamDecoder API. */
+
+#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder
+#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new
+#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position
+#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state
+#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single
+#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata
+#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute
+#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish
+#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete
+
+#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
+
+typedef unsigned flac_read_status_size_t;
+
+#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus
+#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
+#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
+#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
+
+#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus
+#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
+#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
+#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
+
+#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus
+#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
+#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
+#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
+
+#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus
+#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
+#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
+#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
+
+typedef enum {
+ FLAC__STREAM_DECODER_INIT_STATUS_OK,
+ FLAC__STREAM_DECODER_INIT_STATUS_ERROR,
+} FLAC__StreamDecoderInitStatus;
+
+static inline FLAC__StreamDecoderInitStatus
+FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder,
+ FLAC__SeekableStreamDecoderReadCallback read_cb,
+ FLAC__SeekableStreamDecoderSeekCallback seek_cb,
+ FLAC__SeekableStreamDecoderTellCallback tell_cb,
+ FLAC__SeekableStreamDecoderLengthCallback length_cb,
+ FLAC__SeekableStreamDecoderEofCallback eof_cb,
+ FLAC__SeekableStreamDecoderWriteCallback write_cb,
+ FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
+ FLAC__SeekableStreamDecoderErrorCallback error_cb,
+ void *data)
+{
+ return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) &&
+ FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) &&
+ FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) &&
+ FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) &&
+ FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) &&
+ FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) &&
+ FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) &&
+ FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
+ FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) &&
+ FLAC__seekable_stream_decoder_set_client_data(decoder, data) &&
+ FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK
+ ? FLAC__STREAM_DECODER_INIT_STATUS_OK
+ : FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
+}
+
+#else /* FLAC_API_VERSION_CURRENT > 7 */
+
+# include <FLAC/stream_decoder.h>
+
+# define flac_init(a,b,c,d,e,f,g,h,i,j) \
+ (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
+ == FLAC__STREAM_DECODER_INIT_STATUS_OK)
+
+typedef size_t flac_read_status_size_t;
+
+#endif /* FLAC_API_VERSION_CURRENT >= 7 */
+
+#endif /* _FLAC_COMMON_H */
diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c
new file mode 100644
index 000000000..1ff99f151
--- /dev/null
+++ b/src/decoder/flac_metadata.c
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "flac_metadata.h"
+#include "replay_gain.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+static bool
+flac_find_float_comment(const FLAC__StreamMetadata *block,
+ const char *cmnt, float *fl)
+{
+ int offset;
+ size_t pos;
+ int len;
+ unsigned char tmp, *p;
+
+ offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
+ cmnt);
+ if (offset < 0)
+ return false;
+
+ pos = strlen(cmnt) + 1; /* 1 is for '=' */
+ len = block->data.vorbis_comment.comments[offset].length - pos;
+ if (len <= 0)
+ return false;
+
+ p = &block->data.vorbis_comment.comments[offset].entry[pos];
+ tmp = p[len];
+ p[len] = '\0';
+ *fl = (float)atof((char *)p);
+ p[len] = tmp;
+
+ return true;
+}
+
+struct replay_gain_info *
+flac_parse_replay_gain(const FLAC__StreamMetadata *block)
+{
+ struct replay_gain_info *rgi;
+ bool found = false;
+
+ rgi = replay_gain_info_new();
+
+ found = flac_find_float_comment(block, "replaygain_album_gain",
+ &rgi->tuples[REPLAY_GAIN_ALBUM].gain) ||
+ flac_find_float_comment(block, "replaygain_album_peak",
+ &rgi->tuples[REPLAY_GAIN_ALBUM].peak) ||
+ flac_find_float_comment(block, "replaygain_track_gain",
+ &rgi->tuples[REPLAY_GAIN_TRACK].gain) ||
+ flac_find_float_comment(block, "replaygain_track_peak",
+ &rgi->tuples[REPLAY_GAIN_TRACK].peak);
+ if (!found) {
+ replay_gain_info_free(rgi);
+ rgi = NULL;
+ }
+
+ return rgi;
+}
+
+/**
+ * Checks if the specified name matches the entry's name, and if yes,
+ * returns the comment value (not null-temrinated).
+ */
+static const char *
+flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name, const char *char_tnum, size_t *length_r)
+{
+ size_t name_length = strlen(name);
+ size_t char_tnum_length = 0;
+ const char *comment = (const char*)entry->entry;
+
+ if (entry->length <= name_length ||
+ g_ascii_strncasecmp(comment, name, name_length) != 0)
+ return NULL;
+
+ if (char_tnum != NULL) {
+ char_tnum_length = strlen(char_tnum);
+ if (entry->length > name_length + char_tnum_length + 2 &&
+ comment[name_length] == '[' &&
+ g_ascii_strncasecmp(comment + name_length + 1,
+ char_tnum, char_tnum_length) == 0 &&
+ comment[name_length + char_tnum_length + 1] == ']')
+ name_length = name_length + char_tnum_length + 2;
+ else if (entry->length > name_length + char_tnum_length &&
+ g_ascii_strncasecmp(comment + name_length,
+ char_tnum, char_tnum_length) == 0)
+ name_length = name_length + char_tnum_length;
+ }
+
+ if (comment[name_length] == '=') {
+ *length_r = entry->length - name_length - 1;
+ return comment + name_length + 1;
+ }
+
+ return NULL;
+}
+
+/**
+ * Check if the comment's name equals the passed name, and if so, copy
+ * the comment value into the tag.
+ */
+static bool
+flac_copy_comment(struct tag *tag,
+ const FLAC__StreamMetadata_VorbisComment_Entry *entry,
+ const char *name, enum tag_type tag_type,
+ const char *char_tnum)
+{
+ const char *value;
+ size_t value_length;
+
+ value = flac_comment_value(entry, name, char_tnum, &value_length);
+ if (value != NULL) {
+ tag_add_item_n(tag, tag_type, value, value_length);
+ return true;
+ }
+
+ return false;
+}
+
+/* tracknumber is used in VCs, MPD uses "track" ..., all the other
+ * tag names match */
+static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
+static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
+
+static void
+flac_parse_comment(struct tag *tag, const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment_Entry *entry)
+{
+ assert(tag != NULL);
+
+ if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
+ TAG_TRACK, char_tnum) ||
+ flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
+ TAG_DISC, char_tnum) ||
+ flac_copy_comment(tag, entry, "album artist",
+ TAG_ALBUM_ARTIST, char_tnum))
+ return;
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (flac_copy_comment(tag, entry,
+ tag_item_names[i], i, char_tnum))
+ return;
+}
+
+void
+flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment *comment)
+{
+ for (unsigned i = 0; i < comment->num_comments; ++i)
+ flac_parse_comment(tag, char_tnum, &comment->comments[i]);
+}
+
+void
+flac_tag_apply_metadata(struct tag *tag, const char *track,
+ const FLAC__StreamMetadata *block)
+{
+ switch (block->type) {
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ flac_vorbis_comments_to_tag(tag, track,
+ &block->data.vorbis_comment);
+ break;
+
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ tag->time = flac_duration(&block->data.stream_info);
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h
new file mode 100644
index 000000000..ef97288d5
--- /dev/null
+++ b/src/decoder/flac_metadata.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FLAC_METADATA_H
+#define MPD_FLAC_METADATA_H
+
+#include <FLAC/metadata.h>
+
+struct tag;
+
+static inline unsigned
+flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
+{
+ return (stream_info->total_samples + stream_info->sample_rate - 1) /
+ stream_info->sample_rate;
+}
+
+struct replay_gain_info *
+flac_parse_replay_gain(const FLAC__StreamMetadata *block);
+
+void
+flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
+ const FLAC__StreamMetadata_VorbisComment *comment);
+
+void
+flac_tag_apply_metadata(struct tag *tag, const char *track,
+ const FLAC__StreamMetadata *block);
+
+#endif
diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c
new file mode 100644
index 000000000..a8bf6f293
--- /dev/null
+++ b/src/decoder/flac_pcm.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "flac_pcm.h"
+
+#include <assert.h>
+
+static void flac_convert_stereo16(int16_t *dest,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ for (; position < end; ++position) {
+ *dest++ = buf[0][position];
+ *dest++ = buf[1][position];
+ }
+}
+
+static void
+flac_convert_16(int16_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+/**
+ * Note: this function also handles 24 bit files!
+ */
+static void
+flac_convert_32(int32_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+static void
+flac_convert_8(int8_t *dest,
+ unsigned int num_channels,
+ const FLAC__int32 * const buf[],
+ unsigned int position, unsigned int end)
+{
+ unsigned int c_chan;
+
+ for (; position < end; ++position)
+ for (c_chan = 0; c_chan < num_channels; c_chan++)
+ *dest++ = buf[c_chan][position];
+}
+
+void
+flac_convert(void *dest,
+ unsigned int num_channels, enum sample_format sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end)
+{
+ switch (sample_format) {
+ case SAMPLE_FORMAT_S16:
+ if (num_channels == 2)
+ flac_convert_stereo16((int16_t*)dest, buf,
+ position, end);
+ else
+ flac_convert_16((int16_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
+ flac_convert_32((int32_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ flac_convert_8((int8_t*)dest, num_channels, buf,
+ position, end);
+ break;
+
+ case SAMPLE_FORMAT_UNDEFINED:
+ /* unreachable */
+ assert(false);
+ }
+}
diff --git a/src/normalize.c b/src/decoder/flac_pcm.h
index 63c0d15cb..4d7a51c4d 100644
--- a/src/normalize.c
+++ b/src/decoder/flac_pcm.h
@@ -17,33 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "normalize.h"
-#include "compress.h"
-#include "conf.h"
-#include "audio_format.h"
-
-#define DEFAULT_VOLUME_NORMALIZATION 0
-
-int normalizationEnabled;
+#ifndef MPD_FLAC_PCM_H
+#define MPD_FLAC_PCM_H
-void initNormalization(void)
-{
- normalizationEnabled = config_get_bool(CONF_VOLUME_NORMALIZATION,
- DEFAULT_VOLUME_NORMALIZATION);
-
- if (normalizationEnabled)
- CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS);
-}
+#include "audio_format.h"
-void finishNormalization(void)
-{
- if (normalizationEnabled) CompressFree();
-}
+#include <FLAC/ordinals.h>
-void normalizeData(char *buffer, int bufferSize,
- const struct audio_format *format)
-{
- if ((format->bits != 16) || (format->channels != 2)) return;
+void
+flac_convert(void *dest,
+ unsigned int num_channels, enum sample_format sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end);
- CompressDo(buffer, bufferSize);
-}
+#endif
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c
index 1e568f70d..ff1d1af3f 100644
--- a/src/decoder/flac_plugin.c
+++ b/src/decoder/flac_plugin.c
@@ -17,7 +17,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "_flac_common.h"
+#include "flac_compat.h"
+#include "flac_metadata.h"
+
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+#include "_ogg_common.h"
+#endif
#include <glib.h>
@@ -33,8 +40,8 @@
/* this code was based on flac123, from flac-tools */
-static flac_read_status
-flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd,
+static FLAC__StreamDecoderReadStatus
+flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__byte buf[], flac_read_status_size_t *bytes,
void *fdata)
{
@@ -48,53 +55,59 @@ flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd,
if (r == 0) {
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
input_stream_eof(data->input_stream))
- return flac_read_status_eof;
+ return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
else
- return flac_read_status_abort;
+ return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}
- return flac_read_status_continue;
+ return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
-static flac_seek_status
-flac_seek_cb(G_GNUC_UNUSED const flac_decoder *fd,
+static FLAC__StreamDecoderSeekStatus
+flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__uint64 offset, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
- return flac_seek_status_error;
+ if (!data->input_stream->seekable)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
- return flac_seek_status_ok;
+ if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
+ return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+
+ return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}
-static flac_tell_status
-flac_tell_cb(G_GNUC_UNUSED const flac_decoder *fd,
+static FLAC__StreamDecoderTellStatus
+flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__uint64 * offset, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
+ if (!data->input_stream->seekable)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
+
*offset = (long)(data->input_stream->offset);
- return flac_tell_status_ok;
+ return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
-static flac_length_status
-flac_length_cb(G_GNUC_UNUSED const flac_decoder *fd,
+static FLAC__StreamDecoderLengthStatus
+flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__uint64 * length, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
if (data->input_stream->size < 0)
- return flac_length_status_unsupported;
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
*length = (size_t) (data->input_stream->size);
- return flac_length_status_ok;
+ return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
static FLAC__bool
-flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata)
+flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata)
{
struct flac_data *data = (struct flac_data *) fdata;
@@ -104,7 +117,7 @@ flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata)
}
static void
-flac_error_cb(G_GNUC_UNUSED const flac_decoder *fd,
+flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
FLAC__StreamDecoderErrorStatus status, void *fdata)
{
flac_error_common_cb("flac", status, (struct flac_data *) fdata);
@@ -143,31 +156,6 @@ static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
g_warning("%s\n", str);
}
-
-static bool
-flac_init(FLAC__SeekableStreamDecoder *dec,
- FLAC__SeekableStreamDecoderReadCallback read_cb,
- FLAC__SeekableStreamDecoderSeekCallback seek_cb,
- FLAC__SeekableStreamDecoderTellCallback tell_cb,
- FLAC__SeekableStreamDecoderLengthCallback length_cb,
- FLAC__SeekableStreamDecoderEofCallback eof_cb,
- FLAC__SeekableStreamDecoderWriteCallback write_cb,
- FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
- FLAC__SeekableStreamDecoderErrorCallback error_cb,
- void *data)
-{
- return FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb) &&
- FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb) &&
- FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb) &&
- FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb) &&
- FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb) &&
- FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb) &&
- FLAC__seekable_stream_decoder_set_metadata_callback(dec, metadata_cb) &&
- FLAC__seekable_stream_decoder_set_metadata_respond(dec, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
- FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb) &&
- FLAC__seekable_stream_decoder_set_client_data(dec, data) &&
- FLAC__seekable_stream_decoder_init(dec) == FLAC__SEEKABLE_STREAM_DECODER_OK;
-}
#else /* FLAC_API_VERSION_CURRENT >= 7 */
static void flacPrintErroredState(FLAC__StreamDecoderState state)
{
@@ -199,35 +187,31 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state)
}
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
-static void flacMetadata(G_GNUC_UNUSED const flac_decoder * dec,
+static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec,
const FLAC__StreamMetadata * block, void *vdata)
{
flac_metadata_common_cb(block, (struct flac_data *) vdata);
}
static FLAC__StreamDecoderWriteStatus
-flac_write_cb(const flac_decoder *dec, const FLAC__Frame *frame,
+flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
const FLAC__int32 *const buf[], void *vdata)
{
- FLAC__uint32 samples = frame->header.blocksize;
struct flac_data *data = (struct flac_data *) vdata;
- float timeChange;
- FLAC__uint64 newPosition = 0;
-
- timeChange = ((float)samples) / frame->header.sample_rate;
- data->time += timeChange;
-
- flac_get_decode_position(dec, &newPosition);
- if (data->position && newPosition >= data->position) {
- assert(timeChange >= 0);
-
- data->bit_rate =
- ((newPosition - data->position) * 8.0 / timeChange)
- / 1000 + 0.5;
- }
- data->position = newPosition;
+ FLAC__uint64 nbytes = 0;
+
+ if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) {
+ if (data->position > 0 && nbytes > data->position) {
+ nbytes -= data->position;
+ data->position += nbytes;
+ } else {
+ data->position = nbytes;
+ nbytes = 0;
+ }
+ } else
+ nbytes = 0;
- return flac_common_write(data, frame, buf);
+ return flac_common_write(data, frame, buf, nbytes);
}
static struct tag *
@@ -268,12 +252,8 @@ flac_tag_load(const char *file, const char *char_tnum)
block = FLAC__metadata_simple_iterator_get_block(it);
if (!block)
break;
- if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
- flac_vorbis_comments_to_tag(tag, char_tnum, block);
- } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
- tag->time = ((float)block->data.stream_info.total_samples) /
- block->data.stream_info.sample_rate + 0.5;
- }
+
+ flac_tag_apply_metadata(tag, char_tnum, block);
FLAC__metadata_object_delete(block);
} while (FLAC__metadata_simple_iterator_next(it));
@@ -300,6 +280,8 @@ flac_cue_tag_load(const char *file)
FLAC__uint64 track_time = 0;
#ifdef HAVE_CUE /* libcue */
FLAC__StreamMetadata* vc;
+ char* cs_filename;
+ FILE* cs_file;
#endif /* libcue */
FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
FLAC__StreamMetadata* cs;
@@ -329,16 +311,25 @@ flac_cue_tag_load(const char *file)
FLAC__metadata_object_delete(vc);
}
+
+ if (tag == NULL) {
+ cs_filename = g_strconcat(file, ".cue", NULL);
+
+ cs_file = fopen(cs_filename, "rt");
+ g_free(cs_filename);
+
+ if (cs_file != NULL) {
+ tag = cue_tag_file(cs_file, tnum);
+ fclose(cs_file);
+ }
+ }
#endif /* libcue */
if (tag == NULL)
tag = flac_tag_load(file, char_tnum);
- if (char_tnum != NULL)
- {
- tag_add_item( tag,
- TAG_ITEM_TRACK,
- char_tnum);
+ if (char_tnum != NULL) {
+ tag_add_item(tag, TAG_TRACK, char_tnum);
g_free(char_tnum);
}
@@ -382,105 +373,167 @@ flac_tag_dup(const char *file)
return flac_tag_load(file, NULL);
}
-static void
-flac_decode_internal(struct decoder * decoder,
- struct input_stream *input_stream,
- bool is_ogg)
+/**
+ * Some glue code around FLAC__stream_decoder_new().
+ */
+static FLAC__StreamDecoder *
+flac_decoder_new(void)
{
- flac_decoder *flac_dec;
- struct flac_data data;
- enum decoder_command cmd;
- const char *err = NULL;
-
- if (!(flac_dec = flac_new()))
- return;
- flac_data_init(&data, decoder, input_stream);
- data.tag = tag_new();
+ FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
+ if (sd == NULL) {
+ g_warning("FLAC__stream_decoder_new() failed");
+ return NULL;
+ }
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- {
- g_debug("Failed to set metadata respond\n");
- }
+ if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
+ g_debug("FLAC__stream_decoder_set_metadata_respond() has failed");
#endif
- if (is_ogg) {
- if (!flac_ogg_init(flac_dec, flac_read_cb,
- flac_seek_cb, flac_tell_cb,
- flac_length_cb, flac_eof_cb,
- flac_write_cb, flacMetadata,
- flac_error_cb, (void *)&data)) {
- err = "doing Ogg init()";
- goto fail;
- }
- } else {
- if (!flac_init(flac_dec, flac_read_cb,
- flac_seek_cb, flac_tell_cb,
- flac_length_cb, flac_eof_cb,
- flac_write_cb, flacMetadata,
- flac_error_cb, (void *)&data)) {
- err = "doing init()";
- goto fail;
- }
- }
+ return sd;
+}
- if (!flac_process_metadata(flac_dec)) {
- err = "problem reading metadata";
- goto fail;
- }
+static bool
+flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
+ bool seekable, FLAC__uint64 duration)
+{
+ struct audio_format audio_format;
- if (!audio_format_valid(&data.audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
- goto fail;
+ if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
+ g_warning("problem reading metadata");
+ return false;
}
- decoder_initialized(decoder, &data.audio_format,
- input_stream->seekable, data.total_time);
+ if (!flac_data_get_audio_format(data, &audio_format))
+ return false;
+
+ if (duration == 0)
+ duration = data->stream_info.total_samples;
+
+ decoder_initialized(data->decoder, &audio_format,
+ seekable,
+ (float)duration /
+ (float)data->stream_info.sample_rate);
+ return true;
+}
+
+static void
+flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
+ FLAC__uint64 t_start, FLAC__uint64 t_end)
+{
+ struct decoder *decoder = data->decoder;
+ enum decoder_command cmd;
+
+ data->first_frame = t_start;
while (true) {
- if (!tag_is_empty(data.tag)) {
- cmd = decoder_tag(decoder, input_stream, data.tag);
- tag_free(data.tag);
- data.tag = tag_new();
+ if (data->tag != NULL && !tag_is_empty(data->tag)) {
+ cmd = decoder_tag(data->decoder, data->input_stream,
+ data->tag);
+ tag_free(data->tag);
+ data->tag = tag_new();
} else
cmd = decoder_get_command(decoder);
if (cmd == DECODE_COMMAND_SEEK) {
- FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
- data.audio_format.sample_rate + 0.5;
- if (flac_seek_absolute(flac_dec, seek_sample)) {
- data.time = ((float)seek_sample) /
- data.audio_format.sample_rate;
- data.position = 0;
+ FLAC__uint64 seek_sample = t_start +
+ decoder_seek_where(decoder) *
+ data->stream_info.sample_rate;
+ if (seek_sample >= t_start &&
+ (t_end == 0 || seek_sample <= t_end) &&
+ FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
+ data->next_frame = seek_sample;
+ data->position = 0;
decoder_command_finished(decoder);
} else
decoder_seek_error(decoder);
} else if (cmd == DECODE_COMMAND_STOP ||
- flac_get_state(flac_dec) == flac_decoder_eof)
+ FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
break;
- if (!flac_process_single(flac_dec)) {
+ if (t_end != 0 && data->next_frame >= t_end)
+ /* end of this sub track */
+ break;
+
+ if (!FLAC__stream_decoder_process_single(flac_dec)) {
cmd = decoder_get_command(decoder);
if (cmd != DECODE_COMMAND_SEEK)
break;
}
}
+
if (cmd != DECODE_COMMAND_STOP) {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
+ flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
+ FLAC__stream_decoder_finish(flac_dec);
}
+}
-fail:
- if (data.replay_gain_info)
- replay_gain_info_free(data.replay_gain_info);
+static void
+flac_decode_internal(struct decoder * decoder,
+ struct input_stream *input_stream,
+ bool is_ogg)
+{
+ FLAC__StreamDecoder *flac_dec;
+ struct flac_data data;
+ const char *err = NULL;
+
+ flac_dec = flac_decoder_new();
+ if (flac_dec == NULL)
+ return;
+
+ flac_data_init(&data, decoder, input_stream);
+ data.tag = tag_new();
+
+ if (is_ogg) {
+#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
+ FLAC__StreamDecoderInitStatus status =
+ FLAC__stream_decoder_init_ogg_stream(flac_dec,
+ flac_read_cb,
+ flac_seek_cb,
+ flac_tell_cb,
+ flac_length_cb,
+ flac_eof_cb,
+ flac_write_cb,
+ flacMetadata,
+ flac_error_cb,
+ (void *)&data);
+ if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+ err = "doing Ogg init()";
+ goto fail;
+ }
+#else
+ goto fail;
+#endif
+ } else {
+ FLAC__StreamDecoderInitStatus status =
+ FLAC__stream_decoder_init_stream(flac_dec,
+ flac_read_cb,
+ flac_seek_cb,
+ flac_tell_cb,
+ flac_length_cb,
+ flac_eof_cb,
+ flac_write_cb,
+ flacMetadata,
+ flac_error_cb,
+ (void *)&data);
+ if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+ err = "doing init()";
+ goto fail;
+ }
+ }
- tag_free(data.tag);
+ if (!flac_decoder_initialize(&data, flac_dec,
+ input_stream->seekable, 0)) {
+ flac_data_deinit(&data);
+ FLAC__stream_decoder_delete(flac_dec);
+ return;
+ }
- if (flac_dec)
- flac_delete(flac_dec);
+ flac_decoder_loop(&data, flac_dec, 0, 0);
+
+fail:
+ flac_data_deinit(&data);
+ FLAC__stream_decoder_delete(flac_dec);
if (err)
g_warning("%s\n", err);
@@ -509,7 +562,8 @@ flac_container_decode(struct decoder* decoder,
FLAC__uint64 track_time = 0;
FLAC__StreamMetadata* cs = NULL;
- flac_decoder *flac_dec;
+ FLAC__StreamDecoder *flac_dec;
+ FLAC__StreamDecoderInitStatus init_status;
struct flac_data data;
const char *err = NULL;
@@ -542,122 +596,43 @@ flac_container_decode(struct decoder* decoder,
return;
}
- if (!(flac_dec = flac_new()))
- {
- g_free(pathname);
+ flac_dec = flac_decoder_new();
+ if (flac_dec == NULL)
return;
- }
flac_data_init(&data, decoder, NULL);
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- {
- g_debug("Failed to set metadata respond\n");
- }
-#endif
-
-
- if (is_ogg)
- {
- if (FLAC__stream_decoder_init_ogg_file( flac_dec,
- pathname,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void*) &data )
- != FLAC__STREAM_DECODER_INIT_STATUS_OK )
- {
- err = "doing Ogg init()";
- goto fail;
- }
- }
- else
- {
- if (FLAC__stream_decoder_init_file( flac_dec,
- pathname,
- flac_write_cb,
- flacMetadata,
- flac_error_cb,
- (void*) &data )
- != FLAC__STREAM_DECODER_INIT_STATUS_OK )
- {
- err = "doing init()";
- goto fail;
- }
- }
-
- if (!flac_process_metadata(flac_dec))
- {
- err = "problem reading metadata";
+ init_status = is_ogg
+ ? FLAC__stream_decoder_init_ogg_file(flac_dec, pathname,
+ flac_write_cb,
+ flacMetadata,
+ flac_error_cb,
+ &data)
+ : FLAC__stream_decoder_init_file(flac_dec,
+ pathname, flac_write_cb,
+ flacMetadata, flac_error_cb,
+ &data);
+ g_free(pathname);
+ if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
+ err = "doing init()";
goto fail;
}
- if (!audio_format_valid(&data.audio_format))
- {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
- goto fail;
+ if (!flac_decoder_initialize(&data, flac_dec, true, track_time)) {
+ flac_data_deinit(&data);
+ FLAC__stream_decoder_delete(flac_dec);
+ return;
}
- // set track time (order is important: after stream init)
- data.total_time = ((float)track_time / (float)data.audio_format.sample_rate);
- data.position = 0;
-
- decoder_initialized(decoder, &data.audio_format,
- true, data.total_time);
-
// seek to song start (order is important: after decoder init)
- flac_seek_absolute(flac_dec, (FLAC__uint64)t_start);
-
- while (true)
- {
- if (!flac_process_single(flac_dec))
- break;
-
- // we only need to break at the end of track if we are in "cue mode"
- if (data.time >= data.total_time)
- {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
- }
-
- if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
- {
- FLAC__uint64 seek_sample = t_start +
- (decoder_seek_where(decoder) * data.audio_format.sample_rate);
-
- if (seek_sample >= t_start && seek_sample <= t_end &&
- flac_seek_absolute(flac_dec, (FLAC__uint64)seek_sample)) {
- data.time = (float)(seek_sample - t_start) /
- data.audio_format.sample_rate;
- data.position = 0;
+ FLAC__stream_decoder_seek_absolute(flac_dec, (FLAC__uint64)t_start);
+ data.next_frame = t_start;
- decoder_command_finished(decoder);
- } else
- decoder_seek_error(decoder);
- }
- else if (flac_get_state(flac_dec) == flac_decoder_eof)
- break;
- }
-
- if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
- {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
- }
+ flac_decoder_loop(&data, flac_dec, t_start, t_end);
fail:
- if (pathname)
- g_free(pathname);
-
- if (data.replay_gain_info)
- replay_gain_info_free(data.replay_gain_info);
-
- if (flac_dec)
- flac_delete(flac_dec);
+ flac_data_deinit(&data);
+ FLAC__stream_decoder_delete(flac_dec);
if (err)
g_warning("%s\n", err);
@@ -672,24 +647,17 @@ flac_filedecode_internal(struct decoder* decoder,
const char* fname,
bool is_ogg)
{
- flac_decoder *flac_dec;
+ FLAC__StreamDecoder *flac_dec;
struct flac_data data;
const char *err = NULL;
unsigned int flac_err_state = 0;
- if (!(flac_dec = flac_new()))
+ flac_dec = flac_decoder_new();
+ if (flac_dec == NULL)
return;
flac_data_init(&data, decoder, NULL);
-#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
- if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
- {
- g_debug("Failed to set metadata respond\n");
- }
-#endif
-
-
if (is_ogg)
{
if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec,
@@ -727,60 +695,17 @@ flac_filedecode_internal(struct decoder* decoder,
}
}
- if (!flac_process_metadata(flac_dec))
- {
- err = "problem reading metadata";
- goto fail;
- }
-
- if (!audio_format_valid(&data.audio_format))
- {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
- goto fail;
- }
-
- decoder_initialized(decoder, &data.audio_format,
- true, data.total_time);
-
- while (true)
- {
- if (!flac_process_single(flac_dec))
- break;
-
- if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
- {
- FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
- data.audio_format.sample_rate + 0.5;
- if (flac_seek_absolute(flac_dec, seek_sample))
- {
- data.time = ((float)seek_sample) /
- data.audio_format.sample_rate;
- data.position = 0;
- decoder_command_finished(decoder);
- }
- else
- decoder_seek_error(decoder);
-
- }
- else if (flac_get_state(flac_dec) == flac_decoder_eof)
- break;
+ if (!flac_decoder_initialize(&data, flac_dec, true, 0)) {
+ flac_data_deinit(&data);
+ FLAC__stream_decoder_delete(flac_dec);
+ return;
}
- if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
- {
- flacPrintErroredState(flac_get_state(flac_dec));
- flac_finish(flac_dec);
- }
+ flac_decoder_loop(&data, flac_dec, 0, 0);
fail:
- if (data.replay_gain_info)
- replay_gain_info_free(data.replay_gain_info);
-
- if (flac_dec)
- flac_delete(flac_dec);
+ flac_data_deinit(&data);
+ FLAC__stream_decoder_delete(flac_dec);
if (err)
g_warning("%s\n", err);
@@ -836,13 +761,8 @@ oggflac_tag_dup(const char *file)
do {
if (!(block = FLAC__metadata_iterator_get_block(it)))
break;
- if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
- flac_vorbis_comments_to_tag(ret, NULL, block);
- } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
- ret->time = ((float)block->data.stream_info.
- total_samples) /
- block->data.stream_info.sample_rate + 0.5;
- }
+
+ flac_tag_apply_metadata(ret, NULL, block);
} while (FLAC__metadata_iterator_next(it));
FLAC__metadata_iterator_delete(it);
@@ -864,7 +784,7 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(input_stream, 0, SEEK_SET);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
flac_decode_internal(decoder, input_stream, true);
}
diff --git a/src/decoder/fluidsynth_plugin.c b/src/decoder/fluidsynth_plugin.c
index 99c874c09..2656b5ebd 100644
--- a/src/decoder/fluidsynth_plugin.c
+++ b/src/decoder/fluidsynth_plugin.c
@@ -26,9 +26,10 @@
*
*/
-#include "../decoder_api.h"
-#include "../timer.h"
-#include "../conf.h"
+#include "config.h"
+#include "decoder_api.h"
+#include "timer.h"
+#include "conf.h"
#include <glib.h>
@@ -87,7 +88,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = 48000,
- .bits = 16,
+ .format = SAMPLE_FORMAT_S16,
.channels = 2,
};
char setting_sample_rate[] = "synth.sample-rate";
@@ -203,7 +204,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
break;
cmd = decoder_data(decoder, NULL, buffer, sizeof(buffer),
- 0, 0, NULL);
+ 0, NULL);
} while (cmd == DECODE_COMMAND_NONE);
/* clean up */
diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c
index 1ef7183fa..f23ab3c21 100644
--- a/src/decoder/mad_plugin.c
+++ b/src/decoder/mad_plugin.c
@@ -17,10 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
-#include "../conf.h"
#include "config.h"
+#include "decoder_api.h"
+#include "conf.h"
#include "tag_id3.h"
+#include "audio_check.h"
#include <assert.h>
#include <unistd.h>
@@ -164,7 +165,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder,
static bool mp3_seek(struct mp3_data *data, long offset)
{
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
+ if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
return false;
mad_stream_buffer(&data->stream, data->input_buffer, 0);
@@ -779,10 +780,10 @@ mp3_frame_duration(const struct mad_frame *frame)
MAD_UNITS_MILLISECONDS) / 1000.0;
}
-static off_t
+static goffset
mp3_this_frame_offset(const struct mp3_data *data)
{
- off_t offset = data->input_stream->offset;
+ goffset offset = data->input_stream->offset;
if (data->stream.this_frame != NULL)
offset -= data->stream.bufend - data->stream.this_frame;
@@ -792,7 +793,7 @@ mp3_this_frame_offset(const struct mp3_data *data)
return offset;
}
-static off_t
+static goffset
mp3_rest_including_this_frame(const struct mp3_data *data)
{
return data->input_stream->size - mp3_this_frame_offset(data);
@@ -804,7 +805,7 @@ mp3_rest_including_this_frame(const struct mp3_data *data)
static void
mp3_filesize_to_song_length(struct mp3_data *data)
{
- off_t rest = mp3_rest_including_this_frame(data);
+ goffset rest = mp3_rest_including_this_frame(data);
if (rest > 0) {
float frame_duration = mp3_frame_duration(&data->frame);
@@ -919,7 +920,7 @@ static int mp3_total_file_time(const char *file)
struct mp3_data data;
int ret;
- if (!input_stream_open(&input_stream, file))
+ if (!input_stream_open(&input_stream, file, NULL))
return -1;
mp3_data_init(&data, NULL, &input_stream);
if (!mp3_decode_first_frame(&data, NULL, NULL))
@@ -1024,7 +1025,6 @@ mp3_send_pcm(struct mp3_data *data, unsigned i, unsigned pcm_length,
cmd = decoder_data(data->decoder, data->input_stream,
data->output_buffer,
sizeof(data->output_buffer[0]) * num_samples,
- data->elapsed_time,
data->bit_rate / 1000,
replay_gain_info);
if (cmd != DECODE_COMMAND_NONE)
@@ -1170,17 +1170,11 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r)
return ret != DECODE_BREAK;
}
-static void mp3_audio_format(struct mp3_data *data, struct audio_format *af)
-{
- af->bits = 24;
- af->sample_rate = (data->frame).header.samplerate;
- af->channels = MAD_NCHANNELS(&(data->frame).header);
-}
-
static void
mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
{
struct mp3_data data;
+ GError *error = NULL;
struct tag *tag = NULL;
struct replay_gain_info *replay_gain_info = NULL;
struct audio_format audio_format;
@@ -1192,7 +1186,21 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
return;
}
- mp3_audio_format(&data, &audio_format);
+ if (!audio_format_init_checked(&audio_format,
+ data.frame.header.samplerate,
+ SAMPLE_FORMAT_S24_P32,
+ MAD_NCHANNELS(&data.frame.header),
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+
+ if (tag != NULL)
+ tag_free(tag);
+ if (replay_gain_info != NULL)
+ replay_gain_info_free(replay_gain_info);
+ mp3_data_finish(&data);
+ return;
+ }
decoder_initialized(decoder, &audio_format,
data.input_stream->seekable, data.total_time);
diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_plugin.c
index 065c34319..bbb330cb7 100644
--- a/src/decoder/mikmod_plugin.c
+++ b/src/decoder/mikmod_plugin.c
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
#include <glib.h>
#include <mikmod.h>
+#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mikmod"
@@ -29,30 +31,34 @@
#define MIKMOD_FRAME_SIZE 4096
-static BOOL mod_mpd_Init(void)
+static BOOL
+mikmod_mpd_init(void)
{
return VC_Init();
}
-static void mod_mpd_Exit(void)
+static void
+mikmod_mpd_exit(void)
{
VC_Exit();
}
-static void mod_mpd_Update(void)
+static void
+mikmod_mpd_update(void)
{
}
-static BOOL mod_mpd_IsThere(void)
+static BOOL
+mikmod_mpd_is_present(void)
{
- return 1;
+ return true;
}
-static char drv_name[] = "MPD";
-static char drv_version[] = "MPD Output Driver v0.1";
+static char drv_name[] = PACKAGE_NAME;
+static char drv_version[] = VERSION;
#if (LIBMIKMOD_VERSION > 0x030106)
-static char drv_alias[] = "mpd";
+static char drv_alias[] = PACKAGE;
#endif
static MDRIVER drv_mpd = {
@@ -68,18 +74,18 @@ static MDRIVER drv_mpd = {
#endif
NULL, /* CommandLine */
#endif
- mod_mpd_IsThere,
+ mikmod_mpd_is_present,
VC_SampleLoad,
VC_SampleUnload,
VC_SampleSpace,
VC_SampleLength,
- mod_mpd_Init,
- mod_mpd_Exit,
+ mikmod_mpd_init,
+ mikmod_mpd_exit,
NULL,
VC_SetNumVoices,
VC_PlayStart,
VC_PlayStop,
- mod_mpd_Update,
+ mikmod_mpd_update,
NULL,
VC_VoiceSetVolume,
VC_VoiceGetVolume,
@@ -94,11 +100,19 @@ static MDRIVER drv_mpd = {
VC_VoiceRealVolume
};
+static unsigned mikmod_sample_rate;
+
static bool
-mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
+mikmod_decoder_init(const struct config_param *param)
{
static char params[] = "";
+ mikmod_sample_rate = config_get_block_unsigned(param, "sample_rate",
+ 44100);
+ if (!audio_valid_sample_rate(mikmod_sample_rate))
+ g_error("Invalid sample rate in line %d: %u",
+ param->line, mikmod_sample_rate);
+
md_device = 0;
md_reverb = 0;
@@ -106,7 +120,7 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
MikMod_RegisterAllLoaders();
md_pansep = 64;
- md_mixfreq = 44100;
+ md_mixfreq = mikmod_sample_rate;
md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO |
DMODE_16BITS);
@@ -119,115 +133,83 @@ mod_initMikMod(G_GNUC_UNUSED const struct config_param *param)
return true;
}
-static void mod_finishMikMod(void)
+static void
+mikmod_decoder_finish(void)
{
MikMod_Exit();
}
-typedef struct _mod_Data {
- MODULE *moduleHandle;
- SBYTE audio_buffer[MIKMOD_FRAME_SIZE];
-} mod_Data;
-
-static mod_Data *mod_open(const char *path)
-{
- char *path2;
- MODULE *moduleHandle;
- mod_Data *data;
-
- path2 = g_strdup(path);
- moduleHandle = Player_Load(path2, 128, 0);
- g_free(path2);
-
- if (moduleHandle == NULL)
- return NULL;
-
- /* Prevent module from looping forever */
- moduleHandle->loop = 0;
-
- data = g_new(mod_Data, 1);
- data->moduleHandle = moduleHandle;
-
- Player_Start(data->moduleHandle);
-
- return data;
-}
-
-static void mod_close(mod_Data * data)
-{
- Player_Stop();
- Player_Free(data->moduleHandle);
- g_free(data);
-}
-
static void
-mod_decode(struct decoder *decoder, const char *path)
+mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
{
- mod_Data *data;
+ char *path2;
+ MODULE *handle;
struct audio_format audio_format;
- float total_time = 0.0;
int ret;
- float secPerByte;
+ SBYTE buffer[MIKMOD_FRAME_SIZE];
enum decoder_command cmd = DECODE_COMMAND_NONE;
- if (!(data = mod_open(path))) {
- g_warning("failed to open mod: %s\n", path);
+ path2 = g_strdup(path_fs);
+ handle = Player_Load(path2, 128, 0);
+ g_free(path2);
+
+ if (handle == NULL) {
+ g_warning("failed to open mod: %s", path_fs);
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
+ /* Prevent module from looping forever */
+ handle->loop = 0;
- secPerByte =
- 1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
- (float)audio_format.sample_rate);
+ audio_format_init(&audio_format, mikmod_sample_rate, SAMPLE_FORMAT_S16, 2);
+ assert(audio_format_valid(&audio_format));
decoder_initialized(decoder, &audio_format, false, 0);
+ Player_Start(handle);
while (cmd == DECODE_COMMAND_NONE && Player_Active()) {
- ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE);
- total_time += ret * secPerByte;
- cmd = decoder_data(decoder, NULL,
- data->audio_buffer, ret,
- total_time, 0, NULL);
+ ret = VC_WriteBytes(buffer, sizeof(buffer));
+ cmd = decoder_data(decoder, NULL, buffer, ret,
+ 0, NULL);
}
- mod_close(data);
+ Player_Stop();
+ Player_Free(handle);
}
-static struct tag *modTagDup(const char *file)
+static struct tag *
+mikmod_decoder_tag_dup(const char *path_fs)
{
char *path2;
struct tag *ret = NULL;
- MODULE *moduleHandle;
+ MODULE *handle;
char *title;
- path2 = g_strdup(file);
- moduleHandle = Player_Load(path2, 128, 0);
+ path2 = g_strdup(path_fs);
+ handle = Player_Load(path2, 128, 0);
g_free(path2);
- if (moduleHandle == NULL) {
- g_debug("Failed to open file: %s", file);
+ if (handle == NULL) {
+ g_debug("Failed to open file: %s", path_fs);
return NULL;
}
- Player_Free(moduleHandle);
+ Player_Free(handle);
ret = tag_new();
ret->time = 0;
- path2 = g_strdup(file);
+ path2 = g_strdup(path_fs);
title = g_strdup(Player_LoadTitle(path2));
g_free(path2);
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
return ret;
}
-static const char *const modSuffixes[] = {
+static const char *const mikmod_decoder_suffixes[] = {
"amf",
"dsm",
"far",
@@ -248,9 +230,9 @@ static const char *const modSuffixes[] = {
const struct decoder_plugin mikmod_decoder_plugin = {
.name = "mikmod",
- .init = mod_initMikMod,
- .finish = mod_finishMikMod,
- .file_decode = mod_decode,
- .tag_dup = modTagDup,
- .suffixes = modSuffixes,
+ .init = mikmod_decoder_init,
+ .finish = mikmod_decoder_finish,
+ .file_decode = mikmod_decoder_file_decode,
+ .tag_dup = mikmod_decoder_tag_dup,
+ .suffixes = mikmod_decoder_suffixes,
};
diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c
index f636f2fa6..05c9ef2d7 100644
--- a/src/decoder/modplug_plugin.c
+++ b/src/decoder/modplug_plugin.c
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
#include <glib.h>
#include <modplug.h>
+#include <assert.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "modplug"
@@ -92,10 +94,8 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
ModPlug_Settings settings;
GByteArray *bdatas;
struct audio_format audio_format;
- float total_time = 0.0;
- int ret, current;
+ int ret;
char audio_buffer[MODPLUG_FRAME_SIZE];
- float sec_perbyte;
enum decoder_command cmd = DECODE_COMMAND_NONE;
bdatas = mod_loadfile(decoder, is);
@@ -121,37 +121,26 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
return;
}
- audio_format.bits = 16;
- audio_format.sample_rate = 44100;
- audio_format.channels = 2;
-
- sec_perbyte =
- 1.0 / ((audio_format.bits * audio_format.channels / 8.0) *
- (float)audio_format.sample_rate);
-
- total_time = ModPlug_GetLength(f) / 1000;
+ audio_format_init(&audio_format, 44100, SAMPLE_FORMAT_S16, 2);
+ assert(audio_format_valid(&audio_format));
decoder_initialized(decoder, &audio_format,
- is->seekable, total_time);
-
- total_time = 0;
+ is->seekable, ModPlug_GetLength(f) / 1000.0);
do {
ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE);
-
- if (ret == 0) {
+ if (ret <= 0)
break;
- }
- total_time += ret * sec_perbyte;
cmd = decoder_data(decoder, NULL,
audio_buffer, ret,
- total_time, 0, NULL);
+ 0, NULL);
if (cmd == DECODE_COMMAND_SEEK) {
- total_time = decoder_seek_where(decoder);
- current = total_time * 1000;
- ModPlug_Seek(f, current);
+ float where = decoder_seek_where(decoder);
+
+ ModPlug_Seek(f, (int)(where * 1000.0));
+
decoder_command_finished(decoder);
}
@@ -168,7 +157,7 @@ static struct tag *mod_tagdup(const char *file)
char *title;
struct input_stream is;
- if (!input_stream_open(&is, file)) {
+ if (!input_stream_open(&is, file, NULL)) {
g_warning("cant open file %s\n", file);
return NULL;
}
@@ -186,11 +175,11 @@ static struct tag *mod_tagdup(const char *file)
return NULL;
}
ret = tag_new();
- ret->time = 0;
+ ret->time = ModPlug_GetLength(f) / 1000;
title = g_strdup(ModPlug_GetName(f));
if (title)
- tag_add_item(ret, TAG_ITEM_TITLE, title);
+ tag_add_item(ret, TAG_TITLE, title);
g_free(title);
ModPlug_Unload(f);
diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c
index cf9382904..325a97291 100644
--- a/src/decoder/mp4ff_plugin.c
+++ b/src/decoder/mp4ff_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#include <glib.h>
@@ -98,7 +99,7 @@ mp4_seek(void *user_data, uint64_t position)
{
struct mp4_context *ctx = user_data;
- return input_stream_seek(ctx->input_stream, position, SEEK_SET)
+ return input_stream_seek(ctx->input_stream, position, SEEK_SET, NULL)
? 0 : -1;
}
@@ -110,6 +111,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
int track;
uint32_t sample_rate;
unsigned char channels;
+ GError *error = NULL;
decoder = faacDecOpen();
@@ -130,22 +132,17 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format)
return NULL;
}
- *track_r = track;
- *audio_format = (struct audio_format){
- .bits = 16,
- .channels = channels,
- .sample_rate = sample_rate,
- };
-
- if (!audio_format_valid(audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format->sample_rate,
- audio_format->bits,
- audio_format->channels);
+ if (!audio_format_init_checked(audio_format, sample_rate,
+ SAMPLE_FORMAT_S16, channels,
+ &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
faacDecClose(decoder);
return NULL;
}
+ *track_r = track;
+
return decoder;
}
@@ -265,7 +262,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
dur -= offset;
file_time += ((float)dur) / scale;
- if (seeking && file_time > seek_where)
+ if (seeking && file_time >= seek_where)
seek_position_found = true;
if (seeking && seek_position_found) {
@@ -331,7 +328,7 @@ mp4_decode(struct decoder *mpd_decoder, struct input_stream *input_stream)
cmd = decoder_data(mpd_decoder, input_stream,
sample_buffer, sample_buffer_length,
- file_time, bit_rate, NULL);
+ bit_rate, NULL);
}
g_free(seek_table);
@@ -359,7 +356,7 @@ mp4_tag_dup(const char *file)
int32_t scale;
int i;
- if (!input_stream_open(&input_stream, file)) {
+ if (!input_stream_open(&input_stream, file, NULL)) {
g_warning("Failed to open file: %s", file);
return NULL;
}
@@ -395,22 +392,22 @@ mp4_tag_dup(const char *file)
mp4ff_meta_get_by_index(mp4fh, i, &item, &value);
if (0 == g_ascii_strcasecmp("artist", item)) {
- tag_add_item(ret, TAG_ITEM_ARTIST, value);
+ tag_add_item(ret, TAG_ARTIST, value);
} else if (0 == g_ascii_strcasecmp("title", item)) {
- tag_add_item(ret, TAG_ITEM_TITLE, value);
+ tag_add_item(ret, TAG_TITLE, value);
} else if (0 == g_ascii_strcasecmp("album", item)) {
- tag_add_item(ret, TAG_ITEM_ALBUM, value);
+ tag_add_item(ret, TAG_ALBUM, value);
} else if (0 == g_ascii_strcasecmp("track", item)) {
- tag_add_item(ret, TAG_ITEM_TRACK, value);
+ tag_add_item(ret, TAG_TRACK, value);
} else if (0 == g_ascii_strcasecmp("disc", item)) {
/* Is that the correct id? */
- tag_add_item(ret, TAG_ITEM_DISC, value);
+ tag_add_item(ret, TAG_DISC, value);
} else if (0 == g_ascii_strcasecmp("genre", item)) {
- tag_add_item(ret, TAG_ITEM_GENRE, value);
+ tag_add_item(ret, TAG_GENRE, value);
} else if (0 == g_ascii_strcasecmp("date", item)) {
- tag_add_item(ret, TAG_ITEM_DATE, value);
+ tag_add_item(ret, TAG_DATE, value);
} else if (0 == g_ascii_strcasecmp("writer", item)) {
- tag_add_item(ret, TAG_ITEM_COMPOSER, value);
+ tag_add_item(ret, TAG_COMPOSER, value);
}
free(item);
diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_plugin.c
index 26349f93a..a186bc368 100644
--- a/src/decoder/mpcdec_plugin.c
+++ b/src/decoder/mpcdec_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
#ifdef MPC_IS_OLD_API
#include <mpcdec/mpcdec.h>
@@ -27,6 +28,7 @@
#endif
#include <glib.h>
+#include <assert.h>
#include <unistd.h>
#undef G_LOG_DOMAIN
@@ -58,7 +60,7 @@ mpc_seek_cb(cb_first_arg, mpc_int32_t offset)
{
struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
- return input_stream_seek(data->is, offset, SEEK_SET);
+ return input_stream_seek(data->is, offset, SEEK_SET, NULL);
}
static mpc_int32_t
@@ -140,6 +142,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
#endif
mpc_reader reader;
mpc_streaminfo info;
+ GError *error = NULL;
struct audio_format audio_format;
struct mpc_decoder_data data;
@@ -149,10 +152,8 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
mpc_uint32_t ret;
int32_t chunk[G_N_ELEMENTS(sample_buffer)];
long bit_rate = 0;
- unsigned long sample_pos = 0;
mpc_uint32_t vbr_update_acc;
mpc_uint32_t vbr_update_bits;
- float total_time;
struct replay_gain_info *replay_gain_info = NULL;
enum decoder_command cmd = DECODE_COMMAND_NONE;
@@ -193,18 +194,14 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
mpc_demux_get_info(demux, &info);
#endif
- audio_format.bits = 24;
- audio_format.channels = info.channels;
- audio_format.sample_rate = info.sample_freq;
-
- if (!audio_format_valid(&audio_format)) {
+ if (!audio_format_init_checked(&audio_format, info.sample_freq,
+ SAMPLE_FORMAT_S24_P32,
+ info.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
#ifndef MPC_IS_OLD_API
mpc_demux_exit(demux);
#endif
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate,
- audio_format.bits,
- audio_format.channels);
return;
}
@@ -220,16 +217,14 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
do {
if (cmd == DECODE_COMMAND_SEEK) {
- bool success;
-
- sample_pos = decoder_seek_where(mpd_decoder) *
+ mpc_int64_t where = decoder_seek_where(mpd_decoder) *
audio_format.sample_rate;
+ bool success;
#ifdef MPC_IS_OLD_API
- success = mpc_decoder_seek_sample(&decoder,
- sample_pos);
+ success = mpc_decoder_seek_sample(&decoder, where);
#else
- success = mpc_demux_seek_sample(demux, sample_pos)
+ success = mpc_demux_seek_sample(demux, where)
== MPC_STATUS_OK;
#endif
if (success)
@@ -260,19 +255,15 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
ret = frame.samples;
#endif
- sample_pos += ret;
-
ret *= info.channels;
mpc_to_mpd_buffer(chunk, sample_buffer, ret);
- total_time = ((float)sample_pos) / audio_format.sample_rate;
bit_rate = vbr_update_bits * audio_format.sample_rate
/ 1152 / 1000;
cmd = decoder_data(mpd_decoder, is,
chunk, ret * sizeof(chunk[0]),
- total_time,
bit_rate, replay_gain_info);
} while (cmd != DECODE_COMMAND_STOP);
@@ -296,7 +287,7 @@ mpcdec_get_file_duration(const char *file)
mpc_streaminfo info;
struct mpc_decoder_data data;
- if (!input_stream_open(&is, file))
+ if (!input_stream_open(&is, file, NULL))
return -1;
data.is = &is;
diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c
new file mode 100644
index 000000000..0d7cb9e0e
--- /dev/null
+++ b/src/decoder/mpg123_decoder_plugin.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "decoder_api.h"
+#include "audio_check.h"
+
+#include <glib.h>
+
+#include <mpg123.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "mpg123"
+
+static bool
+mpd_mpg123_init(G_GNUC_UNUSED const struct config_param *param)
+{
+ mpg123_init();
+
+ return true;
+}
+
+static void
+mpd_mpg123_finish(void)
+{
+ mpg123_exit();
+}
+
+/**
+ * Opens a file with an existing #mpg123_handle.
+ *
+ * @param handle a handle which was created before; on error, this
+ * function will not free it
+ * @param audio_format this parameter is filled after successful
+ * return
+ * @return true on success
+ */
+static bool
+mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
+ struct audio_format *audio_format)
+{
+ GError *gerror = NULL;
+ char *path_dup;
+ int error;
+ int channels, encoding;
+ long rate;
+
+ /* mpg123_open() wants a writable string :-( */
+ path_dup = g_strdup(path_fs);
+
+ error = mpg123_open(handle, path_dup);
+ g_free(path_dup);
+ if (error != MPG123_OK) {
+ g_warning("libmpg123 failed to open %s: %s",
+ path_fs, mpg123_plain_strerror(error));
+ return false;
+ }
+
+ /* obtain the audio format */
+
+ error = mpg123_getformat(handle, &rate, &channels, &encoding);
+ if (error != MPG123_OK) {
+ g_warning("mpg123_getformat() failed: %s",
+ mpg123_plain_strerror(error));
+ return false;
+ }
+
+ if (encoding != MPG123_ENC_SIGNED_16) {
+ /* other formats not yet implemented */
+ g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding);
+ return false;
+ }
+
+ if (!audio_format_init_checked(audio_format, rate, SAMPLE_FORMAT_S16,
+ channels, &gerror)) {
+ g_warning("%s", gerror->message);
+ g_error_free(gerror);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
+{
+ struct audio_format audio_format;
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples;
+ enum decoder_command cmd;
+
+ /* open the file */
+
+ handle = mpg123_new(NULL, &error);
+ if (handle == NULL) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return;
+ }
+
+ if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+ mpg123_delete(handle);
+ return;
+ }
+
+ num_samples = mpg123_length(handle);
+
+ /* tell MPD core we're ready */
+
+ decoder_initialized(decoder, &audio_format, false,
+ (float)num_samples /
+ (float)audio_format.sample_rate);
+
+ /* the decoder main loop */
+
+ do {
+ unsigned char buffer[8192];
+ size_t nbytes;
+
+ /* decode */
+
+ error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
+ if (error != MPG123_OK) {
+ if (error != MPG123_DONE)
+ g_warning("mpg123_read() failed: %s",
+ mpg123_plain_strerror(error));
+ break;
+ }
+
+ /* send to MPD */
+
+ cmd = decoder_data(decoder, NULL, buffer, nbytes,
+ 0, NULL);
+
+ /* seeking not yet implemented */
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ /* cleanup */
+
+ mpg123_delete(handle);
+}
+
+static struct tag *
+mpd_mpg123_tag_dup(const char *path_fs)
+{
+ struct audio_format audio_format;
+ mpg123_handle *handle;
+ int error;
+ off_t num_samples;
+ struct tag *tag;
+
+ handle = mpg123_new(NULL, &error);
+ if (handle == NULL) {
+ g_warning("mpg123_new() failed: %s",
+ mpg123_plain_strerror(error));
+ return NULL;
+ }
+
+ if (!mpd_mpg123_open(handle, path_fs, &audio_format)) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ num_samples = mpg123_length(handle);
+ if (num_samples <= 0) {
+ mpg123_delete(handle);
+ return NULL;
+ }
+
+ tag = tag_new();
+
+ tag->time = num_samples / audio_format.sample_rate;
+
+ /* ID3 tag support not yet implemented */
+
+ mpg123_delete(handle);
+ return tag;
+}
+
+static const char *const mpg123_suffixes[] = {
+ "mp3",
+ NULL
+};
+
+const struct decoder_plugin mpg123_decoder_plugin = {
+ .name = "mpg123",
+ .init = mpd_mpg123_init,
+ .finish = mpd_mpg123_finish,
+ .file_decode = mpd_mpg123_file_decode,
+ /* streaming not yet implemented */
+ .tag_dup = mpd_mpg123_tag_dup,
+ .suffixes = mpg123_suffixes,
+};
diff --git a/src/decoder/oggflac_plugin.c b/src/decoder/oggflac_plugin.c
index bdd589ccb..b8b087b47 100644
--- a/src/decoder/oggflac_plugin.c
+++ b/src/decoder/oggflac_plugin.c
@@ -21,19 +21,18 @@
* OggFLAC support (half-stolen from flac_plugin.c :))
*/
+#include "config.h" /* must be first for large file support */
#include "_flac_common.h"
#include "_ogg_common.h"
+#include "flac_metadata.h"
#include <glib.h>
#include <OggFLAC/seekable_stream_decoder.h>
#include <assert.h>
#include <unistd.h>
-static void oggflac_cleanup(struct flac_data *data,
- OggFLAC__SeekableStreamDecoder * decoder)
+static void oggflac_cleanup(OggFLAC__SeekableStreamDecoder * decoder)
{
- if (data->replay_gain_info)
- replay_gain_info_free(data->replay_gain_info);
if (decoder)
OggFLAC__seekable_stream_decoder_delete(decoder);
}
@@ -67,7 +66,7 @@ static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(G_GNUC_UNUSED const
{
struct flac_data *data = (struct flac_data *) fdata;
- if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
+ if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
@@ -156,13 +155,8 @@ oggflac_write_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecoder *decoder,
void *vdata)
{
struct flac_data *data = (struct flac_data *) vdata;
- FLAC__uint32 samples = frame->header.blocksize;
- float time_change;
- time_change = ((float)samples) / frame->header.sample_rate;
- data->time += time_change;
-
- return flac_common_write(data, frame, buf);
+ return flac_common_write(data, frame, buf, 0);
}
/* used by TagDup */
@@ -173,17 +167,7 @@ static void of_metadata_dup_cb(G_GNUC_UNUSED const OggFLAC__SeekableStreamDecode
assert(data->tag != NULL);
- switch (block->type) {
- case FLAC__METADATA_TYPE_STREAMINFO:
- data->tag->time = ((float)block->data.stream_info.
- total_samples) /
- block->data.stream_info.sample_rate + 0.5;
- return;
- case FLAC__METADATA_TYPE_VORBIS_COMMENT:
- flac_vorbis_comments_to_tag(data->tag, NULL, block);
- default:
- break;
- }
+ flac_tag_apply_metadata(data->tag, NULL, block);
}
/* used by decode */
@@ -261,12 +245,21 @@ fail:
static struct tag *
oggflac_tag_dup(const char *file)
{
+ GError *error = NULL;
struct input_stream input_stream;
OggFLAC__SeekableStreamDecoder *decoder;
struct flac_data data;
+ struct tag *tag;
+
+ if (!input_stream_open(&input_stream, file, &error)) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
- if (!input_stream_open(&input_stream, file))
return NULL;
+ }
+
if (ogg_stream_type_detect(&input_stream) != FLAC) {
input_stream_close(&input_stream);
return NULL;
@@ -274,7 +267,7 @@ oggflac_tag_dup(const char *file)
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(&input_stream, 0, SEEK_SET);
+ input_stream_seek(&input_stream, 0, SEEK_SET, NULL);
flac_data_init(&data, NULL, &input_stream);
@@ -284,15 +277,18 @@ oggflac_tag_dup(const char *file)
* data.tag will be set or unset, that's all we care about */
decoder = full_decoder_init_and_read_metadata(&data, 1);
- oggflac_cleanup(&data, decoder);
+ oggflac_cleanup(decoder);
input_stream_close(&input_stream);
- if (!tag_is_defined(data.tag)) {
- tag_free(data.tag);
+ if (tag_is_defined(data.tag)) {
+ tag = data.tag;
data.tag = NULL;
- }
+ } else
+ tag = NULL;
- return data.tag;
+ flac_data_deinit(&data);
+
+ return tag;
}
static void
@@ -300,13 +296,14 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
{
OggFLAC__SeekableStreamDecoder *decoder = NULL;
struct flac_data data;
+ struct audio_format audio_format;
if (ogg_stream_type_detect(input_stream) != FLAC)
return;
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(input_stream, 0, SEEK_SET);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
flac_data_init(&data, mpd_decoder, input_stream);
@@ -314,16 +311,13 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
goto fail;
}
- if (!audio_format_valid(&data.audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- data.audio_format.sample_rate,
- data.audio_format.bits,
- data.audio_format.channels);
+ if (!flac_data_get_audio_format(&data, &audio_format))
goto fail;
- }
- decoder_initialized(mpd_decoder, &data.audio_format,
- input_stream->seekable, data.total_time);
+ decoder_initialized(mpd_decoder, &audio_format,
+ input_stream->seekable,
+ (float)data.stream_info.total_samples /
+ (float)data.stream_info.sample_rate);
while (true) {
OggFLAC__seekable_stream_decoder_process_single(decoder);
@@ -333,11 +327,10 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
}
if (decoder_get_command(mpd_decoder) == DECODE_COMMAND_SEEK) {
FLAC__uint64 seek_sample = decoder_seek_where(mpd_decoder) *
- data.audio_format.sample_rate + 0.5;
+ data.stream_info.sample_rate;
if (OggFLAC__seekable_stream_decoder_seek_absolute
(decoder, seek_sample)) {
- data.time = ((float)seek_sample) /
- data.audio_format.sample_rate;
+ data.next_frame = seek_sample;
data.position = 0;
decoder_command_finished(mpd_decoder);
} else
@@ -352,7 +345,8 @@ oggflac_decode(struct decoder * mpd_decoder, struct input_stream *input_stream)
}
fail:
- oggflac_cleanup(&data, decoder);
+ oggflac_cleanup(decoder);
+ flac_data_deinit(&data);
}
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx
index c62e6b4b6..9bfe98f3d 100644
--- a/src/decoder/sidplay_plugin.cxx
+++ b/src/decoder/sidplay_plugin.cxx
@@ -17,18 +17,186 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
+
extern "C" {
#include "../decoder_api.h"
}
+#include <errno.h>
+#include <stdlib.h>
#include <glib.h>
#include <sidplay/sidplay2.h>
#include <sidplay/builders/resid.h>
+#include <sidplay/utils/SidTuneMod.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "sidplay"
+#define SUBTUNE_PREFIX "tune_"
+
+static GPatternSpec *path_with_subtune;
+static const char *songlength_file;
+static GKeyFile *songlength_database;
+
+static bool all_files_are_containers;
+static unsigned default_songlength;
+
+static bool filter_setting;
+
+static GKeyFile *
+sidplay_load_songlength_db(const char *path)
+{
+ GError *error = NULL;
+ gchar *data;
+ gsize size;
+
+ if (!g_file_get_contents(path, &data, &size, &error)) {
+ g_warning("unable to read songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ /* replace any ; comment characters with # */
+ for (gsize i = 0; i < size; i++)
+ if (data[i] == ';')
+ data[i] = '#';
+
+ GKeyFile *db = g_key_file_new();
+ bool success = g_key_file_load_from_data(db, data, size,
+ G_KEY_FILE_NONE, &error);
+ g_free(data);
+ if (!success) {
+ g_warning("unable to parse songlengths file %s: %s",
+ path, error->message);
+ g_error_free(error);
+ g_key_file_free(db);
+ return NULL;
+ }
+
+ g_key_file_set_list_separator(db, ' ');
+ return db;
+}
+
+static bool
+sidplay_init(const struct config_param *param)
+{
+ /* read the songlengths database file */
+ songlength_file=config_get_block_string(param,
+ "songlength_database", NULL);
+ if (songlength_file != NULL)
+ songlength_database = sidplay_load_songlength_db(songlength_file);
+
+ default_songlength=config_get_block_unsigned(param,
+ "default_songlength", 0);
+
+ all_files_are_containers=config_get_block_bool(param,
+ "all_files_are_containers", true);
+
+ path_with_subtune=g_pattern_spec_new(
+ "*/" SUBTUNE_PREFIX "???.sid");
+
+ filter_setting=config_get_block_bool(param, "filter", true);
+
+ return true;
+}
+
+void
+sidplay_finish()
+{
+ g_pattern_spec_free(path_with_subtune);
+
+ if(songlength_database)
+ g_key_file_free(songlength_database);
+}
+
+/**
+ * returns the file path stripped of any /tune_xxx.sid subtune
+ * suffix
+ */
+static char *
+get_container_name(const char *path_fs)
+{
+ char *path_container=g_strdup(path_fs);
+
+ if(!g_pattern_match(path_with_subtune,
+ strlen(path_container), path_container, NULL))
+ return path_container;
+
+ char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX);
+ if(ptr) *ptr='\0';
+
+ return path_container;
+}
+
+/**
+ * returns tune number from file.sid/tune_xxx.sid style path or 1 if
+ * no subtune is appended
+ */
+static int
+get_song_num(const char *path_fs)
+{
+ if(g_pattern_match(path_with_subtune,
+ strlen(path_fs), path_fs, NULL)) {
+ char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX);
+ if(!sub) return 1;
+
+ sub+=strlen("/" SUBTUNE_PREFIX);
+ int song_num=strtol(sub, NULL, 10);
+
+ if (errno == EINVAL)
+ return 1;
+ else
+ return song_num;
+ } else
+ return 1;
+}
+
+/* get the song length in seconds */
+static int
+get_song_length(const char *path_fs)
+{
+ if (songlength_database == NULL)
+ return -1;
+
+ gchar *sid_file=get_container_name(path_fs);
+ SidTuneMod tune(sid_file);
+ g_free(sid_file);
+ if(!tune) {
+ g_warning("failed to load file for calculating md5 sum");
+ return -1;
+ }
+ char md5sum[SIDTUNE_MD5_LENGTH+1];
+ tune.createMD5(md5sum);
+
+ int song_num=get_song_num(path_fs);
+
+ gsize num_items;
+ gchar **values=g_key_file_get_string_list(songlength_database,
+ "Database", md5sum, &num_items, NULL);
+ if(!values || song_num>num_items) {
+ g_strfreev(values);
+ return -1;
+ }
+
+ int minutes=strtol(values[song_num-1], NULL, 10);
+ if(errno==EINVAL) minutes=0;
+
+ int seconds;
+ char *ptr=strchr(values[song_num-1], ':');
+ if(ptr) {
+ seconds=strtol(ptr+1, NULL, 10);
+ if(errno==EINVAL) seconds=0;
+ } else
+ seconds=0;
+
+ g_strfreev(values);
+
+ return (minutes*60)+seconds;
+}
+
static void
sidplay_file_decode(struct decoder *decoder, const char *path_fs)
{
@@ -36,13 +204,19 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* load the tune */
- SidTune tune(path_fs, NULL, true);
+ char *path_container=get_container_name(path_fs);
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune) {
g_warning("failed to load file");
return;
}
- tune.selectSong(1);
+ int song_num=get_song_num(path_fs);
+ tune.selectSong(song_num);
+
+ int song_len=get_song_length(path_fs);
+ if(song_len==-1) song_len=default_songlength;
/* initialize the player */
@@ -67,7 +241,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
return;
}
- builder.filter(false);
+ builder.filter(filter_setting);
if (!builder) {
g_warning("ReSIDBuilder.filter() failed");
return;
@@ -103,14 +277,16 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
/* initialize the MPD decoder */
struct audio_format audio_format;
- audio_format.sample_rate = 48000;
- audio_format.bits = 16;
- audio_format.channels = 2;
+ audio_format_init(&audio_format, 48000, SAMPLE_FORMAT_S16, 2);
+ assert(audio_format_valid(&audio_format));
- decoder_initialized(decoder, &audio_format, false, -1);
+ decoder_initialized(decoder, &audio_format, true, (float)song_len);
/* .. and play */
+ const unsigned timebase = player.timebase();
+ song_len *= timebase;
+
enum decoder_command cmd;
do {
char buffer[4096];
@@ -120,30 +296,108 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
if (nbytes == 0)
break;
+ decoder_timestamp(decoder, (double)player.time() / timebase);
+
cmd = decoder_data(decoder, NULL, buffer, nbytes,
- 0, 0, NULL);
- } while (cmd == DECODE_COMMAND_NONE);
+ 0, NULL);
+
+ if(cmd==DECODE_COMMAND_SEEK) {
+ unsigned data_time = player.time();
+ unsigned target_time = (unsigned)
+ (decoder_seek_where(decoder) * timebase);
+
+ /* can't rewind so return to zero and seek forward */
+ if(target_time<data_time) {
+ player.stop();
+ data_time=0;
+ }
+
+ /* ignore data until target time is reached */
+ while(data_time<target_time) {
+ nbytes=player.play(buffer, sizeof(buffer));
+ if(nbytes==0)
+ break;
+ data_time = player.time();
+ }
+
+ decoder_command_finished(decoder);
+ }
+
+ if (song_len > 0 && player.time() >= song_len)
+ break;
+
+ } while (cmd != DECODE_COMMAND_STOP);
}
static struct tag *
sidplay_tag_dup(const char *path_fs)
{
- SidTune tune(path_fs, NULL, true);
+ int song_num=get_song_num(path_fs);
+ char *path_container=get_container_name(path_fs);
+
+ SidTune tune(path_container, NULL, true);
+ g_free(path_container);
if (!tune)
return NULL;
const SidTuneInfo &info = tune.getInfo();
struct tag *tag = tag_new();
+ /* title */
+ const char *title;
if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]);
-
+ title=info.infoString[0];
+ else
+ title="";
+
+ if(info.songs>1) {
+ char *tag_title=g_strdup_printf("%s (%d/%d)",
+ title, song_num, info.songs);
+ tag_add_item(tag, TAG_TITLE, tag_title);
+ g_free(tag_title);
+ } else
+ tag_add_item(tag, TAG_TITLE, title);
+
+ /* artist */
if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]);
+ tag_add_item(tag, TAG_ARTIST, info.infoString[1]);
+
+ /* track */
+ char *track=g_strdup_printf("%d", song_num);
+ tag_add_item(tag, TAG_TRACK, track);
+ g_free(track);
+
+ /* time */
+ int song_len=get_song_length(path_fs);
+ if(song_len!=-1) tag->time=song_len;
return tag;
}
+static char *
+sidplay_container_scan(const char *path_fs, const unsigned int tnum)
+{
+ SidTune tune(path_fs, NULL, true);
+ if (!tune)
+ return NULL;
+
+ const SidTuneInfo &info=tune.getInfo();
+
+ /* Don't treat sids containing a single tune
+ as containers */
+ if(!all_files_are_containers && info.songs<2)
+ return NULL;
+
+ /* Construct container/tune path names, eg.
+ Delta.sid/tune_001.sid */
+ if(tnum<=info.songs) {
+ char *subtune= g_strdup_printf(
+ SUBTUNE_PREFIX "%03u.sid", tnum);
+ return subtune;
+ } else
+ return NULL;
+}
+
static const char *const sidplay_suffixes[] = {
"sid",
NULL
@@ -152,12 +406,12 @@ static const char *const sidplay_suffixes[] = {
extern const struct decoder_plugin sidplay_decoder_plugin;
const struct decoder_plugin sidplay_decoder_plugin = {
"sidplay",
- NULL, /* init() */
- NULL, /* finish() */
+ sidplay_init,
+ sidplay_finish,
NULL, /* stream_decode() */
sidplay_file_decode,
sidplay_tag_dup,
- NULL, /* container_scan */
+ sidplay_container_scan,
sidplay_suffixes,
NULL, /* mime_types */
};
diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c
new file mode 100644
index 000000000..fd51f4a33
--- /dev/null
+++ b/src/decoder/sndfile_decoder_plugin.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
+
+#include <sndfile.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sndfile"
+
+static sf_count_t
+sndfile_vio_get_filelen(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->size;
+}
+
+static sf_count_t
+sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
+{
+ struct input_stream *is = user_data;
+ bool success;
+
+ success = input_stream_seek(is, offset, whence, NULL);
+ if (!success)
+ return -1;
+
+ return is->offset;
+}
+
+static sf_count_t
+sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
+{
+ struct input_stream *is = user_data;
+ GError *error = NULL;
+ size_t nbytes;
+
+ nbytes = input_stream_read(is, ptr, count, &error);
+ if (nbytes == 0 && error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return -1;
+ }
+
+ return nbytes;
+}
+
+static sf_count_t
+sndfile_vio_write(G_GNUC_UNUSED const void *ptr,
+ G_GNUC_UNUSED sf_count_t count,
+ G_GNUC_UNUSED void *user_data)
+{
+ /* no writing! */
+ return -1;
+}
+
+static sf_count_t
+sndfile_vio_tell(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->offset;
+}
+
+/**
+ * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a
+ * libsndfile stream.
+ */
+static SF_VIRTUAL_IO vio = {
+ .get_filelen = sndfile_vio_get_filelen,
+ .seek = sndfile_vio_seek,
+ .read = sndfile_vio_read,
+ .write = sndfile_vio_write,
+ .tell = sndfile_vio_tell,
+};
+
+/**
+ * Converts a frame number to a timestamp (in seconds).
+ */
+static float
+frame_to_time(sf_count_t frame, const struct audio_format *audio_format)
+{
+ return (float)frame / (float)audio_format->sample_rate;
+}
+
+/**
+ * Converts a timestamp (in seconds) to a frame number.
+ */
+static sf_count_t
+time_to_frame(float t, const struct audio_format *audio_format)
+{
+ return (sf_count_t)(t * audio_format->sample_rate);
+}
+
+static void
+sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ GError *error = NULL;
+ SNDFILE *sf;
+ SF_INFO info;
+ struct audio_format audio_format;
+ size_t frame_size;
+ sf_count_t read_frames, num_frames;
+ int buffer[4096];
+ enum decoder_command cmd;
+
+ info.format = 0;
+
+ sf = sf_open_virtual(&vio, SFM_READ, &info, is);
+ if (sf == NULL) {
+ g_warning("sf_open_virtual() failed");
+ return;
+ }
+
+ /* for now, always read 32 bit samples. Later, we could lower
+ MPD's CPU usage by reading 16 bit samples with
+ sf_readf_short() on low-quality source files. */
+ if (!audio_format_init_checked(&audio_format, info.samplerate,
+ SAMPLE_FORMAT_S32,
+ info.channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ decoder_initialized(decoder, &audio_format, info.seekable,
+ frame_to_time(info.frames, &audio_format));
+
+ frame_size = audio_format_frame_size(&audio_format);
+ read_frames = sizeof(buffer) / frame_size;
+
+ do {
+ num_frames = sf_readf_int(sf, buffer, read_frames);
+ if (num_frames <= 0)
+ break;
+
+ cmd = decoder_data(decoder, is,
+ buffer, num_frames * frame_size,
+ 0, NULL);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ sf_count_t c =
+ time_to_frame(decoder_seek_where(decoder),
+ &audio_format);
+ c = sf_seek(sf, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ sf_close(sf);
+}
+
+static struct tag *
+sndfile_tag_dup(const char *path_fs)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ struct tag *tag;
+ const char *p;
+
+ info.format = 0;
+
+ sf = sf_open(path_fs, SFM_READ, &info);
+ if (sf == NULL)
+ return NULL;
+
+ if (!audio_valid_sample_rate(info.samplerate)) {
+ sf_close(sf);
+ g_warning("Invalid sample rate in %s\n", path_fs);
+ return NULL;
+ }
+
+ tag = tag_new();
+ tag->time = info.frames / info.samplerate;
+
+ p = sf_get_string(sf, SF_STR_TITLE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_TITLE, p);
+
+ p = sf_get_string(sf, SF_STR_ARTIST);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ARTIST, p);
+
+ p = sf_get_string(sf, SF_STR_DATE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_DATE, p);
+
+ sf_close(sf);
+
+ return tag;
+}
+
+static const char *const sndfile_suffixes[] = {
+ "wav", "aiff", "aif", /* Microsoft / SGI / Apple */
+ "au", "snd", /* Sun / DEC / NeXT */
+ "paf", /* Paris Audio File */
+ "iff", "svx", /* Commodore Amiga IFF / SVX */
+ "sf", /* IRCAM */
+ "voc", /* Creative */
+ "w64", /* Soundforge */
+ "pvf", /* Portable Voice Format */
+ "xi", /* Fasttracker */
+ "htk", /* HMM Tool Kit */
+ "caf", /* Apple */
+ "sd2", /* Sound Designer II */
+
+ /* libsndfile also supports FLAC and Ogg Vorbis, but only by
+ linking with libFLAC and libvorbis - we can do better, we
+ have native plugins for these libraries */
+
+ NULL
+};
+
+static const char *const sndfile_mime_types[] = {
+ "audio/x-wav",
+ "audio/x-aiff",
+
+ /* what are the MIME types of the other supported formats? */
+
+ NULL
+};
+
+const struct decoder_plugin sndfile_decoder_plugin = {
+ .name = "sndfile",
+ .stream_decode = sndfile_stream_decode,
+ .tag_dup = sndfile_tag_dup,
+ .suffixes = sndfile_suffixes,
+ .mime_types = sndfile_mime_types,
+};
diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c
index 0ff898647..b323cf775 100644..100755
--- a/src/decoder/vorbis_plugin.c
+++ b/src/decoder/vorbis_plugin.c
@@ -17,13 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */
-
-#include "_ogg_common.h"
#include "config.h"
+#include "_ogg_common.h"
+#include "audio_check.h"
#include "uri.h"
#ifndef HAVE_TREMOR
+#define OV_EXCLUDE_STATIC_CALLBACKS
#include <vorbis/vorbisfile.h>
#else
#include <tremor/ivorbisfile.h>
@@ -55,17 +55,17 @@
#define OGG_DECODE_USE_BIGENDIAN 0
#endif
-typedef struct _OggCallbackData {
+struct vorbis_decoder_data {
struct decoder *decoder;
struct input_stream *input_stream;
bool seekable;
-} OggCallbackData;
+};
static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
{
+ struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata;
size_t ret;
- OggCallbackData *data = (OggCallbackData *) vdata;
ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb);
@@ -76,11 +76,11 @@ static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata)
static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence)
{
- const OggCallbackData *data = (const OggCallbackData *) vdata;
+ struct vorbis_decoder_data *data = (struct vorbis_decoder_data *)vdata;
return data->seekable &&
decoder_get_command(data->decoder) != DECODE_COMMAND_STOP &&
- input_stream_seek(data->input_stream, offset, whence)
+ input_stream_seek(data->input_stream, offset, whence, NULL)
? 0 : -1;
}
@@ -92,12 +92,37 @@ static int ogg_close_cb(G_GNUC_UNUSED void *vdata)
static long ogg_tell_cb(void *vdata)
{
- const OggCallbackData *data = (const OggCallbackData *) vdata;
+ const struct vorbis_decoder_data *data =
+ (const struct vorbis_decoder_data *)vdata;
return (long)data->input_stream->offset;
}
static const char *
+vorbis_strerror(int code)
+{
+ switch (code) {
+ case OV_EREAD:
+ return "read error";
+
+ case OV_ENOTVORBIS:
+ return "not vorbis stream";
+
+ case OV_EVERSION:
+ return "vorbis version mismatch";
+
+ case OV_EBADHEADER:
+ return "invalid vorbis header";
+
+ case OV_EFAULT:
+ return "internal logic error";
+
+ default:
+ return "unknown error";
+ }
+}
+
+static const char *
vorbis_comment_value(const char *comment, const char *needle)
{
size_t len = strlen(needle);
@@ -176,11 +201,11 @@ vorbis_parse_comment(struct tag *tag, const char *comment)
assert(tag != NULL);
if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY,
- TAG_ITEM_TRACK) ||
+ TAG_TRACK) ||
vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY,
- TAG_ITEM_DISC) ||
+ TAG_DISC) ||
vorbis_copy_comment(tag, comment, "album artist",
- TAG_ITEM_ALBUM_ARTIST))
+ TAG_ALBUM_ARTIST))
return;
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
@@ -228,7 +253,7 @@ oggvorbis_seekable(struct decoder *decoder)
uri = decoder_get_uri(decoder);
/* disable seeking on remote streams, because libvorbis seeks
around like crazy, and due to being very expensive, this
- delays song playback my 10 or 20 seconds */
+ delays song playback by 10 or 20 seconds */
seekable = !uri_has_scheme(uri);
g_free(uri);
@@ -240,10 +265,12 @@ static void
vorbis_stream_decode(struct decoder *decoder,
struct input_stream *input_stream)
{
+ GError *error = NULL;
OggVorbis_File vf;
ov_callbacks callbacks;
- OggCallbackData data;
+ struct vorbis_decoder_data data;
struct audio_format audio_format;
+ float total_time;
int current_section;
int prev_section = -1;
long ret;
@@ -251,8 +278,7 @@ vorbis_stream_decode(struct decoder *decoder,
long bitRate = 0;
long test;
struct replay_gain_info *replay_gain_info = NULL;
- char **comments;
- bool initialized = false;
+ const vorbis_info *vi;
enum decoder_command cmd = DECODE_COMMAND_NONE;
if (ogg_stream_type_detect(input_stream) != VORBIS)
@@ -260,7 +286,7 @@ vorbis_stream_decode(struct decoder *decoder,
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
- input_stream_seek(input_stream, 0, SEEK_SET);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
data.decoder = decoder;
data.input_stream = input_stream;
@@ -271,36 +297,33 @@ vorbis_stream_decode(struct decoder *decoder,
callbacks.close_func = ogg_close_cb;
callbacks.tell_func = ogg_tell_cb;
if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
- const char *error;
-
if (decoder_get_command(decoder) != DECODE_COMMAND_NONE)
return;
- switch (ret) {
- case OV_EREAD:
- error = "read error";
- break;
- case OV_ENOTVORBIS:
- error = "not vorbis stream";
- break;
- case OV_EVERSION:
- error = "vorbis version mismatch";
- break;
- case OV_EBADHEADER:
- error = "invalid vorbis header";
- break;
- case OV_EFAULT:
- error = "internal logic error";
- break;
- default:
- error = "unknown error";
- break;
- }
+ g_warning("Error decoding Ogg Vorbis stream: %s",
+ vorbis_strerror(ret));
+ return;
+ }
- g_warning("Error decoding Ogg Vorbis stream: %s", error);
+ vi = ov_info(&vf, -1);
+ if (vi == NULL) {
+ g_warning("ov_info() has failed");
return;
}
- audio_format.bits = 16;
+
+ if (!audio_format_init_checked(&audio_format, vi->rate,
+ SAMPLE_FORMAT_S16,
+ vi->channels, &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ total_time = ov_time_total(&vf, -1);
+ if (total_time < 0)
+ total_time = 0;
+
+ decoder_initialized(decoder, &audio_format, data.seekable, total_time);
do {
if (cmd == DECODE_COMMAND_SEEK) {
@@ -320,30 +343,23 @@ vorbis_stream_decode(struct decoder *decoder,
break;
if (current_section != prev_section) {
- /*printf("new song!\n"); */
- vorbis_info *vi = ov_info(&vf, -1);
+ char **comments;
struct replay_gain_info *new_rgi;
- audio_format.channels = vi->channels;
- audio_format.sample_rate = vi->rate;
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate,
- audio_format.bits,
- audio_format.channels);
+ vi = ov_info(&vf, -1);
+ if (vi == NULL) {
+ g_warning("ov_info() has failed");
break;
}
- if (!initialized) {
- float total_time = ov_time_total(&vf, -1);
- if (total_time < 0)
- total_time = 0;
- decoder_initialized(decoder, &audio_format,
- data.seekable,
- total_time);
- initialized = true;
+ if (vi->rate != (long)audio_format.sample_rate ||
+ vi->channels != (int)audio_format.channels) {
+ /* we don't support audio format
+ change yet */
+ g_warning("audio format change, stopping here");
+ break;
}
+
comments = ov_comment(&vf, -1)->user_comments;
vorbis_send_comments(decoder, input_stream, comments);
new_rgi = vorbis_comments_to_replay_gain(comments);
@@ -352,16 +368,15 @@ vorbis_stream_decode(struct decoder *decoder,
replay_gain_info_free(replay_gain_info);
replay_gain_info = new_rgi;
}
- }
- prev_section = current_section;
+ prev_section = current_section;
+ }
if ((test = ov_bitrate_instant(&vf)) > 0)
bitRate = test / 1000;
cmd = decoder_data(decoder, input_stream,
chunk, ret,
- ov_pcm_tell(&vf) / audio_format.sample_rate,
bitRate, replay_gain_info);
} while (cmd != DECODE_COMMAND_STOP);
@@ -378,7 +393,7 @@ vorbis_tag_dup(const char *file)
FILE *fp;
OggVorbis_File vf;
- fp = fopen(file, "r");
+ fp = fopen(file, "rb");
if (!fp) {
return NULL;
}
diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c
index 7ad3a62b0..fbcebb803 100644
--- a/src/decoder/wavpack_plugin.c
+++ b/src/decoder/wavpack_plugin.c
@@ -17,9 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
-#include "../path.h"
-#include "../utils.h"
+#include "config.h"
+#include "decoder_api.h"
+#include "audio_check.h"
+#include "path.h"
+#include "utils.h"
#include <wavpack/wavpack.h>
#include <glib.h>
@@ -41,17 +43,17 @@ static struct {
const char *name;
enum tag_type type;
} tagtypes[] = {
- { "artist", TAG_ITEM_ARTIST },
- { "album", TAG_ITEM_ALBUM },
- { "title", TAG_ITEM_TITLE },
- { "track", TAG_ITEM_TRACK },
- { "name", TAG_ITEM_NAME },
- { "genre", TAG_ITEM_GENRE },
- { "date", TAG_ITEM_DATE },
- { "composer", TAG_ITEM_COMPOSER },
- { "performer", TAG_ITEM_PERFORMER },
- { "comment", TAG_ITEM_COMMENT },
- { "disc", TAG_ITEM_DISC },
+ { "artist", TAG_ARTIST },
+ { "album", TAG_ALBUM },
+ { "title", TAG_TITLE },
+ { "track", TAG_TRACK },
+ { "name", TAG_NAME },
+ { "genre", TAG_GENRE },
+ { "date", TAG_DATE },
+ { "composer", TAG_COMPOSER },
+ { "performer", TAG_PERFORMER },
+ { "comment", TAG_COMMENT },
+ { "disc", TAG_DISC },
};
/** A pointer type for format converter function. */
@@ -97,19 +99,11 @@ format_samples_int(int bytes_per_sample, void *buffer, uint32_t count)
}
break;
}
+
case 3:
+ case 4:
/* do nothing */
break;
- case 4: {
- uint32_t *dst = buffer;
- assert_static(sizeof(*dst) <= sizeof(*src));
-
- /* downsample to 24-bit */
- while (count--) {
- *dst++ = *src++ >> 8;
- }
- break;
- }
}
}
@@ -129,6 +123,33 @@ format_samples_float(G_GNUC_UNUSED int bytes_per_sample, void *buffer,
}
}
+/**
+ * Choose a MPD sample format from libwavpacks' number of bits.
+ */
+static enum sample_format
+wavpack_bits_to_sample_format(bool is_float, int bytes_per_sample)
+{
+ if (is_float)
+ return SAMPLE_FORMAT_S24_P32;
+
+ switch (bytes_per_sample) {
+ case 1:
+ return SAMPLE_FORMAT_S8;
+
+ case 2:
+ return SAMPLE_FORMAT_S16;
+
+ case 3:
+ return SAMPLE_FORMAT_S24_P32;
+
+ case 4:
+ return SAMPLE_FORMAT_S32;
+
+ default:
+ return SAMPLE_FORMAT_UNDEFINED;
+ }
+}
+
/*
* This does the main decoding thing.
* Requires an already opened WavpackContext.
@@ -137,30 +158,27 @@ static void
wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
struct replay_gain_info *replay_gain_info)
{
+ GError *error = NULL;
+ bool is_float;
+ enum sample_format sample_format;
struct audio_format audio_format;
format_samples_t format_samples;
char chunk[CHUNK_SIZE];
int samples_requested, samples_got;
- float total_time, current_time;
+ float total_time;
int bytes_per_sample, output_sample_size;
- int position;
-
- audio_format.sample_rate = WavpackGetSampleRate(wpc);
- audio_format.channels = WavpackGetReducedChannels(wpc);
- audio_format.bits = WavpackGetBitsPerSample(wpc);
- /* round bitwidth to 8-bit units */
- audio_format.bits = (audio_format.bits + 7) & (~7);
- /* mpd handles max 24-bit samples */
- if (audio_format.bits > 24) {
- audio_format.bits = 24;
- }
-
- if (!audio_format_valid(&audio_format)) {
- g_warning("Invalid audio format: %u:%u:%u\n",
- audio_format.sample_rate,
- audio_format.bits,
- audio_format.channels);
+ is_float = (WavpackGetMode(wpc) & MODE_FLOAT) != 0;
+ sample_format =
+ wavpack_bits_to_sample_format(is_float,
+ WavpackGetBytesPerSample(wpc));
+
+ if (!audio_format_init_checked(&audio_format,
+ WavpackGetSampleRate(wpc),
+ sample_format,
+ WavpackGetNumChannels(wpc), &error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
return;
}
@@ -180,8 +198,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
decoder_initialized(decoder, &audio_format, can_seek, total_time);
- position = 0;
-
do {
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK) {
if (can_seek) {
@@ -189,7 +205,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
audio_format.sample_rate;
if (WavpackSeekSample(wpc, where)) {
- position = where;
decoder_command_finished(decoder);
} else {
decoder_seek_error(decoder);
@@ -209,9 +224,6 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
if (samples_got > 0) {
int bitrate = (int)(WavpackGetInstantBitrate(wpc) /
1000 + 0.5);
- position += samples_got;
- current_time = position;
- current_time /= audio_format.sample_rate;
format_samples(
bytes_per_sample, chunk,
@@ -221,7 +233,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek,
decoder_data(
decoder, NULL, chunk,
samples_got * output_sample_size,
- current_time, bitrate,
+ bitrate,
replay_gain_info
);
}
@@ -397,13 +409,13 @@ wavpack_input_get_pos(void *id)
static int
wavpack_input_set_pos_abs(void *id, uint32_t pos)
{
- return input_stream_seek(wpin(id)->is, pos, SEEK_SET) ? 0 : -1;
+ return input_stream_seek(wpin(id)->is, pos, SEEK_SET, NULL) ? 0 : -1;
}
static int
wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
{
- return input_stream_seek(wpin(id)->is, delta, mode) ? 0 : -1;
+ return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1;
}
static int
@@ -474,7 +486,7 @@ wavpack_open_wvc(struct decoder *decoder, struct input_stream *is_wvc,
wvc_url = g_strconcat(utf8url, "c", NULL);
g_free(utf8url);
- ret = input_stream_open(is_wvc, wvc_url);
+ ret = input_stream_open(is_wvc, wvc_url, NULL);
g_free(wvc_url);
if (!ret) {
@@ -508,7 +520,7 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
char error[ERRORLEN];
WavpackContext *wpc;
struct input_stream is_wvc;
- int open_flags = OPEN_2CH_MAX | OPEN_NORMALIZE;
+ int open_flags = OPEN_NORMALIZE;
struct wavpack_input isp, isp_wvc;
bool can_seek = is->seekable;
@@ -553,7 +565,7 @@ wavpack_filedecode(struct decoder *decoder, const char *fname)
wpc = WavpackOpenFileInput(
fname, error,
- OPEN_TAGS | OPEN_WVC | OPEN_2CH_MAX | OPEN_NORMALIZE, 23
+ OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23
);
if (wpc == NULL) {
g_warning(
diff --git a/src/decoder/wildmidi_plugin.c b/src/decoder/wildmidi_plugin.c
index 8bad6943a..0392c06b9 100644
--- a/src/decoder/wildmidi_plugin.c
+++ b/src/decoder/wildmidi_plugin.c
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../decoder_api.h"
+#include "config.h"
+#include "decoder_api.h"
#include <glib.h>
@@ -58,7 +59,7 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
{
static const struct audio_format audio_format = {
.sample_rate = WILDMIDI_SAMPLE_RATE,
- .bits = 16,
+ .format = SAMPLE_FORMAT_S16,
.channels = 2,
};
midi *wm;
@@ -91,8 +92,6 @@ wildmidi_file_decode(struct decoder *decoder, const char *path_fs)
break;
cmd = decoder_data(decoder, NULL, buffer, len,
- (float)info->current_sample /
- (float)WILDMIDI_SAMPLE_RATE,
0, NULL);
if (cmd == DECODE_COMMAND_SEEK) {
diff --git a/src/decoder_api.c b/src/decoder_api.c
index c696ba101..e902c454a 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_api.h"
#include "decoder_internal.h"
#include "decoder_control.h"
@@ -24,8 +25,6 @@
#include "audio.h"
#include "song.h"
#include "buffer.h"
-
-#include "normalize.h"
#include "pipe.h"
#include "chunk.h"
@@ -37,12 +36,16 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder"
-void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time)
+void
+decoder_initialized(struct decoder *decoder,
+ const struct audio_format *audio_format,
+ bool seekable, float total_time)
{
- assert(dc.state == DECODE_STATE_START);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
+ struct audio_format_string af_string;
+
+ assert(dc->state == DECODE_STATE_START);
+ assert(dc->pipe != NULL);
assert(decoder != NULL);
assert(decoder->stream_tag == NULL);
assert(decoder->decoder_tag == NULL);
@@ -51,47 +54,58 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
assert(audio_format_defined(audio_format));
assert(audio_format_valid(audio_format));
- dc.in_audio_format = *audio_format;
- getOutputAudioFormat(audio_format, &dc.out_audio_format);
+ dc->in_audio_format = *audio_format;
+ getOutputAudioFormat(audio_format, &dc->out_audio_format);
- dc.seekable = seekable;
- dc.total_time = total_time;
+ dc->seekable = seekable;
+ dc->total_time = total_time;
- dc.state = DECODE_STATE_DECODE;
- notify_signal(&pc.notify);
+ decoder_lock(dc);
+ dc->state = DECODE_STATE_DECODE;
+ decoder_unlock(dc);
- g_debug("audio_format=%u:%u:%u, seekable=%s",
- dc.in_audio_format.sample_rate, dc.in_audio_format.bits,
- dc.in_audio_format.channels,
+ player_lock_signal();
+
+ g_debug("audio_format=%s, seekable=%s",
+ audio_format_to_string(&dc->in_audio_format, &af_string),
seekable ? "true" : "false");
- if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format))
- g_debug("converting to %u:%u:%u",
- dc.out_audio_format.sample_rate,
- dc.out_audio_format.bits,
- dc.out_audio_format.channels);
+ if (!audio_format_equals(&dc->in_audio_format,
+ &dc->out_audio_format))
+ g_debug("converting to %s",
+ audio_format_to_string(&dc->out_audio_format,
+ &af_string));
}
char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder)
{
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
+
+ assert(dc->pipe != NULL);
- return song_get_uri(dc.current_song);
+ return song_get_uri(dc->song);
}
enum decoder_command decoder_get_command(G_GNUC_UNUSED struct decoder * decoder)
{
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
- return dc.command;
+ assert(dc->pipe != NULL);
+
+ return dc->command;
}
-void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
+void
+decoder_command_finished(struct decoder *decoder)
{
- assert(dc.command != DECODE_COMMAND_NONE);
- assert(dc.command != DECODE_COMMAND_SEEK ||
- dc.seek_error || decoder->seeking);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
+
+ decoder_lock(dc);
+
+ assert(dc->command != DECODE_COMMAND_NONE);
+ assert(dc->command != DECODE_COMMAND_SEEK ||
+ dc->seek_error || decoder->seeking);
+ assert(dc->pipe != NULL);
if (decoder->seeking) {
decoder->seeking = false;
@@ -99,33 +113,41 @@ void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder)
/* delete frames from the old song position */
if (decoder->chunk != NULL) {
- music_buffer_return(dc.buffer, decoder->chunk);
+ music_buffer_return(dc->buffer, decoder->chunk);
decoder->chunk = NULL;
}
- music_pipe_clear(dc.pipe, dc.buffer);
+ music_pipe_clear(dc->pipe, dc->buffer);
+
+ decoder->timestamp = dc->seek_where;
}
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ dc->command = DECODE_COMMAND_NONE;
+ decoder_unlock(dc);
+
+ player_lock_signal();
}
double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder)
{
- assert(dc.command == DECODE_COMMAND_SEEK);
- assert(dc.pipe != NULL);
+ const struct decoder_control *dc = decoder->dc;
+
+ assert(dc->command == DECODE_COMMAND_SEEK);
+ assert(dc->pipe != NULL);
decoder->seeking = true;
- return dc.seek_where;
+ return dc->seek_where;
}
void decoder_seek_error(struct decoder * decoder)
{
- assert(dc.command == DECODE_COMMAND_SEEK);
- assert(dc.pipe != NULL);
+ struct decoder_control *dc = decoder->dc;
- dc.seek_error = true;
+ assert(dc->command == DECODE_COMMAND_SEEK);
+ assert(dc->pipe != NULL);
+
+ dc->seek_error = true;
decoder->seeking = false;
decoder_command_finished(decoder);
@@ -135,11 +157,14 @@ size_t decoder_read(struct decoder *decoder,
struct input_stream *is,
void *buffer, size_t length)
{
+ const struct decoder_control *dc =
+ decoder != NULL ? decoder->dc : NULL;
+ GError *error = NULL;
size_t nbytes;
assert(decoder == NULL ||
- dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ dc->state == DECODE_STATE_START ||
+ dc->state == DECODE_STATE_DECODE);
assert(is != NULL);
assert(buffer != NULL);
@@ -152,12 +177,19 @@ size_t decoder_read(struct decoder *decoder,
/* ignore the SEEK command during initialization,
the plugin should handle that after it has
initialized successfully */
- (dc.command != DECODE_COMMAND_SEEK ||
- (dc.state != DECODE_STATE_START && !decoder->seeking)) &&
- dc.command != DECODE_COMMAND_NONE)
+ (dc->command != DECODE_COMMAND_SEEK ||
+ (dc->state != DECODE_STATE_START && !decoder->seeking)) &&
+ dc->command != DECODE_COMMAND_NONE)
+ return 0;
+
+ nbytes = input_stream_read(is, buffer, length, &error);
+
+ if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
return 0;
+ }
- nbytes = input_stream_read(is, buffer, length);
if (nbytes > 0 || input_stream_eof(is))
return nbytes;
@@ -167,6 +199,15 @@ size_t decoder_read(struct decoder *decoder,
}
}
+void
+decoder_timestamp(struct decoder *decoder, double t)
+{
+ assert(decoder != NULL);
+ assert(t >= 0);
+
+ decoder->timestamp = t;
+}
+
/**
* Sends a #tag as-is to the music pipe. Flushes the current chunk
* (decoder.chunk) if there is one.
@@ -181,15 +222,15 @@ do_send_tag(struct decoder *decoder, struct input_stream *is,
/* there is a partial chunk - flush it, we want the
tag in a new chunk */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
}
assert(decoder->chunk == NULL);
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
- assert(dc.command != DECODE_COMMAND_NONE);
- return dc.command;
+ assert(decoder->dc->command != DECODE_COMMAND_NONE);
+ return decoder->dc->command;
}
chunk->tag = tag_dup(tag);
@@ -225,31 +266,35 @@ enum decoder_command
decoder_data(struct decoder *decoder,
struct input_stream *is,
const void *_data, size_t length,
- float data_time, uint16_t bitRate,
+ uint16_t kbit_rate,
struct replay_gain_info *replay_gain_info)
{
+ struct decoder_control *dc = decoder->dc;
const char *data = _data;
+ GError *error = NULL;
+ enum decoder_command cmd;
+
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
+ assert(length % audio_format_frame_size(&dc->in_audio_format) == 0);
- assert(dc.state == DECODE_STATE_DECODE);
- assert(dc.pipe != NULL);
- assert(length % audio_format_frame_size(&dc.in_audio_format) == 0);
+ decoder_lock(dc);
+ cmd = dc->command;
+ decoder_unlock(dc);
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK ||
+ if (cmd == DECODE_COMMAND_STOP || cmd == DECODE_COMMAND_SEEK ||
length == 0)
- return dc.command;
+ return cmd;
/* send stream tags */
if (update_stream_tag(decoder, is)) {
- enum decoder_command cmd;
-
if (decoder->decoder_tag != NULL) {
/* merge with tag from decoder plugin */
struct tag *tag;
- tag = tag_merge(decoder->stream_tag,
- decoder->decoder_tag);
+ tag = tag_merge(decoder->decoder_tag,
+ decoder->stream_tag);
cmd = do_send_tag(decoder, is, tag);
tag_free(tag);
} else
@@ -260,17 +305,18 @@ decoder_data(struct decoder *decoder,
return cmd;
}
- if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) {
+ if (!audio_format_equals(&dc->in_audio_format, &dc->out_audio_format)) {
data = pcm_convert(&decoder->conv_state,
- &dc.in_audio_format, data, length,
- &dc.out_audio_format, &length);
-
- /* under certain circumstances, pcm_convert() may
- return an empty buffer - this condition should be
- investigated further, but for now, do this check as
- a workaround: */
- if (data == NULL)
- return DECODE_COMMAND_NONE;
+ &dc->in_audio_format, data, length,
+ &dc->out_audio_format, &length,
+ &error);
+ if (data == NULL) {
+ /* the PCM conversion has failed - stop
+ playback, since we have no better way to
+ bail out */
+ g_warning("%s", error->message);
+ return DECODE_COMMAND_STOP;
+ }
}
while (length > 0) {
@@ -281,16 +327,18 @@ decoder_data(struct decoder *decoder,
chunk = decoder_get_chunk(decoder, is);
if (chunk == NULL) {
- assert(dc.command != DECODE_COMMAND_NONE);
- return dc.command;
+ assert(dc->command != DECODE_COMMAND_NONE);
+ return dc->command;
}
- dest = music_chunk_write(chunk, &dc.out_audio_format,
- data_time, bitRate, &nbytes);
+ dest = music_chunk_write(chunk, &dc->out_audio_format,
+ decoder->timestamp -
+ dc->song->start_ms / 1000.0,
+ kbit_rate, &nbytes);
if (dest == NULL) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
continue;
}
@@ -305,24 +353,30 @@ decoder_data(struct decoder *decoder,
/* apply replay gain or normalization */
- if (replay_gain_info != NULL &&
- replay_gain_mode != REPLAY_GAIN_OFF)
+ if (replay_gain_mode != REPLAY_GAIN_OFF)
replay_gain_apply(replay_gain_info, dest, nbytes,
- &dc.out_audio_format);
- else if (normalizationEnabled)
- normalizeData(dest, nbytes, &dc.out_audio_format);
+ &dc->out_audio_format);
/* expand the music pipe chunk */
- full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes);
+ full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes);
if (full) {
/* the chunk is full, flush it */
decoder_flush_chunk(decoder);
- notify_signal(&pc.notify);
+ player_lock_signal();
}
data += nbytes;
length -= nbytes;
+
+ decoder->timestamp += (double)nbytes /
+ audio_format_time_to_size(&dc->in_audio_format);
+
+ if (dc->song->end_ms > 0 &&
+ decoder->timestamp >= dc->song->end_ms / 1000.0)
+ /* the end of this range has been reached:
+ stop decoding */
+ return DECODE_COMMAND_STOP;
}
return DECODE_COMMAND_NONE;
@@ -332,10 +386,11 @@ enum decoder_command
decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
const struct tag *tag)
{
+ G_GNUC_UNUSED const struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
- assert(dc.state == DECODE_STATE_DECODE);
- assert(dc.pipe != NULL);
+ assert(dc->state == DECODE_STATE_DECODE);
+ assert(dc->pipe != NULL);
assert(tag != NULL);
/* save the tag */
diff --git a/src/decoder_api.h b/src/decoder_api.h
index 37090d8d0..9df6bed19 100644
--- a/src/decoder_api.h
+++ b/src/decoder_api.h
@@ -17,15 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_DECODER_API_H
-#define MPD_DECODER_API_H
-
-/*
+/*! \file
+ * \brief The MPD Decoder API
+ *
* This is the public API which is used by decoder plugins to
* communicate with the mpd core.
- *
*/
+#ifndef MPD_DECODER_API_H
+#define MPD_DECODER_API_H
+
+#include "check.h"
#include "decoder_command.h"
#include "decoder_plugin.h"
#include "input_stream.h"
@@ -36,65 +38,119 @@
#include <stdbool.h>
-
/**
* Notify the player thread that it has finished initialization and
* that it has read the song's meta data.
+ *
+ * @param decoder the decoder object
+ * @param audio_format the audio format which is going to be sent to
+ * decoder_data()
+ * @param seekable true if the song is seekable
+ * @param total_time the total number of seconds in this song; -1 if unknown
*/
-void decoder_initialized(struct decoder * decoder,
- const struct audio_format *audio_format,
- bool seekable, float total_time);
+void
+decoder_initialized(struct decoder *decoder,
+ const struct audio_format *audio_format,
+ bool seekable, float total_time);
/**
* Returns the URI of the current song in UTF-8 encoding.
*
- * The return value is allocated on the heap, and must be freed by the
- * caller.
+ * @param decoder the decoder object
+ * @return an allocated string which must be freed with g_free()
*/
-char *decoder_get_uri(struct decoder *decoder);
+char *
+decoder_get_uri(struct decoder *decoder);
-enum decoder_command decoder_get_command(struct decoder * decoder);
+/**
+ * Determines the pending decoder command.
+ *
+ * @param decoder the decoder object
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
+ */
+enum decoder_command
+decoder_get_command(struct decoder *decoder);
/**
* Called by the decoder when it has performed the requested command
* (dc->command). This function resets dc->command and wakes up the
* player thread.
+ *
+ * @param decoder the decoder object
*/
-void decoder_command_finished(struct decoder * decoder);
+void
+decoder_command_finished(struct decoder *decoder);
-double decoder_seek_where(struct decoder * decoder);
+/**
+ * Call this when you have received the DECODE_COMMAND_SEEK command.
+ *
+ * @param decoder the decoder object
+ * @return the destination position for the week
+ */
+double
+decoder_seek_where(struct decoder *decoder);
+
+/**
+ * Call this right before decoder_command_finished() when seeking has
+ * failed.
+ *
+ * @param decoder the decoder object
+ */
+void
+decoder_seek_error(struct decoder *decoder);
-void decoder_seek_error(struct decoder * decoder);
+/**
+ * Blocking read from the input stream.
+ *
+ * @param decoder the decoder object
+ * @param is the input stream to read from
+ * @param buffer the destination buffer
+ * @param length the maximum number of bytes to read
+ * @return the number of bytes read, or 0 if one of the following
+ * occurs: end of file; error; command (like SEEK or STOP).
+ */
+size_t
+decoder_read(struct decoder *decoder, struct input_stream *is,
+ void *buffer, size_t length);
/**
- * Blocking read from the input stream. Returns the number of bytes
- * read, or 0 if one of the following occurs: end of file; error;
- * command (like SEEK or STOP).
+ * Sets the time stamp for the next data chunk [seconds]. The MPD
+ * core automatically counts it up, and a decoder plugin only needs to
+ * use this function if it thinks that adding to the time stamp based
+ * on the buffer size won't work.
*/
-size_t decoder_read(struct decoder *decoder,
- struct input_stream *inStream,
- void *buffer, size_t length);
+void
+decoder_timestamp(struct decoder *decoder, double t);
/**
* This function is called by the decoder plugin when it has
* successfully decoded block of input data.
*
- * We send inStream for buffering the inputStream while waiting to
- * send the next chunk
+ * @param decoder the decoder object
+ * @param is an input stream which is buffering while we are waiting
+ * for the player
+ * @param data the source buffer
+ * @param length the number of bytes in the buffer
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
enum decoder_command
-decoder_data(struct decoder *decoder,
- struct input_stream *inStream,
- const void *data, size_t datalen,
- float data_time, uint16_t bitRate,
+decoder_data(struct decoder *decoder, struct input_stream *is,
+ const void *data, size_t length,
+ uint16_t kbit_rate,
struct replay_gain_info *replay_gain_info);
/**
* This function is called by the decoder plugin when it has
* successfully decoded a tag.
*
+ * @param decoder the decoder object
* @param is an input stream which is buffering while we are waiting
* for the player
+ * @param tag the tag to send
+ * @return the current command, or DECODE_COMMAND_NONE if there is no
+ * command pending
*/
enum decoder_command
decoder_tag(struct decoder *decoder, struct input_stream *is,
diff --git a/src/decoder_buffer.c b/src/decoder_buffer.c
index b6fa90004..a313eacc5 100644
--- a/src/decoder_buffer.c
+++ b/src/decoder_buffer.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_buffer.h"
#include "decoder_api.h"
diff --git a/src/decoder_control.c b/src/decoder_control.c
index 44bb63e15..a26edd150 100644
--- a/src/decoder_control.c
+++ b/src/decoder_control.c
@@ -17,108 +17,133 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_control.h"
+#include "player_control.h"
#include <assert.h>
-struct decoder_control dc;
+void
+dc_init(struct decoder_control *dc)
+{
+ dc->thread = NULL;
-void dc_init(void)
+ dc->mutex = g_mutex_new();
+ dc->cond = g_cond_new();
+
+ dc->state = DECODE_STATE_STOP;
+ dc->command = DECODE_COMMAND_NONE;
+}
+
+void
+dc_deinit(struct decoder_control *dc)
{
- notify_init(&dc.notify);
- dc.state = DECODE_STATE_STOP;
- dc.command = DECODE_COMMAND_NONE;
+ g_cond_free(dc->cond);
+ g_mutex_free(dc->mutex);
}
-void dc_deinit(void)
+static void
+dc_command_wait_locked(struct decoder_control *dc)
{
- notify_deinit(&dc.notify);
+ while (dc->command != DECODE_COMMAND_NONE)
+ player_wait_decoder(dc);
}
void
-dc_command_wait(struct notify *notify)
+dc_command_wait(struct decoder_control *dc)
{
- while (dc.command != DECODE_COMMAND_NONE) {
- notify_signal(&dc.notify);
- notify_wait(notify);
- }
+ decoder_lock(dc);
+ dc_command_wait_locked(dc);
+ decoder_unlock(dc);
}
static void
-dc_command(struct notify *notify, enum decoder_command cmd)
+dc_command_locked(struct decoder_control *dc, enum decoder_command cmd)
{
- dc.command = cmd;
- dc_command_wait(notify);
+ dc->command = cmd;
+ decoder_signal(dc);
+ dc_command_wait_locked(dc);
}
-static void dc_command_async(enum decoder_command cmd)
+static void
+dc_command(struct decoder_control *dc, enum decoder_command cmd)
{
- dc.command = cmd;
- notify_signal(&dc.notify);
+ decoder_lock(dc);
+ dc_command_locked(dc, cmd);
+ decoder_unlock(dc);
}
-void
-dc_start(struct notify *notify, struct song *song)
+static void
+dc_command_async(struct decoder_control *dc, enum decoder_command cmd)
{
- assert(dc.pipe != NULL);
- assert(song != NULL);
+ decoder_lock(dc);
- dc.next_song = song;
- dc_command(notify, DECODE_COMMAND_START);
+ dc->command = cmd;
+ decoder_signal(dc);
+
+ decoder_unlock(dc);
}
void
-dc_start_async(struct song *song)
+dc_start(struct decoder_control *dc, struct song *song,
+ struct music_buffer *buffer, struct music_pipe *pipe)
{
- assert(dc.pipe != NULL);
assert(song != NULL);
+ assert(buffer != NULL);
+ assert(pipe != NULL);
- dc.next_song = song;
- dc_command_async(DECODE_COMMAND_START);
+ dc->song = song;
+ dc->buffer = buffer;
+ dc->pipe = pipe;
+ dc_command(dc, DECODE_COMMAND_START);
}
void
-dc_stop(struct notify *notify)
+dc_stop(struct decoder_control *dc)
{
- if (dc.command != DECODE_COMMAND_NONE)
+ decoder_lock(dc);
+
+ if (dc->command != DECODE_COMMAND_NONE)
/* Attempt to cancel the current command. If it's too
late and the decoder thread is already executing
the old command, we'll call STOP again in this
function (see below). */
- dc_command(notify, DECODE_COMMAND_STOP);
+ dc_command_locked(dc, DECODE_COMMAND_STOP);
+
+ if (dc->state != DECODE_STATE_STOP && dc->state != DECODE_STATE_ERROR)
+ dc_command_locked(dc, DECODE_COMMAND_STOP);
- if (dc.state != DECODE_STATE_STOP && dc.state != DECODE_STATE_ERROR)
- dc_command(notify, DECODE_COMMAND_STOP);
+ decoder_unlock(dc);
}
bool
-dc_seek(struct notify *notify, double where)
+dc_seek(struct decoder_control *dc, double where)
{
- assert(dc.state != DECODE_STATE_START);
+ assert(dc->state != DECODE_STATE_START);
assert(where >= 0.0);
- if (dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR || !dc.seekable)
+ if (dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR || !dc->seekable)
return false;
- dc.seek_where = where;
- dc.seek_error = false;
- dc_command(notify, DECODE_COMMAND_SEEK);
+ dc->seek_where = where;
+ dc->seek_error = false;
+ dc_command(dc, DECODE_COMMAND_SEEK);
- if (dc.seek_error)
+ if (dc->seek_error)
return false;
return true;
}
void
-dc_quit(void)
+dc_quit(struct decoder_control *dc)
{
- assert(dc.thread != NULL);
+ assert(dc->thread != NULL);
- dc.quit = true;
- dc_command_async(DECODE_COMMAND_STOP);
+ dc->quit = true;
+ dc_command_async(dc, DECODE_COMMAND_STOP);
- g_thread_join(dc.thread);
- dc.thread = NULL;
+ g_thread_join(dc->thread);
+ dc->thread = NULL;
}
diff --git a/src/decoder_control.h b/src/decoder_control.h
index 6a04a1617..38c4f0d83 100644
--- a/src/decoder_control.h
+++ b/src/decoder_control.h
@@ -22,7 +22,8 @@
#include "decoder_command.h"
#include "audio_format.h"
-#include "notify.h"
+
+#include <glib.h>
#include <assert.h>
@@ -45,14 +46,25 @@ struct decoder_control {
thread isn't running */
GThread *thread;
- struct notify notify;
+ /**
+ * This lock protects #state and #command.
+ */
+ GMutex *mutex;
+
+ /**
+ * Trigger this object after you have modified #command. This
+ * is also used by the decoder thread to notify the caller
+ * when it has finished a command.
+ */
+ GCond *cond;
+
+ enum decoder_state state;
+ enum decoder_command command;
- volatile enum decoder_state state;
- volatile enum decoder_command command;
bool quit;
bool seek_error;
bool seekable;
- volatile double seek_where;
+ double seek_where;
/** the format of the song file */
struct audio_format in_audio_format;
@@ -60,54 +72,139 @@ struct decoder_control {
/** the format being sent to the music pipe */
struct audio_format out_audio_format;
- struct song *current_song;
- struct song *next_song;
+ /**
+ * The song currently being decoded. This attribute is set by
+ * the player thread, when it sends the #DECODE_COMMAND_START
+ * command.
+ */
+ const struct song *song;
+
float total_time;
/** the #music_chunk allocator */
struct music_buffer *buffer;
- /** the destination pipe for decoded chunks */
+ /**
+ * The destination pipe for decoded chunks. The caller thread
+ * owns this object, and is responsible for freeing it.
+ */
struct music_pipe *pipe;
};
-extern struct decoder_control dc;
+void
+dc_init(struct decoder_control *dc);
+
+void
+dc_deinit(struct decoder_control *dc);
+
+/**
+ * Locks the #decoder_control object.
+ */
+static inline void
+decoder_lock(struct decoder_control *dc)
+{
+ g_mutex_lock(dc->mutex);
+}
+
+/**
+ * Unlocks the #decoder_control object.
+ */
+static inline void
+decoder_unlock(struct decoder_control *dc)
+{
+ g_mutex_unlock(dc->mutex);
+}
+
+/**
+ * Waits for a signal on the #decoder_control object. This function
+ * is only valid in the decoder thread. The object must be locked
+ * prior to calling this function.
+ */
+static inline void
+decoder_wait(struct decoder_control *dc)
+{
+ g_cond_wait(dc->cond, dc->mutex);
+}
+
+/**
+ * Signals the #decoder_control object. This function is only valid
+ * in the player thread. The object should be locked prior to calling
+ * this function.
+ */
+static inline void
+decoder_signal(struct decoder_control *dc)
+{
+ g_cond_signal(dc->cond);
+}
+
+static inline bool
+decoder_is_idle(const struct decoder_control *dc)
+{
+ return dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR;
+}
+
+static inline bool
+decoder_is_starting(const struct decoder_control *dc)
+{
+ return dc->state == DECODE_STATE_START;
+}
-void dc_init(void);
+static inline bool
+decoder_has_failed(const struct decoder_control *dc)
+{
+ assert(dc->command == DECODE_COMMAND_NONE);
-void dc_deinit(void);
+ return dc->state == DECODE_STATE_ERROR;
+}
-static inline bool decoder_is_idle(void)
+static inline bool
+decoder_lock_is_idle(struct decoder_control *dc)
{
- return (dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR) &&
- dc.command != DECODE_COMMAND_START;
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_is_idle(dc);
+ decoder_unlock(dc);
+
+ return ret;
}
-static inline bool decoder_is_starting(void)
+static inline bool
+decoder_lock_is_starting(struct decoder_control *dc)
{
- return dc.command == DECODE_COMMAND_START ||
- dc.state == DECODE_STATE_START;
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_is_starting(dc);
+ decoder_unlock(dc);
+
+ return ret;
}
-static inline bool decoder_has_failed(void)
+static inline bool
+decoder_lock_has_failed(struct decoder_control *dc)
{
- assert(dc.command == DECODE_COMMAND_NONE);
+ bool ret;
+
+ decoder_lock(dc);
+ ret = decoder_has_failed(dc);
+ decoder_unlock(dc);
- return dc.state == DECODE_STATE_ERROR;
+ return ret;
}
-static inline struct song *
-decoder_current_song(void)
+static inline const struct song *
+decoder_current_song(const struct decoder_control *dc)
{
- switch (dc.state) {
+ switch (dc->state) {
case DECODE_STATE_STOP:
case DECODE_STATE_ERROR:
return NULL;
case DECODE_STATE_START:
case DECODE_STATE_DECODE:
- return dc.current_song;
+ return dc->song;
}
assert(false);
@@ -115,21 +212,27 @@ decoder_current_song(void)
}
void
-dc_command_wait(struct notify *notify);
-
-void
-dc_start(struct notify *notify, struct song *song);
+dc_command_wait(struct decoder_control *dc);
+/**
+ * Start the decoder.
+ *
+ * @param the decoder
+ * @param song the song to be decoded
+ * @param pipe the pipe which receives the decoded chunks (owned by
+ * the caller)
+ */
void
-dc_start_async(struct song *song);
+dc_start(struct decoder_control *dc, struct song *song,
+ struct music_buffer *buffer, struct music_pipe *pipe);
void
-dc_stop(struct notify *notify);
+dc_stop(struct decoder_control *dc);
bool
-dc_seek(struct notify *notify, double where);
+dc_seek(struct decoder_control *dc, double where);
void
-dc_quit(void);
+dc_quit(struct decoder_control *dc);
#endif
diff --git a/src/decoder_internal.c b/src/decoder_internal.c
index 4a56fa5f3..b376a5755 100644
--- a/src/decoder_internal.c
+++ b/src/decoder_internal.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_internal.h"
#include "decoder_control.h"
#include "player_control.h"
@@ -28,21 +29,45 @@
#include <assert.h>
/**
+ * This is a wrapper for input_stream_buffer(). It assumes that the
+ * decoder is currently locked, and temporarily unlocks it while
+ * calling input_stream_buffer(). We shouldn't hold the lock during a
+ * potentially blocking operation.
+ */
+static bool
+decoder_input_buffer(struct decoder_control *dc, struct input_stream *is)
+{
+ GError *error = NULL;
+ int ret;
+
+ decoder_unlock(dc);
+ ret = input_stream_buffer(is, &error);
+ if (ret < 0) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ decoder_lock(dc);
+
+ return ret > 0;
+}
+
+/**
* All chunks are full of decoded data; wait for the player to free
* one.
*/
static enum decoder_command
-need_chunks(struct input_stream *is, bool do_wait)
+need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait)
{
- if (dc.command == DECODE_COMMAND_STOP ||
- dc.command == DECODE_COMMAND_SEEK)
- return dc.command;
+ if (dc->command == DECODE_COMMAND_STOP ||
+ dc->command == DECODE_COMMAND_SEEK)
+ return dc->command;
- if ((is == NULL || input_stream_buffer(is) <= 0) && do_wait) {
- notify_wait(&dc.notify);
- notify_signal(&pc.notify);
+ if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) {
+ decoder_wait(dc);
+ player_signal();
- return dc.command;
+ return dc->command;
}
return DECODE_COMMAND_NONE;
@@ -51,6 +76,7 @@ need_chunks(struct input_stream *is, bool do_wait)
struct music_chunk *
decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
{
+ struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
assert(decoder != NULL);
@@ -59,11 +85,13 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
return decoder->chunk;
do {
- decoder->chunk = music_buffer_allocate(dc.buffer);
+ decoder->chunk = music_buffer_allocate(dc->buffer);
if (decoder->chunk != NULL)
return decoder->chunk;
- cmd = need_chunks(is, true);
+ decoder_lock(dc);
+ cmd = need_chunks(dc, is, true);
+ decoder_unlock(dc);
} while (cmd == DECODE_COMMAND_NONE);
return NULL;
@@ -72,13 +100,15 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
void
decoder_flush_chunk(struct decoder *decoder)
{
+ struct decoder_control *dc = decoder->dc;
+
assert(decoder != NULL);
assert(decoder->chunk != NULL);
if (music_chunk_is_empty(decoder->chunk))
- music_buffer_return(dc.buffer, decoder->chunk);
+ music_buffer_return(dc->buffer, decoder->chunk);
else
- music_pipe_push(dc.pipe, decoder->chunk);
+ music_pipe_push(dc->pipe, decoder->chunk);
decoder->chunk = NULL;
}
diff --git a/src/decoder_internal.h b/src/decoder_internal.h
index cf54dbf6d..df558f4dd 100644
--- a/src/decoder_internal.h
+++ b/src/decoder_internal.h
@@ -26,8 +26,15 @@
struct input_stream;
struct decoder {
+ struct decoder_control *dc;
+
struct pcm_convert_state conv_state;
+ /**
+ * The time stamp of the next data chunk, in seconds.
+ */
+ double timestamp;
+
bool seeking;
/**
diff --git a/src/decoder_list.c b/src/decoder_list.c
index a42585e34..c322bc433 100644
--- a/src/decoder_list.c
+++ b/src/decoder_list.c
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_list.h"
#include "decoder_plugin.h"
#include "utils.h"
-#include "config.h"
#include "conf.h"
#include <glib.h>
@@ -28,9 +28,11 @@
#include <string.h>
extern const struct decoder_plugin mad_decoder_plugin;
+extern const struct decoder_plugin mpg123_decoder_plugin;
extern const struct decoder_plugin vorbis_decoder_plugin;
extern const struct decoder_plugin flac_decoder_plugin;
extern const struct decoder_plugin oggflac_decoder_plugin;
+extern const struct decoder_plugin sndfile_decoder_plugin;
extern const struct decoder_plugin audiofile_decoder_plugin;
extern const struct decoder_plugin mp4ff_decoder_plugin;
extern const struct decoder_plugin faad_decoder_plugin;
@@ -43,10 +45,13 @@ extern const struct decoder_plugin wildmidi_decoder_plugin;
extern const struct decoder_plugin fluidsynth_decoder_plugin;
extern const struct decoder_plugin ffmpeg_decoder_plugin;
-static const struct decoder_plugin *const decoder_plugins[] = {
+const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_MAD
&mad_decoder_plugin,
#endif
+#ifdef HAVE_MPG123
+ &mpg123_decoder_plugin,
+#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
@@ -56,6 +61,9 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
+#ifdef ENABLE_SNDFILE
+ &sndfile_decoder_plugin,
+#endif
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
@@ -89,32 +97,48 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FFMPEG
&ffmpeg_decoder_plugin,
#endif
+ NULL
};
enum {
- num_decoder_plugins = G_N_ELEMENTS(decoder_plugins),
+ num_decoder_plugins = G_N_ELEMENTS(decoder_plugins) - 1,
};
/** which plugins have been initialized successfully? */
-static bool decoder_plugins_enabled[num_decoder_plugins];
+bool decoder_plugins_enabled[num_decoder_plugins];
-const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix, unsigned int next)
+static unsigned
+decoder_plugin_index(const struct decoder_plugin *plugin)
{
- static unsigned i = num_decoder_plugins;
+ unsigned i = 0;
+ while (decoder_plugins[i] != plugin)
+ ++i;
+
+ return i;
+}
+
+static unsigned
+decoder_plugin_next_index(const struct decoder_plugin *plugin)
+{
+ return plugin == 0
+ ? 0 /* start with first plugin */
+ : decoder_plugin_index(plugin) + 1;
+}
+
+const struct decoder_plugin *
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin)
+{
if (suffix == NULL)
return NULL;
- if (!next)
- i = 0;
- for (; i < num_decoder_plugins; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
+ for (unsigned i = decoder_plugin_next_index(plugin);
+ decoder_plugins[i] != NULL; ++i) {
+ plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->suffixes, suffix)) {
- ++i;
+ decoder_plugin_supports_suffix(plugin, suffix))
return plugin;
- }
}
return NULL;
@@ -130,10 +154,10 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
if (!next)
i = 0;
- for (; i < num_decoder_plugins; ++i) {
+ for (; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->mime_types, mimeType)) {
+ decoder_plugin_supports_mime_type(plugin, mimeType)) {
++i;
return plugin;
}
@@ -145,7 +169,7 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next)
const struct decoder_plugin *
decoder_plugin_from_name(const char *name)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i] &&
strcmp(plugin->name, name) == 0)
@@ -155,27 +179,6 @@ decoder_plugin_from_name(const char *name)
return NULL;
}
-void decoder_plugin_print_all_decoders(FILE * fp)
-{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
- const struct decoder_plugin *plugin = decoder_plugins[i];
- const char *const*suffixes;
-
- if (!decoder_plugins_enabled[i])
- continue;
-
- fprintf(fp, "[%s]", plugin->name);
-
- for (suffixes = plugin->suffixes;
- suffixes != NULL && *suffixes != NULL;
- ++suffixes) {
- fprintf(fp, " %s", *suffixes);
- }
-
- fprintf(fp, "\n");
- }
-}
-
/**
* Find the "decoder" configuration block for the specified plugin.
*
@@ -203,7 +206,7 @@ decoder_plugin_config(const char *plugin_name)
void decoder_plugin_init_all(void)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
const struct config_param *param =
decoder_plugin_config(plugin->name);
@@ -219,7 +222,7 @@ void decoder_plugin_init_all(void)
void decoder_plugin_deinit_all(void)
{
- for (unsigned i = 0; i < num_decoder_plugins; ++i) {
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
const struct decoder_plugin *plugin = decoder_plugins[i];
if (decoder_plugins_enabled[i])
diff --git a/src/decoder_list.h b/src/decoder_list.h
index 23788189c..a5fe6b99f 100644
--- a/src/decoder_list.h
+++ b/src/decoder_list.h
@@ -20,14 +20,25 @@
#ifndef MPD_DECODER_LIST_H
#define MPD_DECODER_LIST_H
-#include <stdio.h>
+#include <stdbool.h>
struct decoder_plugin;
+extern const struct decoder_plugin *const decoder_plugins[];
+extern bool decoder_plugins_enabled[];
+
/* interface for using plugins */
+/**
+ * Find the next enabled decoder plugin which supports the specified suffix.
+ *
+ * @param suffix the file name suffix
+ * @param plugin the previous plugin, or NULL to find the first plugin
+ * @return a plugin, or NULL if none matches
+ */
const struct decoder_plugin *
-decoder_plugin_from_suffix(const char *suffix, unsigned int next);
+decoder_plugin_from_suffix(const char *suffix,
+ const struct decoder_plugin *plugin);
const struct decoder_plugin *
decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
@@ -35,8 +46,6 @@ decoder_plugin_from_mime_type(const char *mimeType, unsigned int next);
const struct decoder_plugin *
decoder_plugin_from_name(const char *name);
-void decoder_plugin_print_all_decoders(FILE * fp);
-
/* this is where we "load" all the "plugins" ;-) */
void decoder_plugin_init_all(void);
diff --git a/src/decoder_plugin.c b/src/decoder_plugin.c
new file mode 100644
index 000000000..b5966ff8d
--- /dev/null
+++ b/src/decoder_plugin.c
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "decoder_plugin.h"
+#include "utils.h"
+
+#include <assert.h>
+
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix)
+{
+ assert(plugin != NULL);
+ assert(suffix != NULL);
+
+ return plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix);
+
+}
+
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type)
+{
+ assert(plugin != NULL);
+ assert(mime_type != NULL);
+
+ return plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, mime_type);
+}
diff --git a/src/decoder_plugin.h b/src/decoder_plugin.h
index 66501a0a1..a078540a6 100644
--- a/src/decoder_plugin.h
+++ b/src/decoder_plugin.h
@@ -161,4 +161,18 @@ decoder_plugin_container_scan( const struct decoder_plugin *plugin,
return plugin->container_scan(pathname, tnum);
}
+/**
+ * Does the plugin announce the specified file name suffix?
+ */
+bool
+decoder_plugin_supports_suffix(const struct decoder_plugin *plugin,
+ const char *suffix);
+
+/**
+ * Does the plugin announce the specified MIME type?
+ */
+bool
+decoder_plugin_supports_mime_type(const struct decoder_plugin *plugin,
+ const char *mime_type);
+
#endif
diff --git a/src/decoder_print.c b/src/decoder_print.c
new file mode 100644
index 000000000..5dbb32803
--- /dev/null
+++ b/src/decoder_print.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "decoder_print.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "client.h"
+
+#include <assert.h>
+
+static void
+decoder_plugin_print(struct client *client,
+ const struct decoder_plugin *plugin)
+{
+ const char *const*p;
+
+ assert(plugin != NULL);
+ assert(plugin->name != NULL);
+
+ client_printf(client, "plugin: %s\n", plugin->name);
+
+ if (plugin->suffixes != NULL)
+ for (p = plugin->suffixes; *p != NULL; ++p)
+ client_printf(client, "suffix: %s\n", *p);
+
+ if (plugin->mime_types != NULL)
+ for (p = plugin->mime_types; *p != NULL; ++p)
+ client_printf(client, "mime_type: %s\n", *p);
+}
+
+void
+decoder_list_print(struct client *client)
+{
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i)
+ if (decoder_plugins_enabled[i])
+ decoder_plugin_print(client, decoder_plugins[i]);
+}
diff --git a/src/decoder_print.h b/src/decoder_print.h
new file mode 100644
index 000000000..6ba5dd081
--- /dev/null
+++ b/src/decoder_print.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_DECODER_PRINT_H
+#define MPD_DECODER_PRINT_H
+
+struct client;
+
+void
+decoder_list_print(struct client *client);
+
+#endif
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index d6ff058ec..9c6f3aab8 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "decoder_thread.h"
#include "decoder_control.h"
#include "decoder_internal.h"
@@ -35,6 +36,63 @@
#include <unistd.h>
+static enum decoder_command
+decoder_lock_get_command(struct decoder_control *dc)
+{
+ enum decoder_command command;
+
+ decoder_lock(dc);
+ command = dc->command;
+ decoder_unlock(dc);
+
+ return command;
+}
+
+/**
+ * Opens the input stream with input_stream_open(), and waits until
+ * the stream gets ready. If a decoder STOP command is received
+ * during that, it cancels the operation (but does not close the
+ * stream).
+ *
+ * Unlock the decoder before calling this function.
+ *
+ * @return true on success of if #DECODE_COMMAND_STOP is received,
+ * false on error
+ */
+static bool
+decoder_input_stream_open(struct decoder_control *dc,
+ struct input_stream *is, const char *uri)
+{
+ GError *error = NULL;
+
+ if (!input_stream_open(is, uri, &error)) {
+ if (error != NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ }
+
+ return false;
+ }
+
+ /* wait for the input stream to become ready; its metadata
+ will be available then */
+
+ while (!is->ready &&
+ decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) {
+ int ret;
+
+ ret = input_stream_buffer(is, &error);
+ if (ret < 0) {
+ input_stream_close(is);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+ }
+
+ return true;
+}
+
static bool
decoder_stream_decode(const struct decoder_plugin *plugin,
struct decoder *decoder,
@@ -47,17 +105,24 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
assert(decoder->decoder_tag == NULL);
assert(input_stream != NULL);
assert(input_stream->ready);
- assert(dc.state == DECODE_STATE_START);
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ if (decoder->dc->command == DECODE_COMMAND_STOP)
+ return true;
+
+ decoder_unlock(decoder->dc);
/* rewind the stream, so each plugin gets a fresh start */
- input_stream_seek(input_stream, 0, SEEK_SET);
+ input_stream_seek(input_stream, 0, SEEK_SET, NULL);
decoder_plugin_stream_decode(plugin, decoder, input_stream);
- assert(dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ decoder_lock(decoder->dc);
+
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
- return dc.state != DECODE_STATE_START;
+ return decoder->dc->state != DECODE_STATE_START;
}
static bool
@@ -70,150 +135,201 @@ decoder_file_decode(const struct decoder_plugin *plugin,
assert(decoder->stream_tag == NULL);
assert(decoder->decoder_tag == NULL);
assert(path != NULL);
- assert(path[0] == '/');
- assert(dc.state == DECODE_STATE_START);
+ assert(g_path_is_absolute(path));
+ assert(decoder->dc->state == DECODE_STATE_START);
+
+ if (decoder->dc->command == DECODE_COMMAND_STOP)
+ return true;
+
+ decoder_unlock(decoder->dc);
decoder_plugin_file_decode(plugin, decoder, path);
- assert(dc.state == DECODE_STATE_START ||
- dc.state == DECODE_STATE_DECODE);
+ decoder_lock(decoder->dc);
+
+ assert(decoder->dc->state == DECODE_STATE_START ||
+ decoder->dc->state == DECODE_STATE_DECODE);
- return dc.state != DECODE_STATE_START;
+ return decoder->dc->state != DECODE_STATE_START;
}
-static void decoder_run_song(const struct song *song, const char *uri)
+/**
+ * Try decoding a stream, using plugins matching the stream's MIME type.
+ */
+static bool
+decoder_run_stream_mime_type(struct decoder *decoder, struct input_stream *is)
{
- struct decoder decoder;
- int ret;
- bool close_instream = true;
- struct input_stream input_stream;
const struct decoder_plugin *plugin;
+ unsigned int next = 0;
- if (!input_stream_open(&input_stream, uri)) {
- dc.state = DECODE_STATE_ERROR;
- return;
- }
+ if (is->mime == NULL)
+ return false;
- decoder.seeking = false;
- decoder.song_tag = song->tag != NULL && song_is_file(song)
- ? tag_dup(song->tag) : NULL;
- decoder.stream_tag = NULL;
- decoder.decoder_tag = NULL;
- decoder.chunk = NULL;
+ while ((plugin = decoder_plugin_from_mime_type(is->mime, next++)))
+ if (plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is))
+ return true;
- dc.state = DECODE_STATE_START;
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ return false;
+}
- /* wait for the input stream to become ready; its metadata
- will be available then */
+/**
+ * Try decoding a stream, using plugins matching the stream's URI
+ * suffix.
+ */
+static bool
+decoder_run_stream_suffix(struct decoder *decoder, struct input_stream *is,
+ const char *uri)
+{
+ const char *suffix = uri_get_suffix(uri);
+ const struct decoder_plugin *plugin = NULL;
- while (!input_stream.ready) {
- if (dc.command == DECODE_COMMAND_STOP) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_STOP;
- return;
- }
+ if (suffix == NULL)
+ return false;
- ret = input_stream_buffer(&input_stream);
- if (ret < 0) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_ERROR;
- return;
- }
- }
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL)
+ if (plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is))
+ return true;
- if (dc.command == DECODE_COMMAND_STOP) {
- input_stream_close(&input_stream);
- dc.state = DECODE_STATE_STOP;
- return;
- }
+ return false;
+}
- pcm_convert_init(&decoder.conv_state);
+/**
+ * Try decoding a stream, using the fallback plugin.
+ */
+static bool
+decoder_run_stream_fallback(struct decoder *decoder, struct input_stream *is)
+{
+ const struct decoder_plugin *plugin;
+
+ plugin = decoder_plugin_from_name("mad");
+ return plugin != NULL && plugin->stream_decode != NULL &&
+ decoder_stream_decode(plugin, decoder, is);
+}
+
+/**
+ * Try decoding a stream.
+ */
+static bool
+decoder_run_stream(struct decoder *decoder, const char *uri)
+{
+ struct decoder_control *dc = decoder->dc;
+ struct input_stream input_stream;
+ bool success;
- ret = false;
- if (!song_is_file(song)) {
- unsigned int next = 0;
+ decoder_unlock(dc);
+ if (!decoder_input_stream_open(dc, &input_stream, uri)) {
+ decoder_lock(dc);
+ return false;
+ }
+
+ decoder_lock(dc);
+
+ success = dc->command == DECODE_COMMAND_STOP ||
/* first we try mime types: */
- while ((plugin = decoder_plugin_from_mime_type(input_stream.mime, next++))) {
- if (plugin->stream_decode == NULL)
+ decoder_run_stream_mime_type(decoder, &input_stream) ||
+ /* if that fails, try suffix matching the URL: */
+ decoder_run_stream_suffix(decoder, &input_stream, uri) ||
+ /* fallback to mp3: this is needed for bastard streams
+ that don't have a suffix or set the mimeType */
+ decoder_run_stream_fallback(decoder, &input_stream);
+
+ decoder_unlock(dc);
+ input_stream_close(&input_stream);
+ decoder_lock(dc);
+
+ return success;
+}
+
+/**
+ * Try decoding a file.
+ */
+static bool
+decoder_run_file(struct decoder *decoder, const char *path_fs)
+{
+ struct decoder_control *dc = decoder->dc;
+ const char *suffix = uri_get_suffix(path_fs);
+ const struct decoder_plugin *plugin = NULL;
+
+ if (suffix == NULL)
+ return false;
+
+ decoder_unlock(dc);
+
+ while ((plugin = decoder_plugin_from_suffix(suffix, plugin)) != NULL) {
+ if (plugin->file_decode != NULL) {
+ decoder_lock(dc);
+
+ if (decoder_file_decode(plugin, decoder, path_fs))
+ return true;
+
+ decoder_unlock(dc);
+ } else if (plugin->stream_decode != NULL) {
+ struct input_stream input_stream;
+ bool success;
+
+ if (!decoder_input_stream_open(dc, &input_stream,
+ path_fs))
continue;
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
- plugin = NULL;
- }
+ decoder_lock(dc);
- /* if that fails, try suffix matching the URL: */
- if (plugin == NULL) {
- const char *s = uri_get_suffix(uri);
- next = 0;
- while ((plugin = decoder_plugin_from_suffix(s, next++))) {
- if (plugin->stream_decode == NULL)
- continue;
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
-
- assert(dc.state == DECODE_STATE_START);
- plugin = NULL;
- }
- }
- /* fallback to mp3: */
- /* this is needed for bastard streams that don't have a suffix
- or set the mimeType */
- if (plugin == NULL) {
- /* we already know our mp3Plugin supports streams, no
- * need to check for stream{Types,DecodeFunc} */
- if ((plugin = decoder_plugin_from_name("mad"))) {
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- }
- }
- } else {
- unsigned int next = 0;
- const char *s = uri_get_suffix(uri);
- while ((plugin = decoder_plugin_from_suffix(s, next++))) {
- if (plugin->file_decode != NULL) {
- input_stream_close(&input_stream);
- close_instream = false;
- ret = decoder_file_decode(plugin,
- &decoder, uri);
- if (ret)
- break;
- } else if (plugin->stream_decode != NULL) {
- if (!close_instream) {
- /* the input_stream object has
- been closed before
- decoder_file_decode() -
- reopen it */
- if (input_stream_open(&input_stream, uri))
- close_instream = true;
- else
- continue;
- }
-
- ret = decoder_stream_decode(plugin, &decoder,
- &input_stream);
- if (ret)
- break;
+ success = decoder_stream_decode(plugin, decoder,
+ &input_stream);
+
+ decoder_unlock(dc);
+
+ input_stream_close(&input_stream);
+
+ if (success) {
+ decoder_lock(dc);
+ return true;
}
}
}
+ decoder_lock(dc);
+ return false;
+}
+
+static void
+decoder_run_song(struct decoder_control *dc,
+ const struct song *song, const char *uri)
+{
+ struct decoder decoder = {
+ .dc = dc,
+ };
+ int ret;
+
+ decoder.timestamp = 0.0;
+ decoder.seeking = false;
+ decoder.song_tag = song->tag != NULL && song_is_file(song)
+ ? tag_dup(song->tag) : NULL;
+ decoder.stream_tag = NULL;
+ decoder.decoder_tag = NULL;
+ decoder.chunk = NULL;
+
+ dc->state = DECODE_STATE_START;
+ dc->command = DECODE_COMMAND_NONE;
+
+ player_signal();
+
+ pcm_convert_init(&decoder.conv_state);
+
+ ret = song_is_file(song)
+ ? decoder_run_file(&decoder, uri)
+ : decoder_run_stream(&decoder, uri);
+
+ decoder_unlock(dc);
+
pcm_convert_deinit(&decoder.conv_state);
/* flush the last chunk */
if (decoder.chunk != NULL)
decoder_flush_chunk(&decoder);
- if (close_instream)
- input_stream_close(&input_stream);
-
if (decoder.song_tag != NULL)
tag_free(decoder.song_tag);
@@ -223,66 +339,82 @@ static void decoder_run_song(const struct song *song, const char *uri)
if (decoder.decoder_tag != NULL)
tag_free(decoder.decoder_tag);
- dc.state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
+ decoder_lock(dc);
+
+ dc->state = ret ? DECODE_STATE_STOP : DECODE_STATE_ERROR;
}
-static void decoder_run(void)
+static void
+decoder_run(struct decoder_control *dc)
{
- struct song *song = dc.next_song;
+ const struct song *song = dc->song;
char *uri;
+ assert(song != NULL);
+
if (song_is_file(song))
uri = map_song_fs(song);
else
uri = song_get_uri(song);
if (uri == NULL) {
- dc.state = DECODE_STATE_ERROR;
+ dc->state = DECODE_STATE_ERROR;
return;
}
- dc.current_song = dc.next_song; /* NEED LOCK */
- decoder_run_song(song, uri);
+ decoder_run_song(dc, song, uri);
g_free(uri);
}
-static gpointer decoder_task(G_GNUC_UNUSED gpointer arg)
+static gpointer
+decoder_task(gpointer arg)
{
+ struct decoder_control *dc = arg;
+
+ decoder_lock(dc);
+
do {
- assert(dc.state == DECODE_STATE_STOP ||
- dc.state == DECODE_STATE_ERROR);
+ assert(dc->state == DECODE_STATE_STOP ||
+ dc->state == DECODE_STATE_ERROR);
- switch (dc.command) {
+ switch (dc->command) {
case DECODE_COMMAND_START:
case DECODE_COMMAND_SEEK:
- decoder_run();
+ decoder_run(dc);
+
+ dc->command = DECODE_COMMAND_NONE;
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ player_signal();
break;
case DECODE_COMMAND_STOP:
- dc.command = DECODE_COMMAND_NONE;
- notify_signal(&pc.notify);
+ dc->command = DECODE_COMMAND_NONE;
+
+ player_signal();
break;
case DECODE_COMMAND_NONE:
- notify_wait(&dc.notify);
+ decoder_wait(dc);
break;
}
- } while (dc.command != DECODE_COMMAND_NONE || !dc.quit);
+ } while (dc->command != DECODE_COMMAND_NONE || !dc->quit);
+
+ decoder_unlock(dc);
return NULL;
}
-void decoder_thread_start(void)
+void
+decoder_thread_start(struct decoder_control *dc)
{
GError *e = NULL;
- assert(dc.thread == NULL);
+ assert(dc->thread == NULL);
+
+ dc->quit = false;
- dc.thread = g_thread_create(decoder_task, NULL, true, &e);
- if (dc.thread == NULL)
+ dc->thread = g_thread_create(decoder_task, dc, true, &e);
+ if (dc->thread == NULL)
g_error("Failed to spawn decoder task: %s", e->message);
}
diff --git a/src/decoder_thread.h b/src/decoder_thread.h
index 50ed7116e..a25564b87 100644
--- a/src/decoder_thread.h
+++ b/src/decoder_thread.h
@@ -20,6 +20,9 @@
#ifndef MPD_DECODER_THREAD_H
#define MPD_DECODER_THREAD_H
-void decoder_thread_start(void);
+struct decoder_control;
+
+void
+decoder_thread_start(struct decoder_control *dc);
#endif