diff options
Diffstat (limited to '')
29 files changed, 1674 insertions, 506 deletions
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c index e096750f3..2eaeb2449 100644 --- a/src/decoder/_flac_common.c +++ b/src/decoder/_flac_common.c @@ -170,11 +170,11 @@ flac_parse_comment(struct tag *tag, const char *char_tnum, assert(tag != NULL); if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY, - TAG_ITEM_TRACK, char_tnum) || + TAG_TRACK, char_tnum) || flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY, - TAG_ITEM_DISC, char_tnum) || + TAG_DISC, char_tnum) || flac_copy_comment(tag, entry, "album artist", - TAG_ITEM_ALBUM_ARTIST, char_tnum)) + TAG_ALBUM_ARTIST, char_tnum)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) @@ -201,9 +201,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block, switch (block->type) { case FLAC__METADATA_TYPE_STREAMINFO: - data->audio_format.bits = (int8_t)si->bits_per_sample; - data->audio_format.sample_rate = si->sample_rate; - data->audio_format.channels = (int8_t)si->channels; + audio_format_init(&data->audio_format, si->sample_rate, + si->bits_per_sample, si->channels); data->total_time = ((float)si->total_samples) / (si->sample_rate); break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: @@ -291,14 +290,14 @@ flac_convert_8(int8_t *dest, *dest++ = buf[c_chan][position]; } -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) +static void +flac_convert(void *dest, + unsigned int num_channels, unsigned sample_format, + const FLAC__int32 *const buf[], + unsigned int position, unsigned int end) { - switch (bytes_per_sample) { - case 2: + switch (sample_format) { + case 16: if (num_channels == 2) flac_convert_stereo16((int16_t*)dest, buf, position, end); @@ -307,12 +306,13 @@ static void flac_convert(unsigned char *dest, position, end); break; - case 4: + case 24: + case 32: flac_convert_32((int32_t*)dest, num_channels, buf, position, end); break; - case 1: + case 8: flac_convert_8((int8_t*)dest, num_channels, buf, position, end); break; @@ -324,6 +324,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, const FLAC__int32 *const buf[]) { unsigned int c_samp; + const unsigned sample_format = data->audio_format.bits; const unsigned int num_channels = frame->header.channels; const unsigned int bytes_per_sample = audio_format_sample_size(&data->audio_format); @@ -333,11 +334,6 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, 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 */ - 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; @@ -345,7 +341,7 @@ flac_common_write(struct flac_data *data, const FLAC__Frame * frame, num_samples = max_samples; flac_convert(data->chunk, - num_channels, bytes_per_sample, buf, + num_channels, sample_format, buf, c_samp, c_samp + num_samples); cmd = decoder_data(data->decoder, data->input_stream, diff --git a/src/decoder/audiofile_plugin.c b/src/decoder/audiofile_plugin.c index f66d90dc1..b4959f6c2 100644 --- a/src/decoder/audiofile_plugin.c +++ b/src/decoder/audiofile_plugin.c @@ -136,11 +136,9 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is) afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, AF_SAMPFMT_TWOSCOMP, bits); afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); - audio_format.bits = (uint8_t)bits; - audio_format.sample_rate = - (unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK); - audio_format.channels = - (uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); + + audio_format_init(&audio_format, afGetRate(af_fp, AF_DEFAULT_TRACK), + bits, afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK)); if (!audio_format_valid(&audio_format)) { g_warning("Invalid audio format: %u:%u:%u\n", diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_plugin.c index 7b2806a4c..516f741c7 100644 --- a/src/decoder/faad_plugin.c +++ b/src/decoder/faad_plugin.c @@ -266,11 +266,7 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer, decoder_buffer_consume(buffer, nbytes); - *audio_format = (struct audio_format){ - .bits = 16, - .channels = channels, - .sample_rate = sample_rate, - }; + audio_format_init(audio_format, sample_rate, 16, channels); return true; } diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c index 2f7ed6d1f..63f0f7ece 100644 --- a/src/decoder/ffmpeg_plugin.c +++ b/src/decoder/ffmpeg_plugin.c @@ -267,6 +267,7 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx) struct audio_format audio_format; enum decoder_command cmd; int total_time; + uint8_t bits; total_time = 0; @@ -275,13 +276,13 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx) } #if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0) - audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt); + bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt); #else /* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */ - audio_format.bits = (uint8_t) 16; + bits = (uint8_t) 16; #endif - audio_format.sample_rate = (unsigned int)codec_context->sample_rate; - audio_format.channels = codec_context->channels; + audio_format_init(&audio_format, codec_context->sample_rate, bits, + codec_context->channels); if (!audio_format_valid(&audio_format)) { g_warning("Invalid audio format: %u:%u:%u\n", @@ -342,8 +343,9 @@ static bool ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m, enum tag_type type, const char *name) { - AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0); - if (mt != NULL) + AVMetadataTag *mt = NULL; + + while ((mt = av_metadata_get(m, name, mt, 0)) != NULL) tag_add_item(tag, type, mt->value); return mt != NULL; } @@ -361,35 +363,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 diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c index 1d5d48d09..9692ba49f 100644 --- a/src/decoder/flac_plugin.c +++ b/src/decoder/flac_plugin.c @@ -300,6 +300,8 @@ flac_cue_tag_load(const char *file) FLAC__uint64 track_time = 0; #ifdef HAVE_CUE /* libcue */ FLAC__StreamMetadata* vc; + char* cs_filename; + FILE* cs_file; #endif /* libcue */ FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO); FLAC__StreamMetadata* cs; @@ -329,16 +331,25 @@ flac_cue_tag_load(const char *file) FLAC__metadata_object_delete(vc); } + + if (tag == NULL) { + cs_filename = g_strconcat(file, ".cue", NULL); + + cs_file = fopen(cs_filename, "rt"); + g_free(cs_filename); + + if (cs_file != NULL) { + tag = cue_tag_file(cs_file, tnum); + fclose(cs_file); + } + } #endif /* libcue */ if (tag == NULL) tag = flac_tag_load(file, char_tnum); - if (char_tnum != NULL) - { - tag_add_item( tag, - TAG_ITEM_TRACK, - char_tnum); + if (char_tnum != NULL) { + tag_add_item(tag, TAG_TRACK, char_tnum); g_free(char_tnum); } diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c index 1ef7183fa..27ddf655d 100644 --- a/src/decoder/mad_plugin.c +++ b/src/decoder/mad_plugin.c @@ -779,10 +779,10 @@ mp3_frame_duration(const struct mad_frame *frame) MAD_UNITS_MILLISECONDS) / 1000.0; } -static off_t +static goffset mp3_this_frame_offset(const struct mp3_data *data) { - off_t offset = data->input_stream->offset; + goffset offset = data->input_stream->offset; if (data->stream.this_frame != NULL) offset -= data->stream.bufend - data->stream.this_frame; @@ -792,7 +792,7 @@ mp3_this_frame_offset(const struct mp3_data *data) return offset; } -static off_t +static goffset mp3_rest_including_this_frame(const struct mp3_data *data) { return data->input_stream->size - mp3_this_frame_offset(data); @@ -804,7 +804,7 @@ mp3_rest_including_this_frame(const struct mp3_data *data) static void mp3_filesize_to_song_length(struct mp3_data *data) { - off_t rest = mp3_rest_including_this_frame(data); + goffset rest = mp3_rest_including_this_frame(data); if (rest > 0) { float frame_duration = mp3_frame_duration(&data->frame); @@ -1170,13 +1170,6 @@ mp3_read(struct mp3_data *data, struct replay_gain_info **replay_gain_info_r) return ret != DECODE_BREAK; } -static void mp3_audio_format(struct mp3_data *data, struct audio_format *af) -{ - af->bits = 24; - af->sample_rate = (data->frame).header.samplerate; - af->channels = MAD_NCHANNELS(&(data->frame).header); -} - static void mp3_decode(struct decoder *decoder, struct input_stream *input_stream) { @@ -1192,7 +1185,8 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream) return; } - mp3_audio_format(&data, &audio_format); + audio_format_init(&audio_format, data.frame.header.samplerate, 24, + MAD_NCHANNELS(&data.frame.header)); decoder_initialized(decoder, &audio_format, data.input_stream->seekable, data.total_time); diff --git a/src/decoder/mikmod_plugin.c b/src/decoder/mikmod_plugin.c index 065c34319..850232f34 100644 --- a/src/decoder/mikmod_plugin.c +++ b/src/decoder/mikmod_plugin.c @@ -175,9 +175,7 @@ mod_decode(struct decoder *decoder, const char *path) return; } - audio_format.bits = 16; - audio_format.sample_rate = 44100; - audio_format.channels = 2; + audio_format_init(&audio_format, 44100, 16, 2); secPerByte = 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * @@ -222,7 +220,7 @@ static struct tag *modTagDup(const char *file) title = g_strdup(Player_LoadTitle(path2)); g_free(path2); if (title) - tag_add_item(ret, TAG_ITEM_TITLE, title); + tag_add_item(ret, TAG_TITLE, title); return ret; } diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c index f636f2fa6..301c80674 100644 --- a/src/decoder/modplug_plugin.c +++ b/src/decoder/modplug_plugin.c @@ -121,9 +121,7 @@ mod_decode(struct decoder *decoder, struct input_stream *is) return; } - audio_format.bits = 16; - audio_format.sample_rate = 44100; - audio_format.channels = 2; + audio_format_init(&audio_format, 44100, 16, 2); sec_perbyte = 1.0 / ((audio_format.bits * audio_format.channels / 8.0) * @@ -186,11 +184,11 @@ static struct tag *mod_tagdup(const char *file) return NULL; } ret = tag_new(); - ret->time = 0; + ret->time = ModPlug_GetLength(f) / 1000; title = g_strdup(ModPlug_GetName(f)); if (title) - tag_add_item(ret, TAG_ITEM_TITLE, title); + tag_add_item(ret, TAG_TITLE, title); g_free(title); ModPlug_Unload(f); diff --git a/src/decoder/mp4ff_plugin.c b/src/decoder/mp4ff_plugin.c index cf9382904..745d1e619 100644 --- a/src/decoder/mp4ff_plugin.c +++ b/src/decoder/mp4ff_plugin.c @@ -131,11 +131,7 @@ mp4_faad_new(mp4ff_t *mp4fh, int *track_r, struct audio_format *audio_format) } *track_r = track; - *audio_format = (struct audio_format){ - .bits = 16, - .channels = channels, - .sample_rate = sample_rate, - }; + audio_format_init(audio_format, sample_rate, 16, channels); if (!audio_format_valid(audio_format)) { g_warning("Invalid audio format: %u:%u:%u\n", @@ -395,22 +391,22 @@ mp4_tag_dup(const char *file) mp4ff_meta_get_by_index(mp4fh, i, &item, &value); if (0 == g_ascii_strcasecmp("artist", item)) { - tag_add_item(ret, TAG_ITEM_ARTIST, value); + tag_add_item(ret, TAG_ARTIST, value); } else if (0 == g_ascii_strcasecmp("title", item)) { - tag_add_item(ret, TAG_ITEM_TITLE, value); + tag_add_item(ret, TAG_TITLE, value); } else if (0 == g_ascii_strcasecmp("album", item)) { - tag_add_item(ret, TAG_ITEM_ALBUM, value); + tag_add_item(ret, TAG_ALBUM, value); } else if (0 == g_ascii_strcasecmp("track", item)) { - tag_add_item(ret, TAG_ITEM_TRACK, value); + tag_add_item(ret, TAG_TRACK, value); } else if (0 == g_ascii_strcasecmp("disc", item)) { /* Is that the correct id? */ - tag_add_item(ret, TAG_ITEM_DISC, value); + tag_add_item(ret, TAG_DISC, value); } else if (0 == g_ascii_strcasecmp("genre", item)) { - tag_add_item(ret, TAG_ITEM_GENRE, value); + tag_add_item(ret, TAG_GENRE, value); } else if (0 == g_ascii_strcasecmp("date", item)) { - tag_add_item(ret, TAG_ITEM_DATE, value); + tag_add_item(ret, TAG_DATE, value); } else if (0 == g_ascii_strcasecmp("writer", item)) { - tag_add_item(ret, TAG_ITEM_COMPOSER, value); + tag_add_item(ret, TAG_COMPOSER, value); } free(item); diff --git a/src/decoder/mpcdec_plugin.c b/src/decoder/mpcdec_plugin.c index 26349f93a..a684da104 100644 --- a/src/decoder/mpcdec_plugin.c +++ b/src/decoder/mpcdec_plugin.c @@ -193,9 +193,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is) mpc_demux_get_info(demux, &info); #endif - audio_format.bits = 24; - audio_format.channels = info.channels; - audio_format.sample_rate = info.sample_freq; + audio_format_init(&audio_format, info.sample_freq, 24, info.channels); if (!audio_format_valid(&audio_format)) { #ifndef MPC_IS_OLD_API diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c new file mode 100644 index 000000000..20d9c4a54 --- /dev/null +++ b/src/decoder/mpg123_decoder_plugin.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" /* must be first for large file support */ +#include "decoder_api.h" + +#include <glib.h> + +#include <mpg123.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "mpg123" + +static bool +mpd_mpg123_init(G_GNUC_UNUSED const struct config_param *param) +{ + mpg123_init(); + + return true; +} + +static void +mpd_mpg123_finish(void) +{ + mpg123_exit(); +} + +/** + * Opens a file with an existing #mpg123_handle. + * + * @param handle a handle which was created before; on error, this + * function will not free it + * @param audio_format this parameter is filled after successful + * return + * @return true on success + */ +static bool +mpd_mpg123_open(mpg123_handle *handle, const char *path_fs, + struct audio_format *audio_format) +{ + char *path_dup; + int error; + int channels, encoding; + long rate; + + /* mpg123_open() wants a writable string :-( */ + path_dup = g_strdup(path_fs); + + error = mpg123_open(handle, path_dup); + g_free(path_dup); + if (error != MPG123_OK) { + g_warning("libmpg123 failed to open %s: %s", + path_fs, mpg123_plain_strerror(error)); + return false; + } + + /* obtain the audio format */ + + error = mpg123_getformat(handle, &rate, &channels, &encoding); + if (error != MPG123_OK) { + g_warning("mpg123_getformat() failed: %s", + mpg123_plain_strerror(error)); + return false; + } + + if (encoding != MPG123_ENC_SIGNED_16) { + /* other formats not yet implemented */ + g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding); + return false; + } + + audio_format_init(audio_format, rate, 16, channels); + if (!audio_format_valid(audio_format)) { + g_warning("invalid audio format"); + return false; + } + + return true; +} + +static void +mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs) +{ + struct audio_format audio_format; + mpg123_handle *handle; + int error; + off_t num_samples, position; + enum decoder_command cmd; + + /* open the file */ + + handle = mpg123_new(NULL, &error); + if (handle == NULL) { + g_warning("mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return; + } + + if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { + mpg123_delete(handle); + return; + } + + num_samples = mpg123_length(handle); + + /* tell MPD core we're ready */ + + decoder_initialized(decoder, &audio_format, false, + (float)num_samples / + (float)audio_format.sample_rate); + + /* the decoder main loop */ + + do { + unsigned char buffer[8192]; + size_t nbytes; + + position = mpg123_tell(handle); + + /* decode */ + + error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes); + if (error != MPG123_OK) { + if (error != MPG123_DONE) + g_warning("mpg123_read() failed: %s", + mpg123_plain_strerror(error)); + break; + } + + /* send to MPD */ + + cmd = decoder_data(decoder, NULL, buffer, nbytes, + (float)position / + (float)audio_format.sample_rate, + 0, NULL); + + /* seeking not yet implemented */ + } while (cmd == DECODE_COMMAND_NONE); + + /* cleanup */ + + mpg123_delete(handle); +} + +static struct tag * +mpd_mpg123_tag_dup(const char *path_fs) +{ + struct audio_format audio_format; + mpg123_handle *handle; + int error; + off_t num_samples; + struct tag *tag; + + handle = mpg123_new(NULL, &error); + if (handle == NULL) { + g_warning("mpg123_new() failed: %s", + mpg123_plain_strerror(error)); + return NULL; + } + + if (!mpd_mpg123_open(handle, path_fs, &audio_format)) { + mpg123_delete(handle); + return NULL; + } + + num_samples = mpg123_length(handle); + if (num_samples <= 0) { + mpg123_delete(handle); + return NULL; + } + + tag = tag_new(); + + tag->time = num_samples / audio_format.sample_rate; + + /* ID3 tag support not yet implemented */ + + mpg123_delete(handle); + return tag; +} + +static const char *const mpg123_suffixes[] = { + "mp3", + NULL +}; + +const struct decoder_plugin mpg123_decoder_plugin = { + .name = "mpg123", + .init = mpd_mpg123_init, + .finish = mpd_mpg123_finish, + .file_decode = mpd_mpg123_file_decode, + /* streaming not yet implemented */ + .tag_dup = mpd_mpg123_tag_dup, + .suffixes = mpg123_suffixes, +}; diff --git a/src/decoder/sidplay_plugin.cxx b/src/decoder/sidplay_plugin.cxx index c62e6b4b6..079b178fb 100644 --- a/src/decoder/sidplay_plugin.cxx +++ b/src/decoder/sidplay_plugin.cxx @@ -21,14 +21,180 @@ extern "C" { #include "../decoder_api.h" } +#include <errno.h> +#include <stdlib.h> #include <glib.h> #include <sidplay/sidplay2.h> #include <sidplay/builders/resid.h> +#include <sidplay/utils/SidTuneMod.h> #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "sidplay" +#define SUBTUNE_PREFIX "tune_" + +static GPatternSpec *path_with_subtune; +static const char *songlength_file; +static GKeyFile *songlength_database; + +static bool all_files_are_containers; +static unsigned default_songlength; + +static bool filter_setting; + +static GKeyFile * +sidplay_load_songlength_db(const char *path) +{ + GError *error = NULL; + gchar *data; + gsize size; + + if (!g_file_get_contents(path, &data, &size, &error)) { + g_warning("unable to read songlengths file %s: %s", + path, error->message); + g_error_free(error); + return NULL; + } + + /* replace any ; comment characters with # */ + for (gsize i = 0; i < size; i++) + if (data[i] == ';') + data[i] = '#'; + + GKeyFile *db = g_key_file_new(); + bool success = g_key_file_load_from_data(db, data, size, + G_KEY_FILE_NONE, &error); + g_free(data); + if (!success) { + g_warning("unable to parse songlengths file %s: %s", + path, error->message); + g_error_free(error); + g_key_file_free(db); + return NULL; + } + + g_key_file_set_list_separator(db, ' '); + return db; +} + +static bool +sidplay_init(const struct config_param *param) +{ + /* read the songlengths database file */ + songlength_file=config_get_block_string(param, + "songlength_database", NULL); + if (songlength_file != NULL) + songlength_database = sidplay_load_songlength_db(songlength_file); + + default_songlength=config_get_block_unsigned(param, + "default_songlength", 0); + + all_files_are_containers=config_get_block_bool(param, + "all_files_are_containers", true); + + path_with_subtune=g_pattern_spec_new( + "*/" SUBTUNE_PREFIX "???.sid"); + + filter_setting=config_get_block_bool(param, "filter", true); + + return true; +} + +void +sidplay_finish() +{ + g_pattern_spec_free(path_with_subtune); + + if(songlength_database) + g_key_file_free(songlength_database); +} + +/** + * returns the file path stripped of any /tune_xxx.sid subtune + * suffix + */ +static char * +get_container_name(const char *path_fs) +{ + char *path_container=g_strdup(path_fs); + + if(!g_pattern_match(path_with_subtune, + strlen(path_container), path_container, NULL)) + return path_container; + + char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); + if(ptr) *ptr='\0'; + + return path_container; +} + +/** + * returns tune number from file.sid/tune_xxx.sid style path or 1 if + * no subtune is appended + */ +static int +get_song_num(const char *path_fs) +{ + if(g_pattern_match(path_with_subtune, + strlen(path_fs), path_fs, NULL)) { + char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); + if(!sub) return 1; + + sub+=strlen("/" SUBTUNE_PREFIX); + int song_num=strtol(sub, NULL, 10); + + if (errno == EINVAL) + return 1; + else + return song_num; + } else + return 1; +} + +/* get the song length in seconds */ +static int +get_song_length(const char *path_fs) +{ + if (songlength_database == NULL) + return -1; + + gchar *sid_file=get_container_name(path_fs); + SidTuneMod tune(sid_file); + g_free(sid_file); + if(!tune) { + g_warning("failed to load file for calculating md5 sum"); + return -1; + } + char md5sum[SIDTUNE_MD5_LENGTH+1]; + tune.createMD5(md5sum); + + int song_num=get_song_num(path_fs); + + gsize num_items; + gchar **values=g_key_file_get_string_list(songlength_database, + "Database", md5sum, &num_items, NULL); + if(!values || song_num>num_items) { + g_strfreev(values); + return -1; + } + + int minutes=strtol(values[song_num-1], NULL, 10); + if(errno==EINVAL) minutes=0; + + int seconds; + char *ptr=strchr(values[song_num-1], ':'); + if(ptr) { + seconds=strtol(ptr+1, NULL, 10); + if(errno==EINVAL) seconds=0; + } else + seconds=0; + + g_strfreev(values); + + return (minutes*60)+seconds; +} + static void sidplay_file_decode(struct decoder *decoder, const char *path_fs) { @@ -36,13 +202,19 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) /* load the tune */ - SidTune tune(path_fs, NULL, true); + char *path_container=get_container_name(path_fs); + SidTune tune(path_container, NULL, true); + g_free(path_container); if (!tune) { g_warning("failed to load file"); return; } - tune.selectSong(1); + int song_num=get_song_num(path_fs); + tune.selectSong(song_num); + + int song_len=get_song_length(path_fs); + if(song_len==-1) song_len=default_songlength; /* initialize the player */ @@ -67,7 +239,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) return; } - builder.filter(false); + builder.filter(filter_setting); if (!builder) { g_warning("ReSIDBuilder.filter() failed"); return; @@ -103,14 +275,14 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) /* initialize the MPD decoder */ struct audio_format audio_format; - audio_format.sample_rate = 48000; - audio_format.bits = 16; - audio_format.channels = 2; + audio_format_init(&audio_format, 48000, 16, 2); - decoder_initialized(decoder, &audio_format, false, -1); + decoder_initialized(decoder, &audio_format, true, (float)song_len); /* .. and play */ + float data_time=0; + int timebase=player.timebase(); enum decoder_command cmd; do { char buffer[4096]; @@ -121,29 +293,105 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs) break; cmd = decoder_data(decoder, NULL, buffer, nbytes, - 0, 0, NULL); - } while (cmd == DECODE_COMMAND_NONE); + data_time, 0, NULL); + + data_time=player.time()/timebase; + + if(cmd==DECODE_COMMAND_SEEK) { + int target_time=decoder_seek_where(decoder); + + /* can't rewind so return to zero and seek forward */ + if(target_time<data_time) { + player.stop(); + data_time=0; + } + + /* ignore data until target time is reached */ + while(data_time<target_time) { + nbytes=player.play(buffer, sizeof(buffer)); + if(nbytes==0) + break; + data_time=player.time()/timebase; + } + + decoder_command_finished(decoder); + } + + if(song_len && data_time>=(float)song_len) + break; + + } while (cmd != DECODE_COMMAND_STOP); } static struct tag * sidplay_tag_dup(const char *path_fs) { - SidTune tune(path_fs, NULL, true); + int song_num=get_song_num(path_fs); + char *path_container=get_container_name(path_fs); + + SidTune tune(path_container, NULL, true); + g_free(path_container); if (!tune) return NULL; const SidTuneInfo &info = tune.getInfo(); struct tag *tag = tag_new(); + /* title */ + const char *title; if (info.numberOfInfoStrings > 0 && info.infoString[0] != NULL) - tag_add_item(tag, TAG_ITEM_TITLE, info.infoString[0]); - + title=info.infoString[0]; + else + title=""; + + if(info.songs>1) { + char *tag_title=g_strdup_printf("%s (%d/%d)", + title, song_num, info.songs); + tag_add_item(tag, TAG_TITLE, tag_title); + g_free(tag_title); + } else + tag_add_item(tag, TAG_TITLE, title); + + /* artist */ if (info.numberOfInfoStrings > 1 && info.infoString[1] != NULL) - tag_add_item(tag, TAG_ITEM_ARTIST, info.infoString[1]); + tag_add_item(tag, TAG_ARTIST, info.infoString[1]); + + /* track */ + char *track=g_strdup_printf("%d", song_num); + tag_add_item(tag, TAG_TRACK, track); + g_free(track); + + /* time */ + int song_len=get_song_length(path_fs); + if(song_len!=-1) tag->time=song_len; return tag; } +static char * +sidplay_container_scan(const char *path_fs, const unsigned int tnum) +{ + SidTune tune(path_fs, NULL, true); + if (!tune) + return NULL; + + const SidTuneInfo &info=tune.getInfo(); + + /* Don't treat sids containing a single tune + as containers */ + if(!all_files_are_containers && info.songs<2) + return NULL; + + /* Construct container/tune path names, eg. + Delta.sid/tune_001.sid */ + if(tnum<=info.songs) { + char *subtune= g_strdup_printf( + SUBTUNE_PREFIX "%03u.sid", tnum); + return subtune; + } else + return NULL; +} + static const char *const sidplay_suffixes[] = { "sid", NULL @@ -152,12 +400,12 @@ static const char *const sidplay_suffixes[] = { extern const struct decoder_plugin sidplay_decoder_plugin; const struct decoder_plugin sidplay_decoder_plugin = { "sidplay", - NULL, /* init() */ - NULL, /* finish() */ + sidplay_init, + sidplay_finish, NULL, /* stream_decode() */ sidplay_file_decode, sidplay_tag_dup, - NULL, /* container_scan */ + sidplay_container_scan, sidplay_suffixes, NULL, /* mime_types */ }; diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c new file mode 100644 index 000000000..59993ef89 --- /dev/null +++ b/src/decoder/sndfile_decoder_plugin.c @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "decoder_api.h" + +#include <sndfile.h> + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "sndfile" + +static sf_count_t +sndfile_vio_get_filelen(void *user_data) +{ + const struct input_stream *is = user_data; + + return is->size; +} + +static sf_count_t +sndfile_vio_seek(sf_count_t offset, int whence, void *user_data) +{ + struct input_stream *is = user_data; + bool success; + + success = input_stream_seek(is, offset, whence); + if (!success) + return -1; + + return is->offset; +} + +static sf_count_t +sndfile_vio_read(void *ptr, sf_count_t count, void *user_data) +{ + struct input_stream *is = user_data; + size_t nbytes; + + nbytes = input_stream_read(is, ptr, count); + if (nbytes == 0 && is->error != 0) + return -1; + + return nbytes; +} + +static sf_count_t +sndfile_vio_write(G_GNUC_UNUSED const void *ptr, + G_GNUC_UNUSED sf_count_t count, + G_GNUC_UNUSED void *user_data) +{ + /* no writing! */ + return -1; +} + +static sf_count_t +sndfile_vio_tell(void *user_data) +{ + const struct input_stream *is = user_data; + + return is->offset; +} + +/** + * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a + * libsndfile stream. + */ +static SF_VIRTUAL_IO vio = { + .get_filelen = sndfile_vio_get_filelen, + .seek = sndfile_vio_seek, + .read = sndfile_vio_read, + .write = sndfile_vio_write, + .tell = sndfile_vio_tell, +}; + +/** + * Converts a frame number to a timestamp (in seconds). + */ +static float +frame_to_time(sf_count_t frame, const struct audio_format *audio_format) +{ + return (float)frame / (float)audio_format->sample_rate; +} + +/** + * Converts a timestamp (in seconds) to a frame number. + */ +static sf_count_t +time_to_frame(float t, const struct audio_format *audio_format) +{ + return (sf_count_t)(t * audio_format->sample_rate); +} + +static void +sndfile_stream_decode(struct decoder *decoder, struct input_stream *is) +{ + SNDFILE *sf; + SF_INFO info; + struct audio_format audio_format; + size_t frame_size; + sf_count_t read_frames, num_frames, position = 0; + int buffer[4096]; + enum decoder_command cmd; + + info.format = 0; + + sf = sf_open_virtual(&vio, SFM_READ, &info, is); + if (sf == NULL) { + g_warning("sf_open_virtual() failed"); + return; + } + + /* for now, always read 32 bit samples. Later, we could lower + MPD's CPU usage by reading 16 bit samples with + sf_readf_short() on low-quality source files. */ + audio_format_init(&audio_format, info.samplerate, 32, info.channels); + + if (!audio_format_valid(&audio_format)) { + g_warning("invalid audio format"); + return; + } + + decoder_initialized(decoder, &audio_format, info.seekable, + frame_to_time(info.frames, &audio_format)); + + frame_size = audio_format_frame_size(&audio_format); + read_frames = sizeof(buffer) / frame_size; + + do { + num_frames = sf_readf_int(sf, buffer, read_frames); + if (num_frames <= 0) + break; + + cmd = decoder_data(decoder, is, + buffer, num_frames * frame_size, + frame_to_time(position, &audio_format), + 0, NULL); + if (cmd == DECODE_COMMAND_SEEK) { + sf_count_t c = + time_to_frame(decoder_seek_where(decoder), + &audio_format); + c = sf_seek(sf, c, SEEK_SET); + if (c < 0) + decoder_seek_error(decoder); + else + decoder_command_finished(decoder); + cmd = DECODE_COMMAND_NONE; + } + } while (cmd == DECODE_COMMAND_NONE); + + sf_close(sf); +} + +static struct tag * +sndfile_tag_dup(const char *path_fs) +{ + SNDFILE *sf; + SF_INFO info; + struct tag *tag; + const char *p; + + info.format = 0; + + sf = sf_open(path_fs, SFM_READ, &info); + if (sf == NULL) + return NULL; + + if (!audio_valid_sample_rate(info.samplerate)) { + sf_close(sf); + g_warning("Invalid sample rate in %s\n", path_fs); + return NULL; + } + + tag = tag_new(); + tag->time = info.frames / info.samplerate; + + p = sf_get_string(sf, SF_STR_TITLE); + if (p != NULL) + tag_add_item(tag, TAG_TITLE, p); + + p = sf_get_string(sf, SF_STR_ARTIST); + if (p != NULL) + tag_add_item(tag, TAG_ARTIST, p); + + p = sf_get_string(sf, SF_STR_DATE); + if (p != NULL) + tag_add_item(tag, TAG_DATE, p); + + sf_close(sf); + + return tag; +} + +static const char *const sndfile_suffixes[] = { + "wav", "aiff", "aif", /* Microsoft / SGI / Apple */ + "au", "snd", /* Sun / DEC / NeXT */ + "paf", /* Paris Audio File */ + "iff", "svx", /* Commodore Amiga IFF / SVX */ + "sf", /* IRCAM */ + "voc", /* Creative */ + "w64", /* Soundforge */ + "pvf", /* Portable Voice Format */ + "xi", /* Fasttracker */ + "htk", /* HMM Tool Kit */ + "caf", /* Apple */ + "sd2", /* Sound Designer II */ + + /* libsndfile also supports FLAC and Ogg Vorbis, but only by + linking with libFLAC and libvorbis - we can do better, we + have native plugins for these libraries */ + + NULL +}; + +static const char *const sndfile_mime_types[] = { + "audio/x-wav", + "audio/x-aiff", + + /* what are the MIME types of the other supported formats? */ + + NULL +}; + +const struct decoder_plugin sndfile_decoder_plugin = { + .name = "sndfile", + .stream_decode = sndfile_stream_decode, + .tag_dup = sndfile_tag_dup, + .suffixes = sndfile_suffixes, + .mime_types = sndfile_mime_types, +}; diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c index d4f81e91f..2ac9c3bcb 100644..100755 --- a/src/decoder/vorbis_plugin.c +++ b/src/decoder/vorbis_plugin.c @@ -24,6 +24,7 @@ #include "uri.h" #ifndef HAVE_TREMOR +#define OV_EXCLUDE_STATIC_CALLBACKS #include <vorbis/vorbisfile.h> #else #include <tremor/ivorbisfile.h> @@ -176,11 +177,11 @@ vorbis_parse_comment(struct tag *tag, const char *comment) assert(tag != NULL); if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, - TAG_ITEM_TRACK) || + TAG_TRACK) || vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, - TAG_ITEM_DISC) || + TAG_DISC) || vorbis_copy_comment(tag, comment, "album artist", - TAG_ITEM_ALBUM_ARTIST)) + TAG_ALBUM_ARTIST)) return; for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) @@ -324,8 +325,7 @@ vorbis_stream_decode(struct decoder *decoder, vorbis_info *vi = ov_info(&vf, -1); struct replay_gain_info *new_rgi; - audio_format.channels = vi->channels; - audio_format.sample_rate = vi->rate; + audio_format_init(&audio_format, vi->rate, 16, vi->channels); if (!audio_format_valid(&audio_format)) { g_warning("Invalid audio format: %u:%u:%u\n", @@ -378,7 +378,7 @@ vorbis_tag_dup(const char *file) FILE *fp; OggVorbis_File vf; - fp = fopen(file, "r"); + fp = fopen(file, "rb"); if (!fp) { return NULL; } diff --git a/src/decoder/wavpack_plugin.c b/src/decoder/wavpack_plugin.c index 821536fb5..3e7c0cef7 100644 --- a/src/decoder/wavpack_plugin.c +++ b/src/decoder/wavpack_plugin.c @@ -41,17 +41,17 @@ static struct { const char *name; enum tag_type type; } tagtypes[] = { - { "artist", TAG_ITEM_ARTIST }, - { "album", TAG_ITEM_ALBUM }, - { "title", TAG_ITEM_TITLE }, - { "track", TAG_ITEM_TRACK }, - { "name", TAG_ITEM_NAME }, - { "genre", TAG_ITEM_GENRE }, - { "date", TAG_ITEM_DATE }, - { "composer", TAG_ITEM_COMPOSER }, - { "performer", TAG_ITEM_PERFORMER }, - { "comment", TAG_ITEM_COMMENT }, - { "disc", TAG_ITEM_DISC }, + { "artist", TAG_ARTIST }, + { "album", TAG_ALBUM }, + { "title", TAG_TITLE }, + { "track", TAG_TRACK }, + { "name", TAG_NAME }, + { "genre", TAG_GENRE }, + { "date", TAG_DATE }, + { "composer", TAG_COMPOSER }, + { "performer", TAG_PERFORMER }, + { "comment", TAG_COMMENT }, + { "disc", TAG_DISC }, }; /** A pointer type for format converter function. */ @@ -145,9 +145,9 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek, int bytes_per_sample, output_sample_size; int position; - audio_format.sample_rate = WavpackGetSampleRate(wpc); - audio_format.channels = WavpackGetReducedChannels(wpc); - audio_format.bits = WavpackGetBitsPerSample(wpc); + audio_format_init(&audio_format, WavpackGetSampleRate(wpc), + WavpackGetBitsPerSample(wpc), + WavpackGetReducedChannels(wpc)); /* round bitwidth to 8-bit units */ audio_format.bits = (audio_format.bits + 7) & (~7); diff --git a/src/decoder_api.c b/src/decoder_api.c index 2ece3bb98..2350396a9 100644 --- a/src/decoder_api.c +++ b/src/decoder_api.c @@ -37,12 +37,15 @@ #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; + + assert(dc->state == DECODE_STATE_START); + assert(dc->pipe != NULL); assert(decoder != NULL); assert(decoder->stream_tag == NULL); assert(decoder->decoder_tag == NULL); @@ -51,79 +54,98 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder, assert(audio_format_defined(audio_format)); assert(audio_format_valid(audio_format)); - dc.in_audio_format = *audio_format; - getOutputAudioFormat(audio_format, &dc.out_audio_format); + dc->in_audio_format = *audio_format; + getOutputAudioFormat(audio_format, &dc->out_audio_format); + + dc->seekable = seekable; + dc->total_time = total_time; - dc.seekable = seekable; - dc.total_time = total_time; + decoder_lock(dc); + dc->state = DECODE_STATE_DECODE; + decoder_unlock(dc); - dc.state = DECODE_STATE_DECODE; - notify_signal(&pc.notify); + player_lock_signal(); g_debug("audio_format=%u:%u:%u, seekable=%s", - dc.in_audio_format.sample_rate, dc.in_audio_format.bits, - dc.in_audio_format.channels, + dc->in_audio_format.sample_rate, + dc->in_audio_format.bits, + dc->in_audio_format.channels, seekable ? "true" : "false"); - if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format)) + 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); + dc->out_audio_format.sample_rate, + dc->out_audio_format.bits, + dc->out_audio_format.channels); } char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder) { - assert(dc.pipe != NULL); + const struct decoder_control *dc = decoder->dc; - return song_get_uri(dc.current_song); + assert(dc->pipe != NULL); + + 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; + + assert(dc->pipe != NULL); - return dc.command; + return dc->command; } void decoder_command_finished(G_GNUC_UNUSED struct decoder * decoder) { - assert(dc.command != DECODE_COMMAND_NONE); - assert(dc.command != DECODE_COMMAND_SEEK || - dc.seek_error || decoder->seeking); - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; - if (dc.command == DECODE_COMMAND_SEEK) { + 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 (dc->command == DECODE_COMMAND_SEEK) { /* 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); } - dc.command = DECODE_COMMAND_NONE; - notify_signal(&pc.notify); + dc->command = DECODE_COMMAND_NONE; + decoder_unlock(dc); + + player_lock_signal(); } double decoder_seek_where(G_GNUC_UNUSED struct decoder * decoder) { - assert(dc.command == DECODE_COMMAND_SEEK); - assert(dc.pipe != NULL); + const struct decoder_control *dc = decoder->dc; + + assert(dc->command == DECODE_COMMAND_SEEK); + assert(dc->pipe != NULL); decoder->seeking = true; - return dc.seek_where; + return dc->seek_where; } void decoder_seek_error(struct decoder * decoder) { - assert(dc.command == DECODE_COMMAND_SEEK); - assert(dc.pipe != NULL); + struct decoder_control *dc = decoder->dc; + + assert(dc->command == DECODE_COMMAND_SEEK); + assert(dc->pipe != NULL); - dc.seek_error = true; + dc->seek_error = true; decoder_command_finished(decoder); } @@ -131,11 +153,13 @@ 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; 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); @@ -148,9 +172,9 @@ 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); @@ -177,15 +201,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); @@ -224,22 +248,26 @@ decoder_data(struct decoder *decoder, float data_time, uint16_t bitRate, 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); - if (dc.command == DECODE_COMMAND_STOP || - dc.command == DECODE_COMMAND_SEEK || + decoder_lock(dc); + cmd = dc->command; + decoder_unlock(dc); + + 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; @@ -256,17 +284,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) { @@ -277,16 +306,16 @@ 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, + dest = music_chunk_write(chunk, &dc->out_audio_format, data_time, bitRate, &nbytes); if (dest == NULL) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); continue; } @@ -301,20 +330,19 @@ 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); + &dc->out_audio_format); else if (normalizationEnabled) - normalizeData(dest, nbytes, &dc.out_audio_format); + normalizeData(dest, nbytes, &dc->out_audio_format); /* expand the music pipe chunk */ - full = music_chunk_expand(chunk, &dc.out_audio_format, nbytes); + full = music_chunk_expand(chunk, &dc->out_audio_format, nbytes); if (full) { /* the chunk is full, flush it */ decoder_flush_chunk(decoder); - notify_signal(&pc.notify); + player_lock_signal(); } data += nbytes; @@ -328,10 +356,11 @@ enum decoder_command decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is, const struct tag *tag) { + 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..2ecd98ce7 100644 --- a/src/decoder_api.h +++ b/src/decoder_api.h @@ -17,15 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DECODER_API_H -#define MPD_DECODER_API_H - -/* +/*! \file + * \brief The MPD Decoder API + * * This is the public API which is used by decoder plugins to * communicate with the mpd core. - * */ +#ifndef MPD_DECODER_API_H +#define MPD_DECODER_API_H + #include "decoder_command.h" #include "decoder_plugin.h" #include "input_stream.h" @@ -36,56 +37,97 @@ #include <stdbool.h> - /** * Notify the player thread that it has finished initialization and * that it has read the song's meta data. + * + * @param decoder the decoder object + * @param audio_format the audio format which is going to be sent to + * decoder_data() + * @param seekable true if the song is seekable + * @param total_time the total number of seconds in this song; -1 if unknown */ -void decoder_initialized(struct decoder * decoder, - const struct audio_format *audio_format, - bool seekable, float total_time); +void +decoder_initialized(struct decoder *decoder, + const struct audio_format *audio_format, + bool seekable, float total_time); /** * Returns the URI of the current song in UTF-8 encoding. * - * The return value is allocated on the heap, and must be freed by the - * caller. + * @param decoder the decoder object + * @return an allocated string which must be freed with g_free() */ -char *decoder_get_uri(struct decoder *decoder); +char * +decoder_get_uri(struct decoder *decoder); -enum decoder_command decoder_get_command(struct decoder * decoder); +/** + * Determines the pending decoder command. + * + * @param decoder the decoder object + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending + */ +enum decoder_command +decoder_get_command(struct decoder *decoder); /** * Called by the decoder when it has performed the requested command * (dc->command). This function resets dc->command and wakes up the * player thread. + * + * @param decoder the decoder object */ -void decoder_command_finished(struct decoder * decoder); +void +decoder_command_finished(struct decoder *decoder); -double decoder_seek_where(struct decoder * decoder); +/** + * Call this when you have received the DECODE_COMMAND_SEEK command. + * + * @param decoder the decoder object + * @return the destination position for the week + */ +double +decoder_seek_where(struct decoder *decoder); -void decoder_seek_error(struct decoder * decoder); +/** + * Call this right before decoder_command_finished() when seeking has + * failed. + * + * @param decoder the decoder object + */ +void +decoder_seek_error(struct decoder *decoder); /** - * Blocking read from the input stream. Returns the number of bytes - * read, or 0 if one of the following occurs: end of file; error; - * command (like SEEK or STOP). + * Blocking read from the input stream. + * + * @param decoder the decoder object + * @param is the input stream to read from + * @param buffer the destination buffer + * @param length the maximum number of bytes to read + * @return the number of bytes read, or 0 if one of the following + * occurs: end of file; error; command (like SEEK or STOP). */ -size_t decoder_read(struct decoder *decoder, - struct input_stream *inStream, - void *buffer, size_t length); +size_t +decoder_read(struct decoder *decoder, struct input_stream *is, + void *buffer, size_t length); /** * This function is called by the decoder plugin when it has * successfully decoded block of input data. * - * We send inStream for buffering the inputStream while waiting to - * send the next chunk + * @param decoder the decoder object + * @param is an input stream which is buffering while we are waiting + * for the player + * @param data the source buffer + * @param length the number of bytes in the buffer + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ enum decoder_command -decoder_data(struct decoder *decoder, - struct input_stream *inStream, - const void *data, size_t datalen, +decoder_data(struct decoder *decoder, struct input_stream *is, + const void *data, size_t length, float data_time, uint16_t bitRate, struct replay_gain_info *replay_gain_info); @@ -93,8 +135,12 @@ decoder_data(struct decoder *decoder, * This function is called by the decoder plugin when it has * successfully decoded a tag. * + * @param decoder the decoder object * @param is an input stream which is buffering while we are waiting * for the player + * @param tag the tag to send + * @return the current command, or DECODE_COMMAND_NONE if there is no + * command pending */ enum decoder_command decoder_tag(struct decoder *decoder, struct input_stream *is, diff --git a/src/decoder_control.c b/src/decoder_control.c index 44bb63e15..7fe18088b 100644 --- a/src/decoder_control.c +++ b/src/decoder_control.c @@ -18,107 +18,131 @@ */ #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..d40ef89d7 100644 --- a/src/decoder_internal.c +++ b/src/decoder_internal.c @@ -28,21 +28,39 @@ #include <assert.h> /** + * This is a wrapper for input_stream_buffer(). It assumes that the + * decoder is currently locked, and temporarily unlocks it while + * calling input_stream_buffer(). We shouldn't hold the lock during a + * potentially blocking operation. + */ +static int +decoder_input_buffer(struct decoder_control *dc, struct input_stream *is) +{ + int ret; + + decoder_unlock(dc); + ret = input_stream_buffer(is) > 0; + decoder_lock(dc); + + return ret; +} + +/** * 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) <= 0) && do_wait) { + decoder_wait(dc); + player_signal(); - return dc.command; + return dc->command; } return DECODE_COMMAND_NONE; @@ -51,6 +69,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 +78,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 +93,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..9d422d253 100644 --- a/src/decoder_internal.h +++ b/src/decoder_internal.h @@ -26,6 +26,8 @@ struct input_stream; struct decoder { + struct decoder_control *dc; + struct pcm_convert_state conv_state; bool seeking; diff --git a/src/decoder_list.c b/src/decoder_list.c index a42585e34..2ee4f3ec9 100644 --- a/src/decoder_list.c +++ b/src/decoder_list.c @@ -28,9 +28,11 @@ #include <string.h> extern const struct decoder_plugin mad_decoder_plugin; +extern const struct decoder_plugin mpg123_decoder_plugin; extern const struct decoder_plugin vorbis_decoder_plugin; extern const struct decoder_plugin flac_decoder_plugin; extern const struct decoder_plugin oggflac_decoder_plugin; +extern const struct decoder_plugin sndfile_decoder_plugin; extern const struct decoder_plugin audiofile_decoder_plugin; extern const struct decoder_plugin mp4ff_decoder_plugin; extern const struct decoder_plugin faad_decoder_plugin; @@ -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..79b0d5322 --- /dev/null +++ b/src/decoder_plugin.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "decoder_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..b2d394093 --- /dev/null +++ b/src/decoder_print.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2003-2009 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "decoder_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/input/lastfm_input_plugin.h b/src/decoder_print.h index d0eaf5a55..6ba5dd081 100644 --- a/src/input/lastfm_input_plugin.h +++ b/src/decoder_print.h @@ -17,9 +17,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef LASTFM_INPUT_PLUGIN_H -#define LASTFM_INPUT_PLUGIN_H +#ifndef MPD_DECODER_PRINT_H +#define MPD_DECODER_PRINT_H -extern const struct input_plugin lastfm_input_plugin; +struct client; + +void +decoder_list_print(struct client *client); #endif diff --git a/src/decoder_thread.c b/src/decoder_thread.c index d6ff058ec..9c12ecb07 100644 --- a/src/decoder_thread.c +++ b/src/decoder_thread.c @@ -35,6 +35,53 @@ #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) +{ + if (!input_stream_open(is, uri)) + 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); + if (ret < 0) { + input_stream_close(is); + return false; + } + } + + return true; +} + static bool decoder_stream_decode(const struct decoder_plugin *plugin, struct decoder *decoder, @@ -47,17 +94,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); decoder_plugin_stream_decode(plugin, decoder, input_stream); - assert(dc.state == DECODE_STATE_START || - dc.state == DECODE_STATE_DECODE); + decoder_lock(decoder->dc); - return dc.state != DECODE_STATE_START; + assert(decoder->dc->state == DECODE_STATE_START || + decoder->dc->state == DECODE_STATE_DECODE); + + return decoder->dc->state != DECODE_STATE_START; } static bool @@ -70,150 +124,200 @@ 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.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 +327,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 |