aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/AudioCompress/compress.c184
-rw-r--r--src/AudioCompress/compress.h40
-rw-r--r--src/AudioCompress/config.h19
-rw-r--r--src/aiff.c1
-rw-r--r--src/archive/bz2_plugin.c55
-rw-r--r--src/archive/iso_plugin.c1
-rw-r--r--src/archive/zip_plugin.c3
-rw-r--r--src/archive_api.c5
-rw-r--r--src/archive_api.h63
-rw-r--r--src/archive_list.c25
-rw-r--r--src/archive_list.h2
-rw-r--r--src/archive_plugin.h89
-rw-r--r--src/audio.c15
-rw-r--r--src/audio.h2
-rw-r--r--src/audio_check.c74
-rw-r--r--src/audio_check.h54
-rw-r--r--src/audio_format.c69
-rw-r--r--src/audio_format.h194
-rw-r--r--src/audio_parser.c157
-rw-r--r--src/audio_parser.h6
-rw-r--r--src/buffer.c1
-rw-r--r--src/buffer2array.c133
-rw-r--r--src/check.h47
-rw-r--r--src/chunk.c1
-rw-r--r--src/client.c883
-rw-r--r--src/client_event.c108
-rw-r--r--src/client_expire.c90
-rw-r--r--src/client_global.c73
-rw-r--r--src/client_idle.c89
-rw-r--r--src/client_internal.h145
-rw-r--r--src/client_list.c69
-rw-r--r--src/client_new.c123
-rw-r--r--src/client_process.c146
-rw-r--r--src/client_read.c113
-rw-r--r--src/client_write.c271
-rw-r--r--src/cmdline.c91
-rw-r--r--src/cmdline.h13
-rw-r--r--src/command.c361
-rw-r--r--src/command.h6
-rw-r--r--src/compress.c410
-rw-r--r--src/conf.c488
-rw-r--r--src/conf.h75
-rw-r--r--src/crossfade.c5
-rw-r--r--src/cue/cue_tag.c95
-rw-r--r--src/cue/cue_tag.h5
-rw-r--r--src/daemon.c105
-rw-r--r--src/daemon.h37
-rw-r--r--src/database.c92
-rw-r--r--src/dbUtils.c29
-rw-r--r--src/dbUtils.h4
-rw-r--r--src/decoder/_flac_common.c351
-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.c72
-rw-r--r--src/decoder/faad_plugin.c57
-rw-r--r--src/decoder/ffmpeg_plugin.c76
-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.h33
-rw-r--r--src/decoder/flac_plugin.c544
-rw-r--r--src/decoder/fluidsynth_plugin.c9
-rw-r--r--src/decoder/mad_plugin.c37
-rw-r--r--src/decoder/mikmod_plugin.c157
-rw-r--r--src/decoder/modplug_plugin.c45
-rw-r--r--src/decoder/mp4ff_plugin.c39
-rw-r--r--src/decoder/mpcdec_plugin.c19
-rw-r--r--src/decoder/mpg123_decoder_plugin.c214
-rw-r--r--src/decoder/oggflac_plugin.c62
-rw-r--r--src/decoder/sidplay_plugin.cxx286
-rw-r--r--src/decoder/sndfile_decoder_plugin.c248
-rwxr-xr-x[-rw-r--r--]src/decoder/vorbis_plugin.c136
-rw-r--r--src/decoder/wavpack_plugin.c103
-rw-r--r--src/decoder/wildmidi_plugin.c5
-rw-r--r--src/decoder_api.c184
-rw-r--r--src/decoder_api.h101
-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.c48
-rw-r--r--src/decoder_internal.h2
-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.h (renamed from src/input/lastfm_input_plugin.h)9
-rw-r--r--src/decoder_thread.c395
-rw-r--r--src/decoder_thread.h5
-rw-r--r--src/directory.c1
-rw-r--r--src/directory.h1
-rw-r--r--src/directory_print.c1
-rw-r--r--src/directory_save.c163
-rw-r--r--src/directory_save.h5
-rw-r--r--src/dirvec.c1
-rw-r--r--src/encoder/flac_encoder.c352
-rw-r--r--src/encoder/lame_encoder.c10
-rw-r--r--src/encoder/null_encoder.c124
-rw-r--r--src/encoder/twolame_encoder.c307
-rw-r--r--src/encoder/vorbis_encoder.c10
-rw-r--r--src/encoder/wave_encoder.c270
-rw-r--r--src/encoder_list.c26
-rw-r--r--src/encoder_list.h5
-rw-r--r--src/encoder_plugin.h17
-rw-r--r--src/event_pipe.c13
-rw-r--r--src/event_pipe.h5
-rw-r--r--src/exclude.c95
-rw-r--r--src/exclude.h (renamed from src/compress.h)51
-rw-r--r--src/fd_util.c242
-rw-r--r--src/fd_util.h90
-rw-r--r--src/fifo_buffer.c1
-rw-r--r--src/filter/chain_filter_plugin.c180
-rw-r--r--src/filter/chain_filter_plugin.h48
-rw-r--r--src/filter/convert_filter_plugin.c148
-rw-r--r--src/filter/convert_filter_plugin.h36
-rw-r--r--src/filter/normalize_filter_plugin.c123
-rw-r--r--src/filter/null_filter_plugin.c94
-rw-r--r--src/filter/route_filter_plugin.c345
-rw-r--r--src/filter/volume_filter_plugin.c176
-rw-r--r--src/filter/volume_filter_plugin.h31
-rw-r--r--src/filter_config.c119
-rw-r--r--src/filter_config.h47
-rw-r--r--src/filter_internal.h (renamed from src/buffer2array.h)25
-rw-r--r--src/filter_plugin.c111
-rw-r--r--src/filter_plugin.h145
-rw-r--r--src/filter_registry.c44
-rw-r--r--src/filter_registry.h39
-rw-r--r--src/glib_compat.h62
-rw-r--r--src/icy_metadata.c3
-rw-r--r--src/icy_server.c1
-rw-r--r--src/idle.c2
-rw-r--r--src/idle.h3
-rw-r--r--src/inotify_queue.c135
-rw-r--r--src/inotify_queue.h32
-rw-r--r--src/inotify_source.c165
-rw-r--r--src/inotify_source.h61
-rw-r--r--src/inotify_update.c351
-rw-r--r--src/inotify_update.h47
-rw-r--r--src/input/archive_input_plugin.c1
-rw-r--r--src/input/curl_input_plugin.c33
-rw-r--r--src/input/file_input_plugin.c10
-rw-r--r--src/input/lastfm_input_plugin.c229
-rw-r--r--src/input/mms_input_plugin.c3
-rw-r--r--src/input_init.c100
-rw-r--r--src/input_init.h42
-rw-r--r--src/input_plugin.h6
-rw-r--r--src/input_registry.c52
-rw-r--r--src/input_registry.h35
-rw-r--r--src/input_stream.c96
-rw-r--r--src/input_stream.h24
-rw-r--r--src/listen.c35
-rw-r--r--src/listen.h7
-rw-r--r--src/locate.c7
-rw-r--r--src/log.c10
-rw-r--r--src/ls.c7
-rw-r--r--src/main.c166
-rw-r--r--src/main.h2
-rw-r--r--src/mapper.c53
-rw-r--r--src/mapper.h2
-rw-r--r--src/mixer/alsa_mixer_plugin.c (renamed from src/mixer/alsa_mixer.c)64
-rw-r--r--src/mixer/oss_mixer_plugin.c (renamed from src/mixer/oss_mixer.c)55
-rw-r--r--src/mixer/pulse_mixer.c382
-rw-r--r--src/mixer/pulse_mixer_plugin.c234
-rw-r--r--src/mixer/pulse_mixer_plugin.h39
-rw-r--r--src/mixer/software_mixer_plugin.c109
-rw-r--r--src/mixer/software_mixer_plugin.h33
-rw-r--r--src/mixer_all.c98
-rw-r--r--src/mixer_all.h21
-rw-r--r--src/mixer_api.c1
-rw-r--r--src/mixer_control.c54
-rw-r--r--src/mixer_control.h15
-rw-r--r--src/mixer_list.h7
-rw-r--r--src/mixer_plugin.h33
-rw-r--r--src/mixer_type.c39
-rw-r--r--src/mixer_type.h47
-rw-r--r--src/normalize.c17
-rw-r--r--src/normalize.h2
-rw-r--r--src/notify.c1
-rw-r--r--src/output/alsa_plugin.c166
-rw-r--r--src/output/ao_plugin.c26
-rw-r--r--src/output/fifo_output_plugin.c (renamed from src/output/fifo_plugin.c)12
-rw-r--r--src/output/httpd_client.c7
-rw-r--r--src/output/httpd_internal.h13
-rw-r--r--src/output/httpd_output_plugin.c129
-rw-r--r--src/output/jack_output_plugin.c698
-rw-r--r--src/output/jack_plugin.c450
-rw-r--r--src/output/mvp_plugin.c21
-rw-r--r--src/output/null_plugin.c5
-rw-r--r--src/output/openal_plugin.c277
-rw-r--r--src/output/oss_plugin.c23
-rw-r--r--src/output/osx_plugin.c22
-rw-r--r--src/output/pipe_output_plugin.c1
-rw-r--r--src/output/pulse_output_plugin.c824
-rw-r--r--src/output/pulse_output_plugin.h72
-rw-r--r--src/output/pulse_plugin.c179
-rw-r--r--src/output/recorder_output_plugin.c217
-rw-r--r--src/output/shout_plugin.c14
-rw-r--r--src/output/solaris_output_plugin.c8
-rw-r--r--src/output_all.c115
-rw-r--r--src/output_all.h21
-rw-r--r--src/output_command.c16
-rw-r--r--src/output_control.c153
-rw-r--r--src/output_control.h17
-rw-r--r--src/output_init.c158
-rw-r--r--src/output_internal.h60
-rw-r--r--src/output_list.c18
-rw-r--r--src/output_plugin.h46
-rw-r--r--src/output_print.c1
-rw-r--r--src/output_state.c56
-rw-r--r--src/output_state.h15
-rw-r--r--src/output_thread.c365
-rw-r--r--src/page.c1
-rw-r--r--src/path.c1
-rw-r--r--src/pcm_byteswap.c70
-rw-r--r--src/pcm_byteswap.h50
-rw-r--r--src/pcm_channels.c33
-rw-r--r--src/pcm_channels.h12
-rw-r--r--src/pcm_convert.c148
-rw-r--r--src/pcm_convert.h16
-rw-r--r--src/pcm_dither.c1
-rw-r--r--src/pcm_format.c51
-rw-r--r--src/pcm_format.h8
-rw-r--r--src/pcm_mix.c35
-rw-r--r--src/pcm_resample.c26
-rw-r--r--src/pcm_resample.h18
-rw-r--r--src/pcm_resample_fallback.c5
-rw-r--r--src/pcm_resample_internal.h10
-rw-r--r--src/pcm_resample_libsamplerate.c73
-rw-r--r--src/pcm_utils.h14
-rw-r--r--src/pcm_volume.c37
-rw-r--r--src/permission.c3
-rw-r--r--src/permission.h2
-rw-r--r--src/pipe.c1
-rw-r--r--src/player_control.c183
-rw-r--r--src/player_control.h168
-rw-r--r--src/player_thread.c467
-rw-r--r--src/playlist.c77
-rw-r--r--src/playlist.h110
-rw-r--r--src/playlist/asx_playlist_plugin.c314
-rw-r--r--src/playlist/asx_playlist_plugin.h25
-rw-r--r--src/playlist/extm3u_playlist_plugin.c161
-rw-r--r--src/playlist/extm3u_playlist_plugin.h25
-rw-r--r--src/playlist/lastfm_playlist_plugin.c292
-rw-r--r--src/playlist/lastfm_playlist_plugin.h25
-rw-r--r--src/playlist/m3u_playlist_plugin.c92
-rw-r--r--src/playlist/m3u_playlist_plugin.h25
-rw-r--r--src/playlist/pls_playlist_plugin.c210
-rw-r--r--src/playlist/pls_playlist_plugin.h25
-rw-r--r--src/playlist/xspf_playlist_plugin.c334
-rw-r--r--src/playlist/xspf_playlist_plugin.h25
-rw-r--r--src/playlist_control.c81
-rw-r--r--src/playlist_edit.c167
-rw-r--r--src/playlist_global.c19
-rw-r--r--src/playlist_internal.h2
-rw-r--r--src/playlist_list.c255
-rw-r--r--src/playlist_list.h65
-rw-r--r--src/playlist_plugin.h137
-rw-r--r--src/playlist_print.c3
-rw-r--r--src/playlist_queue.c135
-rw-r--r--src/playlist_queue.h48
-rw-r--r--src/playlist_save.c7
-rw-r--r--src/playlist_state.c112
-rw-r--r--src/playlist_state.h14
-rw-r--r--src/poison.h3
-rw-r--r--src/queue.c25
-rw-r--r--src/queue.h10
-rw-r--r--src/queue_print.c1
-rw-r--r--src/queue_save.c1
-rw-r--r--src/replay_gain.c96
-rw-r--r--src/replay_gain.h16
-rw-r--r--src/riff.c1
-rw-r--r--src/sig_handlers.c1
-rw-r--r--src/socket_util.c5
-rw-r--r--src/song.c172
-rw-r--r--src/song.h9
-rw-r--r--src/song_print.c32
-rw-r--r--src/song_print.h2
-rw-r--r--src/song_save.c138
-rw-r--r--src/song_save.h18
-rw-r--r--src/song_sticker.c1
-rw-r--r--src/song_update.c173
-rw-r--r--src/songvec.c62
-rw-r--r--src/songvec.h2
-rw-r--r--src/state_file.c74
-rw-r--r--src/stats.c7
-rw-r--r--src/sticker.c48
-rw-r--r--src/sticker.h8
-rw-r--r--src/sticker_print.c1
-rw-r--r--src/stored_playlist.c1
-rw-r--r--src/strset.c1
-rw-r--r--src/tag.c79
-rw-r--r--src/tag.h42
-rw-r--r--src/tag_ape.c65
-rw-r--r--src/tag_id3.c299
-rw-r--r--src/tag_id3.h2
-rw-r--r--src/tag_pool.c1
-rw-r--r--src/tag_print.c1
-rw-r--r--src/tag_save.c1
-rw-r--r--src/text_file.c63
-rw-r--r--src/text_file.h39
-rw-r--r--src/text_input_stream.c89
-rw-r--r--src/text_input_stream.h52
-rw-r--r--src/timer.c1
-rw-r--r--src/tokenizer.c222
-rw-r--r--src/tokenizer.h83
-rw-r--r--src/update.c819
-rw-r--r--src/update.h14
-rw-r--r--src/update_internal.h64
-rw-r--r--src/update_queue.c66
-rw-r--r--src/update_remove.c94
-rw-r--r--src/update_walk.c833
-rw-r--r--src/uri.c1
-rw-r--r--src/uri.h5
-rw-r--r--src/utils.c48
-rw-r--r--src/utils.h15
-rw-r--r--src/volume.c222
-rw-r--r--src/volume.h20
-rw-r--r--src/zeroconf-avahi.c1
-rw-r--r--src/zeroconf-bonjour.c1
-rw-r--r--src/zeroconf.c2
-rw-r--r--src/zeroconf.h2
323 files changed, 20789 insertions, 7871 deletions
diff --git a/src/AudioCompress/compress.c b/src/AudioCompress/compress.c
new file mode 100644
index 000000000..bf72b2eab
--- /dev/null
+++ b/src/AudioCompress/compress.c
@@ -0,0 +1,184 @@
+/* compress.c
+ * Compressor logic
+ *
+ * (c)2007 busybee (http://beesbuzz.biz/
+ * Licensed under the terms of the LGPL. See the file COPYING for details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "config.h"
+#include "compress.h"
+
+struct Compressor {
+ //! The compressor's preferences
+ struct CompressorConfig prefs;
+
+ //! History of the peak values
+ int *peaks;
+
+ //! History of the gain values
+ int *gain;
+
+ //! History of clip amounts
+ int *clipped;
+
+ unsigned int pos;
+ unsigned int bufsz;
+};
+
+struct Compressor *Compressor_new(unsigned int history)
+{
+ struct Compressor *obj = malloc(sizeof(struct Compressor));
+
+ obj->prefs.target = TARGET;
+ obj->prefs.maxgain = GAINMAX;
+ obj->prefs.smooth = GAINSMOOTH;
+
+ obj->peaks = obj->gain = obj->clipped = NULL;
+ obj->bufsz = 0;
+ obj->pos = 0;
+
+ Compressor_setHistory(obj, history);
+
+ return obj;
+}
+
+void Compressor_delete(struct Compressor *obj)
+{
+ if (obj->peaks)
+ free(obj->peaks);
+ if (obj->gain)
+ free(obj->gain);
+ if (obj->clipped)
+ free(obj->clipped);
+ free(obj);
+}
+
+static int *resizeArray(int *data, int newsz, int oldsz)
+{
+ data = realloc(data, newsz*sizeof(int));
+ if (newsz > oldsz)
+ memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
+ return data;
+}
+
+void Compressor_setHistory(struct Compressor *obj, unsigned int history)
+{
+ if (!history)
+ history = BUCKETS;
+
+ obj->peaks = resizeArray(obj->peaks, history, obj->bufsz);
+ obj->gain = resizeArray(obj->gain, history, obj->bufsz);
+ obj->clipped = resizeArray(obj->clipped, history, obj->bufsz);
+ obj->bufsz = history;
+}
+
+struct CompressorConfig *Compressor_getConfig(struct Compressor *obj)
+{
+ return &obj->prefs;
+}
+
+void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
+ unsigned int count)
+{
+ struct CompressorConfig *prefs = Compressor_getConfig(obj);
+ int16_t *ap;
+ unsigned int i;
+ int *peaks = obj->peaks;
+ int curGain = obj->gain[obj->pos];
+ int newGain;
+ int peakVal = 1;
+ int peakPos = 0;
+ int slot = (obj->pos + 1) % obj->bufsz;
+ int *clipped = obj->clipped + slot;
+ unsigned int ramp = count;
+ int delta;
+
+ ap = audio;
+ for (i = 0; i < count; i++)
+ {
+ int val = *ap++;
+ if (val < 0)
+ val = -val;
+ if (val > peakVal)
+ {
+ peakVal = val;
+ peakPos = i;
+ }
+ }
+ peaks[slot] = peakVal;
+
+
+ for (i = 0; i < obj->bufsz; i++)
+ {
+ if (peaks[i] > peakVal)
+ {
+ peakVal = peaks[i];
+ peakPos = 0;
+ }
+ }
+
+ //! Determine target gain
+ newGain = (1 << 10)*prefs->target/peakVal;
+
+ //! Adjust the gain with inertia from the previous gain value
+ newGain = (curGain*((1 << prefs->smooth) - 1) + newGain)
+ >> prefs->smooth;
+
+ //! Make sure it's no more than the maximum gain value
+ if (newGain > (prefs->maxgain << 10))
+ newGain = prefs->maxgain << 10;
+
+ //! Make sure it's no less than 1:1
+ if (newGain < (1 << 10))
+ newGain = 1 << 10;
+
+ //! Make sure the adjusted gain won't cause clipping
+ if ((peakVal*newGain >> 10) > 32767)
+ {
+ newGain = (32767 << 10)/peakVal;
+ //! Truncate the ramp time
+ ramp = peakPos;
+ }
+
+ //! Record the new gain
+ obj->gain[slot] = newGain;
+
+ if (!ramp)
+ ramp = 1;
+ if (!curGain)
+ curGain = 1 << 10;
+ delta = (newGain - curGain) / (int)ramp;
+
+ ap = audio;
+ *clipped = 0;
+ for (i = 0; i < count; i++)
+ {
+ int sample;
+
+ //! Amplify the sample
+ sample = *ap*curGain >> 10;
+ if (sample < -32768)
+ {
+ *clipped += -32768 - sample;
+ sample = -32768;
+ } else if (sample > 32767)
+ {
+ *clipped += sample - 32767;
+ sample = 32767;
+ }
+ *ap++ = sample;
+
+ //! Adjust the gain
+ if (i < ramp)
+ curGain += delta;
+ else
+ curGain = newGain;
+ }
+
+ obj->pos = slot;
+}
+
diff --git a/src/AudioCompress/compress.h b/src/AudioCompress/compress.h
new file mode 100644
index 000000000..cc875c6da
--- /dev/null
+++ b/src/AudioCompress/compress.h
@@ -0,0 +1,40 @@
+/*! compress.h
+ * interface to audio compression
+ *
+ * (c)2007 busybee (http://beesbuzz.biz/)
+ * Licensed under the terms of the LGPL. See the file COPYING for details.
+ */
+
+#ifndef COMPRESS_H
+#define COMPRESS_H
+
+#include <sys/types.h>
+
+//! Configuration values for the compressor object
+struct CompressorConfig {
+ int target;
+ int maxgain;
+ int smooth;
+};
+
+struct Compressor;
+
+//! Create a new compressor (use history value of 0 for default)
+struct Compressor *Compressor_new(unsigned int history);
+
+//! Delete a compressor
+void Compressor_delete(struct Compressor *);
+
+//! Set the history length
+void Compressor_setHistory(struct Compressor *, unsigned int history);
+
+//! Get the configuration for a compressor
+struct CompressorConfig *Compressor_getConfig(struct Compressor *);
+
+//! Process 16-bit signed data
+void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count);
+
+//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed
+
+//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring)
+#endif
diff --git a/src/AudioCompress/config.h b/src/AudioCompress/config.h
new file mode 100644
index 000000000..25615ee68
--- /dev/null
+++ b/src/AudioCompress/config.h
@@ -0,0 +1,19 @@
+/* config.h
+** Default values for the configuration, and also a few random debug things
+*/
+
+#ifndef AC_CONFIG_H
+#define AC_CONFIG_H
+
+/*** Version information ***/
+#define ACVERSION "2.0"
+
+/*** Default configuration stuff ***/
+#define TARGET 16384 /*!< Target level (on a scale of 0-32767) */
+
+#define GAINMAX 32 /*!< The maximum amount to amplify by */
+#define GAINSMOOTH 8 /*!< How much inertia ramping has*/
+#define BUCKETS 400 /*!< How long of a history to use by default */
+
+#endif
+
diff --git a/src/aiff.c b/src/aiff.c
index d4bec628b..7fbb7eb92 100644
--- a/src/aiff.c
+++ b/src/aiff.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "aiff.h"
#include <glib.h>
diff --git a/src/archive/bz2_plugin.c b/src/archive/bz2_plugin.c
index 0ef042e90..693dd4dba 100644
--- a/src/archive/bz2_plugin.c
+++ b/src/archive/bz2_plugin.c
@@ -21,9 +21,9 @@
* single bz2 archive handling (requires libbz2)
*/
+#include "config.h"
#include "archive_api.h"
#include "input_plugin.h"
-#include "config.h"
#include <stdint.h>
#include <stddef.h>
@@ -32,23 +32,22 @@
#include <bzlib.h>
#ifdef HAVE_OLDER_BZIP2
-#define BZ2_bzDecompressInit bzDecompressInit
-#define BZ2_bzDecompress bzDecompress
+#define BZ2_bzDecompressInit bzDecompressInit
+#define BZ2_bzDecompress bzDecompress
#endif
#define BZ_BUFSIZE 5000
typedef struct {
- char *name;
- bool reset;
+ char *name;
+ bool reset;
struct input_stream istream;
- int last_bz_result;
- int last_parent_result;
- bz_stream bzstream;
- char *buffer;
+ int last_bz_result;
+ int last_parent_result;
+ bz_stream bzstream;
+ char *buffer;
} bz2_context;
-
static const struct input_plugin bz2_inputplugin;
/* single archive handling allocation helpers */
@@ -85,22 +84,21 @@ bz2_destroy(bz2_context *data)
/* archive open && listing routine */
static struct archive_file *
-bz2_open(char * pathname)
+bz2_open(char *pathname)
{
bz2_context *context;
char *name;
int len;
- context = g_malloc(sizeof(bz2_context));
- if (!context) {
- return NULL;
- }
+ context = g_malloc(sizeof(*context));
+
//open archive
if (!input_stream_open(&context->istream, pathname)) {
g_warning("failed to open an bzip2 archive %s\n",pathname);
g_free(context);
return NULL;
}
+
//capture filename
name = strrchr(pathname, '/');
if (name == NULL) {
@@ -108,12 +106,15 @@ bz2_open(char * pathname)
g_free(context);
return NULL;
}
- context->name = g_strdup(name+1);
+
+ context->name = g_strdup(name + 1);
+
//remove suffix
len = strlen(context->name);
if (len > 4) {
- context->name[len-4] = 0; //remove .bz2 suffix
+ context->name[len - 4] = 0; //remove .bz2 suffix
}
+
return (struct archive_file *) context;
}
@@ -129,10 +130,12 @@ bz2_scan_next(struct archive_file *file)
{
bz2_context *context = (bz2_context *) file;
char *name = NULL;
+
if (context->reset) {
name = context->name;
context->reset = false;
}
+
return name;
}
@@ -154,6 +157,7 @@ bz2_open_stream(struct archive_file *file, struct input_stream *is,
G_GNUC_UNUSED const char *path)
{
bz2_context *context = (bz2_context *) file;
+
//setup file ops
is->plugin = &bz2_inputplugin;
//insert back reference
@@ -164,6 +168,7 @@ bz2_open_stream(struct archive_file *file, struct input_stream *is,
g_warning("alloc bz2 failed\n");
return false;
}
+
return true;
}
@@ -177,9 +182,8 @@ bz2_is_close(struct input_stream *is)
bz2_close((struct archive_file *)context);
}
-static int
-bz2_fillbuffer(bz2_context *context,
- size_t numBytes)
+static bool
+bz2_fillbuffer(bz2_context *context, size_t numBytes)
{
size_t count;
bz_stream *bzstream;
@@ -187,14 +191,15 @@ bz2_fillbuffer(bz2_context *context,
bzstream = &context->bzstream;
if (bzstream->avail_in > 0)
- return 0;
+ return true;
count = input_stream_read(&context->istream,
- context->buffer, BZ_BUFSIZE);
+ context->buffer, BZ_BUFSIZE);
if (count == 0) {
if (bzstream->avail_out == numBytes)
- return -1;
+ return false;
+
if (!input_stream_eof(&context->istream))
context->last_parent_result = 1;
} else {
@@ -202,7 +207,7 @@ bz2_fillbuffer(bz2_context *context,
bzstream->avail_in = count;
}
- return 0;
+ return true;
}
static size_t
@@ -224,7 +229,7 @@ bz2_is_read(struct input_stream *is, void *ptr, size_t size)
bzstream->avail_out = numBytes;
while (bzstream->avail_out != 0) {
- if (bz2_fillbuffer(context, numBytes) != 0)
+ if (!bz2_fillbuffer(context, numBytes))
break;
bz_result = BZ2_bzDecompress(bzstream);
diff --git a/src/archive/iso_plugin.c b/src/archive/iso_plugin.c
index 9063af0fc..b56653d56 100644
--- a/src/archive/iso_plugin.c
+++ b/src/archive/iso_plugin.c
@@ -21,6 +21,7 @@
* iso archive handling (requires cdio, and iso9660)
*/
+#include "config.h"
#include "archive_api.h"
#include "input_plugin.h"
diff --git a/src/archive/zip_plugin.c b/src/archive/zip_plugin.c
index 243d46418..95bc1d02b 100644
--- a/src/archive/zip_plugin.c
+++ b/src/archive/zip_plugin.c
@@ -21,6 +21,7 @@
* zip archive handling (requires zziplib)
*/
+#include "config.h"
#include "archive_api.h"
#include "archive_api.h"
#include "input_plugin.h"
@@ -160,7 +161,7 @@ zip_is_eof(struct input_stream *is)
static bool
zip_is_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence)
{
zip_context *context = (zip_context *) is->data;
zzip_off_t ofs = zzip_seek(context->file, offset, whence);
diff --git a/src/archive_api.c b/src/archive_api.c
index 153afa361..574960558 100644
--- a/src/archive_api.c
+++ b/src/archive_api.c
@@ -17,6 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
+#include "archive_api.h"
+
#include <stdio.h>
#include <string.h>
@@ -26,8 +29,6 @@
#include <errno.h>
#include <glib.h>
-#include "archive_api.h"
-
/**
*
* archive_lookup is used to determine if part of pathname refers to an regular
diff --git a/src/archive_api.h b/src/archive_api.h
index 2efcc1e6a..20a4f9277 100644
--- a/src/archive_api.h
+++ b/src/archive_api.h
@@ -27,72 +27,11 @@
*/
#include "archive_internal.h"
+#include "archive_plugin.h"
#include "input_stream.h"
#include <stdbool.h>
-struct archive_file;
-
-struct archive_plugin {
- const char *name;
-
- /**
- * optional, set this to NULL if the archive plugin doesn't
- * have/need one this must false if there is an error and
- * true otherwise
- */
- bool (*init)(void);
-
- /**
- * optional, set this to NULL if the archive plugin doesn't
- * have/need one
- */
- void (*finish)(void);
-
- /**
- * tryes to open archive file and associates handle with archive
- * returns pointer to handle used is all operations with this archive
- * or NULL when opening fails
- */
- struct archive_file *(*open)(char * pathname);
-
- /**
- * reset routine will move current read index in archive to default
- * position and then the filenames from archives can be read
- * via scan_next routine
- */
- void (*scan_reset)(struct archive_file *);
-
- /**
- * the read method will return corresponding files from archive
- * (as pathnames) and move read index to next file. When there is no
- * next file it return NULL.
- */
- char *(*scan_next)(struct archive_file *);
-
- /**
- * Opens an input_stream of a file within the archive.
- *
- * If this function succeeds, then the #input_stream "owns"
- * the archive file and will automatically close it.
- *
- * @param path the path within the archive
- */
- bool (*open_stream)(struct archive_file *, struct input_stream *is,
- const char *path);
-
- /**
- * closes archive file.
- */
- void (*close)(struct archive_file *);
-
- /**
- * suffixes handled by this plugin.
- * last element in these arrays must always be a NULL
- */
- const char *const*suffixes;
-};
-
bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
#endif
diff --git a/src/archive_list.c b/src/archive_list.c
index 8228fc961..0edbb305f 100644
--- a/src/archive_list.c
+++ b/src/archive_list.c
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "archive_list.h"
-#include "archive_api.h"
+#include "archive_plugin.h"
#include "utils.h"
-#include "config.h"
#include <string.h>
#include <glib.h>
@@ -42,25 +42,20 @@ static const struct archive_plugin *const archive_plugins[] = {
NULL
};
-enum {
- num_archive_plugins = G_N_ELEMENTS(archive_plugins)-1,
-};
-
/** which plugins have been initialized successfully? */
-static bool archive_plugins_enabled[num_archive_plugins+1];
+static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1];
const struct archive_plugin *
archive_plugin_from_suffix(const char *suffix)
{
- unsigned i;
-
if (suffix == NULL)
return NULL;
- for (i=0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] &&
- stringFoundInStringArray(plugin->suffixes, suffix)) {
+ plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
++i;
return plugin;
}
@@ -71,7 +66,7 @@ archive_plugin_from_suffix(const char *suffix)
const struct archive_plugin *
archive_plugin_from_name(const char *name)
{
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] &&
strcmp(plugin->name, name) == 0)
@@ -84,7 +79,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
{
const char *const*suffixes;
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (!archive_plugins_enabled[i])
continue;
@@ -101,7 +96,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
void archive_plugin_init_all(void)
{
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (plugin->init == NULL || archive_plugins[i]->init())
archive_plugins_enabled[i] = true;
@@ -110,7 +105,7 @@ void archive_plugin_init_all(void)
void archive_plugin_deinit_all(void)
{
- for (unsigned i = 0; i < num_archive_plugins; ++i) {
+ for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
const struct archive_plugin *plugin = archive_plugins[i];
if (archive_plugins_enabled[i] && plugin->finish != NULL)
archive_plugins[i]->finish();
diff --git a/src/archive_list.h b/src/archive_list.h
index 55278fbc4..2534b2b18 100644
--- a/src/archive_list.h
+++ b/src/archive_list.h
@@ -20,8 +20,6 @@
#ifndef MPD_ARCHIVE_LIST_H
#define MPD_ARCHIVE_LIST_H
-#include "archive_api.h"
-
#include <stdio.h>
struct archive_plugin;
diff --git a/src/archive_plugin.h b/src/archive_plugin.h
new file mode 100644
index 000000000..df2dcff47
--- /dev/null
+++ b/src/archive_plugin.h
@@ -0,0 +1,89 @@
+/*
+ * 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_ARCHIVE_PLUGIN_H
+#define MPD_ARCHIVE_PLUGIN_H
+
+#include <stdbool.h>
+
+struct input_stream;
+struct archive_file;
+
+struct archive_plugin {
+ const char *name;
+
+ /**
+ * optional, set this to NULL if the archive plugin doesn't
+ * have/need one this must false if there is an error and
+ * true otherwise
+ */
+ bool (*init)(void);
+
+ /**
+ * optional, set this to NULL if the archive plugin doesn't
+ * have/need one
+ */
+ void (*finish)(void);
+
+ /**
+ * tryes to open archive file and associates handle with archive
+ * returns pointer to handle used is all operations with this archive
+ * or NULL when opening fails
+ */
+ struct archive_file *(*open)(char * pathname);
+
+ /**
+ * reset routine will move current read index in archive to default
+ * position and then the filenames from archives can be read
+ * via scan_next routine
+ */
+ void (*scan_reset)(struct archive_file *);
+
+ /**
+ * the read method will return corresponding files from archive
+ * (as pathnames) and move read index to next file. When there is no
+ * next file it return NULL.
+ */
+ char *(*scan_next)(struct archive_file *);
+
+ /**
+ * Opens an input_stream of a file within the archive.
+ *
+ * If this function succeeds, then the #input_stream "owns"
+ * the archive file and will automatically close it.
+ *
+ * @param path the path within the archive
+ */
+ bool (*open_stream)(struct archive_file *, struct input_stream *is,
+ const char *path);
+
+ /**
+ * closes archive file.
+ */
+ void (*close)(struct archive_file *);
+
+ /**
+ * suffixes handled by this plugin.
+ * last element in these arrays must always be a NULL
+ */
+ const char *const*suffixes;
+};
+
+#endif
+
diff --git a/src/audio.c b/src/audio.c
index d48558e46..1d234bf5b 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "audio.h"
#include "audio_format.h"
#include "audio_parser.h"
@@ -35,9 +36,8 @@ static struct audio_format configured_audio_format;
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
struct audio_format *outAudioFormat)
{
- *outAudioFormat = audio_format_defined(&configured_audio_format)
- ? configured_audio_format
- : *inAudioFormat;
+ *outAudioFormat = *inAudioFormat;
+ audio_format_mask_apply(outAudioFormat, &configured_audio_format);
}
void initAudioConfig(void)
@@ -46,17 +46,12 @@ void initAudioConfig(void)
GError *error = NULL;
bool ret;
- if (NULL == param || NULL == param->value)
+ if (param == NULL)
return;
ret = audio_format_parse(&configured_audio_format, param->value,
- &error);
+ true, &error);
if (!ret)
g_error("error parsing \"%s\" at line %i: %s",
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
}
-
-void finishAudioConfig(void)
-{
- audio_format_clear(&configured_audio_format);
-}
diff --git a/src/audio.h b/src/audio.h
index 4d80ee0e6..4b80cfd27 100644
--- a/src/audio.h
+++ b/src/audio.h
@@ -30,6 +30,4 @@ void getOutputAudioFormat(const struct audio_format *inFormat,
/* make sure initPlayerData is called before this function!! */
void initAudioConfig(void);
-void finishAudioConfig(void);
-
#endif
diff --git a/src/audio_check.c b/src/audio_check.c
new file mode 100644
index 000000000..a843975fa
--- /dev/null
+++ b/src/audio_check.c
@@ -0,0 +1,74 @@
+/*
+ * 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 "audio_check.h"
+#include "audio_format.h"
+
+#include <assert.h>
+
+bool
+audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
+{
+ if (!audio_valid_sample_rate(sample_rate)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid sample rate: %lu", sample_rate);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_check_sample_format(enum sample_format sample_format, GError **error_r)
+{
+ if (!audio_valid_sample_format(sample_format)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid sample format: %u", sample_format);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_check_channel_count(unsigned channels, GError **error_r)
+{
+ if (!audio_valid_channel_count(channels)) {
+ g_set_error(error_r, audio_format_quark(), 0,
+ "Invalid channel count: %u", channels);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
+ enum sample_format sample_format, unsigned channels,
+ GError **error_r)
+{
+ if (audio_check_sample_rate(sample_rate, error_r) &&
+ audio_check_sample_format(sample_format, error_r) &&
+ audio_check_channel_count(channels, error_r)) {
+ audio_format_init(af, sample_rate, sample_format, channels);
+ assert(audio_format_valid(af));
+ return true;
+ } else
+ return false;
+}
diff --git a/src/audio_check.h b/src/audio_check.h
new file mode 100644
index 000000000..197dedd48
--- /dev/null
+++ b/src/audio_check.h
@@ -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.
+ */
+
+#ifndef MPD_AUDIO_CHECK_H
+#define MPD_AUDIO_CHECK_H
+
+#include "audio_format.h"
+
+#include <glib.h>
+#include <stdbool.h>
+
+/**
+ * The GLib quark used for errors reported by this library.
+ */
+static inline GQuark
+audio_format_quark(void)
+{
+ return g_quark_from_static_string("audio_format");
+}
+
+bool
+audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
+
+bool
+audio_check_sample_format(unsigned sample_format, GError **error_r);
+
+bool
+audio_check_channel_count(unsigned sample_format, GError **error_r);
+
+/**
+ * Wrapper for audio_format_init(), which checks all attributes.
+ */
+bool
+audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
+ enum sample_format sample_format, unsigned channels,
+ GError **error_r);
+
+#endif
diff --git a/src/audio_format.c b/src/audio_format.c
new file mode 100644
index 000000000..33cd90f58
--- /dev/null
+++ b/src/audio_format.c
@@ -0,0 +1,69 @@
+/*
+ * 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 "audio_format.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+#if G_BYTE_ORDER == G_BIG_ENDIAN
+#define REVERSE_ENDIAN_SUFFIX "_le"
+#else
+#define REVERSE_ENDIAN_SUFFIX "_be"
+#endif
+
+const char *
+sample_format_to_string(enum sample_format format)
+{
+ switch (format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ return "?";
+
+ case SAMPLE_FORMAT_S8:
+ return "8";
+
+ case SAMPLE_FORMAT_S16:
+ return "16";
+
+ case SAMPLE_FORMAT_S24_P32:
+ return "24";
+
+ case SAMPLE_FORMAT_S32:
+ return "32";
+ }
+
+ /* unreachable */
+ assert(false);
+ return "?";
+}
+
+const char *
+audio_format_to_string(const struct audio_format *af,
+ struct audio_format_string *s)
+{
+ assert(af != NULL);
+ assert(s != NULL);
+
+ snprintf(s->buffer, sizeof(s->buffer), "%u:%s%s:%u",
+ af->sample_rate, sample_format_to_string(af->format),
+ af->reverse_endian ? REVERSE_ENDIAN_SUFFIX : "",
+ af->channels);
+
+ return s->buffer;
+}
diff --git a/src/audio_format.h b/src/audio_format.h
index 64087d070..6e7d50c46 100644
--- a/src/audio_format.h
+++ b/src/audio_format.h
@@ -23,25 +23,118 @@
#include <stdint.h>
#include <stdbool.h>
+enum sample_format {
+ SAMPLE_FORMAT_UNDEFINED = 0,
+
+ SAMPLE_FORMAT_S8,
+ SAMPLE_FORMAT_S16,
+
+ /**
+ * Signed 24 bit integer samples, packed in 32 bit integers
+ * (the most significant byte is filled with the sign bit).
+ */
+ SAMPLE_FORMAT_S24_P32,
+
+ SAMPLE_FORMAT_S32,
+};
+
+/**
+ * This structure describes the format of a raw PCM stream.
+ */
struct audio_format {
+ /**
+ * The sample rate in Hz. A better name for this attribute is
+ * "frame rate", because technically, you have two samples per
+ * frame in stereo sound.
+ */
uint32_t sample_rate;
- uint8_t bits;
+
+ /**
+ * The format samples are stored in. See the #sample_format
+ * enum for valid values.
+ */
+ uint8_t format;
+
+ /**
+ * The number of channels. Only mono (1) and stereo (2) are
+ * fully supported currently.
+ */
uint8_t channels;
+
+ /**
+ * If zero, then samples are stored in host byte order. If
+ * nonzero, then samples are stored in the reverse host byte
+ * order.
+ */
+ uint8_t reverse_endian;
+};
+
+/**
+ * Buffer for audio_format_string().
+ */
+struct audio_format_string {
+ char buffer[24];
};
+/**
+ * Clears the #audio_format object, i.e. sets all attributes to an
+ * undefined (invalid) value.
+ */
static inline void audio_format_clear(struct audio_format *af)
{
af->sample_rate = 0;
- af->bits = 0;
+ af->format = SAMPLE_FORMAT_UNDEFINED;
af->channels = 0;
+ af->reverse_endian = 0;
}
+/**
+ * Initializes an #audio_format object, i.e. sets all
+ * attributes to valid values.
+ */
+static inline void audio_format_init(struct audio_format *af,
+ uint32_t sample_rate,
+ enum sample_format format, uint8_t channels)
+{
+ af->sample_rate = sample_rate;
+ af->format = (uint8_t)format;
+ af->channels = channels;
+ af->reverse_endian = 0;
+}
+
+/**
+ * Checks whether the specified #audio_format object has a defined
+ * value.
+ */
static inline bool audio_format_defined(const struct audio_format *af)
{
return af->sample_rate != 0;
}
/**
+ * Checks whether the specified #audio_format object is full, i.e. all
+ * attributes are defined. This is more complete than
+ * audio_format_defined(), but slower.
+ */
+static inline bool
+audio_format_fully_defined(const struct audio_format *af)
+{
+ return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED &&
+ af->channels != 0;
+}
+
+/**
+ * Checks whether the specified #audio_format object has at least one
+ * defined value.
+ */
+static inline bool
+audio_format_mask_defined(const struct audio_format *af)
+{
+ return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED ||
+ af->channels != 0;
+}
+
+/**
* Checks whether the sample rate is valid.
*
* @param sample_rate the sample rate in Hz
@@ -58,9 +151,20 @@ audio_valid_sample_rate(unsigned sample_rate)
* @param bits the number of significant bits per sample
*/
static inline bool
-audio_valid_sample_format(unsigned bits)
+audio_valid_sample_format(enum sample_format format)
{
- return bits == 16 || bits == 24 || bits == 32 || bits == 8;
+ switch (format) {
+ case SAMPLE_FORMAT_S8:
+ case SAMPLE_FORMAT_S16:
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
+ return true;
+
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+ }
+
+ return false;
}
/**
@@ -79,16 +183,44 @@ audio_valid_channel_count(unsigned channels)
static inline bool audio_format_valid(const struct audio_format *af)
{
return audio_valid_sample_rate(af->sample_rate) &&
- audio_valid_sample_format(af->bits) &&
+ audio_valid_sample_format((enum sample_format)af->format) &&
audio_valid_channel_count(af->channels);
}
+/**
+ * Returns false if the format mask is not valid for playback with
+ * MPD. This function performs some basic validity checks.
+ */
+static inline bool audio_format_mask_valid(const struct audio_format *af)
+{
+ return (af->sample_rate == 0 ||
+ audio_valid_sample_rate(af->sample_rate)) &&
+ (af->format == SAMPLE_FORMAT_UNDEFINED ||
+ audio_valid_sample_format((enum sample_format)af->format)) &&
+ (af->channels == 0 || audio_valid_channel_count(af->channels));
+}
+
static inline bool audio_format_equals(const struct audio_format *a,
const struct audio_format *b)
{
return a->sample_rate == b->sample_rate &&
- a->bits == b->bits &&
- a->channels == b->channels;
+ a->format == b->format &&
+ a->channels == b->channels &&
+ a->reverse_endian == b->reverse_endian;
+}
+
+static inline void
+audio_format_mask_apply(struct audio_format *af,
+ const struct audio_format *mask)
+{
+ if (mask->sample_rate != 0)
+ af->sample_rate = mask->sample_rate;
+
+ if (mask->format != SAMPLE_FORMAT_UNDEFINED)
+ af->format = mask->format;
+
+ if (mask->channels != 0)
+ af->channels = mask->channels;
}
/**
@@ -96,28 +228,62 @@ static inline bool audio_format_equals(const struct audio_format *a,
*/
static inline unsigned audio_format_sample_size(const struct audio_format *af)
{
- if (af->bits <= 8)
+ switch (af->format) {
+ case SAMPLE_FORMAT_S8:
return 1;
- else if (af->bits <= 16)
+
+ case SAMPLE_FORMAT_S16:
return 2;
- else
+
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
return 4;
+
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+ }
+
+ return 0;
}
+/**
+ * Returns the size of each full frame in bytes.
+ */
static inline unsigned
audio_format_frame_size(const struct audio_format *af)
{
return audio_format_sample_size(af) * af->channels;
}
+/**
+ * Returns the floating point factor which converts a time span to a
+ * storage size in bytes.
+ */
static inline double audio_format_time_to_size(const struct audio_format *af)
{
return af->sample_rate * audio_format_frame_size(af);
}
-static inline double audioFormatSizeToTime(const struct audio_format *af)
-{
- return 1.0 / audio_format_time_to_size(af);
-}
+/**
+ * Renders a #sample_format enum into a string, e.g. for printing it
+ * in a log file.
+ *
+ * @param format a #sample_format enum value
+ * @return the string
+ */
+const char *
+sample_format_to_string(enum sample_format format);
+
+/**
+ * Renders the #audio_format object into a string, e.g. for printing
+ * it in a log file.
+ *
+ * @param af the #audio_format object
+ * @param s a buffer to print into
+ * @return the string, or NULL if the #audio_format object is invalid
+ */
+const char *
+audio_format_to_string(const struct audio_format *af,
+ struct audio_format_string *s);
#endif
diff --git a/src/audio_parser.c b/src/audio_parser.c
index 906b0f819..210ea7a62 100644
--- a/src/audio_parser.c
+++ b/src/audio_parser.c
@@ -22,9 +22,12 @@
*
*/
+#include "config.h"
#include "audio_parser.h"
#include "audio_format.h"
+#include "audio_check.h"
+#include <assert.h>
#include <stdlib.h>
/**
@@ -36,64 +39,154 @@ audio_parser_quark(void)
return g_quark_from_static_string("audio_parser");
}
-bool
-audio_format_parse(struct audio_format *dest, const char *src, GError **error)
+static bool
+parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
+ const char **endptr_r, GError **error_r)
{
- char *endptr;
unsigned long value;
+ char *endptr;
- audio_format_clear(dest);
-
- /* parse sample rate */
+ if (mask && *src == '*') {
+ *sample_rate_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
value = strtoul(src, &endptr, 10);
if (endptr == src) {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample rate missing");
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample rate");
return false;
- } else if (*endptr != ':') {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample format missing");
+ } else if (!audio_check_sample_rate(value, error_r))
+ return false;
+
+ *sample_rate_r = value;
+ *endptr_r = endptr;
+ return true;
+}
+
+static bool
+parse_sample_format(const char *src, bool mask,
+ enum sample_format *sample_format_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+ enum sample_format sample_format;
+
+ if (mask && *src == '*') {
+ *sample_format_r = SAMPLE_FORMAT_UNDEFINED;
+ *endptr_r = src + 1;
+ return true;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the sample format");
return false;
- } else if (!audio_valid_sample_rate(value)) {
- g_set_error(error, audio_parser_quark(), 0,
- "Invalid sample rate: %lu", value);
+ }
+
+ switch (value) {
+ case 8:
+ sample_format = SAMPLE_FORMAT_S8;
+ break;
+
+ case 16:
+ sample_format = SAMPLE_FORMAT_S16;
+ break;
+
+ case 24:
+ sample_format = SAMPLE_FORMAT_S24_P32;
+ break;
+
+ case 32:
+ sample_format = SAMPLE_FORMAT_S32;
+ break;
+
+ default:
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Invalid sample format: %lu", value);
return false;
}
- dest->sample_rate = value;
+ assert(audio_valid_sample_format(sample_format));
- /* parse sample format */
+ *sample_format_r = sample_format;
+ *endptr_r = endptr;
+ return true;
+}
+
+static bool
+parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
+ const char **endptr_r, GError **error_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *channels_r = 0;
+ *endptr_r = src + 1;
+ return true;
+ }
- src = endptr + 1;
value = strtoul(src, &endptr, 10);
if (endptr == src) {
- g_set_error(error, audio_parser_quark(), 0,
- "Sample format missing");
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Failed to parse the channel count");
return false;
- } else if (*endptr != ':') {
- g_set_error(error, audio_parser_quark(), 0,
- "Channel count missing");
+ } else if (!audio_check_channel_count(value, error_r))
return false;
- } else if (!audio_valid_sample_format(value)) {
- g_set_error(error, audio_parser_quark(), 0,
- "Invalid sample format: %lu", value);
+
+ *channels_r = value;
+ *endptr_r = endptr;
+ return true;
+}
+
+bool
+audio_format_parse(struct audio_format *dest, const char *src,
+ bool mask, GError **error_r)
+{
+ uint32_t rate;
+ enum sample_format sample_format;
+ uint8_t channels;
+
+ audio_format_clear(dest);
+
+ /* parse sample rate */
+
+ if (!parse_sample_rate(src, mask, &rate, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Sample format missing");
return false;
}
- dest->bits = value;
+ /* parse sample format */
+
+ if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
+ return false;
+
+ if (*src++ != ':') {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Channel count missing");
+ return false;
+ }
/* parse channel count */
- src = endptr + 1;
- value = strtoul(src, &endptr, 10);
- if (*endptr != 0 || !audio_valid_channel_count(value)) {
- g_set_error(error, audio_parser_quark(), 0,
- "Invalid channel count: %s", src);
+ if (!parse_channel_count(src, mask, &channels, &src, error_r))
+ return false;
+
+ if (*src != 0) {
+ g_set_error(error_r, audio_parser_quark(), 0,
+ "Extra data after channel count: %s", src);
return false;
}
- dest->channels = value;
+ audio_format_init(dest, rate, sample_format, channels);
return true;
}
diff --git a/src/audio_parser.h b/src/audio_parser.h
index 30a927456..d50c17489 100644
--- a/src/audio_parser.h
+++ b/src/audio_parser.h
@@ -37,11 +37,13 @@ struct audio_format;
*
* @param dest the destination #audio_format struct
* @param src the input string
- * @param error location to store the error occuring, or NULL to
+ * @param mask if true, then "*" is allowed for any number of items
+ * @param error_r location to store the error occuring, or NULL to
* ignore errors
* @return true on success
*/
bool
-audio_format_parse(struct audio_format *dest, const char *src, GError **error);
+audio_format_parse(struct audio_format *dest, const char *src,
+ bool mask, GError **error_r);
#endif
diff --git a/src/buffer.c b/src/buffer.c
index 24715a744..898197492 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "buffer.h"
#include "chunk.h"
#include "poison.h"
diff --git a/src/buffer2array.c b/src/buffer2array.c
deleted file mode 100644
index b6029d754..000000000
--- a/src/buffer2array.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "buffer2array.h"
-
-#include <glib.h>
-
-#include <string.h>
-
-int buffer2array(char *buffer, char *array[], const int max)
-{
- int i = 0;
- char *c = buffer;
-
- while (*c != '\0' && i < max) {
- if (*c == '\"') {
- array[i++] = ++c;
- while (*c != '\0') {
- if (*c == '\"') {
- *(c++) = '\0';
- break;
- }
- else if (*(c++) == '\\' && *c != '\0') {
- memmove(c - 1, c, strlen(c) + 1);
- }
- }
- } else {
- c = g_strchug(c);
- if (*c == '\0')
- return i;
-
- array[i++] = c++;
-
- while (!g_ascii_isspace(*c) && *c != '\0')
- ++c;
- }
- if (*c == '\0')
- return i;
- *(c++) = '\0';
-
- c = g_strchug(c);
- }
- return i;
-}
-
-#ifdef UNIT_TEST
-
-#include <stdio.h>
-#include <string.h>
-#include <assert.h>
-
-int main()
-{
- char *a[4] = { NULL };
- char *b;
- int max;
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir/name \"test\" something else", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir\\\\name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir\\name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"/some/dir name\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("/some/dir name", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\"\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\"", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"\\\"/some/dir\\\" x\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("\"/some/dir\" x", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"single quote\\'d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("single quote\'d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- b = strdup("lsinfo \"double quote\\\"d from php magicquotes\"");
- assert(b);
- max = buffer2array(b, a, 4);
- assert( !strcmp("lsinfo", a[0]) );
- assert( !strcmp("double quote\"d from php magicquotes", a[1]) );
- assert( !a[2] );
-
- return 0;
-}
-
-#endif
diff --git a/src/check.h b/src/check.h
new file mode 100644
index 000000000..6fdb2535e
--- /dev/null
+++ b/src/check.h
@@ -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.
+ */
+
+#ifndef MPD_CHECK_H
+#define MPD_CHECK_H
+
+/*
+ * All sources must include config.h on the first line to ensure that
+ * Large File Support is configured properly. This header checks
+ * whether this has happened.
+ *
+ * Usage: include this header before you use any of the above types.
+ * It will stop the compiler if something went wrong.
+ *
+ * This is Linux/glibc specific, and only enabled in the debug build,
+ * so bugs in this headers don't affect users with production builds.
+ *
+ */
+
+#ifndef PACKAGE_VERSION
+#error config.h missing
+#endif
+
+#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \
+ defined(_FEATURES_H) && defined(__i386__) && \
+ !defined(__USE_FILE_OFFSET64)
+/* on i386, check if LFS is enabled */
+#error config.h was included too late
+#endif
+
+#endif
diff --git a/src/chunk.c b/src/chunk.c
index 3ac190633..9cfaa010a 100644
--- a/src/chunk.c
+++ b/src/chunk.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "chunk.h"
#include "audio_format.h"
#include "tag.h"
diff --git a/src/client.c b/src/client.c
index 6a256998f..dae7b8d20 100644
--- a/src/client.c
+++ b/src/client.c
@@ -17,110 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "client.h"
-#include "fifo_buffer.h"
-#include "command.h"
-#include "conf.h"
-#include "listen.h"
-#include "socket_util.h"
-#include "permission.h"
-#include "event_pipe.h"
-#include "idle.h"
-#include "main.h"
#include "config.h"
-
-#include <glib.h>
-#include <assert.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-
-#ifdef WIN32
-#include <ws2tcpip.h>
-#include <winsock.h>
-#else
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "client"
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
-
-static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
-
-#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
-#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
-#define CLIENT_LIST_MODE_END "command_list_end"
-#define CLIENT_TIMEOUT_DEFAULT (60)
-#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
-#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
-#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
-
-/* set this to zero to indicate we have no possible clients */
-static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
-static int client_timeout;
-static size_t client_max_command_list_size;
-static size_t client_max_output_buffer_size;
-
-struct deferred_buffer {
- size_t size;
- char data[sizeof(long)];
-};
-
-struct client {
- GIOChannel *channel;
- guint source_id;
-
- /** the buffer for reading lines from the #channel */
- struct fifo_buffer *input;
-
- unsigned permission;
-
- /** the uid of the client process, or -1 if unknown */
- int uid;
-
- /**
- * How long since the last activity from this client?
- */
- GTimer *last_activity;
-
- GSList *cmd_list; /* for when in list mode */
- int cmd_list_OK; /* print OK after each command execution */
- size_t cmd_list_size; /* mem cmd_list consumes */
- GQueue *deferred_send; /* for output if client is slow */
- size_t deferred_bytes; /* mem deferred_send consumes */
- unsigned int num; /* client number */
-
- char send_buf[4096];
- size_t send_buf_used; /* bytes used this instance */
-
- /** is this client waiting for an "idle" response? */
- bool idle_waiting;
-
- /** idle flags pending on this client, to be sent as soon as
- the client enters "idle" */
- unsigned idle_flags;
-
- /** idle flags that the client wants to receive */
- unsigned idle_subscriptions;
-};
-
-static GList *clients;
-static unsigned num_clients;
-static guint expire_source_id;
-
-static void client_write_deferred(struct client *client);
-
-static void client_write_output(struct client *client);
-
-static void client_manager_expire(void);
-
-static gboolean
-client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
+#include "client_internal.h"
bool client_is_expired(const struct client *client)
{
@@ -141,782 +39,3 @@ void client_set_permission(struct client *client, unsigned permission)
{
client->permission = permission;
}
-
-/**
- * An idle event which calls client_manager_expire().
- */
-static gboolean
-client_manager_expire_event(G_GNUC_UNUSED gpointer data)
-{
- expire_source_id = 0;
- client_manager_expire();
- return false;
-}
-
-static inline void client_set_expired(struct client *client)
-{
- if (expire_source_id == 0 && !client_is_expired(client))
- /* delayed deletion */
- expire_source_id = g_idle_add(client_manager_expire_event,
- NULL);
-
- if (client->source_id != 0) {
- g_source_remove(client->source_id);
- client->source_id = 0;
- }
-
- if (client->channel != NULL) {
- g_io_channel_unref(client->channel);
- client->channel = NULL;
- }
-}
-
-static void client_init(struct client *client, int fd)
-{
- static unsigned int next_client_num;
-
- assert(fd >= 0);
-
- client->cmd_list_size = 0;
- client->cmd_list_OK = -1;
-
-#ifndef G_OS_WIN32
- client->channel = g_io_channel_unix_new(fd);
-#else
- client->channel = g_io_channel_win32_new_socket(fd);
-#endif
- /* GLib is responsible for closing the file descriptor */
- g_io_channel_set_close_on_unref(client->channel, true);
- /* NULL encoding means the stream is binary safe; the MPD
- protocol is UTF-8 only, but we are doing this call anyway
- to prevent GLib from messing around with the stream */
- g_io_channel_set_encoding(client->channel, NULL, NULL);
- /* we prefer to do buffering */
- g_io_channel_set_buffered(client->channel, false);
-
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
-
- client->input = fifo_buffer_new(4096);
-
- client->cmd_list = NULL;
- client->deferred_send = g_queue_new();
- client->deferred_bytes = 0;
- client->num = next_client_num++;
- client->send_buf_used = 0;
-
- client->permission = getDefaultPermissions();
-
- (void)write(fd, GREETING, sizeof(GREETING) - 1);
-}
-
-static void free_cmd_list(GSList *list)
-{
- for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
- g_free(tmp->data);
-
- g_slist_free(list);
-}
-
-static void new_cmd_list_ptr(struct client *client, char *s)
-{
- client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
-}
-
-static void
-deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct deferred_buffer *buffer = data;
- g_free(buffer);
-}
-
-static void client_close(struct client *client)
-{
- assert(num_clients > 0);
- assert(clients != NULL);
-
- clients = g_list_remove(clients, client);
- --num_clients;
-
- client_set_expired(client);
-
- g_timer_destroy(client->last_activity);
-
- if (client->cmd_list) {
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- }
-
- g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
- g_queue_free(client->deferred_send);
-
- fifo_buffer_free(client->input);
-
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] closed", client->num);
- g_free(client);
-}
-
-void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
-{
- struct client *client;
- char *remote;
-
- if (num_clients >= client_max_connections) {
- g_warning("Max Connections Reached!");
- close(fd);
- return;
- }
-
- client = g_new0(struct client, 1);
- clients = g_list_prepend(clients, client);
- ++num_clients;
-
- client_init(client, fd);
- client->uid = uid;
-
- client->last_activity = g_timer_new();
-
- remote = sockaddr_to_string(sa, sa_length, NULL);
- g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
- "[%u] opened from %s", client->num, remote);
- g_free(remote);
-}
-
-static int client_process_line(struct client *client, char *line)
-{
- int ret = 1;
-
- if (strcmp(line, "noidle") == 0) {
- if (client->idle_waiting) {
- /* send empty idle response and leave idle mode */
- client->idle_waiting = false;
- command_success(client);
- client_write_output(client);
- }
-
- /* do nothing if the client wasn't idling: the client
- has already received the full idle response from
- client_idle_notify(), which he can now evaluate */
-
- return 0;
- } else if (client->idle_waiting) {
- /* during idle mode, clients must not send anything
- except "noidle" */
- g_warning("[%u] command \"%s\" during idle",
- client->num, line);
- return COMMAND_RETURN_CLOSE;
- }
-
- if (client->cmd_list_OK >= 0) {
- if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
- g_debug("[%u] process command list",
- client->num);
-
- /* for scalability reasons, we have prepended
- each new command; now we have to reverse it
- to restore the correct order */
- client->cmd_list = g_slist_reverse(client->cmd_list);
-
- ret = command_process_list(client,
- client->cmd_list_OK,
- client->cmd_list);
- g_debug("[%u] process command "
- "list returned %i", client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == 0)
- command_success(client);
-
- client_write_output(client);
- free_cmd_list(client->cmd_list);
- client->cmd_list = NULL;
- client->cmd_list_OK = -1;
- } else {
- size_t len = strlen(line) + 1;
- client->cmd_list_size += len;
- if (client->cmd_list_size >
- client_max_command_list_size) {
- g_warning("[%u] command list size (%lu) "
- "is larger than the max (%lu)",
- client->num,
- (unsigned long)client->cmd_list_size,
- (unsigned long)client_max_command_list_size);
- return COMMAND_RETURN_CLOSE;
- } else
- new_cmd_list_ptr(client, line);
- }
- } else {
- if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 0;
- ret = 1;
- } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
- client->cmd_list_OK = 1;
- ret = 1;
- } else {
- g_debug("[%u] process command \"%s\"",
- client->num, line);
- ret = command_process(client, line);
- g_debug("[%u] command returned %i",
- client->num, ret);
-
- if (ret == COMMAND_RETURN_CLOSE ||
- client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
-
- if (ret == 0)
- command_success(client);
-
- client_write_output(client);
- }
- }
-
- return ret;
-}
-
-static char *
-client_read_line(struct client *client)
-{
- const char *p, *newline;
- size_t length;
- char *line;
-
- p = fifo_buffer_read(client->input, &length);
- if (p == NULL)
- return NULL;
-
- newline = memchr(p, '\n', length);
- if (newline == NULL)
- return NULL;
-
- line = g_strndup(p, newline - p);
- fifo_buffer_consume(client->input, newline - p + 1);
-
- return g_strchomp(line);
-}
-
-static int client_input_received(struct client *client, size_t bytesRead)
-{
- char *line;
- int ret;
-
- fifo_buffer_append(client->input, bytesRead);
-
- /* process all lines */
-
- while ((line = client_read_line(client)) != NULL) {
- ret = client_process_line(client, line);
- g_free(line);
-
- if (ret == COMMAND_RETURN_KILL ||
- ret == COMMAND_RETURN_CLOSE)
- return ret;
- if (client_is_expired(client))
- return COMMAND_RETURN_CLOSE;
- }
-
- return 0;
-}
-
-static int client_read(struct client *client)
-{
- char *p;
- size_t max_length;
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_read;
-
- assert(client != NULL);
- assert(client->channel != NULL);
-
- p = fifo_buffer_write(client->input, &max_length);
- if (p == NULL) {
- g_warning("[%u] buffer overflow", client->num);
- return COMMAND_RETURN_CLOSE;
- }
-
- status = g_io_channel_read_chars(client->channel, p, max_length,
- &bytes_read, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return client_input_received(client, bytes_read);
-
- case G_IO_STATUS_AGAIN:
- /* try again later, after select() */
- return 0;
-
- case G_IO_STATUS_EOF:
- /* peer disconnected */
- return COMMAND_RETURN_CLOSE;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
- g_warning("failed to read from client %d: %s",
- client->num, error->message);
- g_error_free(error);
- return COMMAND_RETURN_CLOSE;
- }
-
- /* unreachable */
- return COMMAND_RETURN_CLOSE;
-}
-
-static gboolean
-client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data);
-
-static gboolean
-client_in_event(G_GNUC_UNUSED GIOChannel *source,
- GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
- int ret;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_IN) {
- client_set_expired(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- ret = client_read(client);
- switch (ret) {
- case COMMAND_RETURN_KILL:
- client_close(client);
- g_main_loop_quit(main_loop);
- return false;
-
- case COMMAND_RETURN_CLOSE:
- client_close(client);
- return false;
- }
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- if (!g_queue_is_empty(client->deferred_send)) {
- /* deferred buffers exist: schedule write */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_OUT|G_IO_ERR|G_IO_HUP,
- client_out_event, client);
- return false;
- }
-
- /* read more */
- return true;
-}
-
-static gboolean
-client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
- gpointer data)
-{
- struct client *client = data;
-
- assert(!client_is_expired(client));
-
- if (condition != G_IO_OUT) {
- client_set_expired(client);
- return false;
- }
-
- client_write_deferred(client);
-
- if (client_is_expired(client)) {
- client_close(client);
- return false;
- }
-
- g_timer_start(client->last_activity);
-
- if (g_queue_is_empty(client->deferred_send)) {
- /* done sending deferred buffers exist: schedule
- read */
- client->source_id = g_io_add_watch(client->channel,
- G_IO_IN|G_IO_ERR|G_IO_HUP,
- client_in_event, client);
- return false;
- }
-
- /* write more */
- return true;
-}
-
-void client_manager_init(void)
-{
- client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
- CLIENT_TIMEOUT_DEFAULT);
- client_max_connections =
- config_get_positive(CONF_MAX_CONN,
- CLIENT_MAX_CONNECTIONS_DEFAULT);
- client_max_command_list_size =
- config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
- CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
- * 1024;
-
- client_max_output_buffer_size =
- config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
- CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
- * 1024;
-}
-
-static void client_close_all(void)
-{
- while (clients != NULL) {
- struct client *client = clients->data;
-
- client_close(client);
- }
-
- assert(num_clients == 0);
-}
-
-void client_manager_deinit(void)
-{
- client_close_all();
-
- client_max_connections = 0;
-
- if (expire_source_id != 0)
- g_source_remove(expire_source_id);
-}
-
-static void
-client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
-{
- struct client *client = data;
-
- if (client_is_expired(client)) {
- g_debug("[%u] expired", client->num);
- client_close(client);
- } else if (!client->idle_waiting && /* idle clients
- never expire */
- (int)g_timer_elapsed(client->last_activity, NULL) >
- client_timeout) {
- g_debug("[%u] timeout", client->num);
- client_close(client);
- }
-}
-
-static void
-client_manager_expire(void)
-{
- g_list_foreach(clients, client_check_expired_callback, NULL);
-}
-
-static size_t
-client_write_deferred_buffer(struct client *client,
- const struct deferred_buffer *buffer)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(buffer != NULL);
-
- status = g_io_channel_write_chars
- (client->channel, buffer->data, buffer->size,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- return bytes_written;
-
- case G_IO_STATUS_AGAIN:
- return 0;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return 0;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to flush buffer for %i: %s",
- client->num, error->message);
- g_error_free(error);
- return 0;
- }
-
- /* unreachable */
- return 0;
-}
-
-static void client_write_deferred(struct client *client)
-{
- size_t ret;
-
- while (!g_queue_is_empty(client->deferred_send)) {
- struct deferred_buffer *buf =
- g_queue_peek_head(client->deferred_send);
-
- assert(buf->size > 0);
- assert(buf->size <= client->deferred_bytes);
-
- ret = client_write_deferred_buffer(client, buf);
- if (ret == 0)
- break;
-
- if (ret < buf->size) {
- assert(client->deferred_bytes >= (size_t)ret);
- client->deferred_bytes -= ret;
- buf->size -= ret;
- memmove(buf->data, buf->data + ret, buf->size);
- break;
- } else {
- size_t decr = sizeof(*buf) -
- sizeof(buf->data) + buf->size;
-
- assert(client->deferred_bytes >= decr);
- client->deferred_bytes -= decr;
- g_free(buf);
- g_queue_pop_head(client->deferred_send);
- }
-
- g_timer_start(client->last_activity);
- }
-
- if (g_queue_is_empty(client->deferred_send)) {
- g_debug("[%u] buffer empty %lu", client->num,
- (unsigned long)client->deferred_bytes);
- assert(client->deferred_bytes == 0);
- }
-}
-
-static void client_defer_output(struct client *client,
- const void *data, size_t length)
-{
- size_t alloc;
- struct deferred_buffer *buf;
-
- assert(length > 0);
-
- alloc = sizeof(*buf) - sizeof(buf->data) + length;
- client->deferred_bytes += alloc;
- if (client->deferred_bytes > client_max_output_buffer_size) {
- g_warning("[%u] output buffer size (%lu) is "
- "larger than the max (%lu)",
- client->num,
- (unsigned long)client->deferred_bytes,
- (unsigned long)client_max_output_buffer_size);
- /* cause client to close */
- client_set_expired(client);
- return;
- }
-
- buf = g_malloc(alloc);
- buf->size = length;
- memcpy(buf->data, data, length);
-
- g_queue_push_tail(client->deferred_send, buf);
-}
-
-static void client_write_direct(struct client *client,
- const char *data, size_t length)
-{
- GError *error = NULL;
- GIOStatus status;
- gsize bytes_written;
-
- assert(client != NULL);
- assert(client->channel != NULL);
- assert(data != NULL);
- assert(length > 0);
- assert(g_queue_is_empty(client->deferred_send));
-
- status = g_io_channel_write_chars(client->channel, data, length,
- &bytes_written, &error);
- switch (status) {
- case G_IO_STATUS_NORMAL:
- case G_IO_STATUS_AGAIN:
- break;
-
- case G_IO_STATUS_EOF:
- /* client has disconnected */
-
- client_set_expired(client);
- return;
-
- case G_IO_STATUS_ERROR:
- /* I/O error */
-
- client_set_expired(client);
- g_warning("failed to write to %i: %s",
- client->num, error->message);
- g_error_free(error);
- return;
- }
-
- if (bytes_written < length)
- client_defer_output(client, data + bytes_written,
- length - bytes_written);
-
- if (!g_queue_is_empty(client->deferred_send))
- g_debug("[%u] buffer created", client->num);
-}
-
-static void client_write_output(struct client *client)
-{
- if (client_is_expired(client) || !client->send_buf_used)
- return;
-
- if (!g_queue_is_empty(client->deferred_send)) {
- client_defer_output(client, client->send_buf,
- client->send_buf_used);
-
- if (client_is_expired(client))
- return;
-
- /* try to flush the deferred buffers now; the current
- server command may take too long to finish, and
- meanwhile try to feed output to the client,
- otherwise it will time out. One reason why
- deferring is slow might be that currently each
- client_write() allocates a new deferred buffer.
- This should be optimized after MPD 0.14. */
- client_write_deferred(client);
- } else
- client_write_direct(client, client->send_buf,
- client->send_buf_used);
-
- client->send_buf_used = 0;
-}
-
-/**
- * Write a block of data to the client.
- */
-static void client_write(struct client *client, const char *buffer, size_t buflen)
-{
- /* if the client is going to be closed, do nothing */
- if (client_is_expired(client))
- return;
-
- while (buflen > 0 && !client_is_expired(client)) {
- size_t copylen;
-
- assert(client->send_buf_used < sizeof(client->send_buf));
-
- copylen = sizeof(client->send_buf) - client->send_buf_used;
- if (copylen > buflen)
- copylen = buflen;
-
- memcpy(client->send_buf + client->send_buf_used, buffer,
- copylen);
- buflen -= copylen;
- client->send_buf_used += copylen;
- buffer += copylen;
- if (client->send_buf_used >= sizeof(client->send_buf))
- client_write_output(client);
- }
-}
-
-void client_puts(struct client *client, const char *s)
-{
- client_write(client, s, strlen(s));
-}
-
-void client_vprintf(struct client *client, const char *fmt, va_list args)
-{
- va_list tmp;
- int length;
- char *buffer;
-
- va_copy(tmp, args);
- length = vsnprintf(NULL, 0, fmt, tmp);
- va_end(tmp);
-
- if (length <= 0)
- /* wtf.. */
- return;
-
- buffer = g_malloc(length + 1);
- vsnprintf(buffer, length + 1, fmt, args);
- client_write(client, buffer, length);
- g_free(buffer);
-}
-
-G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
-{
- va_list args;
-
- va_start(args, fmt);
- client_vprintf(client, fmt, args);
- va_end(args);
-}
-
-/**
- * Send "idle" response to this client.
- */
-static void
-client_idle_notify(struct client *client)
-{
- unsigned flags, i;
- const char *const* idle_names;
-
- assert(client->idle_waiting);
- assert(client->idle_flags != 0);
-
- flags = client->idle_flags;
- client->idle_flags = 0;
- client->idle_waiting = false;
-
- idle_names = idle_get_names();
- for (i = 0; idle_names[i]; ++i) {
- if (flags & (1 << i) & client->idle_subscriptions)
- client_printf(client, "changed: %s\n",
- idle_names[i]);
- }
-
- client_puts(client, "OK\n");
- g_timer_start(client->last_activity);
-}
-
-static void
-client_idle_callback(gpointer data, gpointer user_data)
-{
- struct client *client = data;
- unsigned flags = GPOINTER_TO_UINT(user_data);
-
- if (client_is_expired(client))
- return;
-
- client->idle_flags |= flags;
- if (client->idle_waiting
- && (client->idle_flags & client->idle_subscriptions)) {
- client_idle_notify(client);
- client_write_output(client);
- }
-}
-
-void client_manager_idle_add(unsigned flags)
-{
- assert(flags != 0);
-
- g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags));
-}
-
-bool client_idle_wait(struct client *client, unsigned flags)
-{
- assert(!client->idle_waiting);
-
- client->idle_waiting = true;
- client->idle_subscriptions = flags;
-
- if (client->idle_flags & client->idle_subscriptions) {
- client_idle_notify(client);
- return true;
- } else
- return false;
-}
diff --git a/src/client_event.c b/src/client_event.c
new file mode 100644
index 000000000..e67bb1d70
--- /dev/null
+++ b/src/client_event.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 "client_internal.h"
+#include "main.h"
+
+#include <assert.h>
+
+static gboolean
+client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct client *client = data;
+
+ assert(!client_is_expired(client));
+
+ if (condition != G_IO_OUT) {
+ client_set_expired(client);
+ return false;
+ }
+
+ client_write_deferred(client);
+
+ if (client_is_expired(client)) {
+ client_close(client);
+ return false;
+ }
+
+ g_timer_start(client->last_activity);
+
+ if (g_queue_is_empty(client->deferred_send)) {
+ /* done sending deferred buffers exist: schedule
+ read */
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_IN|G_IO_ERR|G_IO_HUP,
+ client_in_event, client);
+ return false;
+ }
+
+ /* write more */
+ return true;
+}
+
+gboolean
+client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
+ gpointer data)
+{
+ struct client *client = data;
+ enum command_return ret;
+
+ assert(!client_is_expired(client));
+
+ if (condition != G_IO_IN) {
+ client_set_expired(client);
+ return false;
+ }
+
+ g_timer_start(client->last_activity);
+
+ ret = client_read(client);
+ switch (ret) {
+ case COMMAND_RETURN_OK:
+ case COMMAND_RETURN_ERROR:
+ break;
+
+ case COMMAND_RETURN_KILL:
+ client_close(client);
+ g_main_loop_quit(main_loop);
+ return false;
+
+ case COMMAND_RETURN_CLOSE:
+ client_close(client);
+ return false;
+ }
+
+ if (client_is_expired(client)) {
+ client_close(client);
+ return false;
+ }
+
+ if (!g_queue_is_empty(client->deferred_send)) {
+ /* deferred buffers exist: schedule write */
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_OUT|G_IO_ERR|G_IO_HUP,
+ client_out_event, client);
+ return false;
+ }
+
+ /* read more */
+ return true;
+}
diff --git a/src/client_expire.c b/src/client_expire.c
new file mode 100644
index 000000000..f544a0ff5
--- /dev/null
+++ b/src/client_expire.c
@@ -0,0 +1,90 @@
+/*
+ * 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 "client_internal.h"
+
+static guint expire_source_id;
+
+void
+client_set_expired(struct client *client)
+{
+ if (!client_is_expired(client))
+ client_schedule_expire();
+
+ if (client->source_id != 0) {
+ g_source_remove(client->source_id);
+ client->source_id = 0;
+ }
+
+ if (client->channel != NULL) {
+ g_io_channel_unref(client->channel);
+ client->channel = NULL;
+ }
+}
+
+static void
+client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct client *client = data;
+
+ if (client_is_expired(client)) {
+ g_debug("[%u] expired", client->num);
+ client_close(client);
+ } else if (!client->idle_waiting && /* idle clients
+ never expire */
+ (int)g_timer_elapsed(client->last_activity, NULL) >
+ client_timeout) {
+ g_debug("[%u] timeout", client->num);
+ client_close(client);
+ }
+}
+
+static void
+client_manager_expire(void)
+{
+ client_list_foreach(client_check_expired_callback, NULL);
+}
+
+/**
+ * An idle event which calls client_manager_expire().
+ */
+static gboolean
+client_manager_expire_event(G_GNUC_UNUSED gpointer data)
+{
+ expire_source_id = 0;
+ client_manager_expire();
+ return false;
+}
+
+void
+client_schedule_expire(void)
+{
+ if (expire_source_id == 0)
+ /* delayed deletion */
+ expire_source_id = g_idle_add(client_manager_expire_event,
+ NULL);
+}
+
+void
+client_deinit_expire(void)
+{
+ if (expire_source_id != 0)
+ g_source_remove(expire_source_id);
+}
diff --git a/src/client_global.c b/src/client_global.c
new file mode 100644
index 000000000..2c5e26416
--- /dev/null
+++ b/src/client_global.c
@@ -0,0 +1,73 @@
+/*
+ * 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 "client_internal.h"
+#include "conf.h"
+
+#include <assert.h>
+
+#define CLIENT_TIMEOUT_DEFAULT (60)
+#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
+#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
+#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
+
+/* set this to zero to indicate we have no possible clients */
+unsigned int client_max_connections;
+int client_timeout;
+size_t client_max_command_list_size;
+size_t client_max_output_buffer_size;
+
+void client_manager_init(void)
+{
+ client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
+ CLIENT_TIMEOUT_DEFAULT);
+ client_max_connections =
+ config_get_positive(CONF_MAX_CONN,
+ CLIENT_MAX_CONNECTIONS_DEFAULT);
+ client_max_command_list_size =
+ config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
+ CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
+ * 1024;
+
+ client_max_output_buffer_size =
+ config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
+ CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
+ * 1024;
+}
+
+static void client_close_all(void)
+{
+ while (!client_list_is_empty()) {
+ struct client *client = client_list_get_first();
+
+ client_close(client);
+ }
+
+ assert(client_list_is_empty());
+}
+
+void client_manager_deinit(void)
+{
+ client_close_all();
+
+ client_max_connections = 0;
+
+ client_deinit_expire();
+}
diff --git a/src/client_idle.c b/src/client_idle.c
new file mode 100644
index 000000000..23011b8c5
--- /dev/null
+++ b/src/client_idle.c
@@ -0,0 +1,89 @@
+/*
+ * 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 "client_internal.h"
+#include "idle.h"
+
+#include <assert.h>
+
+/**
+ * Send "idle" response to this client.
+ */
+static void
+client_idle_notify(struct client *client)
+{
+ unsigned flags, i;
+ const char *const* idle_names;
+
+ assert(client->idle_waiting);
+ assert(client->idle_flags != 0);
+
+ flags = client->idle_flags;
+ client->idle_flags = 0;
+ client->idle_waiting = false;
+
+ idle_names = idle_get_names();
+ for (i = 0; idle_names[i]; ++i) {
+ if (flags & (1 << i) & client->idle_subscriptions)
+ client_printf(client, "changed: %s\n",
+ idle_names[i]);
+ }
+
+ client_puts(client, "OK\n");
+ g_timer_start(client->last_activity);
+}
+
+static void
+client_idle_callback(gpointer data, gpointer user_data)
+{
+ struct client *client = data;
+ unsigned flags = GPOINTER_TO_UINT(user_data);
+
+ if (client_is_expired(client))
+ return;
+
+ client->idle_flags |= flags;
+ if (client->idle_waiting
+ && (client->idle_flags & client->idle_subscriptions)) {
+ client_idle_notify(client);
+ client_write_output(client);
+ }
+}
+
+void client_manager_idle_add(unsigned flags)
+{
+ assert(flags != 0);
+
+ client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
+}
+
+bool client_idle_wait(struct client *client, unsigned flags)
+{
+ assert(!client->idle_waiting);
+
+ client->idle_waiting = true;
+ client->idle_subscriptions = flags;
+
+ if (client->idle_flags & client->idle_subscriptions) {
+ client_idle_notify(client);
+ return true;
+ } else
+ return false;
+}
diff --git a/src/client_internal.h b/src/client_internal.h
new file mode 100644
index 000000000..91e360fe0
--- /dev/null
+++ b/src/client_internal.h
@@ -0,0 +1,145 @@
+/*
+ * 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_CLIENT_INTERNAL_H
+#define MPD_CLIENT_INTERNAL_H
+
+#include "client.h"
+#include "command.h"
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "client"
+
+struct deferred_buffer {
+ size_t size;
+ char data[sizeof(long)];
+};
+
+struct client {
+ GIOChannel *channel;
+ guint source_id;
+
+ /** the buffer for reading lines from the #channel */
+ struct fifo_buffer *input;
+
+ unsigned permission;
+
+ /** the uid of the client process, or -1 if unknown */
+ int uid;
+
+ /**
+ * How long since the last activity from this client?
+ */
+ GTimer *last_activity;
+
+ GSList *cmd_list; /* for when in list mode */
+ int cmd_list_OK; /* print OK after each command execution */
+ size_t cmd_list_size; /* mem cmd_list consumes */
+ GQueue *deferred_send; /* for output if client is slow */
+ size_t deferred_bytes; /* mem deferred_send consumes */
+ unsigned int num; /* client number */
+
+ char send_buf[4096];
+ size_t send_buf_used; /* bytes used this instance */
+
+ /** is this client waiting for an "idle" response? */
+ bool idle_waiting;
+
+ /** idle flags pending on this client, to be sent as soon as
+ the client enters "idle" */
+ unsigned idle_flags;
+
+ /** idle flags that the client wants to receive */
+ unsigned idle_subscriptions;
+};
+
+extern unsigned int client_max_connections;
+extern int client_timeout;
+extern size_t client_max_command_list_size;
+extern size_t client_max_output_buffer_size;
+
+bool
+client_list_is_empty(void);
+
+bool
+client_list_is_full(void);
+
+struct client *
+client_list_get_first(void);
+
+void
+client_list_add(struct client *client);
+
+void
+client_list_foreach(GFunc func, gpointer user_data);
+
+void
+client_list_remove(struct client *client);
+
+void
+client_close(struct client *client);
+
+static inline void
+new_cmd_list_ptr(struct client *client, const char *s)
+{
+ client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
+}
+
+static inline void
+free_cmd_list(GSList *list)
+{
+ for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
+ g_free(tmp->data);
+
+ g_slist_free(list);
+}
+
+void
+client_set_expired(struct client *client);
+
+/**
+ * Schedule an "expired" check for all clients: permanently delete
+ * clients which have been set "expired" with client_set_expired().
+ */
+void
+client_schedule_expire(void);
+
+/**
+ * Removes a scheduled "expired" check.
+ */
+void
+client_deinit_expire(void);
+
+enum command_return
+client_read(struct client *client);
+
+enum command_return
+client_process_line(struct client *client, char *line);
+
+void
+client_write_deferred(struct client *client);
+
+void
+client_write_output(struct client *client);
+
+gboolean
+client_in_event(GIOChannel *source, GIOCondition condition,
+ gpointer data);
+
+#endif
diff --git a/src/client_list.c b/src/client_list.c
new file mode 100644
index 000000000..f2134d5f2
--- /dev/null
+++ b/src/client_list.c
@@ -0,0 +1,69 @@
+/*
+ * 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 "client_internal.h"
+
+#include <assert.h>
+
+static GList *clients;
+static unsigned num_clients;
+
+bool
+client_list_is_empty(void)
+{
+ return num_clients == 0;
+}
+
+bool
+client_list_is_full(void)
+{
+ return num_clients >= client_max_connections;
+}
+
+struct client *
+client_list_get_first(void)
+{
+ assert(clients != NULL);
+
+ return clients->data;
+}
+
+void
+client_list_add(struct client *client)
+{
+ clients = g_list_prepend(clients, client);
+ ++num_clients;
+}
+
+void
+client_list_foreach(GFunc func, gpointer user_data)
+{
+ g_list_foreach(clients, func, user_data);
+}
+
+void
+client_list_remove(struct client *client)
+{
+ assert(num_clients > 0);
+ assert(clients != NULL);
+
+ clients = g_list_remove(clients, client);
+ --num_clients;
+}
diff --git a/src/client_new.c b/src/client_new.c
new file mode 100644
index 000000000..c2c3a1e30
--- /dev/null
+++ b/src/client_new.c
@@ -0,0 +1,123 @@
+/*
+ * 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 "client_internal.h"
+#include "fifo_buffer.h"
+#include "socket_util.h"
+#include "permission.h"
+
+#include <assert.h>
+#include <unistd.h>
+
+#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+
+static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
+
+void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
+{
+ static unsigned int next_client_num;
+ struct client *client;
+ char *remote;
+
+ assert(fd >= 0);
+
+ if (client_list_is_full()) {
+ g_warning("Max Connections Reached!");
+ close(fd);
+ return;
+ }
+
+ client = g_new0(struct client, 1);
+
+#ifndef G_OS_WIN32
+ client->channel = g_io_channel_unix_new(fd);
+#else
+ client->channel = g_io_channel_win32_new_socket(fd);
+#endif
+ /* GLib is responsible for closing the file descriptor */
+ g_io_channel_set_close_on_unref(client->channel, true);
+ /* NULL encoding means the stream is binary safe; the MPD
+ protocol is UTF-8 only, but we are doing this call anyway
+ to prevent GLib from messing around with the stream */
+ g_io_channel_set_encoding(client->channel, NULL, NULL);
+ /* we prefer to do buffering */
+ g_io_channel_set_buffered(client->channel, false);
+
+ client->source_id = g_io_add_watch(client->channel,
+ G_IO_IN|G_IO_ERR|G_IO_HUP,
+ client_in_event, client);
+
+ client->input = fifo_buffer_new(4096);
+
+ client->permission = getDefaultPermissions();
+ client->uid = uid;
+
+ client->last_activity = g_timer_new();
+
+ client->cmd_list = NULL;
+ client->cmd_list_OK = -1;
+ client->cmd_list_size = 0;
+
+ client->deferred_send = g_queue_new();
+ client->deferred_bytes = 0;
+ client->num = next_client_num++;
+
+ client->send_buf_used = 0;
+
+ (void)write(fd, GREETING, sizeof(GREETING) - 1);
+
+ client_list_add(client);
+
+ remote = sockaddr_to_string(sa, sa_length, NULL);
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "[%u] opened from %s", client->num, remote);
+ g_free(remote);
+}
+
+static void
+deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct deferred_buffer *buffer = data;
+ g_free(buffer);
+}
+
+void
+client_close(struct client *client)
+{
+ client_list_remove(client);
+
+ client_set_expired(client);
+
+ g_timer_destroy(client->last_activity);
+
+ if (client->cmd_list) {
+ free_cmd_list(client->cmd_list);
+ client->cmd_list = NULL;
+ }
+
+ g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
+ g_queue_free(client->deferred_send);
+
+ fifo_buffer_free(client->input);
+
+ g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
+ "[%u] closed", client->num);
+ g_free(client);
+}
diff --git a/src/client_process.c b/src/client_process.c
new file mode 100644
index 000000000..18976c941
--- /dev/null
+++ b/src/client_process.c
@@ -0,0 +1,146 @@
+/*
+ * 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 "client_internal.h"
+
+#include <string.h>
+
+#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
+#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
+#define CLIENT_LIST_MODE_END "command_list_end"
+
+static enum command_return
+client_process_command_list(struct client *client, bool list_ok, GSList *list)
+{
+ enum command_return ret = COMMAND_RETURN_OK;
+ unsigned num = 0;
+
+ for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
+ char *cmd = cur->data;
+
+ g_debug("command_process_list: process command \"%s\"",
+ cmd);
+ ret = command_process(client, num++, cmd);
+ g_debug("command_process_list: command returned %i", ret);
+ if (ret != COMMAND_RETURN_OK || client_is_expired(client))
+ break;
+ else if (list_ok)
+ client_puts(client, "list_OK\n");
+ }
+
+ return ret;
+}
+
+enum command_return
+client_process_line(struct client *client, char *line)
+{
+ enum command_return ret;
+
+ if (strcmp(line, "noidle") == 0) {
+ if (client->idle_waiting) {
+ /* send empty idle response and leave idle mode */
+ client->idle_waiting = false;
+ command_success(client);
+ client_write_output(client);
+ }
+
+ /* do nothing if the client wasn't idling: the client
+ has already received the full idle response from
+ client_idle_notify(), which he can now evaluate */
+
+ return COMMAND_RETURN_OK;
+ } else if (client->idle_waiting) {
+ /* during idle mode, clients must not send anything
+ except "noidle" */
+ g_warning("[%u] command \"%s\" during idle",
+ client->num, line);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ if (client->cmd_list_OK >= 0) {
+ if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
+ g_debug("[%u] process command list",
+ client->num);
+
+ /* for scalability reasons, we have prepended
+ each new command; now we have to reverse it
+ to restore the correct order */
+ client->cmd_list = g_slist_reverse(client->cmd_list);
+
+ ret = client_process_command_list(client,
+ client->cmd_list_OK,
+ client->cmd_list);
+ g_debug("[%u] process command "
+ "list returned %i", client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+
+ client_write_output(client);
+ free_cmd_list(client->cmd_list);
+ client->cmd_list = NULL;
+ client->cmd_list_OK = -1;
+ } else {
+ size_t len = strlen(line) + 1;
+ client->cmd_list_size += len;
+ if (client->cmd_list_size >
+ client_max_command_list_size) {
+ g_warning("[%u] command list size (%lu) "
+ "is larger than the max (%lu)",
+ client->num,
+ (unsigned long)client->cmd_list_size,
+ (unsigned long)client_max_command_list_size);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ new_cmd_list_ptr(client, line);
+ ret = COMMAND_RETURN_OK;
+ }
+ } else {
+ if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
+ client->cmd_list_OK = 0;
+ ret = COMMAND_RETURN_OK;
+ } else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
+ client->cmd_list_OK = 1;
+ ret = COMMAND_RETURN_OK;
+ } else {
+ g_debug("[%u] process command \"%s\"",
+ client->num, line);
+ ret = command_process(client, 0, line);
+ g_debug("[%u] command returned %i",
+ client->num, ret);
+
+ if (ret == COMMAND_RETURN_CLOSE ||
+ client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+
+ if (ret == COMMAND_RETURN_OK)
+ command_success(client);
+
+ client_write_output(client);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/client_read.c b/src/client_read.c
new file mode 100644
index 000000000..534bf5a6f
--- /dev/null
+++ b/src/client_read.c
@@ -0,0 +1,113 @@
+/*
+ * 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 "client_internal.h"
+#include "fifo_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+static char *
+client_read_line(struct client *client)
+{
+ const char *p, *newline;
+ size_t length;
+ char *line;
+
+ p = fifo_buffer_read(client->input, &length);
+ if (p == NULL)
+ return NULL;
+
+ newline = memchr(p, '\n', length);
+ if (newline == NULL)
+ return NULL;
+
+ line = g_strndup(p, newline - p);
+ fifo_buffer_consume(client->input, newline - p + 1);
+
+ return g_strchomp(line);
+}
+
+static enum command_return
+client_input_received(struct client *client, size_t bytesRead)
+{
+ char *line;
+
+ fifo_buffer_append(client->input, bytesRead);
+
+ /* process all lines */
+
+ while ((line = client_read_line(client)) != NULL) {
+ enum command_return ret = client_process_line(client, line);
+ g_free(line);
+
+ if (ret == COMMAND_RETURN_KILL ||
+ ret == COMMAND_RETURN_CLOSE)
+ return ret;
+ if (client_is_expired(client))
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+enum command_return
+client_read(struct client *client)
+{
+ char *p;
+ size_t max_length;
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_read;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+
+ p = fifo_buffer_write(client->input, &max_length);
+ if (p == NULL) {
+ g_warning("[%u] buffer overflow", client->num);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ status = g_io_channel_read_chars(client->channel, p, max_length,
+ &bytes_read, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ return client_input_received(client, bytes_read);
+
+ case G_IO_STATUS_AGAIN:
+ /* try again later, after select() */
+ return COMMAND_RETURN_OK;
+
+ case G_IO_STATUS_EOF:
+ /* peer disconnected */
+ return COMMAND_RETURN_CLOSE;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+ g_warning("failed to read from client %d: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return COMMAND_RETURN_CLOSE;
+ }
+
+ /* unreachable */
+ return COMMAND_RETURN_CLOSE;
+}
diff --git a/src/client_write.c b/src/client_write.c
new file mode 100644
index 000000000..9bac20fa5
--- /dev/null
+++ b/src/client_write.c
@@ -0,0 +1,271 @@
+/*
+ * 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 "client_internal.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+static size_t
+client_write_deferred_buffer(struct client *client,
+ const struct deferred_buffer *buffer)
+{
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_written;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+ assert(buffer != NULL);
+
+ status = g_io_channel_write_chars
+ (client->channel, buffer->data, buffer->size,
+ &bytes_written, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ return bytes_written;
+
+ case G_IO_STATUS_AGAIN:
+ return 0;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ client_set_expired(client);
+ return 0;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ client_set_expired(client);
+ g_warning("failed to flush buffer for %i: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return 0;
+ }
+
+ /* unreachable */
+ return 0;
+}
+
+void
+client_write_deferred(struct client *client)
+{
+ size_t ret;
+
+ while (!g_queue_is_empty(client->deferred_send)) {
+ struct deferred_buffer *buf =
+ g_queue_peek_head(client->deferred_send);
+
+ assert(buf->size > 0);
+ assert(buf->size <= client->deferred_bytes);
+
+ ret = client_write_deferred_buffer(client, buf);
+ if (ret == 0)
+ break;
+
+ if (ret < buf->size) {
+ assert(client->deferred_bytes >= (size_t)ret);
+ client->deferred_bytes -= ret;
+ buf->size -= ret;
+ memmove(buf->data, buf->data + ret, buf->size);
+ break;
+ } else {
+ size_t decr = sizeof(*buf) -
+ sizeof(buf->data) + buf->size;
+
+ assert(client->deferred_bytes >= decr);
+ client->deferred_bytes -= decr;
+ g_free(buf);
+ g_queue_pop_head(client->deferred_send);
+ }
+
+ g_timer_start(client->last_activity);
+ }
+
+ if (g_queue_is_empty(client->deferred_send)) {
+ g_debug("[%u] buffer empty %lu", client->num,
+ (unsigned long)client->deferred_bytes);
+ assert(client->deferred_bytes == 0);
+ }
+}
+
+static void client_defer_output(struct client *client,
+ const void *data, size_t length)
+{
+ size_t alloc;
+ struct deferred_buffer *buf;
+
+ assert(length > 0);
+
+ alloc = sizeof(*buf) - sizeof(buf->data) + length;
+ client->deferred_bytes += alloc;
+ if (client->deferred_bytes > client_max_output_buffer_size) {
+ g_warning("[%u] output buffer size (%lu) is "
+ "larger than the max (%lu)",
+ client->num,
+ (unsigned long)client->deferred_bytes,
+ (unsigned long)client_max_output_buffer_size);
+ /* cause client to close */
+ client_set_expired(client);
+ return;
+ }
+
+ buf = g_malloc(alloc);
+ buf->size = length;
+ memcpy(buf->data, data, length);
+
+ g_queue_push_tail(client->deferred_send, buf);
+}
+
+static void client_write_direct(struct client *client,
+ const char *data, size_t length)
+{
+ GError *error = NULL;
+ GIOStatus status;
+ gsize bytes_written;
+
+ assert(client != NULL);
+ assert(client->channel != NULL);
+ assert(data != NULL);
+ assert(length > 0);
+ assert(g_queue_is_empty(client->deferred_send));
+
+ status = g_io_channel_write_chars(client->channel, data, length,
+ &bytes_written, &error);
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ case G_IO_STATUS_AGAIN:
+ break;
+
+ case G_IO_STATUS_EOF:
+ /* client has disconnected */
+
+ client_set_expired(client);
+ return;
+
+ case G_IO_STATUS_ERROR:
+ /* I/O error */
+
+ client_set_expired(client);
+ g_warning("failed to write to %i: %s",
+ client->num, error->message);
+ g_error_free(error);
+ return;
+ }
+
+ if (bytes_written < length)
+ client_defer_output(client, data + bytes_written,
+ length - bytes_written);
+
+ if (!g_queue_is_empty(client->deferred_send))
+ g_debug("[%u] buffer created", client->num);
+}
+
+void
+client_write_output(struct client *client)
+{
+ if (client_is_expired(client) || !client->send_buf_used)
+ return;
+
+ if (!g_queue_is_empty(client->deferred_send)) {
+ client_defer_output(client, client->send_buf,
+ client->send_buf_used);
+
+ if (client_is_expired(client))
+ return;
+
+ /* try to flush the deferred buffers now; the current
+ server command may take too long to finish, and
+ meanwhile try to feed output to the client,
+ otherwise it will time out. One reason why
+ deferring is slow might be that currently each
+ client_write() allocates a new deferred buffer.
+ This should be optimized after MPD 0.14. */
+ client_write_deferred(client);
+ } else
+ client_write_direct(client, client->send_buf,
+ client->send_buf_used);
+
+ client->send_buf_used = 0;
+}
+
+/**
+ * Write a block of data to the client.
+ */
+static void client_write(struct client *client, const char *buffer, size_t buflen)
+{
+ /* if the client is going to be closed, do nothing */
+ if (client_is_expired(client))
+ return;
+
+ while (buflen > 0 && !client_is_expired(client)) {
+ size_t copylen;
+
+ assert(client->send_buf_used < sizeof(client->send_buf));
+
+ copylen = sizeof(client->send_buf) - client->send_buf_used;
+ if (copylen > buflen)
+ copylen = buflen;
+
+ memcpy(client->send_buf + client->send_buf_used, buffer,
+ copylen);
+ buflen -= copylen;
+ client->send_buf_used += copylen;
+ buffer += copylen;
+ if (client->send_buf_used >= sizeof(client->send_buf))
+ client_write_output(client);
+ }
+}
+
+void client_puts(struct client *client, const char *s)
+{
+ client_write(client, s, strlen(s));
+}
+
+void client_vprintf(struct client *client, const char *fmt, va_list args)
+{
+ va_list tmp;
+ int length;
+ char *buffer;
+
+ va_copy(tmp, args);
+ length = vsnprintf(NULL, 0, fmt, tmp);
+ va_end(tmp);
+
+ if (length <= 0)
+ /* wtf.. */
+ return;
+
+ buffer = g_malloc(length + 1);
+ vsnprintf(buffer, length + 1, fmt, args);
+ client_write(client, buffer, length);
+ g_free(buffer);
+}
+
+G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ client_vprintf(client, fmt, args);
+ va_end(args);
+}
diff --git a/src/cmdline.c b/src/cmdline.c
index e0274ef36..908e8f27d 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -17,15 +17,20 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "cmdline.h"
#include "path.h"
#include "log.h"
#include "conf.h"
#include "decoder_list.h"
-#include "config.h"
+#include "decoder_plugin.h"
#include "output_list.h"
#include "ls.h"
+#ifdef ENABLE_ENCODER
+#include "encoder_list.h"
+#endif
+
#ifdef ENABLE_ARCHIVE
#include "archive_list.h"
#endif
@@ -35,10 +40,34 @@
#include <stdio.h>
#include <stdlib.h>
-#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf"
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
+static GQuark
+cmdline_quark(void)
+{
+ return g_quark_from_static_string("cmdline");
+}
+
+static void
+print_all_decoders(FILE *fp)
+{
+ for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
+ const struct decoder_plugin *plugin = decoder_plugins[i];
+ const char *const*suffixes;
+
+ fprintf(fp, "[%s]", plugin->name);
+
+ for (suffixes = plugin->suffixes;
+ suffixes != NULL && *suffixes != NULL;
+ ++suffixes) {
+ fprintf(fp, " %s", *suffixes);
+ }
+
+ fprintf(fp, "\n");
+ }
+}
+
G_GNUC_NORETURN
static void version(void)
{
@@ -51,13 +80,19 @@ static void version(void)
"\n"
"Supported decoders:\n");
- decoder_plugin_init_all();
- decoder_plugin_print_all_decoders(stdout);
+ print_all_decoders(stdout);
puts("\n"
"Supported outputs:\n");
audio_output_plugin_print_all_types(stdout);
+#ifdef ENABLE_ENCODER
+ puts("\n"
+ "Supported encoders:\n");
+ encoder_plugin_print_all_types(stdout);
+#endif
+
+
#ifdef ENABLE_ARCHIVE
puts("\n"
"Supported archives:\n");
@@ -72,31 +107,29 @@ static void version(void)
exit(EXIT_SUCCESS);
}
-#if GLIB_CHECK_VERSION(2,12,0)
static const char *summary =
"Music Player Daemon - a daemon for playing music.";
-#endif
-void parseOptions(int argc, char **argv, Options *options)
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r)
{
GError *error = NULL;
GOptionContext *context;
bool ret;
static gboolean option_version,
- option_create_db, option_no_create_db, option_no_daemon,
+ option_no_daemon,
option_no_config;
const GOptionEntry entries[] = {
- { "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db,
- "force (re)creation of database", NULL },
{ "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
"kill the currently running mpd session", NULL },
{ "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
"don't read from config", NULL },
- { "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db,
- "don't create database, even if it doesn't exist", NULL },
{ "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
"don't detach from console", NULL },
- { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput,
+ { "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
+ NULL, NULL },
+ { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
"print messages to stderr", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
"verbose logging", NULL },
@@ -107,16 +140,13 @@ void parseOptions(int argc, char **argv, Options *options)
options->kill = false;
options->daemon = true;
- options->stdOutput = false;
+ options->log_stderr = false;
options->verbose = false;
- options->createDB = 0;
context = g_option_context_new("[path/to/mpd.conf]");
g_option_context_add_main_entries(context, entries, NULL);
-#if GLIB_CHECK_VERSION(2,12,0)
g_option_context_set_summary(context, summary);
-#endif
ret = g_option_context_parse(context, &argc, &argv, &error);
g_option_context_free(context);
@@ -133,18 +163,11 @@ void parseOptions(int argc, char **argv, Options *options)
parser can use it already */
log_early_init(options->verbose);
- if (option_create_db && option_no_create_db)
- g_error("Cannot use both --create-db and --no-create-db\n");
-
- if (option_no_create_db)
- options->createDB = -1;
- else if (option_create_db)
- options->createDB = 1;
-
options->daemon = !option_no_daemon;
if (option_no_config) {
g_debug("Ignoring config, using daemon defaults\n");
+ return true;
} else if (argc <= 1) {
/* default configuration file path */
char *path1;
@@ -155,17 +178,23 @@ void parseOptions(int argc, char **argv, Options *options)
path2 = g_build_filename(g_get_home_dir(),
USER_CONFIG_FILE_LOCATION2, NULL);
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
- config_read_file(path1);
+ ret = config_read_file(path1, error_r);
else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
- config_read_file(path2);
+ ret = config_read_file(path2, error_r);
else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
G_FILE_TEST_IS_REGULAR))
- config_read_file(SYSTEM_CONFIG_FILE_LOCATION);
+ ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION,
+ error_r);
g_free(path1);
g_free(path2);
+
+ return ret;
} else if (argc == 2) {
/* specified configuration file */
- config_read_file(argv[1]);
- } else
- g_error("too many arguments");
+ return config_read_file(argv[1], error_r);
+ } else {
+ g_set_error(error_r, cmdline_quark(), 0,
+ "too many arguments");
+ return false;
+ }
}
diff --git a/src/cmdline.h b/src/cmdline.h
index 673701055..945868b8c 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -22,14 +22,17 @@
#include <glib.h>
-typedef struct _Options {
+#include <stdbool.h>
+
+struct options {
gboolean kill;
gboolean daemon;
- gboolean stdOutput;
+ gboolean log_stderr;
gboolean verbose;
- int createDB;
-} Options;
+};
-void parseOptions(int argc, char **argv, Options *options);
+bool
+parse_cmdline(int argc, char **argv, struct options *options,
+ GError **error_r);
#endif
diff --git a/src/command.c b/src/command.c
index d30b63594..db0bafa31 100644
--- a/src/command.c
+++ b/src/command.c
@@ -17,14 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "command.h"
#include "player_control.h"
#include "playlist.h"
#include "playlist_print.h"
#include "playlist_save.h"
+#include "playlist_queue.h"
#include "queue_print.h"
#include "ls.h"
#include "uri.h"
+#include "decoder_print.h"
#include "directory.h"
#include "directory_print.h"
#include "database.h"
@@ -32,7 +35,7 @@
#include "volume.h"
#include "stats.h"
#include "permission.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "stored_playlist.h"
#include "ack.h"
#include "output_command.h"
@@ -43,8 +46,8 @@
#include "client.h"
#include "tag_print.h"
#include "path.h"
+#include "replay_gain.h"
#include "idle.h"
-#include "config.h"
#ifdef ENABLE_SQLITE
#include "sticker.h"
@@ -58,7 +61,6 @@
#include <stdlib.h>
#include <errno.h>
-#define COMMAND_STATUS_VOLUME "volume"
#define COMMAND_STATUS_STATE "state"
#define COMMAND_STATUS_REPEAT "repeat"
#define COMMAND_STATUS_SINGLE "single"
@@ -166,8 +168,8 @@ check_int(struct client *client, int *value_r,
return false;
}
-#if LONG_MAX > INT_MAX
- if (value < INT_MIN || value > INT_MAX) {
+#if G_MAXLONG > G_MAXINT
+ if (value < G_MININT || value > G_MAXINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -198,7 +200,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
/* compatibility with older MPD versions: specifying
"-1" makes MPD display the whole list */
*value_r1 = 0;
- *value_r2 = UINT_MAX;
+ *value_r2 = G_MAXUINT;
return true;
}
@@ -208,8 +210,8 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
return false;
}
-#if LONG_MAX > UINT_MAX
- if (value > UINT_MAX) {
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -220,7 +222,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
if (*test == ':') {
value = strtol(++test, &test2, 10);
- if (*test2 != '\0' || test == test2) {
+ if (*test2 != '\0') {
va_list args;
va_start(args, fmt);
command_error_v(client, ACK_ERROR_ARG, fmt, args);
@@ -228,14 +230,17 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
return false;
}
+ if (test == test2)
+ value = G_MAXUINT;
+
if (value < 0) {
command_error(client, ACK_ERROR_ARG,
"Number is negative: %s", s);
return false;
}
-#if LONG_MAX > UINT_MAX
- if (value > UINT_MAX) {
+#if G_MAXLONG > G_MAXUINT
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -262,7 +267,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s)
return false;
}
- if (value > UINT_MAX) {
+ if (value > G_MAXUINT) {
command_error(client, ACK_ERROR_ARG,
"Number too large: %s", s);
return false;
@@ -385,6 +390,14 @@ handle_urlhandlers(struct client *client,
}
static enum command_return
+handle_decoders(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ decoder_list_print(client);
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
handle_tagtypes(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
@@ -400,7 +413,7 @@ handle_play(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = playPlaylist(&g_playlist, song);
+ result = playlist_play(&g_playlist, song);
return print_playlist_result(client, result);
}
@@ -413,7 +426,7 @@ handle_playid(struct client *client, int argc, char *argv[])
if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = playPlaylistById(&g_playlist, id);
+ result = playlist_play_id(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -421,7 +434,7 @@ static enum command_return
handle_stop(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- stopPlaylist(&g_playlist);
+ playlist_stop(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -441,11 +454,11 @@ handle_pause(struct client *client,
bool pause_flag;
if (!check_bool(client, &pause_flag, argv[1]))
return COMMAND_RETURN_ERROR;
- playerSetPause(pause_flag);
- return COMMAND_RETURN_OK;
- }
- playerPause();
+ pc_set_pause(pause_flag);
+ } else
+ pc_pause();
+
return COMMAND_RETURN_OK;
}
@@ -454,10 +467,14 @@ handle_status(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
const char *state = NULL;
+ struct player_status player_status;
int updateJobId;
+ char *error;
int song;
- switch (getPlayerState()) {
+ pc_get_status(&player_status);
+
+ switch (player_status.state) {
case PLAYER_STATE_STOP:
state = "stop";
break;
@@ -470,7 +487,7 @@ handle_status(struct client *client,
}
client_printf(client,
- COMMAND_STATUS_VOLUME ": %i\n"
+ "volume: %i\n"
COMMAND_STATUS_REPEAT ": %i\n"
COMMAND_STATUS_RANDOM ": %i\n"
COMMAND_STATUS_SINGLE ": %i\n"
@@ -480,32 +497,37 @@ handle_status(struct client *client,
COMMAND_STATUS_CROSSFADE ": %i\n"
COMMAND_STATUS_STATE ": %s\n",
volume_level_get(),
- getPlaylistRepeatStatus(&g_playlist),
- getPlaylistRandomStatus(&g_playlist),
- getPlaylistSingleStatus(&g_playlist),
- getPlaylistConsumeStatus(&g_playlist),
- getPlaylistVersion(&g_playlist),
- getPlaylistLength(&g_playlist),
- (int)(getPlayerCrossFade() + 0.5),
+ playlist_get_repeat(&g_playlist),
+ playlist_get_random(&g_playlist),
+ playlist_get_single(&g_playlist),
+ playlist_get_consume(&g_playlist),
+ playlist_get_version(&g_playlist),
+ playlist_get_length(&g_playlist),
+ (int)(pc_get_cross_fade() + 0.5),
state);
- song = getPlaylistCurrentSong(&g_playlist);
+ song = playlist_get_current_song(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_SONG ": %i\n"
COMMAND_STATUS_SONGID ": %u\n",
- song, getPlaylistSongId(&g_playlist, song));
+ song, playlist_get_song_id(&g_playlist, song));
}
- if (getPlayerState() != PLAYER_STATE_STOP) {
- const struct audio_format *af = player_get_audio_format();
+ if (player_status.state != PLAYER_STATE_STOP) {
+ struct audio_format_string af_string;
+
client_printf(client,
COMMAND_STATUS_TIME ": %i:%i\n"
- COMMAND_STATUS_BITRATE ": %li\n"
- COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
- getPlayerElapsedTime(), getPlayerTotalTime(),
- getPlayerBitRate(),
- af->sample_rate, af->bits, af->channels);
+ "elapsed: %1.3f\n"
+ COMMAND_STATUS_BITRATE ": %u\n"
+ COMMAND_STATUS_AUDIO ": %s\n",
+ (int)(player_status.elapsed_time + 0.5),
+ (int)(player_status.total_time + 0.5),
+ player_status.elapsed_time,
+ player_status.bit_rate,
+ audio_format_to_string(&player_status.audio_format,
+ &af_string));
}
if ((updateJobId = isUpdatingDB())) {
@@ -514,18 +536,20 @@ handle_status(struct client *client,
updateJobId);
}
- if (getPlayerError() != PLAYER_ERROR_NOERROR) {
+ error = pc_get_error_message();
+ if (error != NULL) {
client_printf(client,
COMMAND_STATUS_ERROR ": %s\n",
- getPlayerErrorStr());
+ error);
+ g_free(error);
}
- song = getPlaylistNextSong(&g_playlist);
+ song = playlist_get_next_song(&g_playlist);
if (song >= 0) {
client_printf(client,
COMMAND_STATUS_NEXTSONG ": %i\n"
COMMAND_STATUS_NEXTSONGID ": %u\n",
- song, getPlaylistSongId(&g_playlist, song));
+ song, playlist_get_song_id(&g_playlist, song));
}
return COMMAND_RETURN_OK;
@@ -569,7 +593,7 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = addToPlaylist(&g_playlist, uri, NULL);
+ result = playlist_append_uri(&g_playlist, uri, NULL);
return print_playlist_result(client, result);
}
@@ -605,7 +629,7 @@ handle_addid(struct client *client, int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- result = addToPlaylist(&g_playlist, uri, &added_id);
+ result = playlist_append_uri(&g_playlist, uri, &added_id);
}
if (result != PLAYLIST_RESULT_SUCCESS)
@@ -615,11 +639,11 @@ handle_addid(struct client *client, int argc, char *argv[])
int to;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongInPlaylistById(&g_playlist, added_id, to);
+ result = playlist_move_id(&g_playlist, added_id, to);
if (result != PLAYLIST_RESULT_SUCCESS) {
enum command_return ret =
print_playlist_result(client, result);
- deleteFromPlaylistById(&g_playlist, added_id);
+ playlist_delete_id(&g_playlist, added_id);
return ret;
}
}
@@ -631,13 +655,13 @@ handle_addid(struct client *client, int argc, char *argv[])
static enum command_return
handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- int song;
+ unsigned start, end;
enum playlist_result result;
- if (!check_int(client, &song, argv[1], need_positive))
+ if (!check_range(client, &start, &end, argv[1], need_range))
return COMMAND_RETURN_ERROR;
- result = deleteFromPlaylist(&g_playlist, song);
+ result = playlist_delete_range(&g_playlist, start, end);
return print_playlist_result(client, result);
}
@@ -650,7 +674,7 @@ handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &id, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = deleteFromPlaylistById(&g_playlist, id);
+ result = playlist_delete_id(&g_playlist, id);
return print_playlist_result(client, result);
}
@@ -671,7 +695,7 @@ handle_shuffle(G_GNUC_UNUSED struct client *client,
argv[1], need_range))
return COMMAND_RETURN_ERROR;
- shufflePlaylist(&g_playlist, start, end);
+ playlist_shuffle(&g_playlist, start, end);
return COMMAND_RETURN_OK;
}
@@ -679,7 +703,7 @@ static enum command_return
handle_clear(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- clearPlaylist(&g_playlist);
+ playlist_clear(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -698,6 +722,10 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
enum playlist_result result;
+ result = playlist_open_into_queue(argv[1], &g_playlist);
+ if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
+ return result;
+
result = playlist_load_spl(&g_playlist, argv[1]);
return print_playlist_result(client, result);
}
@@ -808,7 +836,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[
static enum command_return
handle_playlistinfo(struct client *client, int argc, char *argv[])
{
- unsigned start = 0, end = UINT_MAX;
+ unsigned start = 0, end = G_MAXUINT;
bool ret;
if (argc == 2 && !check_range(client, &start, &end,
@@ -837,7 +865,7 @@ handle_playlistid(struct client *client, int argc, char *argv[])
return print_playlist_result(client,
PLAYLIST_RESULT_NO_SUCH_SONG);
} else {
- playlist_print_info(client, &g_playlist, 0, UINT_MAX);
+ playlist_print_info(client, &g_playlist, 0, G_MAXUINT);
}
return COMMAND_RETURN_OK;
@@ -869,6 +897,30 @@ handle_find(struct client *client, int argc, char *argv[])
}
static enum command_return
+handle_findadd(struct client *client, int argc, char *argv[])
+{
+ int ret;
+ struct locate_item_list *list =
+ locate_item_list_parse(argv + 1, argc - 1);
+ if (list == NULL || list->length == 0) {
+ if (list != NULL)
+ locate_item_list_free(list);
+
+ command_error(client, ACK_ERROR_ARG, "incorrect arguments");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ ret = findAddIn(client, NULL, list);
+ if (ret == -1)
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "directory or file not found");
+
+ locate_item_list_free(list);
+
+ return ret;
+}
+
+static enum command_return
handle_search(struct client *client, int argc, char *argv[])
{
int ret;
@@ -993,14 +1045,35 @@ handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
static enum command_return
handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
- char *path = NULL;
+ const char *path = NULL;
+ unsigned ret;
+
+ assert(argc <= 2);
+ if (argc == 2)
+ path = argv[1];
+
+ ret = update_enqueue(path, false);
+ if (ret > 0) {
+ client_printf(client, "updating_db: %i\n", ret);
+ return COMMAND_RETURN_OK;
+ } else {
+ command_error(client, ACK_ERROR_UPDATE_ALREADY,
+ "already updating");
+ return COMMAND_RETURN_ERROR;
+ }
+}
+
+static enum command_return
+handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ const char *path = NULL;
unsigned ret;
assert(argc <= 2);
if (argc == 2)
- path = g_strdup(argv[1]);
+ path = argv[1];
- ret = directory_update_init(path);
+ ret = update_enqueue(path, true);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return COMMAND_RETURN_OK;
@@ -1020,7 +1093,7 @@ handle_next(G_GNUC_UNUSED struct client *client,
int single = g_playlist.queue.single;
g_playlist.queue.single = false;
- nextSongInPlaylist(&g_playlist);
+ playlist_next(&g_playlist);
g_playlist.queue.single = single;
return COMMAND_RETURN_OK;
@@ -1030,7 +1103,7 @@ static enum command_return
handle_previous(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- previousSongInPlaylist(&g_playlist);
+ playlist_previous(&g_playlist);
return COMMAND_RETURN_OK;
}
@@ -1052,25 +1125,6 @@ handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
}
static enum command_return
-handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
-{
- int change;
- bool success;
-
- if (!check_int(client, &change, argv[1], need_integer))
- return COMMAND_RETURN_ERROR;
-
- success = volume_level_change(change, true);
- if (!success) {
- command_error(client, ACK_ERROR_SYSTEM,
- "problems setting volume");
- return COMMAND_RETURN_ERROR;
- }
-
- return COMMAND_RETURN_OK;
-}
-
-static enum command_return
handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
int level;
@@ -1079,7 +1133,12 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &level, argv[1], need_integer))
return COMMAND_RETURN_ERROR;
- success = volume_level_change(level, 0);
+ if (level < 0 || level > 100) {
+ command_error(client, ACK_ERROR_ARG, "Invalid volume value");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ success = volume_level_change(level);
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
@@ -1103,7 +1162,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistRepeatStatus(&g_playlist, status);
+ playlist_set_repeat(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1121,7 +1180,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistSingleStatus(&g_playlist, status);
+ playlist_set_single(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1139,7 +1198,7 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistConsumeStatus(&g_playlist, status);
+ playlist_set_consume(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1157,7 +1216,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
}
- setPlaylistRandomStatus(&g_playlist, status);
+ playlist_set_random(&g_playlist, status);
return COMMAND_RETURN_OK;
}
@@ -1172,7 +1231,7 @@ static enum command_return
handle_clearerror(G_GNUC_UNUSED struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
- clearPlayerError();
+ pc_clear_error();
return COMMAND_RETURN_OK;
}
@@ -1196,17 +1255,17 @@ handle_list(struct client *client, int argc, char *argv[])
/* for compatibility with < 0.12.0 */
if (argc == 3) {
- if (tagType != TAG_ITEM_ALBUM) {
+ if (tagType != TAG_ALBUM) {
command_error(client, ACK_ERROR_ARG,
"should be \"%s\" for 3 arguments",
- tag_item_names[TAG_ITEM_ALBUM]);
+ tag_item_names[TAG_ALBUM]);
return COMMAND_RETURN_ERROR;
}
locate_item_list_parse(argv + 1, argc - 1);
conditionals = locate_item_list_new(1);
- conditionals->items[0].tag = TAG_ITEM_ARTIST;
+ conditionals->items[0].tag = TAG_ARTIST;
conditionals->items[0].needle = g_strdup(argv[2]);
} else {
conditionals =
@@ -1241,7 +1300,7 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongRangeInPlaylist(&g_playlist, start, end, to);
+ result = playlist_move_range(&g_playlist, start, end, to);
return print_playlist_result(client, result);
}
@@ -1255,7 +1314,7 @@ handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = moveSongInPlaylistById(&g_playlist, id, to);
+ result = playlist_move_id(&g_playlist, id, to);
return print_playlist_result(client, result);
}
@@ -1269,7 +1328,7 @@ handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = swapSongsInPlaylist(&g_playlist, song1, song2);
+ result = playlist_swap_songs(&g_playlist, song1, song2);
return print_playlist_result(client, result);
}
@@ -1283,7 +1342,7 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
return COMMAND_RETURN_ERROR;
if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = swapSongsInPlaylistById(&g_playlist, id1, id2);
+ result = playlist_swap_songs_id(&g_playlist, id1, id2);
return print_playlist_result(client, result);
}
@@ -1298,7 +1357,7 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = seekSongInPlaylist(&g_playlist, song, seek_time);
+ result = playlist_seek_song(&g_playlist, song, seek_time);
return print_playlist_result(client, result);
}
@@ -1313,7 +1372,7 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
return COMMAND_RETURN_ERROR;
- result = seekSongInPlaylistById(&g_playlist, id, seek_time);
+ result = playlist_seek_song_id(&g_playlist, id, seek_time);
return print_playlist_result(client, result);
}
@@ -1363,7 +1422,7 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_unsigned(client, &xfade_time, argv[1]))
return COMMAND_RETURN_ERROR;
- setPlayerCrossFade(xfade_time);
+ pc_set_cross_fade(xfade_time);
return COMMAND_RETURN_OK;
}
@@ -1477,6 +1536,28 @@ handle_listplaylists(struct client *client,
}
static enum command_return
+handle_replay_gain_mode(struct client *client,
+ G_GNUC_UNUSED int argc, char *argv[])
+{
+ if (!replay_gain_set_mode_string(argv[1])) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized replay gain mode");
+ return COMMAND_RETURN_ERROR;
+ }
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_replay_gain_status(struct client *client,
+ G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
+{
+ client_printf(client, "replay_gain_mode: %s\n",
+ replay_gain_get_mode_string());
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
handle_idle(struct client *client,
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
{
@@ -1519,13 +1600,14 @@ sticker_song_find_print_cb(struct song *song, const char *value,
{
struct sticker_song_find_data *data = user_data;
- song_print_url(data->client, song);
+ song_print_uri(data->client, song);
sticker_print_value(data->client, data->name, value);
}
static enum command_return
handle_sticker_song(struct client *client, int argc, char *argv[])
{
+ /* get song song_id key */
if (argc == 5 && strcmp(argv[1], "get") == 0) {
struct song *song;
char *value;
@@ -1548,6 +1630,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
g_free(value);
return COMMAND_RETURN_OK;
+ /* list song song_id */
} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
struct song *song;
struct sticker *sticker;
@@ -1570,6 +1653,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
sticker_free(sticker);
return COMMAND_RETURN_OK;
+ /* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
struct song *song;
bool ret;
@@ -1589,6 +1673,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
return COMMAND_RETURN_OK;
+ /* delete song song_id [key] */
} else if ((argc == 4 || argc == 5) &&
strcmp(argv[1], "delete") == 0) {
struct song *song;
@@ -1611,6 +1696,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
}
return COMMAND_RETURN_OK;
+ /* find song dir key */
} else if (argc == 5 && strcmp(argv[1], "find") == 0) {
/* "sticker find song a/directory name" */
struct directory *directory;
@@ -1679,11 +1765,13 @@ static const struct command commands[] = {
{ "count", PERMISSION_READ, 2, -1, handle_count },
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
+ { "decoders", PERMISSION_READ, 0, 0, handle_decoders },
{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
{ "find", PERMISSION_READ, 2, -1, handle_find },
+ { "findadd", PERMISSION_READ, 2, -1, handle_findadd},
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
{ "list", PERMISSION_READ, 1, -1, handle_list },
@@ -1719,6 +1807,11 @@ static const struct command commands[] = {
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
{ "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
+ { "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
+ handle_replay_gain_mode },
+ { "replay_gain_status", PERMISSION_READ, 0, 0,
+ handle_replay_gain_status },
+ { "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan },
{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
{ "search", PERMISSION_READ, 2, -1, handle_search },
@@ -1738,7 +1831,6 @@ static const struct command commands[] = {
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
{ "update", PERMISSION_ADMIN, 0, 1, handle_update },
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
- { "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
};
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
@@ -1892,48 +1984,71 @@ command_checked_lookup(struct client *client, unsigned permission,
}
enum command_return
-command_process(struct client *client, char *commandString)
+command_process(struct client *client, unsigned num, char *line)
{
+ GError *error = NULL;
int argc;
char *argv[COMMAND_ARGV_MAX] = { NULL };
const struct command *cmd;
enum command_return ret = COMMAND_RETURN_ERROR;
- if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
- return COMMAND_RETURN_OK;
+ command_list_num = num;
- cmd = command_checked_lookup(client, client_get_permission(client),
- argc, argv);
- if (cmd)
- ret = cmd->handler(client, argc, argv);
+ /* get the command name (first word on the line) */
- current_command = NULL;
+ argv[0] = tokenizer_next_word(&line, &error);
+ if (argv[0] == NULL) {
+ current_command = "";
+ if (*line == 0)
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "No command given");
+ else {
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "%s", error->message);
+ g_error_free(error);
+ }
+ current_command = NULL;
- return ret;
-}
+ return COMMAND_RETURN_ERROR;
+ }
-enum command_return
-command_process_list(struct client *client,
- bool list_ok, GSList *list)
-{
- enum command_return ret = COMMAND_RETURN_OK;
+ argc = 1;
- command_list_num = 0;
+ /* now parse the arguments (quoted or unquoted) */
+
+ while (argc < (int)G_N_ELEMENTS(argv) &&
+ (argv[argc] =
+ tokenizer_next_param(&line, &error)) != NULL)
+ ++argc;
+
+ /* some error checks; we have to set current_command because
+ command_error() expects it to be set */
+
+ current_command = argv[0];
- for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
- char *cmd = cur->data;
-
- g_debug("command_process_list: process command \"%s\"",
- cmd);
- ret = command_process(client, cmd);
- g_debug("command_process_list: command returned %i", ret);
- if (ret != COMMAND_RETURN_OK || client_is_expired(client))
- break;
- else if (list_ok)
- client_puts(client, "list_OK\n");
- command_list_num++;
+ if (argc >= (int)G_N_ELEMENTS(argv)) {
+ command_error(client, ACK_ERROR_ARG, "Too many arguments");
+ current_command = NULL;
+ return COMMAND_RETURN_ERROR;
}
+ if (*line != 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "%s", error->message);
+ current_command = NULL;
+ g_error_free(error);
+ return COMMAND_RETURN_ERROR;
+ }
+
+ /* look up and invoke the command handler */
+
+ cmd = command_checked_lookup(client, client_get_permission(client),
+ argc, argv);
+ if (cmd)
+ ret = cmd->handler(client, argc, argv);
+
+ current_command = NULL;
command_list_num = 0;
+
return ret;
}
diff --git a/src/command.h b/src/command.h
index a7c408ed7..614a414b6 100644
--- a/src/command.h
+++ b/src/command.h
@@ -39,11 +39,7 @@ void command_init(void);
void command_finish(void);
enum command_return
-command_process_list(struct client *client,
- bool list_ok, GSList *list);
-
-enum command_return
-command_process(struct client *client, char *commandString);
+command_process(struct client *client, unsigned num, char *line);
void command_success(struct client *client);
diff --git a/src/compress.c b/src/compress.c
deleted file mode 100644
index 3a0b4beb0..000000000
--- a/src/compress.c
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-/*
- * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
- */
-
-#include "compress.h"
-
-#include <glib.h>
-
-#include <stdint.h>
-#include <string.h>
-
-#ifdef USE_X
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-
-static Display *display;
-static Window window;
-static Visual *visual;
-static int screen;
-static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC;
-#endif
-
-static int *peaks;
-static int gainCurrent, gainTarget;
-
-static struct {
- int show_mon;
- int anticlip;
- int target;
- int gainmax;
- int gainsmooth;
- unsigned buckets;
-} prefs;
-
-#ifdef USE_X
-static int mon_init;
-#endif
-
-void CompressCfg(int show_mon, int anticlip, int target, int gainmax,
- int gainsmooth, unsigned buckets)
-{
- static unsigned lastsize;
-
- prefs.show_mon = show_mon;
- prefs.anticlip = anticlip;
- prefs.target = target;
- prefs.gainmax = gainmax;
- prefs.gainsmooth = gainsmooth;
- prefs.buckets = buckets;
-
- /* Allocate the peak structure */
- peaks = g_realloc(peaks, sizeof(int)*prefs.buckets);
-
- if (prefs.buckets > lastsize)
- memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets
- - lastsize));
- lastsize = prefs.buckets;
-
-#ifdef USE_X
- /* Configure the monitor window if needed */
- if (show_mon && !mon_init)
- {
- display = XOpenDisplay(getenv("DISPLAY"));
-
- /* We really shouldn't try to init X if there's no X */
- if (!display)
- {
- fprintf(stderr,
- "X not detected; disabling monitor window\n");
- show_mon = prefs.show_mon = 0;
- }
- }
-
- if (show_mon && !mon_init)
- {
- XGCValues gcv;
- XColor col;
-
- gainCurrent = gainTarget = (1 << GAINSHIFT);
-
-
-
- screen = DefaultScreen(display);
- visual = DefaultVisual(display, screen);
- window = XCreateSimpleWindow(display,
- RootWindow(display, screen),
- 0, 0, prefs.buckets, 128 + 8, 0,
- BlackPixel(display, screen),
- WhitePixel(display, screen));
- XStoreName(display, window, "AudioCompress monitor");
-
- gcv.foreground = BlackPixel(display, screen);
- blackGC = XCreateGC(display, window, GCForeground, &gcv);
- gcv.foreground = WhitePixel(display, screen);
- whiteGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 0;
- col.green = 0;
- col.blue = 65535;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- blueGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 65535;
- col.green = 65535;
- col.blue = 0;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- yellowGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 32767;
- col.green = 32767;
- col.blue = 0;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- dkyellowGC = XCreateGC(display, window, GCForeground, &gcv);
- col.red = 65535;
- col.green = 0;
- col.blue = 0;
- XAllocColor(display, DefaultColormap(display, screen), &col);
- gcv.foreground = col.pixel;
- redGC = XCreateGC(display, window, GCForeground, &gcv);
- mon_init = 1;
- }
-
- if (mon_init)
- {
- if (show_mon)
- XMapWindow(display, window);
- else
- XUnmapWindow(display, window);
- XResizeWindow(display, window, prefs.buckets, 128 + 8);
- XFlush(display);
- }
-#endif
-}
-
-void CompressFree(void)
-{
-#ifdef USE_X
- if (mon_init)
- {
- XFreeGC(display, blackGC);
- XFreeGC(display, whiteGC);
- XFreeGC(display, blueGC);
- XFreeGC(display, yellowGC);
- XFreeGC(display, dkyellowGC);
- XFreeGC(display, redGC);
- XDestroyWindow(display, window);
- XCloseDisplay(display);
- }
-#endif
-
- g_free(peaks);
-}
-
-void CompressDo(void *data, unsigned int length)
-{
- int16_t *audio = (int16_t *)data, *ap;
- int peak;
- unsigned int i, pos;
- int gr, gf, gn;
- static int pn = -1;
-#ifdef STATS
- static int clip;
-#endif
- static int clipped;
-
- if (!peaks)
- return;
-
- if (pn == -1)
- {
- for (i = 0; i < prefs.buckets; i++)
- peaks[i] = 0;
- }
- pn = (pn + 1)%prefs.buckets;
-
-#ifdef DEBUG
- fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data,
- length);
-#endif
-
- /* Determine peak's value and position */
- peak = 1;
- pos = 0;
-
-#ifdef DEBUG
- fprintf(stderr, "finding peak(b=%d)\n", pn);
-#endif
-
- ap = audio;
- for (i = 0; i < length/2; i++)
- {
- int val = *ap;
- if (val > peak)
- {
- peak = val;
- pos = i;
- } else if (-val > peak)
- {
- peak = -val;
- pos = i;
- }
- ap++;
- }
- peaks[pn] = peak;
-
- /* Only draw if needed, of course */
-#ifdef USE_X
- if (prefs.show_mon)
- {
- /* current amplitude */
- XDrawLine(display, window, whiteGC,
- pn, 0,
- pn,
- 127 -
- (peaks[pn]*gainCurrent >> (GAINSHIFT + 8)));
-
- /* amplification */
- XDrawLine(display, window, yellowGC,
- pn,
- 127 - (peaks[pn]*gainCurrent
- >> (GAINSHIFT + 8)),
- pn, 127);
-
- /* peak */
- XDrawLine(display, window, blackGC,
- pn, 127 - (peaks[pn] >> 8), pn, 127);
-
- /* clip indicator */
- if (clipped)
- XDrawLine(display, window, redGC,
- (pn + prefs.buckets - 1)%prefs.buckets,
- 126 - clipped/(length*512),
- (pn + prefs.buckets - 1)%prefs.buckets,
- 127);
- clipped = 0;
-
- /* target line */
- /* XDrawPoint(display, window, redGC, */
- /* pn, 127 - TARGET/256); */
- /* amplification edge */
- XDrawLine(display, window, dkyellowGC,
- pn,
- 127 - (peaks[pn]*gainCurrent
- >> (GAINSHIFT + 8)),
- pn - 1,
- 127 -
- (peaks[(pn + prefs.buckets
- - 1)%prefs.buckets]*gainCurrent
- >> (GAINSHIFT + 8)));
- }
-#endif
-
- for (i = 0; i < prefs.buckets; i++)
- {
- if (peaks[i] > peak)
- {
- peak = peaks[i];
- pos = 0;
- }
- }
-
- /* Determine target gain */
- gn = (1 << GAINSHIFT)*prefs.target/peak;
-
- if (gn <(1 << GAINSHIFT))
- gn = 1 << GAINSHIFT;
-
- gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn)
- >> prefs.gainsmooth;
-
- /* Give it an extra insignifigant nudge to counteract possible
- ** rounding error
- */
-
- if (gn < gainTarget)
- gainTarget--;
- else if (gn > gainTarget)
- gainTarget++;
-
- if (gainTarget > prefs.gainmax << GAINSHIFT)
- gainTarget = prefs.gainmax << GAINSHIFT;
-
-
-#ifdef USE_X
- if (prefs.show_mon)
- {
- int x;
-
- /* peak*gain */
- XDrawPoint(display, window, redGC,
- pn,
- 127 - (peak*gainCurrent
- >> (GAINSHIFT + 8)));
-
- /* gain indicator */
- XFillRectangle(display, window, whiteGC, 0, 128,
- prefs.buckets, 8);
- x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets
- / ((prefs.gainmax - 1) << GAINSHIFT);
- XDrawLine(display, window, redGC, x,
- 128, x, 128 + 8);
-
- x = (gn - (1 << GAINSHIFT))*prefs.buckets
- / ((prefs.gainmax - 1) << GAINSHIFT);
-
- XDrawLine(display, window, blackGC,
- x, 132 - 1,
- x, 132 + 1);
-
- /* blue peak line */
- XDrawLine(display, window, blueGC,
- 0, 127 - (peak >> 8), prefs.buckets,
- 127 - (peak >> 8));
- XFlush(display);
- XDrawLine(display, window, whiteGC,
- 0, 127 - (peak >> 8), prefs.buckets,
- 127 - (peak >> 8));
- }
-#endif
-
- /* See if a peak is going to clip */
- gn = (1 << GAINSHIFT)*32768/peak;
-
- if (gn < gainTarget)
- {
- gainTarget = gn;
-
- if (prefs.anticlip)
- pos = 0;
-
- } else
- {
- /* We're ramping up, so draw it out over the whole frame */
- pos = length;
- }
-
- /* Determine gain rate necessary to make target */
- if (!pos)
- pos = 1;
-
- gr = ((gainTarget - gainCurrent) << 16)/(int)pos;
-
- /* Do the shiznit */
- gf = gainCurrent << 16;
-
-#ifdef STATS
- fprintf(stderr, "\rgain = %2.2f%+.2e ",
- gainCurrent*1.0/(1 << GAINSHIFT),
- (gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT));
-#endif
-
- ap = audio;
- for (i = 0; i < length/2; i++)
- {
- int sample;
-
- /* Interpolate the gain */
- gainCurrent = gf >> 16;
- if (i < pos)
- gf += gr;
- else if (i == pos)
- gf = gainTarget << 16;
-
- /* Amplify */
- sample = (*ap)*gainCurrent >> GAINSHIFT;
- if (sample < -32768)
- {
-#ifdef STATS
- clip++;
-#endif
- clipped += -32768 - sample;
- sample = -32768;
- } else if (sample > 32767)
- {
-#ifdef STATS
- clip++;
-#endif
- clipped += sample - 32767;
- sample = 32767;
- }
- *ap++ = sample;
- }
-#ifdef STATS
- fprintf(stderr, "clip %d b%-3d ", clip, pn);
-#endif
-
-#ifdef DEBUG
- fprintf(stderr, "\ndone\n");
-#endif
-}
-
diff --git a/src/conf.c b/src/conf.c
index cce7dbf27..574ad9ddb 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -17,10 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "conf.h"
#include "utils.h"
-#include "buffer2array.h"
+#include "tokenizer.h"
#include "path.h"
+#include "glib_compat.h"
#include <glib.h>
@@ -36,37 +38,82 @@
#define MAX_STRING_SIZE MPD_PATH_MAX+80
#define CONF_COMMENT '#'
-#define CONF_BLOCK_BEGIN "{"
-#define CONF_BLOCK_END "}"
-
-#define CONF_REPEATABLE_MASK 0x01
-#define CONF_BLOCK_MASK 0x02
-#define CONF_LINE_TOKEN_MAX 3
struct config_entry {
- const char *name;
- unsigned char mask;
+ const char *const name;
+ const bool repeatable;
+ const bool block;
GSList *params;
};
-static GSList *config_entries;
+static struct config_entry config_entries[] = {
+ { .name = CONF_MUSIC_DIR, false, false },
+ { .name = CONF_PLAYLIST_DIR, false, false },
+ { .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
+ { .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
+ { .name = CONF_DB_FILE, false, false },
+ { .name = CONF_STICKER_FILE, false, false },
+ { .name = CONF_LOG_FILE, false, false },
+ { .name = CONF_PID_FILE, false, false },
+ { .name = CONF_STATE_FILE, false, false },
+ { .name = CONF_USER, false, false },
+ { .name = CONF_GROUP, false, false },
+ { .name = CONF_BIND_TO_ADDRESS, true, false },
+ { .name = CONF_PORT, false, false },
+ { .name = CONF_LOG_LEVEL, false, false },
+ { .name = CONF_ZEROCONF_NAME, false, false },
+ { .name = CONF_ZEROCONF_ENABLED, false, false },
+ { .name = CONF_PASSWORD, true, false },
+ { .name = CONF_DEFAULT_PERMS, false, false },
+ { .name = CONF_AUDIO_OUTPUT, true, true },
+ { .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
+ { .name = CONF_MIXER_TYPE, false, false },
+ { .name = CONF_REPLAYGAIN, false, false },
+ { .name = CONF_REPLAYGAIN_PREAMP, false, false },
+ { .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
+ { .name = CONF_VOLUME_NORMALIZATION, false, false },
+ { .name = CONF_SAMPLERATE_CONVERTER, false, false },
+ { .name = CONF_AUDIO_BUFFER_SIZE, false, false },
+ { .name = CONF_BUFFER_BEFORE_PLAY, false, false },
+ { .name = CONF_HTTP_PROXY_HOST, false, false },
+ { .name = CONF_HTTP_PROXY_PORT, false, false },
+ { .name = CONF_HTTP_PROXY_USER, false, false },
+ { .name = CONF_HTTP_PROXY_PASSWORD, false, false },
+ { .name = CONF_CONN_TIMEOUT, false, false },
+ { .name = CONF_MAX_CONN, false, false },
+ { .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
+ { .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
+ { .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
+ { .name = CONF_FS_CHARSET, false, false },
+ { .name = CONF_ID3V1_ENCODING, false, false },
+ { .name = CONF_METADATA_TO_USE, false, false },
+ { .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
+ { .name = CONF_DECODER, true, true },
+ { .name = CONF_INPUT, true, true },
+ { .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
+ { .name = CONF_PLAYLIST_PLUGIN, true, true },
+ { .name = CONF_AUTO_UPDATE, false, false },
+ { .name = "filter", true, true },
+};
-static int get_bool(const char *value)
+static bool
+get_bool(const char *value, bool *value_r)
{
- const char **x;
static const char *t[] = { "yes", "true", "1", NULL };
static const char *f[] = { "no", "false", "0", NULL };
- for (x = t; *x; x++) {
- if (!g_ascii_strcasecmp(*x, value))
- return 1;
+ if (string_array_contains(t, value)) {
+ *value_r = true;
+ return true;
}
- for (x = f; *x; x++) {
- if (!g_ascii_strcasecmp(*x, value))
- return 0;
+
+ if (string_array_contains(f, value)) {
+ *value_r = false;
+ return true;
}
- return CONF_BOOL_INVALID;
+
+ return false;
}
struct config_param *
@@ -83,15 +130,14 @@ config_new_param(const char *value, int line)
ret->num_block_params = 0;
ret->block_params = NULL;
+ ret->used = false;
return ret;
}
static void
-config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+config_param_free(struct config_param *param)
{
- struct config_param *param = data;
-
g_free(param->value);
for (unsigned i = 0; i < param->num_block_params; i++) {
@@ -105,42 +151,19 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
g_free(param);
}
-static struct config_entry *
-newConfigEntry(const char *name, int repeatable, int block)
-{
- struct config_entry *ret = g_new(struct config_entry, 1);
-
- ret->name = name;
- ret->mask = 0;
- ret->params = NULL;
-
- if (repeatable)
- ret->mask |= CONF_REPEATABLE_MASK;
- if (block)
- ret->mask |= CONF_BLOCK_MASK;
-
- return ret;
-}
-
static void
-config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
+config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
- struct config_entry *entry = data;
-
- g_slist_foreach(entry->params, config_param_free, NULL);
- g_slist_free(entry->params);
+ struct config_param *param = data;
- g_free(entry);
+ config_param_free(param);
}
static struct config_entry *
config_entry_get(const char *name)
{
- GSList *list;
-
- for (list = config_entries; list != NULL;
- list = g_slist_next(list)) {
- struct config_entry *entry = list->data;
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
if (strcmp(entry->name, name) == 0)
return entry;
}
@@ -148,82 +171,65 @@ config_entry_get(const char *name)
return NULL;
}
-static void registerConfigParam(const char *name, int repeatable, int block)
+void config_global_finish(void)
{
- struct config_entry *entry;
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
- entry = config_entry_get(name);
- if (entry != NULL)
- g_error("config parameter \"%s\" already registered\n", name);
+ g_slist_foreach(entry->params,
+ config_param_free_callback, NULL);
+ g_slist_free(entry->params);
+ }
+}
- entry = newConfigEntry(name, repeatable, block);
- config_entries = g_slist_prepend(config_entries, entry);
+void config_global_init(void)
+{
}
-void config_global_finish(void)
+static void
+config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
- g_slist_foreach(config_entries, config_entry_free, NULL);
- g_slist_free(config_entries);
+ struct config_param *param = data;
+
+ if (!param->used)
+ /* this whole config_param was not queried at all -
+ the feature might be disabled at compile time?
+ Silently ignore it here. */
+ return;
+
+ for (unsigned i = 0; i < param->num_block_params; i++) {
+ struct block_param *bp = &param->block_params[i];
+
+ if (!bp->used)
+ g_warning("option '%s' on line %i was not recognized",
+ bp->name, bp->line);
+ }
}
-void config_global_init(void)
+void config_global_check(void)
{
- config_entries = NULL;
-
- /* registerConfigParam(name, repeatable, block); */
- registerConfigParam(CONF_MUSIC_DIR, 0, 0);
- registerConfigParam(CONF_PLAYLIST_DIR, 0, 0);
- registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
- registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
- registerConfigParam(CONF_DB_FILE, 0, 0);
- registerConfigParam(CONF_STICKER_FILE, false, false);
- registerConfigParam(CONF_LOG_FILE, 0, 0);
- registerConfigParam(CONF_ERROR_FILE, 0, 0);
- registerConfigParam(CONF_PID_FILE, 0, 0);
- registerConfigParam(CONF_STATE_FILE, 0, 0);
- registerConfigParam(CONF_USER, 0, 0);
- registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0);
- registerConfigParam(CONF_PORT, 0, 0);
- registerConfigParam(CONF_LOG_LEVEL, 0, 0);
- registerConfigParam(CONF_ZEROCONF_NAME, 0, 0);
- registerConfigParam(CONF_ZEROCONF_ENABLED, 0, 0);
- registerConfigParam(CONF_PASSWORD, 1, 0);
- registerConfigParam(CONF_DEFAULT_PERMS, 0, 0);
- registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1);
- registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0);
- registerConfigParam(CONF_MIXER_TYPE, 0, 0);
- registerConfigParam(CONF_MIXER_DEVICE, 0, 0);
- registerConfigParam(CONF_MIXER_CONTROL, 0, 0);
- registerConfigParam(CONF_REPLAYGAIN, 0, 0);
- registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0);
- registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0);
- registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0);
- registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0);
- registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0);
- registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0);
- registerConfigParam(CONF_CONN_TIMEOUT, 0, 0);
- registerConfigParam(CONF_MAX_CONN, 0, 0);
- registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0);
- registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0);
- registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0);
- registerConfigParam(CONF_FS_CHARSET, 0, 0);
- registerConfigParam(CONF_ID3V1_ENCODING, 0, 0);
- registerConfigParam(CONF_METADATA_TO_USE, 0, 0);
- registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0);
- registerConfigParam(CONF_DECODER, true, true);
- registerConfigParam(CONF_INPUT, true, true);
- registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0);
+ for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
+ struct config_entry *entry = &config_entries[i];
+
+ g_slist_foreach(entry->params, config_param_check, NULL);
+ }
}
-void
+bool
config_add_block_param(struct config_param * param, const char *name,
- const char *value, int line)
+ const char *value, int line, GError **error_r)
{
struct block_param *bp;
+ bp = config_get_block_param(param, name);
+ if (bp != NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "\"%s\" first defined on line %i, and "
+ "redefined on line %i\n", name,
+ bp->line, line);
+ return false;
+ }
+
param->num_block_params++;
param->block_params = g_realloc(param->block_params,
@@ -235,67 +241,97 @@ config_add_block_param(struct config_param * param, const char *name,
bp->name = g_strdup(name);
bp->value = g_strdup(value);
bp->line = line;
+ bp->used = false;
+
+ return true;
}
static struct config_param *
-config_read_block(FILE *fp, int *count, char *string)
+config_read_block(FILE *fp, int *count, char *string, GError **error_r)
{
struct config_param *ret = config_new_param(NULL, *count);
-
- int i;
- int numberOfArgs;
- int argsMinusComment;
-
- while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ GError *error = NULL;
+ bool success;
+
+ while (true) {
+ char *line;
+ const char *name, *value;
+
+ line = fgets(string, MAX_STRING_SIZE, fp);
+ if (line == NULL) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "Expected '}' before end-of-file");
+ return NULL;
+ }
(*count)++;
+ line = g_strchug(line);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
+ if (*line == '}') {
+ /* end of this block; return from the function
+ (and from this "while" loop) */
+
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '}'",
+ *count);
+ return false;
+ }
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
+ return ret;
}
- argsMinusComment = i;
+ /* parse name and value */
- if (0 == argsMinusComment) {
- continue;
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ config_param_free(ret);
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", *count);
+ return NULL;
}
- if (1 == argsMinusComment &&
- 0 == strcmp(array[0], CONF_BLOCK_END)) {
- break;
+ value = tokenizer_next_string(&line, &error);
+ if (value == NULL) {
+ config_param_free(ret);
+ if (*line == 0)
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Value missing", *count);
+ else
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ",
+ *count);
+ return NULL;
}
- if (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", *count, string);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ config_param_free(ret);
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after value",
+ *count);
+ return NULL;
}
- if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[1], CONF_BLOCK_BEGIN) ||
- 0 == strcmp(array[0], CONF_BLOCK_END) ||
- 0 == strcmp(array[1], CONF_BLOCK_END)) {
- g_error("improperly formatted config file at line %i: %s "
- "in block beginning at line %i\n",
- *count, string, ret->line);
+ success = config_add_block_param(ret, name, value, *count,
+ error_r);
+ if (!success) {
+ config_param_free(ret);
+ return false;
}
-
- config_add_block_param(ret, array[0], array[1], *count);
}
-
- return ret;
}
-void config_read_file(const char *file)
+bool
+config_read_file(const char *file, GError **error_r)
{
FILE *fp;
char string[MAX_STRING_SIZE + 1];
- int i;
- int numberOfArgs;
- int argsMinusComment;
int count = 0;
struct config_entry *entry;
struct config_param *param;
@@ -303,67 +339,110 @@ void config_read_file(const char *file)
g_debug("loading file %s", file);
if (!(fp = fopen(file, "r"))) {
- g_error("problems opening file %s for reading: %s\n",
- file, strerror(errno));
+ g_set_error(error_r, config_quark(), errno,
+ "Failed to open %s: %s",
+ file, strerror(errno));
+ return false;
}
while (fgets(string, MAX_STRING_SIZE, fp)) {
- char *array[CONF_LINE_TOKEN_MAX] = { NULL };
+ char *line;
+ const char *name, *value;
+ GError *error = NULL;
count++;
- numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
+ line = g_strchug(string);
+ if (*line == 0 || *line == CONF_COMMENT)
+ continue;
+
+ /* the first token in each line is the name, followed
+ by either the value or '{' */
- for (i = 0; i < numberOfArgs; i++) {
- if (array[i][0] == CONF_COMMENT)
- break;
+ name = tokenizer_next_word(&line, &error);
+ if (name == NULL) {
+ assert(*line != 0);
+ g_propagate_prefixed_error(error_r, error,
+ "line %i: ", count);
+ return false;
}
- argsMinusComment = i;
+ /* get the definition of that option, and check the
+ "repeatable" flag */
- if (0 == argsMinusComment) {
- continue;
+ entry = config_entry_get(name);
+ if (entry == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "unrecognized parameter in config file at "
+ "line %i: %s\n", count, name);
+ return false;
}
- if (2 != argsMinusComment) {
- g_error("improperly formatted config file at line %i:"
- " %s\n", count, string);
+ if (entry->params != NULL && !entry->repeatable) {
+ param = entry->params->data;
+ g_set_error(error_r, config_quark(), 0,
+ "config parameter \"%s\" is first defined "
+ "on line %i and redefined on line %i\n",
+ name, param->line, count);
+ return false;
}
- entry = config_entry_get(array[0]);
- if (entry == NULL)
- g_error("unrecognized parameter in config file at "
- "line %i: %s\n", count, string);
+ /* now parse the block or the value */
- if (!(entry->mask & CONF_REPEATABLE_MASK) &&
- entry->params != NULL) {
- param = entry->params->data;
- g_error("config parameter \"%s\" is first defined on "
- "line %i and redefined on line %i\n",
- array[0], param->line, count);
- }
+ if (entry->block) {
+ /* it's a block, call config_read_block() */
- if (entry->mask & CONF_BLOCK_MASK) {
- if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
- g_error("improperly formatted config file at "
- "line %i: %s\n", count, string);
+ if (*line != '{') {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: '{' expected", count);
+ return false;
}
- param = config_read_block(fp, &count, string);
- } else
- param = config_new_param(array[1], count);
+
+ line = g_strchug(line + 1);
+ if (*line != 0 && *line != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after '{'",
+ count);
+ return false;
+ }
+
+ param = config_read_block(fp, &count, string, error_r);
+ if (param == NULL)
+ return false;
+ } else {
+ /* a string value */
+
+ value = tokenizer_next_string(&line, &error);
+ if (value == NULL) {
+ if (*line == 0)
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Value missing",
+ count);
+ else {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: %s", count,
+ error->message);
+ g_error_free(error);
+ }
+
+ return false;
+ }
+
+ if (*line != 0 && *line != CONF_COMMENT) {
+ g_set_error(error_r, config_quark(), 0,
+ "line %i: Unknown tokens after value",
+ count);
+ return false;
+ }
+
+ param = config_new_param(value, count);
+ }
entry->params = g_slist_append(entry->params, param);
}
fclose(fp);
-}
-
-void
-config_add_param(const char *name, struct config_param *param)
-{
- struct config_entry *entry = config_entry_get(name);
- assert(entry != NULL);
- entry->params = g_slist_append(entry->params, param);
+ return true;
}
struct config_param *
@@ -391,7 +470,7 @@ config_get_next_param(const char *name, const struct config_param * last)
return NULL;
param = node->data;
-
+ param->used = true;
return param;
}
@@ -447,43 +526,35 @@ config_get_positive(const char *name, unsigned default_value)
struct block_param *
config_get_block_param(const struct config_param * param, const char *name)
{
- struct block_param *ret = NULL;
-
if (param == NULL)
return NULL;
for (unsigned i = 0; i < param->num_block_params; i++) {
if (0 == strcmp(name, param->block_params[i].name)) {
- if (ret) {
- g_warning("\"%s\" first defined on line %i, and "
- "redefined on line %i\n", name,
- ret->line, param->block_params[i].line);
- }
- ret = param->block_params + i;
+ struct block_param *bp = &param->block_params[i];
+ bp->used = true;
+ return bp;
}
}
- return ret;
+ return NULL;
}
bool config_get_bool(const char *name, bool default_value)
{
const struct config_param *param = config_get_param(name);
- int value;
+ bool success, value;
if (param == NULL)
return default_value;
- value = get_bool(param->value);
- if (value == CONF_BOOL_INVALID)
+ success = get_bool(param->value, &value);
+ if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, param->line);
- if (value == CONF_BOOL_UNSET)
- return default_value;
-
- return !!value;
+ return value;
}
const char *
@@ -524,19 +595,16 @@ config_get_block_bool(const struct config_param *param, const char *name,
bool default_value)
{
struct block_param *bp = config_get_block_param(param, name);
- int value;
+ bool success, value;
if (bp == NULL)
return default_value;
- value = get_bool(bp->value);
- if (value == CONF_BOOL_INVALID)
+ success = get_bool(bp->value, &value);
+ if (!success)
g_error("%s is not a boolean value (yes, true, 1) or "
"(no, false, 0) on line %i\n",
name, bp->line);
- if (value == CONF_BOOL_UNSET)
- return default_value;
-
- return !!value;
+ return value;
}
diff --git a/src/conf.h b/src/conf.h
index c5e49960e..049c980ec 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -30,10 +30,10 @@
#define CONF_DB_FILE "db_file"
#define CONF_STICKER_FILE "sticker_file"
#define CONF_LOG_FILE "log_file"
-#define CONF_ERROR_FILE "error_file"
#define CONF_PID_FILE "pid_file"
#define CONF_STATE_FILE "state_file"
#define CONF_USER "user"
+#define CONF_GROUP "group"
#define CONF_BIND_TO_ADDRESS "bind_to_address"
#define CONF_PORT "port"
#define CONF_LOG_LEVEL "log_level"
@@ -42,12 +42,12 @@
#define CONF_PASSWORD "password"
#define CONF_DEFAULT_PERMS "default_permissions"
#define CONF_AUDIO_OUTPUT "audio_output"
+#define CONF_AUDIO_FILTER "filter"
#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
#define CONF_MIXER_TYPE "mixer_type"
-#define CONF_MIXER_DEVICE "mixer_device"
-#define CONF_MIXER_CONTROL "mixer_control"
#define CONF_REPLAYGAIN "replaygain"
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
+#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
@@ -68,17 +68,24 @@
#define CONF_DECODER "decoder"
#define CONF_INPUT "input"
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
-
-#define CONF_BOOL_UNSET -1
-#define CONF_BOOL_INVALID -2
+#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
+#define CONF_AUTO_UPDATE "auto_update"
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
+#define MAX_FILTER_CHAIN_LENGTH 255
+
struct block_param {
char *name;
char *value;
int line;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ bool used;
};
struct config_param {
@@ -87,31 +94,57 @@ struct config_param {
struct block_param *block_params;
unsigned num_block_params;
+
+ /**
+ * This flag is false when nobody has queried the value of
+ * this option yet.
+ */
+ bool used;
};
+/**
+ * A GQuark for GError instances, resulting from malformed
+ * configuration.
+ */
+static inline GQuark
+config_quark(void)
+{
+ return g_quark_from_static_string("config");
+}
+
void config_global_init(void);
void config_global_finish(void);
-void config_read_file(const char *file);
-
/**
- * Adds a new configuration parameter. The name must be registered
- * with registerConfigParam().
+ * Call this function after all configuration has been evaluated. It
+ * checks for unused parameters, and logs warnings.
*/
-void
-config_add_param(const char *name, struct config_param *param);
+void config_global_check(void);
+
+bool
+config_read_file(const char *file, GError **error_r);
/* don't free the returned value
set _last_ to NULL to get first entry */
+G_GNUC_PURE
struct config_param *
config_get_next_param(const char *name, const struct config_param *last);
+G_GNUC_PURE
static inline struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
}
+/* Note on G_GNUC_PURE: Some of the functions declared pure are not
+ really pure in strict sense. They have side effect such that they
+ validate parameter's value and signal an error if it's invalid.
+ However, if the argument was already validated or we don't care
+ about the argument at all, this may be ignored so in the end, we
+ should be fine with calling those functions pure. */
+
+G_GNUC_PURE
const char *
config_get_string(const char *name, const char *default_value);
@@ -120,17 +153,27 @@ config_get_string(const char *name, const char *default_value);
* absolute path. If there is a tilde prefix, it is expanded. Aborts
* MPD if the path is not a valid absolute path.
*/
+/* We lie here really. This function is not pure as it has side
+ effects -- it parse the value and creates new string freeing
+ previous one. However, because this works the very same way each
+ time (ie. from the outside it appears as if function had no side
+ effects) we should be in the clear declaring it pure. */
+G_GNUC_PURE
const char *
config_get_path(const char *name);
+G_GNUC_PURE
unsigned
config_get_positive(const char *name, unsigned default_value);
+G_GNUC_PURE
struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
+G_GNUC_PURE
bool config_get_bool(const char *name, bool default_value);
+G_GNUC_PURE
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
@@ -142,10 +185,12 @@ config_dup_block_string(const struct config_param *param, const char *name,
return g_strdup(config_get_block_string(param, name, default_value));
}
+G_GNUC_PURE
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value);
+G_GNUC_PURE
bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
@@ -153,8 +198,8 @@ config_get_block_bool(const struct config_param *param, const char *name,
struct config_param *
config_new_param(const char *value, int line);
-void
-config_add_block_param(struct config_param *param, const char *name,
- const char *value, int line);
+bool
+config_add_block_param(struct config_param * param, const char *name,
+ const char *value, int line, GError **error_r);
#endif
diff --git a/src/crossfade.c b/src/crossfade.c
index 01552bf65..11dcba532 100644
--- a/src/crossfade.c
+++ b/src/crossfade.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "crossfade.h"
#include "pcm_mix.h"
#include "chunk.h"
@@ -39,9 +40,7 @@ unsigned cross_fade_calc(float duration, float total_time,
return 0;
assert(duration > 0);
- assert(af->bits > 0);
- assert(af->channels > 0);
- assert(af->sample_rate > 0);
+ assert(audio_format_valid(af));
chunks = audio_format_time_to_size(af) / CHUNK_SIZE;
chunks = (chunks * duration + 0.5);
diff --git a/src/cue/cue_tag.c b/src/cue/cue_tag.c
index ce8202a81..6c042a725 100644
--- a/src/cue/cue_tag.c
+++ b/src/cue/cue_tag.c
@@ -1,3 +1,4 @@
+#include "config.h"
#include "cue_tag.h"
static struct tag*
@@ -13,64 +14,64 @@ cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem)
tag_begin_add(tag);
- { /* TAG_ITEM_ALBUM_ARTIST */
+ { /* TAG_ALBUM_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
- /* TAG_ITEM_ALBUM_ARTIST */ }
+ tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
+ /* TAG_ALBUM_ARTIST */ }
- { /* TAG_ITEM_ARTIST */
+ { /* TAG_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
- /* TAG_ITEM_ARTIST */ }
+ tag_add_item(tag, TAG_ARTIST, tmp);
+ /* TAG_ARTIST */ }
- /* TAG_ITEM_PERFORMER */
+ /* TAG_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
+ tag_add_item(tag, TAG_PERFORMER, tmp);
- /* TAG_ITEM_COMPOSER */
+ /* TAG_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
+ tag_add_item(tag, TAG_COMPOSER, tmp);
- /* TAG_ITEM_ALBUM */
+ /* TAG_ALBUM */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ALBUM, tmp);
+ tag_add_item(tag, TAG_ALBUM, tmp);
- /* TAG_ITEM_GENRE */
+ /* TAG_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_GENRE, tmp);
+ tag_add_item(tag, TAG_GENRE, tmp);
- /* TAG_ITEM_DATE */
+ /* TAG_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_ITEM_DATE, tmp);
+ tag_add_item(tag, TAG_DATE, tmp);
- /* TAG_ITEM_COMMENT */
+ /* TAG_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
+ tag_add_item(tag, TAG_COMMENT, tmp);
- /* TAG_ITEM_DISC */
+ /* TAG_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_DISC, tmp);
+ tag_add_item(tag, TAG_DISC, tmp);
/* stream name, usually empty
- * tag_add_item(tag, TAG_ITEM_NAME,);
+ * tag_add_item(tag, TAG_NAME,);
*/
/* REM MUSICBRAINZ entry?
@@ -109,47 +110,47 @@ cue_tag_track(struct Cdtext* cdtext, struct Rem* rem)
tag_begin_add(tag);
- { /* TAG_ITEM_ARTIST */
+ { /* TAG_ARTIST */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
+ tag_add_item(tag, TAG_ARTIST, tmp);
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
- /* TAG_ITEM_ARTIST */ }
+ tag_add_item(tag, TAG_ARTIST, tmp);
+ /* TAG_ARTIST */ }
- /* TAG_ITEM_TITLE */
+ /* TAG_TITLE */
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_TITLE, tmp);
+ tag_add_item(tag, TAG_TITLE, tmp);
- /* TAG_ITEM_GENRE */
+ /* TAG_GENRE */
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_GENRE, tmp);
+ tag_add_item(tag, TAG_GENRE, tmp);
- /* TAG_ITEM_DATE */
+ /* TAG_DATE */
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
- tag_add_item(tag, TAG_ITEM_DATE, tmp);
+ tag_add_item(tag, TAG_DATE, tmp);
- /* TAG_ITEM_COMPOSER */
+ /* TAG_COMPOSER */
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
+ tag_add_item(tag, TAG_COMPOSER, tmp);
- /* TAG_ITEM_PERFORMER */
+ /* TAG_PERFORMER */
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
+ tag_add_item(tag, TAG_PERFORMER, tmp);
- /* TAG_ITEM_COMMENT */
+ /* TAG_COMMENT */
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
+ tag_add_item(tag, TAG_COMMENT, tmp);
- /* TAG_ITEM_DISC */
+ /* TAG_DISC */
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
- tag_add_item(tag, TAG_ITEM_DISC, tmp);
+ tag_add_item(tag, TAG_DISC, tmp);
tag_end_add(tag);
diff --git a/src/cue/cue_tag.h b/src/cue/cue_tag.h
index adc4c466e..aea585338 100644
--- a/src/cue/cue_tag.h
+++ b/src/cue/cue_tag.h
@@ -1,12 +1,13 @@
#ifndef MPD_CUE_TAG_H
#define MPD_CUE_TAG_H
-#include "config.h"
+#include "check.h"
#ifdef HAVE_CUE /* libcue */
+#include "tag.h"
+
#include <libcue/libcue.h>
-#include "../tag.h"
struct tag*
cue_tag_file( FILE*,
diff --git a/src/daemon.c b/src/daemon.c
index 33b2953a9..192fa8d37 100644
--- a/src/daemon.c
+++ b/src/daemon.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "daemon.h"
#include <glib.h>
@@ -45,20 +46,21 @@
static char *user_name;
/** the Unix user id which MPD runs as */
-static uid_t user_uid;
+static uid_t user_uid = (uid_t)-1;
/** the Unix group id which MPD runs as */
-static gid_t user_gid;
+static gid_t user_gid = (pid_t)-1;
/** the absolute path of the pidfile */
static char *pidfile;
-#endif
+/* whether "group" conf. option was given */
+static bool had_group = false;
+
void
daemonize_kill(void)
{
-#ifndef WIN32
FILE *fp;
int pid, ret;
@@ -82,41 +84,34 @@ daemonize_kill(void)
pid, g_strerror(errno));
exit(EXIT_SUCCESS);
-#else
- g_error("--kill is not available on WIN32");
-#endif
}
void
daemonize_close_stdin(void)
{
- int fd = open("/dev/null", O_RDONLY);
-
- if (fd < 0)
- close(STDIN_FILENO);
- else if (fd != STDIN_FILENO) {
- dup2(fd, STDIN_FILENO);
- close(fd);
- }
+ close(STDIN_FILENO);
+ open("/dev/null", O_RDONLY);
}
void
daemonize_set_user(void)
{
-#ifndef WIN32
if (user_name == NULL)
return;
- /* get uid */
- if (setgid(user_gid) == -1) {
- g_error("cannot setgid for user \"%s\": %s",
- user_name, g_strerror(errno));
+ /* set gid */
+ if (user_gid != (gid_t)-1 && user_gid != getgid()) {
+ if (setgid(user_gid) == -1) {
+ g_error("cannot setgid to %d: %s",
+ (int)user_gid, g_strerror(errno));
+ }
}
+
#ifdef _BSD_SOURCE
/* init suplementary groups
* (must be done before we change our uid)
*/
- if (initgroups(user_name, user_gid) == -1) {
+ if (!had_group && initgroups(user_name, user_gid) == -1) {
g_warning("cannot init supplementary groups "
"of user \"%s\": %s",
user_name, g_strerror(errno));
@@ -124,32 +119,38 @@ daemonize_set_user(void)
#endif
/* set uid */
- if (setuid(user_uid) == -1) {
+ if (user_uid != (uid_t)-1 && user_uid != getuid() &&
+ setuid(user_uid) == -1) {
g_error("cannot change to uid of user \"%s\": %s",
user_name, g_strerror(errno));
}
-#endif
}
-#ifndef G_OS_WIN32
static void
daemonize_detach(void)
{
- pid_t pid;
-
/* flush all file handles before duplicating the buffers */
fflush(NULL);
+#ifdef HAVE_DAEMON
+
+ if (daemon(0, 1))
+ g_error("daemon() failed: %s", g_strerror(errno));
+
+#elif defined(HAVE_FORK)
+
/* detach from parent process */
- pid = fork();
- if (pid < 0)
+ switch (fork()) {
+ case -1:
g_error("fork() failed: %s", g_strerror(errno));
-
- if (pid > 0)
+ case 0:
+ break;
+ default:
/* exit the parent process */
_exit(EXIT_SUCCESS);
+ }
/* release the current working directory */
@@ -160,14 +161,16 @@ daemonize_detach(void)
setsid();
+#else
+ g_error("no support for daemonizing");
+#endif
+
g_debug("daemonized!");
}
-#endif
void
daemonize(bool detach)
{
-#ifndef WIN32
FILE *fp = NULL;
if (pidfile != NULL) {
@@ -189,47 +192,45 @@ daemonize(bool detach)
fprintf(fp, "%lu\n", (unsigned long)getpid());
fclose(fp);
}
-#else
- /* no daemonization on WIN32 */
- (void)detach;
-#endif
}
void
-daemonize_init(const char *user, const char *_pidfile)
+daemonize_init(const char *user, const char *group, const char *_pidfile)
{
-#ifndef WIN32
- if (user != NULL && strcmp(user, g_get_user_name()) != 0) {
- struct passwd *pwd;
-
- user_name = g_strdup(user);
-
- pwd = getpwnam(user_name);
- if (pwd == NULL)
- g_error("no such user \"%s\"", user_name);
+ if (user) {
+ struct passwd *pwd = getpwnam(user);
+ if (!pwd)
+ g_error("no such user \"%s\"", user);
user_uid = pwd->pw_uid;
user_gid = pwd->pw_gid;
+ user_name = g_strdup(user);
+
/* this is needed by libs such as arts */
g_setenv("HOME", pwd->pw_dir, true);
}
+ if (group) {
+ struct group *grp = grp = getgrnam(group);
+ if (!grp)
+ g_error("no such group \"%s\"", group);
+ user_gid = grp->gr_gid;
+ had_group = true;
+ }
+
+
pidfile = g_strdup(_pidfile);
-#else
- (void)user;
- (void)_pidfile;
-#endif
}
void
daemonize_finish(void)
{
-#ifndef WIN32
if (pidfile != NULL)
unlink(pidfile);
g_free(user_name);
g_free(pidfile);
-#endif
}
+
+#endif
diff --git a/src/daemon.h b/src/daemon.h
index 5b3f9a7dc..a29945607 100644
--- a/src/daemon.h
+++ b/src/daemon.h
@@ -22,32 +22,67 @@
#include <stdbool.h>
+#ifndef WIN32
void
-daemonize_init(const char *user, const char *pidfile);
+daemonize_init(const char *user, const char *group, const char *pidfile);
+#else
+static inline void
+daemonize_init(const char *user, const char *group, const char *pidfile)
+{ (void)user; (void)group; (void)pidfile; }
+#endif
+#ifndef WIN32
void
daemonize_finish(void);
+#else
+static inline void
+daemonize_finish(void)
+{ /* nop */ }
+#endif
/**
* Kill the MPD which is currently running, pid determined from the
* pid file.
*/
+#ifndef WIN32
void
daemonize_kill(void);
+#else
+static inline void
+daemonize_kill(void)
+{ g_error("--kill is not available on WIN32"); }
+#endif
/**
* Close stdin (fd 0) and re-open it as /dev/null.
*/
+#ifndef WIN32
void
daemonize_close_stdin(void);
+#else
+static inline void
+daemonize_close_stdin(void) {}
+#endif
/**
* Change to the configured Unix user.
*/
+#ifndef WIN32
void
daemonize_set_user(void);
+#else
+static inline void
+daemonize_set_user(void)
+{ /* nop */ }
+#endif
+#ifndef WIN32
void
daemonize(bool detach);
+#else
+static inline void
+daemonize(bool detach)
+{ (void)detach; }
+#endif
#endif
diff --git a/src/database.c b/src/database.c
index 5a06dda98..b1c0df764 100644
--- a/src/database.c
+++ b/src/database.c
@@ -17,13 +17,16 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "database.h"
#include "directory.h"
#include "directory_save.h"
#include "song.h"
#include "path.h"
#include "stats.h"
-#include "config.h"
+#include "text_file.h"
+#include "tag.h"
+#include "tag_internal.h"
#include <glib.h>
@@ -40,8 +43,14 @@
#define DIRECTORY_INFO_BEGIN "info_begin"
#define DIRECTORY_INFO_END "info_end"
+#define DB_FORMAT_PREFIX "format: "
#define DIRECTORY_MPD_VERSION "mpd_version: "
#define DIRECTORY_FS_CHARSET "fs_charset: "
+#define DB_TAG_PREFIX "tag: "
+
+enum {
+ DB_FORMAT = 1,
+};
static char *database_path;
@@ -232,11 +241,19 @@ db_save(void)
/* block signals when writing the db so we don't get a corrupted db */
fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
+ fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
+ if (!ignore_tag_items[i])
+ fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
+
fprintf(fp, "%s\n", DIRECTORY_INFO_END);
- if (directory_save(fp, music_root) < 0) {
+ directory_save(fp, music_root);
+
+ if (ferror(fp)) {
g_warning("Failed to write to database file: %s",
strerror(errno));
while (fclose(fp) && errno == EINTR);
@@ -256,64 +273,64 @@ db_load(GError **error)
{
FILE *fp = NULL;
struct stat st;
- char buffer[100];
+ GString *buffer = g_string_sized_new(1024);
+ char *line;
+ int format = 0;
bool found_charset = false, found_version = false;
bool success;
+ bool tags[TAG_NUM_OF_ITEM_TYPES];
assert(database_path != NULL);
assert(music_root != NULL);
- if (!music_root)
- music_root = directory_new("", NULL);
while (!(fp = fopen(database_path, "r")) && errno == EINTR) ;
if (fp == NULL) {
g_set_error(error, db_quark(), errno,
"Failed to open database file \"%s\": %s",
database_path, strerror(errno));
+ g_string_free(buffer, true);
return false;
}
/* get initial info */
- if (!fgets(buffer, sizeof(buffer), fp)) {
- fclose(fp);
- g_set_error(error, db_quark(), 0, "Unexpected end of file");
- return false;
- }
-
- g_strchomp(buffer);
-
- if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
+ line = read_text_line(fp, buffer);
+ if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
fclose(fp);
g_set_error(error, db_quark(), 0, "Database corrupted");
+ g_string_free(buffer, true);
return false;
}
- while (fgets(buffer, sizeof(buffer), fp) &&
- !g_str_has_prefix(buffer, DIRECTORY_INFO_END)) {
- g_strchomp(buffer);
+ memset(tags, false, sizeof(tags));
- if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) {
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ strcmp(line, DIRECTORY_INFO_END) != 0) {
+ if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
+ format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
+ } else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
if (found_version) {
fclose(fp);
g_set_error(error, db_quark(), 0,
"Duplicate version line");
+ g_string_free(buffer, true);
return false;
}
found_version = true;
- } else if (g_str_has_prefix(buffer, DIRECTORY_FS_CHARSET)) {
+ } else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
const char *new_charset, *old_charset;
if (found_charset) {
fclose(fp);
g_set_error(error, db_quark(), 0,
"Duplicate charset line");
+ g_string_free(buffer, true);
return false;
}
found_charset = true;
- new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
+ new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
old_charset = path_get_fs_charset();
if (old_charset != NULL
&& strcmp(new_charset, old_charset)) {
@@ -323,19 +340,50 @@ db_load(GError **error)
"\"%s\" instead of \"%s\"; "
"discarding database file",
new_charset, old_charset);
+ g_string_free(buffer, true);
return false;
}
+ } else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
+ const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
+ enum tag_type tag = tag_name_parse(name);
+ if (tag == TAG_NUM_OF_ITEM_TYPES) {
+ g_set_error(error, db_quark(), 0,
+ "Unrecognized tag '%s', "
+ "discarding database file",
+ name);
+ return false;
+ }
+
+ tags[tag] = true;
} else {
fclose(fp);
g_set_error(error, db_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
+ g_string_free(buffer, true);
+ return false;
+ }
+ }
+
+ if (format != DB_FORMAT) {
+ g_set_error(error, db_quark(), 0,
+ "Database format mismatch, "
+ "discarding database file");
+ return false;
+ }
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ if (!ignore_tag_items[i] && !tags[i]) {
+ g_set_error(error, db_quark(), 0,
+ "Tag list mismatch, "
+ "discarding database file");
return false;
}
}
g_debug("reading DB");
- success = directory_load(fp, music_root, error);
+ success = directory_load(fp, music_root, buffer, error);
+ g_string_free(buffer, true);
while (fclose(fp) && errno == EINTR) ;
if (!success)
diff --git a/src/dbUtils.c b/src/dbUtils.c
index f89148c1b..359c8db47 100644
--- a/src/dbUtils.c
+++ b/src/dbUtils.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "dbUtils.h"
#include "locate.h"
#include "directory.h"
@@ -59,7 +60,7 @@ static int
printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
{
struct client *client = data;
- song_print_url(client, song);
+ song_print_uri(client, song);
return 0;
}
@@ -168,7 +169,7 @@ int printAllIn(struct client *client, const char *name)
static int
directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data)
{
- return addSongToPlaylist(&g_playlist, song, NULL);
+ return playlist_append_song(&g_playlist, song, NULL);
}
struct add_data {
@@ -200,6 +201,28 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file)
}
static int
+findAddInDirectory(struct song *song, void *_data)
+{
+ struct search_data *data = _data;
+
+ if (locate_song_match(song, data->criteria))
+ return directoryAddSongToPlaylist(song, data);
+
+ return 0;
+}
+
+int findAddIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria)
+{
+ struct search_data data;
+
+ data.client = client;
+ data.criteria = criteria;
+
+ return db_walk(name, findAddInDirectory, NULL, &data);
+}
+
+static int
directoryPrintSongInfo(struct song *song, void *data)
{
struct client *client = data;
@@ -236,7 +259,7 @@ visitTag(struct client *client, struct strset *set,
struct tag *tag = song->tag;
if (tagType == LOCATE_TAG_FILE_TYPE) {
- song_print_url(client, song);
+ song_print_uri(client, song);
return;
}
diff --git a/src/dbUtils.h b/src/dbUtils.h
index 1382c243e..914b6fa84 100644
--- a/src/dbUtils.h
+++ b/src/dbUtils.h
@@ -40,6 +40,10 @@ findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
int
+findAddIn(struct client *client, const char *name,
+ const struct locate_item_list *criteria);
+
+int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index 7c8fe9875..70b2c0202 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,50 @@ 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;
+ enum decoder_command cmd;
+ size_t buffer_size = frame->header.blocksize * data->frame_size;
+ void *buffer;
+ float position;
+ unsigned bit_rate;
- for (; position < end; ++position)
- for (c_chan = 0; c_chan < num_channels; c_chan++)
- *dest++ = buf[c_chan][position];
-}
+ buffer = pcm_buffer_get(&data->buffer, buffer_size);
-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;
+ flac_convert(buffer, frame->header.channels,
+ data->sample_format, buf,
+ 0, frame->header.blocksize);
- case 4:
- flac_convert_32((int32_t*)dest, num_channels, buf,
- position, end);
- break;
+ if (data->next_frame >= data->first_frame)
+ position = (float)(data->next_frame - data->first_frame) /
+ frame->header.sample_rate;
+ else
+ position = 0;
- 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,
+ position, 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..fcb431db7 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>
@@ -99,13 +101,52 @@ 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;
@@ -126,26 +167,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;
}
diff --git a/src/decoder/faad_plugin.c b/src/decoder/faad_plugin.c
index 7b2806a4c..2a05e33e8 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.
*/
@@ -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);
}
/**
@@ -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,6 +382,7 @@ faad_get_file_time(const char *file)
static void
faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
{
+ GError *error = NULL;
float file_time;
float total_time = 0;
faacDecHandle decoder;
@@ -408,15 +420,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;
}
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index 86c20a882..d9a5f7feb 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>
@@ -276,9 +277,30 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
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 +311,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 +372,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 +392,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_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/decoder/flac_pcm.h b/src/decoder/flac_pcm.h
new file mode 100644
index 000000000..4d7a51c4d
--- /dev/null
+++ b/src/decoder/flac_pcm.h
@@ -0,0 +1,33 @@
+/*
+ * 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_PCM_H
+#define MPD_FLAC_PCM_H
+
+#include "audio_format.h"
+
+#include <FLAC/ordinals.h>
+
+void
+flac_convert(void *dest,
+ unsigned int num_channels, enum sample_format sample_format,
+ const FLAC__int32 *const buf[],
+ unsigned int position, unsigned int end);
+
+#endif
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c
index 0c0d994b7..427d2c4d9 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 (!data->input_stream->seekable)
+ return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
+
if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
- return flac_seek_status_error;
+ return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
- return flac_seek_status_ok;
+ 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);
diff --git a/src/decoder/fluidsynth_plugin.c b/src/decoder/fluidsynth_plugin.c
index 99c874c09..1b1e6a531 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";
diff --git a/src/decoder/mad_plugin.c b/src/decoder/mad_plugin.c
index 1ef7183fa..cba40aea0 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>
@@ -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);
@@ -1170,17 +1171,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 +1187,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..2185fe21c 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,88 @@ 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];
+ unsigned frame_size, current_frame = 0;
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);
+ frame_size = audio_format_frame_size(&audio_format);
+
+ 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));
+ current_frame += ret / frame_size;
+ cmd = decoder_data(decoder, NULL, buffer, ret,
+ (float)current_frame / (float)mikmod_sample_rate,
+ 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 +235,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..02292992d 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,9 @@ 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;
+ unsigned frame_size, current_frame = 0;
enum decoder_command cmd = DECODE_COMMAND_NONE;
bdatas = mod_loadfile(decoder, is);
@@ -121,37 +122,31 @@ 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);
+ is->seekable, ModPlug_GetLength(f) / 1000.0);
- total_time = 0;
+ frame_size = audio_format_frame_size(&audio_format);
do {
ret = ModPlug_Read(f, audio_buffer, MODPLUG_FRAME_SIZE);
-
- if (ret == 0) {
+ if (ret <= 0)
break;
- }
- total_time += ret * sec_perbyte;
+ current_frame += (unsigned)ret / frame_size;
cmd = decoder_data(decoder, NULL,
audio_buffer, ret,
- total_time, 0, NULL);
+ (float)current_frame / (float)audio_format.sample_rate,
+ 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));
+ current_frame = (unsigned)(where * audio_format.sample_rate);
+
decoder_command_finished(decoder);
}
@@ -186,11 +181,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..8d3a4b9e9 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>
@@ -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;
}
@@ -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..2f1936e55 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
@@ -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;
@@ -193,18 +196,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;
}
diff --git a/src/decoder/mpg123_decoder_plugin.c b/src/decoder/mpg123_decoder_plugin.c
new file mode 100644
index 000000000..62e6b00b0
--- /dev/null
+++ b/src/decoder/mpg123_decoder_plugin.c
@@ -0,0 +1,214 @@
+/*
+ * 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, 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/oggflac_plugin.c b/src/decoder/oggflac_plugin.c
index c0e7e35e1..3b6987c6d 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);
}
@@ -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 */
@@ -264,6 +248,7 @@ oggflac_tag_dup(const char *file)
struct input_stream input_stream;
OggFLAC__SeekableStreamDecoder *decoder;
struct flac_data data;
+ struct tag *tag;
if (!input_stream_open(&input_stream, file))
return NULL;
@@ -284,15 +269,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;
+
+ flac_data_deinit(&data);
- return data.tag;
+ return tag;
}
static void
@@ -300,6 +288,7 @@ 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;
@@ -314,16 +303,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 +319,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 +337,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..63e46a285 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,17 @@ 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 */
+ unsigned data_time = 0;
+ const unsigned timebase = player.timebase();
+ song_len *= timebase;
+
enum decoder_command cmd;
do {
char buffer[4096];
@@ -121,29 +298,106 @@ 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);
+ (float)data_time / (float)timebase,
+ 0, NULL);
+ data_time = player.time();
+
+ if(cmd==DECODE_COMMAND_SEEK) {
+ 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 && data_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..fee0f8292
--- /dev/null
+++ b/src/decoder/sndfile_decoder_plugin.c
@@ -0,0 +1,248 @@
+/*
+ * 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);
+ 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)
+{
+ GError *error = NULL;
+ 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. */
+ 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,
+ 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..cb61e5999 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,7 +76,7 @@ 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 &&
@@ -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)
@@ -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,9 +368,9 @@ 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;
@@ -378,7 +394,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 645c8962e..68a958788 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,6 +158,9 @@ 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];
@@ -145,22 +169,17 @@ 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);
-
- /* 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;
}
@@ -509,7 +528,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;
@@ -554,7 +573,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..70b4d5ef9 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;
diff --git a/src/decoder_api.c b/src/decoder_api.c
index 2ece3bb98..5f0425ce1 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"
@@ -37,12 +38,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,79 +56,95 @@ void decoder_initialized(G_GNUC_UNUSED struct decoder * decoder,
assert(audio_format_defined(audio_format));
assert(audio_format_valid(audio_format));
- dc.in_audio_format = *audio_format;
- getOutputAudioFormat(audio_format, &dc.out_audio_format);
+ dc->in_audio_format = *audio_format;
+ getOutputAudioFormat(audio_format, &dc->out_audio_format);
+
+ dc->seekable = seekable;
+ dc->total_time = total_time;
- dc.seekable = seekable;
- dc.total_time = total_time;
+ decoder_lock(dc);
+ dc->state = DECODE_STATE_DECODE;
+ decoder_unlock(dc);
- dc.state = DECODE_STATE_DECODE;
- notify_signal(&pc.notify);
+ player_lock_signal();
- g_debug("audio_format=%u:%u:%u, seekable=%s",
- dc.in_audio_format.sample_rate, dc.in_audio_format.bits,
- dc.in_audio_format.channels,
+ g_debug("audio_format=%s, seekable=%s",
+ audio_format_to_string(&dc->in_audio_format, &af_string),
seekable ? "true" : "false");
- if (!audio_format_equals(&dc.in_audio_format, &dc.out_audio_format))
- g_debug("converting to %u:%u:%u",
- dc.out_audio_format.sample_rate,
- dc.out_audio_format.bits,
- dc.out_audio_format.channels);
+ if (!audio_format_equals(&dc->in_audio_format,
+ &dc->out_audio_format))
+ g_debug("converting to %s",
+ audio_format_to_string(&dc->out_audio_format,
+ &af_string));
}
char *decoder_get_uri(G_GNUC_UNUSED struct decoder *decoder)
{
- 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 +152,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 +171,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 +200,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,28 +247,32 @@ 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;
- 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
@@ -256,17 +283,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 +305,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 +329,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 +355,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..81f75623a 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,56 +38,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 +136,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_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..60c43e679 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,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 +70,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 +79,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 +94,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..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/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..c055d2a32 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,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 +95,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 +125,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 +328,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
diff --git a/src/directory.c b/src/directory.c
index ef8c038a3..62a297e14 100644
--- a/src/directory.c
+++ b/src/directory.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "directory.h"
#include "song.h"
#include "path.h"
diff --git a/src/directory.h b/src/directory.h
index 8207bd3a2..c8789fbe3 100644
--- a/src/directory.h
+++ b/src/directory.h
@@ -20,6 +20,7 @@
#ifndef MPD_DIRECTORY_H
#define MPD_DIRECTORY_H
+#include "check.h"
#include "dirvec.h"
#include "songvec.h"
diff --git a/src/directory_print.c b/src/directory_print.c
index e0575e80f..8e86abf41 100644
--- a/src/directory_print.c
+++ b/src/directory_print.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "directory_print.h"
#include "directory.h"
#include "client.h"
diff --git a/src/directory_save.c b/src/directory_save.c
index 132508447..0204e71e1 100644
--- a/src/directory_save.c
+++ b/src/directory_save.c
@@ -17,10 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "directory_save.h"
#include "directory.h"
#include "song.h"
-#include "path.h"
+#include "text_file.h"
#include "song_save.h"
#include <assert.h>
@@ -39,109 +40,137 @@ directory_quark(void)
return g_quark_from_static_string("directory");
}
-/* TODO error checking */
-int
+void
directory_save(FILE *fp, struct directory *directory)
{
struct dirvec *children = &directory->children;
size_t i;
- int retv;
if (!directory_is_root(directory)) {
fprintf(fp, DIRECTORY_MTIME "%lu\n",
(unsigned long)directory->mtime);
- retv = fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
- directory_get_path(directory));
- if (retv < 0)
- return -1;
+ fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
+ directory_get_path(directory));
}
for (i = 0; i < children->nr; ++i) {
struct directory *cur = children->base[i];
char *base = g_path_get_basename(cur->path);
- retv = fprintf(fp, DIRECTORY_DIR "%s\n", base);
+ fprintf(fp, DIRECTORY_DIR "%s\n", base);
g_free(base);
- if (retv < 0)
- return -1;
- if (directory_save(fp, cur) < 0)
- return -1;
+
+ directory_save(fp, cur);
+
+ if (ferror(fp))
+ return;
}
songvec_save(fp, &directory->songs);
- if (!directory_is_root(directory) &&
- fprintf(fp, DIRECTORY_END "%s\n",
- directory_get_path(directory)) < 0)
- return -1;
- return 0;
+ if (!directory_is_root(directory))
+ fprintf(fp, DIRECTORY_END "%s\n",
+ directory_get_path(directory));
}
-bool
-directory_load(FILE *fp, struct directory *directory, GError **error)
+static struct directory *
+directory_load_subdir(FILE *fp, struct directory *parent, const char *name,
+ GString *buffer, GError **error_r)
{
- char buffer[MPD_PATH_MAX * 2];
- char key[MPD_PATH_MAX * 2];
- char *name;
+ struct directory *directory;
+ const char *line;
bool success;
- while (fgets(buffer, sizeof(buffer), fp)
- && !g_str_has_prefix(buffer, DIRECTORY_END)) {
- if (g_str_has_prefix(buffer, DIRECTORY_DIR)) {
- struct directory *subdir;
+ if (directory_get_child(parent, name) != NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Duplicate subdirectory '%s'", name);
+ return NULL;
+ }
- g_strchomp(buffer);
- strcpy(key, &(buffer[strlen(DIRECTORY_DIR)]));
- if (!fgets(buffer, sizeof(buffer), fp)) {
- g_set_error(error, directory_quark(), 0,
- "Unexpected end of file");
- return false;
- }
+ if (directory_is_root(parent)) {
+ directory = directory_new(name, parent);
+ } else {
+ char *path = g_strconcat(directory_get_path(parent), "/",
+ name, NULL);
+ directory = directory_new(path, parent);
+ g_free(path);
+ }
- if (g_str_has_prefix(buffer, DIRECTORY_MTIME)) {
- directory->mtime =
- g_ascii_strtoull(buffer + sizeof(DIRECTORY_MTIME) - 1,
- NULL, 10);
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory_free(directory);
+ return NULL;
+ }
- if (!fgets(buffer, sizeof(buffer), fp)) {
- g_set_error(error, directory_quark(), 0,
- "Unexpected end of file");
- return false;
- }
- }
+ if (g_str_has_prefix(line, DIRECTORY_MTIME)) {
+ directory->mtime =
+ g_ascii_strtoull(line + sizeof(DIRECTORY_MTIME) - 1,
+ NULL, 10);
+
+ line = read_text_line(fp, buffer);
+ if (line == NULL) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Unexpected end of file");
+ directory_free(directory);
+ return NULL;
+ }
+ }
- if (!g_str_has_prefix(buffer, DIRECTORY_BEGIN)) {
- g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", buffer);
- return false;
- }
+ if (!g_str_has_prefix(line, DIRECTORY_BEGIN)) {
+ g_set_error(error_r, directory_quark(), 0,
+ "Malformed line: %s", line);
+ directory_free(directory);
+ return NULL;
+ }
- g_strchomp(buffer);
- name = &(buffer[strlen(DIRECTORY_BEGIN)]);
- if (!g_str_has_prefix(name, directory->path) != 0) {
- g_set_error(error, directory_quark(), 0,
- "Wrong path in database: '%s' in '%s'",
- name, directory->path);
+ success = directory_load(fp, directory, buffer, error_r);
+ if (!success) {
+ directory_free(directory);
+ return NULL;
+ }
+
+ return directory;
+}
+
+bool
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error)
+{
+ const char *line;
+
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ !g_str_has_prefix(line, DIRECTORY_END)) {
+ if (g_str_has_prefix(line, DIRECTORY_DIR)) {
+ struct directory *subdir =
+ directory_load_subdir(fp, directory,
+ line + sizeof(DIRECTORY_DIR) - 1,
+ buffer, error);
+ if (subdir == NULL)
return false;
- }
- subdir = directory_get_child(directory, name);
- if (subdir != NULL) {
- assert(subdir->parent == directory);
- } else {
- subdir = directory_new(name, directory);
- dirvec_add(&directory->children, subdir);
+ dirvec_add(&directory->children, subdir);
+ } else if (g_str_has_prefix(line, SONG_BEGIN)) {
+ const char *name = line + sizeof(SONG_BEGIN) - 1;
+ struct song *song;
+
+ if (songvec_find(&directory->songs, name) != NULL) {
+ g_set_error(error, directory_quark(), 0,
+ "Duplicate song '%s'", name);
+ return NULL;
}
- success = directory_load(fp, subdir, error);
- if (!success)
+ song = song_load(fp, directory, name,
+ buffer, error);
+ if (song == NULL)
return false;
- } else if (g_str_has_prefix(buffer, SONG_BEGIN)) {
- readSongInfoIntoList(fp, &directory->songs, directory);
+
+ songvec_add(&directory->songs, song);
} else {
g_set_error(error, directory_quark(), 0,
- "Malformed line: %s", buffer);
+ "Malformed line: %s", line);
return false;
}
}
diff --git a/src/directory_save.h b/src/directory_save.h
index 28ec094ad..fa2775624 100644
--- a/src/directory_save.h
+++ b/src/directory_save.h
@@ -27,10 +27,11 @@
struct directory;
-int
+void
directory_save(FILE *fp, struct directory *directory);
bool
-directory_load(FILE *fp, struct directory *directory, GError **error);
+directory_load(FILE *fp, struct directory *directory,
+ GString *buffer, GError **error);
#endif
diff --git a/src/dirvec.c b/src/dirvec.c
index 3ccb5d413..8061835d5 100644
--- a/src/dirvec.c
+++ b/src/dirvec.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "dirvec.h"
#include "directory.h"
diff --git a/src/encoder/flac_encoder.c b/src/encoder/flac_encoder.c
new file mode 100644
index 000000000..00b59761b
--- /dev/null
+++ b/src/encoder/flac_encoder.c
@@ -0,0 +1,352 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "audio_format.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+#include <FLAC/stream_encoder.h>
+
+struct flac_encoder {
+ struct encoder encoder;
+
+ struct audio_format audio_format;
+ unsigned compression;
+
+ FLAC__StreamEncoder *fse;
+
+ struct pcm_buffer expand_buffer;
+
+ struct pcm_buffer buffer;
+ size_t buffer_length;
+};
+
+extern const struct encoder_plugin flac_encoder_plugin;
+
+
+static inline GQuark
+flac_encoder_quark(void)
+{
+ return g_quark_from_static_string("flac_encoder");
+}
+
+static bool
+flac_encoder_configure(struct flac_encoder *encoder,
+ const struct config_param *param, G_GNUC_UNUSED GError **error)
+{
+ encoder->compression = config_get_block_unsigned(param,
+ "compression", 5);
+
+ return true;
+}
+
+static struct encoder *
+flac_encoder_init(const struct config_param *param, GError **error)
+{
+ struct flac_encoder *encoder;
+
+ encoder = g_new(struct flac_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &flac_encoder_plugin);
+
+ /* load configuration from "param" */
+ if (!flac_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ g_free(encoder);
+ return NULL;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+flac_encoder_finish(struct encoder *_encoder)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ /* the real libFLAC cleanup was already performed by
+ flac_encoder_close(), so no real work here */
+ g_free(encoder);
+}
+
+static bool
+flac_encoder_setup(struct flac_encoder *encoder, unsigned bits_per_sample,
+ GError **error)
+{
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+#else
+ if ( !FLAC__stream_encoder_set_compression_level(encoder->fse,
+ encoder->compression)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac compression to %d",
+ encoder->compression);
+ return false;
+ }
+#endif
+ if ( !FLAC__stream_encoder_set_channels(encoder->fse,
+ encoder->audio_format.channels)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac channels num to %d",
+ encoder->audio_format.channels);
+ return false;
+ }
+ if ( !FLAC__stream_encoder_set_bits_per_sample(encoder->fse,
+ bits_per_sample)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac bit format to %d",
+ bits_per_sample);
+ return false;
+ }
+ if ( !FLAC__stream_encoder_set_sample_rate(encoder->fse,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "error setting flac sample rate to %d",
+ encoder->audio_format.sample_rate);
+ return false;
+ }
+ return true;
+}
+
+static FLAC__StreamEncoderWriteStatus
+flac_write_callback(G_GNUC_UNUSED const FLAC__StreamEncoder *fse,
+ const FLAC__byte data[], size_t bytes, G_GNUC_UNUSED unsigned samples,
+ G_GNUC_UNUSED unsigned current_frame, void *client_data)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *) client_data;
+
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + bytes);
+
+ //transfer data to buffer
+ memcpy( buffer + encoder->buffer_length, data, bytes);
+ encoder->buffer_length += bytes;
+
+ return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
+}
+
+static void
+flac_encoder_close(struct encoder *_encoder)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ FLAC__stream_encoder_delete(encoder->fse);
+
+ pcm_buffer_deinit(&encoder->buffer);
+ pcm_buffer_deinit(&encoder->expand_buffer);
+}
+
+static bool
+flac_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
+ GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ unsigned bits_per_sample;
+
+ encoder->audio_format = *audio_format;
+
+ /* FIXME: flac should support 32bit as well */
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ bits_per_sample = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ bits_per_sample = 16;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ bits_per_sample = 24;
+ break;
+
+ default:
+ bits_per_sample = 24;
+ audio_format->format = SAMPLE_FORMAT_S24_P32;
+ }
+
+ /* allocate the encoder */
+ encoder->fse = FLAC__stream_encoder_new();
+ if (encoder->fse == NULL) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "flac_new() failed");
+ return false;
+ }
+
+ if (!flac_encoder_setup(encoder, bits_per_sample, error)) {
+ FLAC__stream_encoder_delete(encoder->fse);
+ return false;
+ }
+
+ encoder->buffer_length = 0;
+ pcm_buffer_init(&encoder->buffer);
+ pcm_buffer_init(&encoder->expand_buffer);
+
+ /* this immediatelly outputs data throught callback */
+
+#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
+ {
+ FLAC__StreamEncoderState init_status;
+
+ FLAC__stream_encoder_set_write_callback(encoder->fse,
+ flac_write_callback);
+
+ init_status = FLAC__stream_encoder_init(encoder->fse);
+
+ if (init_status != FLAC__STREAM_ENCODER_OK) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "failed to initialize encoder: %s\n",
+ FLAC__StreamEncoderStateString[init_status]);
+ flac_encoder_close(_encoder);
+ return false;
+ }
+ }
+#else
+ {
+ FLAC__StreamEncoderInitStatus init_status;
+
+ init_status = FLAC__stream_encoder_init_stream(encoder->fse,
+ flac_write_callback,
+ NULL, NULL, NULL, encoder);
+
+ if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "failed to initialize encoder: %s\n",
+ FLAC__StreamEncoderInitStatusString[init_status]);
+ flac_encoder_close(_encoder);
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+
+static bool
+flac_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+
+ (void) FLAC__stream_encoder_finish(encoder->fse);
+ return true;
+}
+
+static inline void
+pcm8_to_flac(int32_t *out, const int8_t *in, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ *out++ = *in++;
+ --num_samples;
+ }
+}
+
+static inline void
+pcm16_to_flac(int32_t *out, const int16_t *in, unsigned num_samples)
+{
+ while (num_samples > 0) {
+ *out++ = *in++;
+ --num_samples;
+ }
+}
+
+static bool
+flac_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ unsigned num_frames, num_samples;
+ void *exbuffer;
+ const void *buffer = NULL;
+
+ /* format conversion */
+
+ num_frames = length / audio_format_frame_size(&encoder->audio_format);
+ num_samples = num_frames * encoder->audio_format.channels;
+
+ switch (encoder->audio_format.format) {
+ case SAMPLE_FORMAT_S8:
+ exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*4);
+ pcm8_to_flac(exbuffer, data, num_samples);
+ buffer = exbuffer;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ exbuffer = pcm_buffer_get(&encoder->expand_buffer, length*2);
+ pcm16_to_flac(exbuffer, data, num_samples);
+ buffer = exbuffer;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ case SAMPLE_FORMAT_S32:
+ /* nothing need to be done; format is the same for
+ both mpd and libFLAC */
+ buffer = data;
+ break;
+ }
+
+ /* feed samples to encoder */
+
+ if (!FLAC__stream_encoder_process_interleaved(encoder->fse, buffer,
+ num_frames)) {
+ g_set_error(error, flac_encoder_quark(), 0,
+ "flac encoder process failed");
+ return false;
+ }
+
+ return true;
+}
+
+static size_t
+flac_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct flac_encoder *encoder = (struct flac_encoder *)_encoder;
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(buffer, buffer + length, encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+flac_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/flac";
+}
+
+const struct encoder_plugin flac_encoder_plugin = {
+ .name = "flac",
+ .init = flac_encoder_init,
+ .finish = flac_encoder_finish,
+ .open = flac_encoder_open,
+ .close = flac_encoder_close,
+ .flush = flac_encoder_flush,
+ .write = flac_encoder_write,
+ .read = flac_encoder_read,
+ .get_mime_type = flac_encoder_get_mime_type,
+};
+
diff --git a/src/encoder/lame_encoder.c b/src/encoder/lame_encoder.c
index acaf4470f..279a676d9 100644
--- a/src/encoder/lame_encoder.c
+++ b/src/encoder/lame_encoder.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "encoder_api.h"
#include "encoder_plugin.h"
#include "audio_format.h"
@@ -184,7 +185,7 @@ lame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
{
struct lame_encoder *encoder = (struct lame_encoder *)_encoder;
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
audio_format->channels = 2;
encoder->audio_format = *audio_format;
@@ -274,6 +275,12 @@ lame_encoder_read(struct encoder *_encoder, void *dest, size_t length)
return length;
}
+static const char *
+lame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
const struct encoder_plugin lame_encoder_plugin = {
.name = "lame",
.init = lame_encoder_init,
@@ -282,4 +289,5 @@ const struct encoder_plugin lame_encoder_plugin = {
.close = lame_encoder_close,
.write = lame_encoder_write,
.read = lame_encoder_read,
+ .get_mime_type = lame_encoder_get_mime_type,
};
diff --git a/src/encoder/null_encoder.c b/src/encoder/null_encoder.c
new file mode 100644
index 000000000..3a73e3987
--- /dev/null
+++ b/src/encoder/null_encoder.c
@@ -0,0 +1,124 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct null_encoder {
+ struct encoder encoder;
+
+ struct pcm_buffer buffer;
+ size_t buffer_length;
+};
+
+extern const struct encoder_plugin null_encoder_plugin;
+
+static inline GQuark
+null_encoder_quark(void)
+{
+ return g_quark_from_static_string("null_encoder");
+}
+
+static struct encoder *
+null_encoder_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder;
+
+ encoder = g_new(struct null_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &null_encoder_plugin);
+
+ return &encoder->encoder;
+}
+
+static void
+null_encoder_finish(struct encoder *_encoder)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ g_free(encoder);
+}
+
+static void
+null_encoder_close(struct encoder *_encoder)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ pcm_buffer_deinit(&encoder->buffer);
+}
+
+
+static bool
+null_encoder_open(struct encoder *_encoder,
+ G_GNUC_UNUSED struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+
+ encoder->buffer_length = 0;
+ pcm_buffer_init(&encoder->buffer);
+
+ return true;
+}
+
+static bool
+null_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
+
+ memcpy(buffer+encoder->buffer_length, data, length);
+
+ encoder->buffer_length += length;
+ return true;
+}
+
+static size_t
+null_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct null_encoder *encoder = (struct null_encoder *)_encoder;
+ char *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length);
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(buffer, buffer + length, encoder->buffer_length);
+
+ return length;
+}
+
+const struct encoder_plugin null_encoder_plugin = {
+ .name = "null",
+ .init = null_encoder_init,
+ .finish = null_encoder_finish,
+ .open = null_encoder_open,
+ .close = null_encoder_close,
+ .write = null_encoder_write,
+ .read = null_encoder_read,
+};
diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c
new file mode 100644
index 000000000..5081db9fb
--- /dev/null
+++ b/src/encoder/twolame_encoder.c
@@ -0,0 +1,307 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "audio_format.h"
+
+#include <twolame.h>
+#include <assert.h>
+#include <string.h>
+
+struct twolame_encoder {
+ struct encoder encoder;
+
+ struct audio_format audio_format;
+ float quality;
+ int bitrate;
+
+ twolame_options *options;
+
+ unsigned char buffer[32768];
+ size_t buffer_length;
+
+ /**
+ * Call libtwolame's flush function when the buffer is empty?
+ */
+ bool flush;
+};
+
+extern const struct encoder_plugin twolame_encoder_plugin;
+
+static inline GQuark
+twolame_encoder_quark(void)
+{
+ return g_quark_from_static_string("twolame_encoder");
+}
+
+static bool
+twolame_encoder_configure(struct twolame_encoder *encoder,
+ const struct config_param *param, GError **error)
+{
+ const char *value;
+ char *endptr;
+
+ value = config_get_block_string(param, "quality", NULL);
+ if (value != NULL) {
+ /* a quality was configured (VBR) */
+
+ encoder->quality = g_ascii_strtod(value, &endptr);
+
+ if (*endptr != '\0' || encoder->quality < -1.0 ||
+ encoder->quality > 10.0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality \"%s\" is not a number in the "
+ "range -1 to 10, line %i",
+ value, param->line);
+ return false;
+ }
+
+ if (config_get_block_string(param, "bitrate", NULL) != NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "quality and bitrate are "
+ "both defined (line %i)",
+ param->line);
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ value = config_get_block_string(param, "bitrate", NULL);
+ if (value == NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "neither bitrate nor quality defined "
+ "at line %i",
+ param->line);
+ return false;
+ }
+
+ encoder->quality = -2.0;
+ encoder->bitrate = g_ascii_strtoll(value, &endptr, 10);
+
+ if (*endptr != '\0' || encoder->bitrate <= 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "bitrate at line %i should be a positive integer",
+ param->line);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static struct encoder *
+twolame_encoder_init(const struct config_param *param, GError **error)
+{
+ struct twolame_encoder *encoder;
+
+ g_debug("libtwolame version %s", get_twolame_version());
+
+ encoder = g_new(struct twolame_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &twolame_encoder_plugin);
+
+ /* load configuration from "param" */
+ if (!twolame_encoder_configure(encoder, param, error)) {
+ /* configuration has failed, roll back and return error */
+ g_free(encoder);
+ return NULL;
+ }
+
+ return &encoder->encoder;
+}
+
+static void
+twolame_encoder_finish(struct encoder *_encoder)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ /* the real libtwolame cleanup was already performed by
+ twolame_encoder_close(), so no real work here */
+ g_free(encoder);
+}
+
+static bool
+twolame_encoder_setup(struct twolame_encoder *encoder, GError **error)
+{
+ if (encoder->quality >= -1.0) {
+ /* a quality was configured (VBR) */
+
+ if (0 != twolame_set_VBR(encoder->options, true)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR mode");
+ return false;
+ }
+ if (0 != twolame_set_VBR_q(encoder->options, encoder->quality)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame VBR quality");
+ return false;
+ }
+ } else {
+ /* a bit rate was configured */
+
+ if (0 != twolame_set_brate(encoder->options, encoder->bitrate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame bitrate");
+ return false;
+ }
+ }
+
+ if (0 != twolame_set_num_channels(encoder->options,
+ encoder->audio_format.channels)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame num channels");
+ return false;
+ }
+
+ if (0 != twolame_set_in_samplerate(encoder->options,
+ encoder->audio_format.sample_rate)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error setting twolame sample rate");
+ return false;
+ }
+
+ if (0 > twolame_init_params(encoder->options)) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "error initializing twolame params");
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+twolame_encoder_open(struct encoder *_encoder, struct audio_format *audio_format,
+ GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ audio_format->format = SAMPLE_FORMAT_S16;
+ audio_format->channels = 2;
+
+ encoder->audio_format = *audio_format;
+
+ encoder->options = twolame_init();
+ if (encoder->options == NULL) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame_init() failed");
+ return false;
+ }
+
+ if (!twolame_encoder_setup(encoder, error)) {
+ twolame_close(&encoder->options);
+ return false;
+ }
+
+ encoder->buffer_length = 0;
+ encoder->flush = false;
+
+ return true;
+}
+
+static void
+twolame_encoder_close(struct encoder *_encoder)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ twolame_close(&encoder->options);
+}
+
+static bool
+twolame_encoder_flush(struct encoder *_encoder, G_GNUC_UNUSED GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ encoder->flush = true;
+ return true;
+}
+
+static bool
+twolame_encoder_write(struct encoder *_encoder,
+ const void *data, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+ unsigned num_frames;
+ const int16_t *src = (const int16_t*)data;
+ int bytes_out;
+
+ assert(encoder->buffer_length == 0);
+
+ num_frames =
+ length / audio_format_frame_size(&encoder->audio_format);
+
+ bytes_out = twolame_encode_buffer_interleaved(encoder->options,
+ src, num_frames,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (bytes_out < 0) {
+ g_set_error(error, twolame_encoder_quark(), 0,
+ "twolame encoder failed");
+ return false;
+ }
+
+ encoder->buffer_length = (size_t)bytes_out;
+ return true;
+}
+
+static size_t
+twolame_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct twolame_encoder *encoder = (struct twolame_encoder *)_encoder;
+
+ if (encoder->buffer_length == 0 && encoder->flush) {
+ int ret = twolame_encode_flush(encoder->options,
+ encoder->buffer,
+ sizeof(encoder->buffer));
+ if (ret > 0)
+ encoder->buffer_length = (size_t)ret;
+
+ encoder->flush = false;
+ }
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, encoder->buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(encoder->buffer, encoder->buffer + length,
+ encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+twolame_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/mpeg";
+}
+
+const struct encoder_plugin twolame_encoder_plugin = {
+ .name = "twolame",
+ .init = twolame_encoder_init,
+ .finish = twolame_encoder_finish,
+ .open = twolame_encoder_open,
+ .close = twolame_encoder_close,
+ .flush = twolame_encoder_flush,
+ .write = twolame_encoder_write,
+ .read = twolame_encoder_read,
+ .get_mime_type = twolame_encoder_get_mime_type,
+};
diff --git a/src/encoder/vorbis_encoder.c b/src/encoder/vorbis_encoder.c
index a5f6387f6..8e118e90a 100644
--- a/src/encoder/vorbis_encoder.c
+++ b/src/encoder/vorbis_encoder.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "encoder_api.h"
#include "encoder_plugin.h"
#include "tag.h"
@@ -211,7 +212,7 @@ vorbis_encoder_open(struct encoder *_encoder,
struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder;
bool ret;
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
encoder->audio_format = *audio_format;
@@ -381,6 +382,12 @@ vorbis_encoder_read(struct encoder *_encoder, void *_dest, size_t length)
return nbytes;
}
+static const char *
+vorbis_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "application/x-ogg";
+}
+
const struct encoder_plugin vorbis_encoder_plugin = {
.name = "vorbis",
.init = vorbis_encoder_init,
@@ -391,4 +398,5 @@ const struct encoder_plugin vorbis_encoder_plugin = {
.tag = vorbis_encoder_tag,
.write = vorbis_encoder_write,
.read = vorbis_encoder_read,
+ .get_mime_type = vorbis_encoder_get_mime_type,
};
diff --git a/src/encoder/wave_encoder.c b/src/encoder/wave_encoder.c
new file mode 100644
index 000000000..3f6e21845
--- /dev/null
+++ b/src/encoder/wave_encoder.c
@@ -0,0 +1,270 @@
+/*
+ * 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 "encoder_api.h"
+#include "encoder_plugin.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct wave_encoder {
+ struct encoder encoder;
+ unsigned bits;
+
+ struct pcm_buffer buffer;
+ size_t buffer_length;
+};
+
+struct wave_header {
+ uint32_t id_riff;
+ uint32_t riff_size;
+ uint32_t id_wave;
+ uint32_t id_fmt;
+ uint32_t fmt_size;
+ uint16_t format;
+ uint16_t channels;
+ uint32_t freq;
+ uint32_t byterate;
+ uint16_t blocksize;
+ uint16_t bits;
+ uint32_t id_data;
+ uint32_t data_size;
+};
+
+extern const struct encoder_plugin wave_encoder_plugin;
+
+static inline GQuark
+wave_encoder_quark(void)
+{
+ return g_quark_from_static_string("wave_encoder");
+}
+
+static void
+fill_wave_header(struct wave_header *header, int channels, int bits,
+ int freq, int block_size)
+{
+ int data_size = 0x0FFFFFFF;
+
+ /* constants */
+ header->id_riff = GUINT32_TO_LE(0x46464952);
+ header->id_wave = GUINT32_TO_LE(0x45564157);
+ header->id_fmt = GUINT32_TO_LE(0x20746d66);
+ header->id_data = GUINT32_TO_LE(0x61746164);
+
+ /* wave format */
+ header->format = GUINT16_TO_LE(1); // PCM_FORMAT
+ header->channels = GUINT16_TO_LE(channels);
+ header->bits = GUINT16_TO_LE(bits);
+ header->freq = GUINT32_TO_LE(freq);
+ header->blocksize = GUINT16_TO_LE(block_size);
+ header->byterate = GUINT32_TO_LE(freq * block_size);
+
+ /* chunk sizes (fake data length) */
+ header->fmt_size = GUINT32_TO_LE(16);
+ header->data_size = GUINT32_TO_LE(data_size);
+ header->riff_size = GUINT32_TO_LE(4 + (8 + 16) +
+ (8 + data_size));
+}
+
+static struct encoder *
+wave_encoder_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ struct wave_encoder *encoder;
+
+ encoder = g_new(struct wave_encoder, 1);
+ encoder_struct_init(&encoder->encoder, &wave_encoder_plugin);
+ pcm_buffer_init(&encoder->buffer);
+
+ return &encoder->encoder;
+}
+
+static void
+wave_encoder_finish(struct encoder *_encoder)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+
+ pcm_buffer_deinit(&encoder->buffer);
+ g_free(encoder);
+}
+
+static bool
+wave_encoder_open(struct encoder *_encoder,
+ G_GNUC_UNUSED struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+ void *buffer;
+
+ assert(audio_format_valid(audio_format));
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ encoder->bits = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ encoder->bits = 16;
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ encoder->bits = 24;
+ break;
+
+ case SAMPLE_FORMAT_S32:
+ encoder->bits = 32;
+ break;
+
+ default:
+ audio_format->format = SAMPLE_FORMAT_S16;
+ encoder->bits = 16;
+ break;
+ }
+
+ buffer = pcm_buffer_get(&encoder->buffer, sizeof(struct wave_header) );
+
+ /* create PCM wave header in initial buffer */
+ fill_wave_header((struct wave_header *) buffer,
+ audio_format->channels,
+ encoder->bits,
+ audio_format->sample_rate,
+ (encoder->bits / 8) * audio_format->channels );
+
+ encoder->buffer_length = sizeof(struct wave_header);
+ return true;
+}
+
+static inline size_t
+pcm16_to_wave(uint16_t *dst16, const uint16_t *src16, size_t length)
+{
+ size_t cnt = length >> 1;
+ while (cnt > 0) {
+ *dst16++ = GUINT16_TO_LE(*src16++);
+ cnt--;
+ }
+ return length;
+}
+
+static inline size_t
+pcm32_to_wave(uint32_t *dst32, const uint32_t *src32, size_t length)
+{
+ size_t cnt = length >> 2;
+ while (cnt > 0){
+ *dst32++ = GUINT32_TO_LE(*src32++);
+ cnt--;
+ }
+ return length;
+}
+
+static inline size_t
+pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length)
+{
+ uint32_t value;
+ uint8_t *dst_old = dst8;
+
+ length = length >> 2;
+ while (length > 0){
+ value = *src32++;
+ *dst8++ = (value) & 0xFF;
+ *dst8++ = (value >> 8) & 0xFF;
+ *dst8++ = (value >> 16) & 0xFF;
+ length--;
+ }
+ //correct buffer length
+ return (dst8 - dst_old);
+}
+
+static bool
+wave_encoder_write(struct encoder *_encoder,
+ const void *src, size_t length,
+ G_GNUC_UNUSED GError **error)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+ void *dst;
+
+ dst = pcm_buffer_get(&encoder->buffer, encoder->buffer_length + length);
+
+#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
+ switch (encoder->bits) {
+ case 8:
+ case 16:
+ case 32:// optimized cases
+ memcpy(dst, src, length);
+ break;
+ case 24:
+ length = pcm24_to_wave(dst, src, length);
+ break;
+ }
+#elif (G_BYTE_ORDER == G_BIG_ENDIAN)
+ switch (encoder->bits) {
+ case 8:
+ memcpy(dst, src, length);
+ break;
+ case 16:
+ length = pcm16_to_wave(dst, src, length);
+ break;
+ case 24:
+ length = pcm24_to_wave(dst, src, length);
+ break;
+ case 32:
+ length = pcm32_to_wave(dst, src, length);
+ break;
+ }
+#else
+#error G_BYTE_ORDER set to G_PDP_ENDIAN is not supported by wave_encoder
+#endif
+
+ encoder->buffer_length += length;
+ return true;
+}
+
+static size_t
+wave_encoder_read(struct encoder *_encoder, void *dest, size_t length)
+{
+ struct wave_encoder *encoder = (struct wave_encoder *)_encoder;
+ uint8_t *buffer = pcm_buffer_get(&encoder->buffer, encoder->buffer_length );
+
+ if (length > encoder->buffer_length)
+ length = encoder->buffer_length;
+
+ memcpy(dest, buffer, length);
+
+ encoder->buffer_length -= length;
+ memmove(buffer, buffer + length, encoder->buffer_length);
+
+ return length;
+}
+
+static const char *
+wave_encoder_get_mime_type(G_GNUC_UNUSED struct encoder *_encoder)
+{
+ return "audio/wav";
+}
+
+const struct encoder_plugin wave_encoder_plugin = {
+ .name = "wave",
+ .init = wave_encoder_init,
+ .finish = wave_encoder_finish,
+ .open = wave_encoder_open,
+ .write = wave_encoder_write,
+ .read = wave_encoder_read,
+ .get_mime_type = wave_encoder_get_mime_type,
+};
diff --git a/src/encoder_list.c b/src/encoder_list.c
index d563b6bc8..d86753d50 100644
--- a/src/encoder_list.c
+++ b/src/encoder_list.c
@@ -17,22 +17,36 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "encoder_list.h"
#include "encoder_plugin.h"
-#include "config.h"
#include <string.h>
+extern const struct encoder_plugin null_encoder_plugin;
extern const struct encoder_plugin vorbis_encoder_plugin;
extern const struct encoder_plugin lame_encoder_plugin;
+extern const struct encoder_plugin twolame_encoder_plugin;
+extern const struct encoder_plugin wave_encoder_plugin;
+extern const struct encoder_plugin flac_encoder_plugin;
static const struct encoder_plugin *encoder_plugins[] = {
+ &null_encoder_plugin,
#ifdef ENABLE_VORBIS_ENCODER
&vorbis_encoder_plugin,
#endif
#ifdef ENABLE_LAME_ENCODER
&lame_encoder_plugin,
#endif
+#ifdef ENABLE_TWOLAME_ENCODER
+ &twolame_encoder_plugin,
+#endif
+#ifdef ENABLE_WAVE_ENCODER
+ &wave_encoder_plugin,
+#endif
+#ifdef ENABLE_FLAC_ENCODER
+ &flac_encoder_plugin,
+#endif
NULL
};
@@ -45,3 +59,13 @@ encoder_plugin_get(const char *name)
return NULL;
}
+
+void
+encoder_plugin_print_all_types(FILE * fp)
+{
+ for (unsigned i = 0; encoder_plugins[i] != NULL; ++i)
+ fprintf(fp, "%s ", encoder_plugins[i]->name);
+
+ fprintf(fp, "\n");
+ fflush(fp);
+}
diff --git a/src/encoder_list.h b/src/encoder_list.h
index bc20ad8c5..26caab242 100644
--- a/src/encoder_list.h
+++ b/src/encoder_list.h
@@ -20,6 +20,8 @@
#ifndef MPD_ENCODER_LIST_H
#define MPD_ENCODER_LIST_H
+#include <stdio.h>
+
struct encoder_plugin;
/**
@@ -32,4 +34,7 @@ struct encoder_plugin;
const struct encoder_plugin *
encoder_plugin_get(const char *name);
+void
+encoder_plugin_print_all_types(FILE * fp);
+
#endif
diff --git a/src/encoder_plugin.h b/src/encoder_plugin.h
index 958fe97cf..e8f2e4527 100644
--- a/src/encoder_plugin.h
+++ b/src/encoder_plugin.h
@@ -58,6 +58,8 @@ struct encoder_plugin {
GError **error);
size_t (*read)(struct encoder *encoder, void *dest, size_t length);
+
+ const char *(*get_mime_type)(struct encoder *encoder);
};
/**
@@ -192,4 +194,19 @@ encoder_read(struct encoder *encoder, void *dest, size_t length)
return encoder->plugin->read(encoder, dest, length);
}
+/**
+ * Get mime type of encoded content.
+ *
+ * @param plugin the encoder plugin
+ * @return an constant string, NULL on failure
+ */
+static inline const char *
+encoder_get_mime_type(struct encoder *encoder)
+{
+ /* this method is optional */
+ return encoder->plugin->get_mime_type != NULL
+ ? encoder->plugin->get_mime_type(encoder)
+ : NULL;
+}
+
#endif
diff --git a/src/event_pipe.c b/src/event_pipe.c
index 3e5009150..dbec23aa8 100644
--- a/src/event_pipe.c
+++ b/src/event_pipe.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "event_pipe.h"
-#include "utils.h"
+#include "fd_util.h"
#include <stdbool.h>
#include <assert.h>
@@ -84,17 +85,9 @@ void event_pipe_init(void)
GIOChannel *channel;
int ret;
-#ifdef WIN32
- ret = _pipe(event_pipe, 512, _O_BINARY);
-#else
- ret = pipe(event_pipe);
-#endif
+ ret = pipe_cloexec_nonblock(event_pipe);
if (ret < 0)
g_error("Couldn't open pipe: %s", strerror(errno));
-#ifndef WIN32
- if (set_nonblocking(event_pipe[1]) < 0)
- g_error("Couldn't set non-blocking I/O: %s", strerror(errno));
-#endif
channel = g_io_channel_unix_new(event_pipe[0]);
event_pipe_source_id = g_io_add_watch(channel, G_IO_IN,
diff --git a/src/event_pipe.h b/src/event_pipe.h
index ecb7ec9e8..4614ef25c 100644
--- a/src/event_pipe.h
+++ b/src/event_pipe.h
@@ -32,7 +32,7 @@ enum pipe_event {
/** an idle event was emitted */
PIPE_EVENT_IDLE,
- /** must call syncPlayerAndPlaylist() */
+ /** must call playlist_sync() */
PIPE_EVENT_PLAYLIST,
/** the current song's tag has changed */
@@ -41,6 +41,9 @@ enum pipe_event {
/** SIGHUP received: reload configuration, roll log file */
PIPE_EVENT_RELOAD,
+ /** a hardware mixer plugin has detected a change */
+ PIPE_EVENT_MIXER,
+
PIPE_EVENT_MAX
};
diff --git a/src/exclude.c b/src/exclude.c
new file mode 100644
index 000000000..5bf7ccbbd
--- /dev/null
+++ b/src/exclude.c
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+/*
+ * The .mpdignore backend code.
+ *
+ */
+
+#include "config.h"
+#include "exclude.h"
+#include "path.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+
+GSList *
+exclude_list_load(const char *path_fs)
+{
+ FILE *file;
+ char line[1024];
+ GSList *list = NULL;
+
+ assert(path_fs != NULL);
+
+ file = fopen(path_fs, "r");
+ if (file == NULL) {
+ if (errno != ENOENT) {
+ char *path_utf8 = fs_charset_to_utf8(path_fs);
+ g_debug("Failed to open %s: %s",
+ path_utf8, g_strerror(errno));
+ g_free(path_utf8);
+ }
+
+ return NULL;
+ }
+
+ while (fgets(line, sizeof(line), file) != NULL) {
+ char *p = strchr(line, '#');
+ if (p != NULL)
+ *p = 0;
+
+ p = g_strstrip(line);
+ if (*p != 0)
+ list = g_slist_prepend(list, g_pattern_spec_new(p));
+ }
+
+ fclose(file);
+
+ return list;
+}
+
+void
+exclude_list_free(GSList *list)
+{
+ while (list != NULL) {
+ GPatternSpec *pattern = list->data;
+ g_pattern_spec_free(pattern);
+ list = g_slist_remove(list, list->data);
+ }
+}
+
+bool
+exclude_list_check(GSList *list, const char *name_fs)
+{
+ assert(name_fs != NULL);
+
+ /* XXX include full path name in check */
+
+ for (; list != NULL; list = list->next) {
+ GPatternSpec *pattern = list->data;
+
+ if (g_pattern_match_string(pattern, name_fs))
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/compress.h b/src/exclude.h
index 3e3afb565..637feb846 100644
--- a/src/compress.h
+++ b/src/exclude.h
@@ -18,31 +18,34 @@
*/
/*
- * Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
+ * The .mpdignore backend code.
+ *
*/
-#ifndef MPD_COMPRESS_H
-#define MPD_COMPRESS_H
-
-/* These are copied from the AudioCompress config.h, mainly because CompressDo
- * needs GAINSHIFT defined. The rest are here so they can be used as defaults
- * to pass to CompressCfg(). -- jat */
-#define ANTICLIP 0 /* Strict clipping protection */
-#define TARGET 25000 /* Target level */
-#define GAINMAX 32 /* The maximum amount to amplify by */
-#define GAINSHIFT 10 /* How fine-grained the gain is */
-#define GAINSMOOTH 8 /* How much inertia ramping has*/
-#define BUCKETS 400 /* How long of a history to store */
-
-void CompressCfg(int monitor,
- int anticlip,
- int target,
- int maxgain,
- int smooth,
- unsigned buckets);
-
-void CompressDo(void *data, unsigned int numSamples);
-
-void CompressFree(void);
+#ifndef MPD_EXCLUDE_H
+#define MPD_EXCLUDE_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+
+/**
+ * Loads and parses a .mpdignore file.
+ */
+GSList *
+exclude_list_load(const char *path_fs);
+
+/**
+ * Frees a list returned by exclude_list_load().
+ */
+void
+exclude_list_free(GSList *list);
+
+/**
+ * Checks whether one of the patterns in the .mpdignore file matches
+ * the specified file name.
+ */
+bool
+exclude_list_check(GSList *list, const char *name_fs);
#endif
diff --git a/src/fd_util.c b/src/fd_util.c
new file mode 100644
index 000000000..9c60d00ae
--- /dev/null
+++ b/src/fd_util.c
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h" /* must be first for large file support */
+#include "fd_util.h"
+
+#if !defined(_GNU_SOURCE) && (defined(HAVE_PIPE2) || defined(HAVE_ACCEPT4))
+#define _GNU_SOURCE
+#endif
+
+#include <assert.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_INOTIFY_INIT
+#include <sys/inotify.h>
+#endif
+
+#ifndef WIN32
+
+static int
+fd_mask_flags(int fd, int and_mask, int xor_mask)
+{
+ int ret;
+
+ assert(fd >= 0);
+
+ ret = fcntl(fd, F_GETFD, 0);
+ if (ret < 0)
+ return ret;
+
+ return fcntl(fd, F_SETFD, (ret & and_mask) ^ xor_mask);
+}
+
+#endif /* !WIN32 */
+
+static int
+fd_set_cloexec(int fd, bool enable)
+{
+#ifndef WIN32
+ return fd_mask_flags(fd, ~FD_CLOEXEC, enable ? FD_CLOEXEC : 0);
+#else
+ (void)fd;
+ (void)enable;
+#endif
+}
+
+/**
+ * Enables non-blocking mode for the specified file descriptor. On
+ * WIN32, this function only works for sockets.
+ */
+static int
+fd_set_nonblock(int fd)
+{
+#ifdef WIN32
+ u_long val = 1;
+ return ioctlsocket(fd, FIONBIO, &val);
+#else
+ int flags;
+
+ assert(fd >= 0);
+
+ flags = fcntl(fd, F_GETFL);
+ if (flags < 0)
+ return flags;
+
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+}
+
+int
+open_cloexec(const char *path_fs, int flags, int mode)
+{
+ int fd;
+
+#ifdef O_CLOEXEC
+ flags |= O_CLOEXEC;
+#endif
+
+#ifdef O_NOCTTY
+ flags |= O_NOCTTY;
+#endif
+
+ fd = open(path_fs, flags, mode);
+ if (fd >= 0)
+ fd_set_cloexec(fd, true);
+
+ return fd;
+}
+
+int
+pipe_cloexec(int fd[2])
+{
+#ifdef WIN32
+ return _pipe(event_pipe, 512, _O_BINARY);
+#else
+ int ret;
+
+#ifdef HAVE_PIPE2
+ ret = pipe2(fd, O_CLOEXEC);
+ if (ret >= 0 || errno != ENOSYS)
+ return ret;
+#endif
+
+ ret = pipe(fd);
+ if (ret >= 0) {
+ fd_set_cloexec(fd[0], true);
+ fd_set_cloexec(fd[1], true);
+ }
+
+ return ret;
+#endif
+}
+
+int
+pipe_cloexec_nonblock(int fd[2])
+{
+#ifdef WIN32
+ return _pipe(event_pipe, 512, _O_BINARY);
+#else
+ int ret;
+
+#ifdef HAVE_PIPE2
+ ret = pipe2(fd, O_CLOEXEC|O_NONBLOCK);
+ if (ret >= 0 || errno != ENOSYS)
+ return ret;
+#endif
+
+ ret = pipe(fd);
+ if (ret >= 0) {
+ fd_set_cloexec(fd[0], true);
+ fd_set_cloexec(fd[1], true);
+
+ fd_set_nonblock(fd[0]);
+ fd_set_nonblock(fd[1]);
+ }
+
+ return ret;
+#endif
+}
+
+int
+socket_cloexec_nonblock(int domain, int type, int protocol)
+{
+ int fd;
+
+#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
+ fd = socket(domain, type | SOCK_CLOEXEC | SOCK_NONBLOCK, protocol);
+ if (fd >= 0 || errno != EINVAL)
+ return fd;
+#endif
+
+ fd = socket(domain, type, protocol);
+ if (fd >= 0) {
+ fd_set_cloexec(fd, true);
+ fd_set_nonblock(fd);
+ }
+
+ return fd;
+}
+
+int
+accept_cloexec_nonblock(int fd, struct sockaddr *address,
+ size_t *address_length_r)
+{
+ int ret;
+ socklen_t address_length = *address_length_r;
+
+#ifdef HAVE_ACCEPT4
+ ret = accept4(fd, address, &address_length,
+ SOCK_CLOEXEC|SOCK_NONBLOCK);
+ if (ret >= 0 || errno != ENOSYS) {
+ if (ret >= 0)
+ *address_length_r = address_length;
+
+ return ret;
+ }
+#endif
+
+ ret = accept(fd, address, &address_length);
+ if (ret >= 0) {
+ fd_set_cloexec(ret, true);
+ fd_set_nonblock(ret);
+ *address_length_r = address_length;
+ }
+
+ return ret;
+}
+
+#ifdef HAVE_INOTIFY_INIT
+
+int
+inotify_init_cloexec(void)
+{
+ int fd;
+
+#ifdef HAVE_INOTIFY_INIT1
+ fd = inotify_init1(IN_CLOEXEC);
+ if (fd >= 0 || errno != ENOSYS)
+ return fd;
+#endif
+
+ fd = inotify_init();
+ if (fd >= 0)
+ fd_set_cloexec(fd, true);
+
+ return fd;
+}
+
+#endif
diff --git a/src/fd_util.h b/src/fd_util.h
new file mode 100644
index 000000000..57ad63288
--- /dev/null
+++ b/src/fd_util.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This library provides easy helper functions for working with file
+ * descriptors. It has wrappers for taking advantage of Linux 2.6
+ * specific features like O_CLOEXEC.
+ *
+ */
+
+#ifndef FD_UTIL_H
+#define FD_UTIL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct sockaddr;
+
+/**
+ * Wrapper for open(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ */
+int
+open_cloexec(const char *path_fs, int flags, int mode);
+
+/**
+ * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ */
+int
+pipe_cloexec(int fd[2]);
+
+/**
+ * Wrapper for pipe(), which sets the CLOEXEC flag (atomically if
+ * supported by the OS).
+ *
+ * On systems that supports it (everybody except for Windows), it also
+ * sets the NONBLOCK flag.
+ */
+int
+pipe_cloexec_nonblock(int fd[2]);
+
+/**
+ * Wrapper for socket(), which sets the CLOEXEC and the NONBLOCK flag
+ * (atomically if supported by the OS).
+ */
+int
+socket_cloexec_nonblock(int domain, int type, int protocol);
+
+/**
+ * Wrapper for accept(), which sets the CLOEXEC and the NONBLOCK flags
+ * (atomically if supported by the OS).
+ */
+int
+accept_cloexec_nonblock(int fd, struct sockaddr *address,
+ size_t *address_length_r);
+
+/**
+ * Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
+ * if supported by the OS).
+ */
+int
+inotify_init_cloexec(void);
+
+#endif
diff --git a/src/fifo_buffer.c b/src/fifo_buffer.c
index adee438c0..ceff6e605 100644
--- a/src/fifo_buffer.c
+++ b/src/fifo_buffer.c
@@ -28,6 +28,7 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#include "config.h"
#include "fifo_buffer.h"
#include <glib.h>
diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c
new file mode 100644
index 000000000..48100bc4b
--- /dev/null
+++ b/src/filter/chain_filter_plugin.c
@@ -0,0 +1,180 @@
+/*
+ * 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 "conf.h"
+#include "filter/chain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+#include <assert.h>
+
+struct filter_chain {
+ /** the base class */
+ struct filter base;
+
+ GSList *children;
+};
+
+static struct filter *
+chain_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct filter_chain *chain = g_new(struct filter_chain, 1);
+
+ filter_init(&chain->base, &chain_filter_plugin);
+ chain->children = NULL;
+
+ return &chain->base;
+}
+
+static void
+chain_free_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct filter *filter = data;
+
+ filter_free(filter);
+}
+
+static void
+chain_filter_finish(struct filter *_filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ g_slist_foreach(chain->children, chain_free_child, NULL);
+ g_slist_free(chain->children);
+
+ g_free(chain);
+}
+
+/**
+ * Close all filters in the chain until #until is reached. #until
+ * itself is not closed.
+ */
+static void
+chain_close_until(struct filter_chain *chain, const struct filter *until)
+{
+ GSList *i = chain->children;
+ struct filter *filter;
+
+ while (true) {
+ /* this assertion fails if #until does not exist
+ (anymore) */
+ assert(i != NULL);
+
+ if (i->data == until)
+ /* don't close this filter */
+ break;
+
+ /* close this filter */
+ filter = i->data;
+ filter_close(filter);
+
+ i = g_slist_next(i);
+ }
+}
+
+static const struct audio_format *
+chain_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
+ struct filter *filter = i->data;
+
+ audio_format = filter_open(filter, audio_format, error_r);
+ if (audio_format == NULL) {
+ /* rollback, close all children */
+ chain_close_until(chain, filter);
+ return NULL;
+ }
+ }
+
+ /* return the output format of the last filter */
+ return audio_format;
+}
+
+static void
+chain_close_child(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct filter *filter = data;
+
+ filter_close(filter);
+}
+
+static void
+chain_filter_close(struct filter *_filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ g_slist_foreach(chain->children, chain_close_child, NULL);
+}
+
+static const void *
+chain_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct filter_chain *chain = (struct filter_chain *)_filter;
+
+ for (GSList *i = chain->children; i != NULL; i = g_slist_next(i)) {
+ struct filter *filter = i->data;
+
+ /* feed the output of the previous filter as input
+ into the current one */
+ src = filter_filter(filter, src, src_size, &src_size, error_r);
+ if (src == NULL)
+ chain_close_until(chain, filter);
+ }
+
+ /* return the output of the last filter */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin chain_filter_plugin = {
+ .name = "chain",
+ .init = chain_filter_init,
+ .finish = chain_filter_finish,
+ .open = chain_filter_open,
+ .close = chain_filter_close,
+ .filter = chain_filter_filter,
+};
+
+struct filter *
+filter_chain_new(void)
+{
+ struct filter *filter = filter_new(&chain_filter_plugin, NULL, NULL);
+ /* chain_filter_init() never fails */
+ assert(filter != NULL);
+
+ return filter;
+}
+
+void
+filter_chain_append(struct filter *_chain, struct filter *filter)
+{
+ struct filter_chain *chain = (struct filter_chain *)_chain;
+
+ chain->children = g_slist_append(chain->children, filter);
+}
+
diff --git a/src/filter/chain_filter_plugin.h b/src/filter/chain_filter_plugin.h
new file mode 100644
index 000000000..f8462b22d
--- /dev/null
+++ b/src/filter/chain_filter_plugin.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * A filter chain is a container for several filters. They are
+ * chained together, i.e. called in a row, one filter passing its
+ * output to the next one.
+ */
+
+#ifndef MPD_FILTER_CHAIN_H
+#define MPD_FILTER_CHAIN_H
+
+struct filter;
+
+/**
+ * Creates a new filter chain.
+ */
+struct filter *
+filter_chain_new(void);
+
+/**
+ * Appends a new filter at the end of the filter chain. You must call
+ * this function before the first filter_open() call.
+ *
+ * @param chain the filter chain created with filter_chain_new()
+ * @param filter the filter to be appended to #chain
+ */
+void
+filter_chain_append(struct filter *chain, struct filter *filter);
+
+#endif
diff --git a/src/filter/convert_filter_plugin.c b/src/filter/convert_filter_plugin.c
new file mode 100644
index 000000000..982ec7c4c
--- /dev/null
+++ b/src/filter/convert_filter_plugin.c
@@ -0,0 +1,148 @@
+/*
+ * 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 "filter/convert_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+#include "pcm_convert.h"
+#include "audio_format.h"
+#include "poison.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct convert_filter {
+ struct filter base;
+
+ /**
+ * The current convert, from 0 to #PCM_CONVERT_1.
+ */
+ unsigned convert;
+
+ /**
+ * The input audio format; PCM data is passed to the filter()
+ * method in this format.
+ */
+ struct audio_format in_audio_format;
+
+ /**
+ * The output audio format; the consumer of this plugin
+ * expects PCM data in this format. This defaults to
+ * #in_audio_format, and can be set with convert_filter_set().
+ */
+ struct audio_format out_audio_format;
+
+ struct pcm_convert_state state;
+};
+
+static struct filter *
+convert_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct convert_filter *filter = g_new(struct convert_filter, 1);
+
+ filter_init(&filter->base, &convert_filter_plugin);
+ return &filter->base;
+}
+
+static void
+convert_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+convert_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ assert(audio_format_valid(audio_format));
+
+ filter->in_audio_format = filter->out_audio_format = *audio_format;
+ pcm_convert_init(&filter->state);
+
+ return audio_format;
+}
+
+static void
+convert_filter_close(struct filter *_filter)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ pcm_convert_deinit(&filter->state);
+
+ poison_undefined(&filter->in_audio_format,
+ sizeof(filter->in_audio_format));
+ poison_undefined(&filter->out_audio_format,
+ sizeof(filter->out_audio_format));
+}
+
+static const void *
+convert_filter_filter(struct filter *_filter, const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+ const void *dest;
+
+ if (audio_format_equals(&filter->in_audio_format,
+ &filter->out_audio_format)) {
+ /* optimized special case: no-op */
+ *dest_size_r = src_size;
+ return src;
+ }
+
+ dest = pcm_convert(&filter->state, &filter->in_audio_format,
+ src, src_size,
+ &filter->out_audio_format, dest_size_r,
+ error_r);
+ if (dest == NULL)
+ return NULL;
+
+ return dest;
+}
+
+const struct filter_plugin convert_filter_plugin = {
+ .name = "convert",
+ .init = convert_filter_init,
+ .finish = convert_filter_finish,
+ .open = convert_filter_open,
+ .close = convert_filter_close,
+ .filter = convert_filter_filter,
+};
+
+void
+convert_filter_set(struct filter *_filter,
+ const struct audio_format *out_audio_format)
+{
+ struct convert_filter *filter = (struct convert_filter *)_filter;
+
+ assert(filter != NULL);
+ assert(audio_format_valid(&filter->in_audio_format));
+ assert(audio_format_valid(&filter->out_audio_format));
+ assert(out_audio_format != NULL);
+ assert(audio_format_valid(out_audio_format));
+ assert(filter->in_audio_format.reverse_endian == 0);
+
+ filter->out_audio_format = *out_audio_format;
+}
diff --git a/src/filter/convert_filter_plugin.h b/src/filter/convert_filter_plugin.h
new file mode 100644
index 000000000..8d370b0cb
--- /dev/null
+++ b/src/filter/convert_filter_plugin.h
@@ -0,0 +1,36 @@
+/*
+ * 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 CONVERT_FILTER_PLUGIN_H
+#define CONVERT_FILTER_PLUGIN_H
+
+struct filter;
+struct audio_format;
+
+/**
+ * Sets the output audio format for the specified filter. You must
+ * call this after the filter has been opened. Since this audio
+ * format switch is a violation of the filter API, this filter must be
+ * the last in a chain.
+ */
+void
+convert_filter_set(struct filter *filter,
+ const struct audio_format *out_audio_format);
+
+#endif
diff --git a/src/filter/normalize_filter_plugin.c b/src/filter/normalize_filter_plugin.c
new file mode 100644
index 000000000..2694646ee
--- /dev/null
+++ b/src/filter/normalize_filter_plugin.c
@@ -0,0 +1,123 @@
+/*
+ * 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 "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "pcm_buffer.h"
+#include "audio_format.h"
+#include "AudioCompress/compress.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct normalize_filter {
+ struct filter filter;
+
+ struct Compressor *compressor;
+
+ struct pcm_buffer buffer;
+};
+
+static inline GQuark
+normalize_quark(void)
+{
+ return g_quark_from_static_string("normalize");
+}
+
+static struct filter *
+normalize_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct normalize_filter *filter = g_new(struct normalize_filter, 1);
+
+ filter_init(&filter->filter, &normalize_filter_plugin);
+
+ return &filter->filter;
+}
+
+static void
+normalize_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+normalize_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct normalize_filter *filter = (struct normalize_filter *)_filter;
+
+ if (audio_format->format != SAMPLE_FORMAT_S16) {
+ g_set_error(error_r, normalize_quark(), 0,
+ "Unsupported audio format");
+ return false;
+ }
+
+ if (audio_format->reverse_endian) {
+ g_set_error(error_r, normalize_quark(), 0,
+ "Normalize for reverse endian "
+ "samples is not implemented");
+ return false;
+ }
+
+ filter->compressor = Compressor_new(0);
+
+ pcm_buffer_init(&filter->buffer);
+
+ return audio_format;
+}
+
+static void
+normalize_filter_close(struct filter *_filter)
+{
+ struct normalize_filter *filter = (struct normalize_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->buffer);
+ Compressor_delete(filter->compressor);
+}
+
+static const void *
+normalize_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size, size_t *dest_size_r,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct normalize_filter *filter = (struct normalize_filter *)_filter;
+ void *dest;
+
+ dest = pcm_buffer_get(&filter->buffer, src_size);
+
+ memcpy(dest, src, src_size);
+
+ Compressor_Process_int16(filter->compressor, dest, src_size / 2);
+
+ *dest_size_r = src_size;
+ return dest;
+}
+
+const struct filter_plugin normalize_filter_plugin = {
+ .name = "normalize",
+ .init = normalize_filter_init,
+ .finish = normalize_filter_finish,
+ .open = normalize_filter_open,
+ .close = normalize_filter_close,
+ .filter = normalize_filter_filter,
+};
diff --git a/src/filter/null_filter_plugin.c b/src/filter/null_filter_plugin.c
new file mode 100644
index 000000000..5671ba907
--- /dev/null
+++ b/src/filter/null_filter_plugin.c
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This filter plugin does nothing. That is not quite useful, except
+ * for testing the filter core, or as a template for new filter
+ * plugins.
+ */
+
+#include "config.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+#include <assert.h>
+
+struct null_filter {
+ struct filter filter;
+};
+
+static struct filter *
+null_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = g_new(struct null_filter, 1);
+
+ filter_init(&filter->filter, &null_filter_plugin);
+ return &filter->filter;
+}
+
+static void
+null_filter_finish(struct filter *_filter)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+
+ g_free(filter);
+}
+
+static const struct audio_format *
+null_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+
+ return audio_format;
+}
+
+static void
+null_filter_close(struct filter *_filter)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+}
+
+static const void *
+null_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, G_GNUC_UNUSED GError **error_r)
+{
+ struct null_filter *filter = (struct null_filter *)_filter;
+ (void)filter;
+
+ /* return the unmodified source buffer */
+ *dest_size_r = src_size;
+ return src;
+}
+
+const struct filter_plugin null_filter_plugin = {
+ .name = "null",
+ .init = null_filter_init,
+ .finish = null_filter_finish,
+ .open = null_filter_open,
+ .close = null_filter_close,
+ .filter = null_filter_filter,
+};
diff --git a/src/filter/route_filter_plugin.c b/src/filter/route_filter_plugin.c
new file mode 100644
index 000000000..5610220fc
--- /dev/null
+++ b/src/filter/route_filter_plugin.c
@@ -0,0 +1,345 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This filter copies audio data between channels. Useful for
+ * upmixing mono/stereo audio to surround speaker configurations.
+ *
+ * Its configuration consists of a "filter" section with a single
+ * "routes" entry, formatted as: \\
+ * routes "0>1, 1>0, 2>2, 3>3, 3>4" \\
+ * where each pair of numbers signifies a set of channels.
+ * Each source>dest pair leads to the data from channel #source
+ * being copied to channel #dest in the output.
+ *
+ * Example: \\
+ * routes "0>0, 1>1, 0>2, 1>3"\\
+ * upmixes stereo audio to a 4-speaker system, copying the front-left
+ * (0) to front left (0) and rear left (2), copying front-right (1) to
+ * front-right (1) and rear-right (3).
+ *
+ * If multiple sources are copied to the same destination channel, only
+ * one of them takes effect.
+ */
+
+#include "config.h"
+#include "conf.h"
+#include "audio_format.h"
+#include "audio_check.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "pcm_buffer.h"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+
+struct route_filter {
+
+ /**
+ * Inherit (and support cast to/from) filter
+ */
+ struct filter base;
+
+ /**
+ * The minimum number of channels we need for output
+ * to be able to perform all the copies the user has specified
+ */
+ unsigned char min_output_channels;
+
+ /**
+ * The minimum number of input channels we need to
+ * copy all the data the user has requested. If fewer
+ * than this many are supplied by the input, undefined
+ * copy operations are given zeroed sources in stead.
+ */
+ unsigned char min_input_channels;
+
+ /**
+ * The set of copy operations to perform on each sample
+ * The index is an output channel to use, the value is
+ * a corresponding input channel from which to take the
+ * data. A -1 means "no source"
+ */
+ signed char* sources;
+
+ /**
+ * The actual input format of our signal, once opened
+ */
+ struct audio_format input_format;
+
+ /**
+ * The decided upon output format, once opened
+ */
+ struct audio_format output_format;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * input buffer
+ */
+ size_t input_frame_size;
+
+ /**
+ * The size, in bytes, of each multichannel frame in the
+ * output buffer
+ */
+ size_t output_frame_size;
+
+ /**
+ * The output buffer used last time around, can be reused if the size doesn't differ.
+ */
+ struct pcm_buffer output_buffer;
+
+};
+
+/**
+ * Parse the "routes" section, a string on the form
+ * a>b, c>d, e>f, ...
+ * where a... are non-unique, non-negative integers
+ * and input channel a gets copied to output channel b, etc.
+ * @param param the configuration block to read
+ * @param filter a route_filter whose min_channels and sources[] to set
+ */
+static void
+route_filter_parse(const struct config_param *param,
+ struct route_filter *filter,
+ GError **error_r) {
+
+ /* TODO:
+ * With a more clever way of marking "don't copy to output N",
+ * This could easily be merged into a single loop with some
+ * dynamic g_realloc() instead of one count run and one g_malloc().
+ */
+
+ gchar **tokens;
+ int number_of_copies;
+
+ // A cowardly default, just passthrough stereo
+ const char *routes =
+ config_get_block_string(param, "routes", "0>0, 1>1");
+
+ filter->min_input_channels = 0;
+ filter->min_output_channels = 0;
+
+ tokens = g_strsplit(routes, ",", 255);
+ number_of_copies = g_strv_length(tokens);
+
+ // Start by figuring out a few basic things about the routing set
+ for (int c=0; c<number_of_copies; ++c) {
+
+ // String and int representations of the source/destination
+ gchar **sd;
+ int source, dest;
+
+ // Squeeze whitespace
+ g_strstrip(tokens[c]);
+
+ // Split the a>b string into source and destination
+ sd = g_strsplit(tokens[c], ">", 2);
+ if (g_strv_length(sd) != 2) {
+ g_set_error(error_r, config_quark(), 1,
+ "Invalid copy around %d in routes spec: %s",
+ param->line, tokens[c]);
+ g_strfreev(sd);
+ g_strfreev(tokens);
+ return;
+ }
+
+ source = strtol(sd[0], NULL, 10);
+ dest = strtol(sd[1], NULL, 10);
+
+ // Keep track of the highest channel numbers seen
+ // as either in- or outputs
+ if (source >= filter->min_input_channels)
+ filter->min_input_channels = source + 1;
+ if (dest >= filter->min_output_channels)
+ filter->min_output_channels = dest + 1;
+
+ g_strfreev(sd);
+ }
+
+ // Allocate a map of "copy nothing to me"
+ filter->sources =
+ g_malloc(filter->min_output_channels * sizeof(signed char));
+
+ for (int i=0; i<filter->min_output_channels; ++i)
+ filter->sources[i] = -1;
+
+ // Run through the spec again, and save the
+ // actual mapping output <- input
+ for (int c=0; c<number_of_copies; ++c) {
+
+ // String and int representations of the source/destination
+ gchar **sd;
+ int source, dest;
+
+ // Split the a>b string into source and destination
+ sd = g_strsplit(tokens[c], ">", 2);
+ if (g_strv_length(sd) != 2) {
+ g_set_error(error_r, config_quark(), 1,
+ "Invalid copy around %d in routes spec: %s",
+ param->line, tokens[c]);
+ g_strfreev(sd);
+ g_strfreev(tokens);
+ return;
+ }
+
+ source = strtol(sd[0], NULL, 10);
+ dest = strtol(sd[1], NULL, 10);
+
+ filter->sources[dest] = source;
+
+ g_strfreev(sd);
+ }
+
+ g_strfreev(tokens);
+}
+
+static struct filter *
+route_filter_init(const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct route_filter *filter = g_new(struct route_filter, 1);
+ filter_init(&filter->base, &route_filter_plugin);
+
+ // Allocate and set the filter->sources[] array
+ route_filter_parse(param, filter, error_r);
+
+ return &filter->base;
+}
+
+static void
+route_filter_finish(struct filter *_filter)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ g_free(filter->sources);
+ g_free(filter);
+}
+
+static const struct audio_format *
+route_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ // Copy the input format for later reference
+ filter->input_format = *audio_format;
+ filter->input_frame_size =
+ audio_format_frame_size(&filter->input_format);
+
+ if (!audio_valid_channel_count(filter->min_output_channels)) {
+ g_set_error(error_r, audio_format_quark(), 2,
+ "Invalid number of output channels requested: %d",
+ filter->min_output_channels);
+ return NULL;
+ }
+
+ // Decide on an output format which has enough channels,
+ // and is otherwise identical
+ filter->output_format = *audio_format;
+ filter->output_format.channels = filter->min_output_channels;
+
+ // Precalculate this simple value, to speed up allocation later
+ filter->output_frame_size =
+ audio_format_frame_size(&filter->output_format);
+
+ // This buffer grows as needed
+ pcm_buffer_init(&filter->output_buffer);
+
+ return &filter->output_format;
+}
+
+static void
+route_filter_close(struct filter *_filter)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->output_buffer);
+}
+
+static const void *
+route_filter_filter(struct filter *_filter,
+ const void *src, size_t src_size,
+ size_t *dest_size_r, G_GNUC_UNUSED GError **error_r)
+{
+ struct route_filter *filter = (struct route_filter *)_filter;
+
+ size_t number_of_frames = src_size / filter->input_frame_size;
+
+ size_t bytes_per_frame_per_channel =
+ audio_format_sample_size(&filter->input_format);
+
+ // A moving pointer that always refers to channel 0 in the input, at the currently handled frame
+ const uint8_t *base_source = src;
+
+ // A moving pointer that always refers to the currently filled channel of the currently handled frame, in the output
+ uint8_t *chan_destination;
+
+ // Grow our reusable buffer, if needed, and set the moving pointer
+ *dest_size_r = number_of_frames * filter->output_frame_size;
+ chan_destination = pcm_buffer_get(&filter->output_buffer, *dest_size_r);
+
+
+ // Perform our copy operations, with N input channels and M output channels
+ for (unsigned int s=0; s<number_of_frames; ++s) {
+
+ // Need to perform one copy per output channel
+ for (unsigned int c=0; c<filter->min_output_channels; ++c) {
+ if (filter->sources[c] == -1 ||
+ filter->sources[c] >= filter->input_format.channels) {
+ // No source for this destination output,
+ // give it zeroes as input
+ memset(chan_destination,
+ 0x00,
+ bytes_per_frame_per_channel);
+ } else {
+ // Get the data from channel sources[c]
+ // and copy it to the output
+ const uint8_t *data = base_source +
+ (filter->sources[c] * bytes_per_frame_per_channel);
+ memcpy(chan_destination,
+ data,
+ bytes_per_frame_per_channel);
+ }
+ // Move on to the next output channel
+ chan_destination += bytes_per_frame_per_channel;
+ }
+
+
+ // Go on to the next N input samples
+ base_source += filter->input_frame_size;
+ }
+
+ // Here it is, ladies and gentlemen! Rerouted data!
+ return (void *) filter->output_buffer.buffer;
+}
+
+const struct filter_plugin route_filter_plugin = {
+ .name = "route",
+ .init = route_filter_init,
+ .finish = route_filter_finish,
+ .open = route_filter_open,
+ .close = route_filter_close,
+ .filter = route_filter_filter,
+};
diff --git a/src/filter/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c
new file mode 100644
index 000000000..285a4b7a4
--- /dev/null
+++ b/src/filter/volume_filter_plugin.c
@@ -0,0 +1,176 @@
+/*
+ * 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 "filter/volume_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+#include "pcm_buffer.h"
+#include "pcm_volume.h"
+#include "audio_format.h"
+#include "player_control.h"
+
+#include <assert.h>
+#include <string.h>
+
+struct volume_filter {
+ struct filter filter;
+
+ /**
+ * The current volume, from 0 to #PCM_VOLUME_1.
+ */
+ unsigned volume;
+
+ struct audio_format audio_format;
+
+ struct pcm_buffer buffer;
+};
+
+static inline GQuark
+volume_quark(void)
+{
+ return g_quark_from_static_string("pcm_volume");
+}
+
+static struct filter *
+volume_filter_init(G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct volume_filter *filter = g_new(struct volume_filter, 1);
+
+ filter_init(&filter->filter, &volume_filter_plugin);
+ filter->volume = PCM_VOLUME_1;
+
+ return &filter->filter;
+}
+
+static void
+volume_filter_finish(struct filter *filter)
+{
+ g_free(filter);
+}
+
+static const struct audio_format *
+volume_filter_open(struct filter *_filter,
+ const struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ if (audio_format->format != SAMPLE_FORMAT_S8 &&
+ audio_format->format != SAMPLE_FORMAT_S16 &&
+ audio_format->format != SAMPLE_FORMAT_S24_P32) {
+ g_set_error(error_r, volume_quark(), 0,
+ "Unsupported audio format");
+ return false;
+ }
+
+ if (audio_format->reverse_endian) {
+ g_set_error(error_r, volume_quark(), 0,
+ "Software volume for reverse endian "
+ "samples is not implemented");
+ return false;
+ }
+
+ filter->audio_format = *audio_format;
+ pcm_buffer_init(&filter->buffer);
+
+ return &filter->audio_format;
+}
+
+static void
+volume_filter_close(struct filter *_filter)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ pcm_buffer_deinit(&filter->buffer);
+}
+
+static const void *
+volume_filter_filter(struct filter *_filter, const void *src, size_t src_size,
+ size_t *dest_size_r, GError **error_r)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+ bool success;
+ void *dest;
+
+ if (filter->volume >= PCM_VOLUME_1) {
+ /* optimized special case: 100% volume = no-op */
+ *dest_size_r = src_size;
+ return src;
+ }
+
+ dest = pcm_buffer_get(&filter->buffer, src_size);
+ *dest_size_r = src_size;
+
+ if (filter->volume <= 0) {
+ /* optimized special case: 0% volume = memset(0) */
+ /* XXX is this valid for all sample formats? What
+ about floating point? */
+ memset(dest, 0, src_size);
+ return dest;
+ }
+
+ memcpy(dest, src, src_size);
+
+ success = pcm_volume(dest, src_size, &filter->audio_format,
+ filter->volume);
+ if (!success) {
+ g_set_error(error_r, volume_quark(), 0,
+ "pcm_volume() has failed");
+ return NULL;
+ }
+
+ return dest;
+}
+
+const struct filter_plugin volume_filter_plugin = {
+ .name = "volume",
+ .init = volume_filter_init,
+ .finish = volume_filter_finish,
+ .open = volume_filter_open,
+ .close = volume_filter_close,
+ .filter = volume_filter_filter,
+};
+
+unsigned
+volume_filter_get(const struct filter *_filter)
+{
+ const struct volume_filter *filter =
+ (const struct volume_filter *)_filter;
+
+ assert(filter->filter.plugin == &volume_filter_plugin);
+ assert(filter->volume <= PCM_VOLUME_1);
+
+ return filter->volume;
+}
+
+void
+volume_filter_set(struct filter *_filter, unsigned volume)
+{
+ struct volume_filter *filter = (struct volume_filter *)_filter;
+
+ assert(filter->filter.plugin == &volume_filter_plugin);
+ assert(volume <= PCM_VOLUME_1);
+
+ filter->volume = volume;
+}
+
diff --git a/src/filter/volume_filter_plugin.h b/src/filter/volume_filter_plugin.h
new file mode 100644
index 000000000..c064741a2
--- /dev/null
+++ b/src/filter/volume_filter_plugin.h
@@ -0,0 +1,31 @@
+/*
+ * 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 VOLUME_FILTER_PLUGIN_H
+#define VOLUME_FILTER_PLUGIN_H
+
+struct filter;
+
+unsigned
+volume_filter_get(const struct filter *filter);
+
+void
+volume_filter_set(struct filter *filter, unsigned volume);
+
+#endif
diff --git a/src/filter_config.c b/src/filter_config.c
new file mode 100644
index 000000000..1a92916ea
--- /dev/null
+++ b/src/filter_config.c
@@ -0,0 +1,119 @@
+/*
+ * 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 "filter_config.h"
+#include "config.h"
+#include "conf.h"
+#include "filter/chain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+#include <string.h>
+
+
+static GQuark
+filter_quark(void)
+{
+ return g_quark_from_static_string("filter");
+}
+
+/**
+ * Find the "filter" configuration block for the specified name.
+ *
+ * @param filter_template_name the name of the filter template
+ * @param error_r space to return an error description
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+filter_plugin_config(const char *filter_template_name, GError **error_r)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_AUDIO_FILTER, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "name", NULL);
+ if (name == NULL) {
+ g_set_error(error_r, filter_quark(), 1,
+ "filter configuration without 'name' name in line %d",
+ param->line);
+ return NULL;
+ }
+
+ if (strcmp(name, filter_template_name) == 0)
+ return param;
+ }
+
+ g_set_error(error_r, filter_quark(), 1,
+ "filter template not found: %s",
+ filter_template_name);
+
+ return NULL;
+}
+
+/**
+ * Builds a filter chain from a configuration string on the form
+ * "name1, name2, name3, ..." by looking up each name among the
+ * configured filter sections.
+ * @param chain the chain to append filters on
+ * @param spec the filter chain specification
+ * @param error_r space to return an error description
+ * @return the number of filters which were successfully added
+ */
+unsigned int
+filter_chain_parse(struct filter *chain, const char *spec, GError **error_r)
+{
+
+ // Split on comma
+ gchar** tokens = g_strsplit_set(spec, ",", 255);
+
+ int added_filters = 0;
+
+ // Add each name to the filter chain by instantiating an actual filter
+ char **template_names = tokens;
+ while (*template_names != NULL) {
+ struct filter *f;
+ const struct config_param *cfg;
+
+ // Squeeze whitespace
+ g_strstrip(*template_names);
+
+ cfg = filter_plugin_config(*template_names, error_r);
+ if (cfg == NULL) {
+ // The error has already been set, just stop.
+ break;
+ }
+
+ // Instantiate one of those filter plugins with the template name as a hint
+ f = filter_configured_new(cfg, error_r);
+ if (f == NULL) {
+ // The error has already been set, just stop.
+ break;
+ }
+
+ filter_chain_append(chain, f);
+ ++added_filters;
+
+ ++template_names;
+ }
+
+ g_strfreev(tokens);
+
+ return added_filters;
+}
diff --git a/src/filter_config.h b/src/filter_config.h
new file mode 100644
index 000000000..8e20320ff
--- /dev/null
+++ b/src/filter_config.h
@@ -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.
+ */
+
+/** \file
+ *
+ * Utility functions for filter configuration
+ */
+
+#ifndef MPD_FILTER_CONFIG_H
+#define MPD_FILTER_CONFIG_H
+
+#include "conf.h"
+#include "filter/chain_filter_plugin.h"
+#include "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+
+
+/**
+ * Builds a filter chain from a configuration string on the form
+ * "name1, name2, name3, ..." by looking up each name among the
+ * configured filter sections.
+ * @param chain the chain to append filters on
+ * @param spec the filter chain specification
+ * @param error_r space to return an error description
+ * @return the number of filters which were successfully added
+ */
+unsigned int
+filter_chain_parse(struct filter *chain, const char *spec, GError **error_r);
+
+#endif
diff --git a/src/buffer2array.h b/src/filter_internal.h
index bed23a29f..b086e31b1 100644
--- a/src/buffer2array.h
+++ b/src/filter_internal.h
@@ -17,15 +17,22 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifndef MPD_BUFFER_2_ARRAY_H
-#define MPD_BUFFER_2_ARRAY_H
-
-/* tokenizes up to max elements in buffer (a null-terminated string) and
- * stores the result in array (which must be capable of holding up to
- * max elements). Tokenization is based on C string quoting rules.
- * The arguments buffer and array are modified.
- * Returns the number of elements tokenized.
+/** \file
+ *
+ * Internal stuff for the filter core and filter plugins.
*/
-int buffer2array(char *buffer, char *array[], const int max);
+
+#ifndef MPD_FILTER_INTERNAL_H
+#define MPD_FILTER_INTERNAL_H
+
+struct filter {
+ const struct filter_plugin *plugin;
+};
+
+static inline void
+filter_init(struct filter *filter, const struct filter_plugin *plugin)
+{
+ filter->plugin = plugin;
+}
#endif
diff --git a/src/filter_plugin.c b/src/filter_plugin.c
new file mode 100644
index 000000000..ecc4b5432
--- /dev/null
+++ b/src/filter_plugin.c
@@ -0,0 +1,111 @@
+/*
+ * 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 "filter_plugin.h"
+#include "filter_internal.h"
+#include "filter_registry.h"
+#include "conf.h"
+
+#ifndef NDEBUG
+#include "audio_format.h"
+#endif
+
+#include <assert.h>
+
+struct filter *
+filter_new(const struct filter_plugin *plugin,
+ const struct config_param *param, GError **error_r)
+{
+ assert(plugin != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return plugin->init(param, error_r);
+}
+
+struct filter *
+filter_configured_new(const struct config_param *param, GError **error_r)
+{
+ const char *plugin_name;
+ const struct filter_plugin *plugin;
+
+ assert(param != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ plugin_name = config_get_block_string(param, "plugin", NULL);
+ if (plugin_name == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "No filter plugin specified");
+ return NULL;
+ }
+
+ plugin = filter_plugin_by_name(plugin_name);
+ if (plugin == NULL) {
+ g_set_error(error_r, config_quark(), 0,
+ "No such filter plugin: %s", plugin_name);
+ return NULL;
+ }
+
+ return filter_new(plugin, param, error_r);
+}
+
+void
+filter_free(struct filter *filter)
+{
+ assert(filter != NULL);
+
+ filter->plugin->finish(filter);
+}
+
+const struct audio_format *
+filter_open(struct filter *filter, const struct audio_format *audio_format,
+ GError **error_r)
+{
+ assert(filter != NULL);
+ assert(audio_format != NULL);
+ assert(audio_format_valid(audio_format));
+ assert(error_r == NULL || *error_r == NULL);
+
+ audio_format = filter->plugin->open(filter, audio_format, error_r);
+ assert(audio_format == NULL || audio_format_valid(audio_format));
+
+ return audio_format;
+}
+
+void
+filter_close(struct filter *filter)
+{
+ assert(filter != NULL);
+
+ filter->plugin->close(filter);
+}
+
+const void *
+filter_filter(struct filter *filter, const void *src, size_t src_size,
+ size_t *dest_size_r,
+ GError **error_r)
+{
+ assert(filter != NULL);
+ assert(src != NULL);
+ assert(src_size > 0);
+ assert(dest_size_r != NULL);
+ assert(error_r == NULL || *error_r == NULL);
+
+ return filter->plugin->filter(filter, src, src_size, dest_size_r, error_r);
+}
diff --git a/src/filter_plugin.h b/src/filter_plugin.h
new file mode 100644
index 000000000..dc5903b59
--- /dev/null
+++ b/src/filter_plugin.h
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This header declares the filter_plugin class. It describes a
+ * plugin API for objects which filter raw PCM data.
+ */
+
+#ifndef MPD_FILTER_PLUGIN_H
+#define MPD_FILTER_PLUGIN_H
+
+#include <glib.h>
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct config_param;
+struct filter;
+
+struct filter_plugin {
+ const char *name;
+
+ /**
+ * Allocates and configures a filter.
+ */
+ struct filter *(*init)(const struct config_param *param,
+ GError **error_r);
+
+ /**
+ * Free instance data.
+ */
+ void (*finish)(struct filter *filter);
+
+ /**
+ * Opens a filter.
+ */
+ const struct audio_format *
+ (*open)(struct filter *filter,
+ const struct audio_format *audio_format,
+ GError **error_r);
+
+ /**
+ * Closes a filter.
+ */
+ void (*close)(struct filter *filter);
+
+ /**
+ * Filters a block of PCM data.
+ */
+ const void *(*filter)(struct filter *filter,
+ const void *src, size_t src_size,
+ size_t *dest_buffer_r,
+ GError **error_r);
+};
+
+/**
+ * Creates a new instance of the specified filter plugin.
+ *
+ * @param plugin the filter plugin
+ * @param param optional configuration section
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+struct filter *
+filter_new(const struct filter_plugin *plugin,
+ const struct config_param *param, GError **error_r);
+
+/**
+ * Creates a new filter, loads configuration and the plugin name from
+ * the specified configuration section.
+ *
+ * @param param the configuration section
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return a new filter object, or NULL on error
+ */
+struct filter *
+filter_configured_new(const struct config_param *param, GError **error_r);
+
+/**
+ * Deletes a filter. It must be closed prior to calling this
+ * function, see filter_close().
+ *
+ * @param filter the filter object
+ */
+void
+filter_free(struct filter *filter);
+
+/**
+ * Opens the filter, preparing it for filter_filter().
+ *
+ * @param filter the filter object
+ * @param audio_format the audio format of incoming data
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return the format of outgoing data
+ */
+const struct audio_format *
+filter_open(struct filter *filter, const struct audio_format *audio_format,
+ GError **error_r);
+
+/**
+ * Closes the filter. After that, you may call filter_open() again.
+ *
+ * @param filter the filter object
+ */
+void
+filter_close(struct filter *filter);
+
+/**
+ * Filters a block of PCM data.
+ *
+ * @param filter the filter object
+ * @param src the input buffer
+ * @param src_size the size of #src_buffer in bytes
+ * @param dest_size_r the size of the returned buffer
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return the destination buffer on success (will be invalidated by
+ * filter_close() or filter_filter()), NULL on error
+ */
+const void *
+filter_filter(struct filter *filter, const void *src, size_t src_size,
+ size_t *dest_size_r,
+ GError **error_r);
+
+#endif
diff --git a/src/filter_registry.c b/src/filter_registry.c
new file mode 100644
index 000000000..e428e1cdc
--- /dev/null
+++ b/src/filter_registry.c
@@ -0,0 +1,44 @@
+/*
+ * 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 "filter_registry.h"
+#include "filter_plugin.h"
+
+#include <stddef.h>
+#include <string.h>
+
+const struct filter_plugin *const filter_plugins[] = {
+ &null_filter_plugin,
+ &chain_filter_plugin,
+ &route_filter_plugin,
+ &normalize_filter_plugin,
+ &volume_filter_plugin,
+ NULL,
+};
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name)
+{
+ for (unsigned i = 0; filter_plugins[i] != NULL; ++i)
+ if (strcmp(filter_plugins[i]->name, name) == 0)
+ return filter_plugins[i];
+
+ return NULL;
+}
diff --git a/src/filter_registry.h b/src/filter_registry.h
new file mode 100644
index 000000000..8651f7938
--- /dev/null
+++ b/src/filter_registry.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/** \file
+ *
+ * This library manages all filter plugins which are enabled at
+ * compile time.
+ */
+
+#ifndef MPD_FILTER_REGISTRY_H
+#define MPD_FILTER_REGISTRY_H
+
+extern const struct filter_plugin null_filter_plugin;
+extern const struct filter_plugin chain_filter_plugin;
+extern const struct filter_plugin convert_filter_plugin;
+extern const struct filter_plugin route_filter_plugin;
+extern const struct filter_plugin normalize_filter_plugin;
+extern const struct filter_plugin volume_filter_plugin;
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name);
+
+#endif
diff --git a/src/glib_compat.h b/src/glib_compat.h
new file mode 100644
index 000000000..641fef99a
--- /dev/null
+++ b/src/glib_compat.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+/*
+ * Compatibility with older GLib versions. Some of this isn't
+ * implemented properly, just "good enough" to allow users with older
+ * operating systems to run MPD.
+ */
+
+#ifndef MPD_GLIB_COMPAT_H
+#define MPD_GLIB_COMPAT_H
+
+#include <glib.h>
+
+#if !GLIB_CHECK_VERSION(2,14,0)
+
+#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
+
+static inline guint
+g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data)
+{
+ return g_timeout_add(interval * 1000, function, data);
+}
+
+#endif /* !2.14 */
+
+#if !GLIB_CHECK_VERSION(2,16,0)
+
+static inline void
+g_propagate_prefixed_error(GError **dest_r, GError *src,
+ G_GNUC_UNUSED const gchar *format, ...)
+{
+ g_propagate_error(dest_r, src);
+}
+
+static inline char *
+g_uri_escape_string(const char *unescaped,
+ G_GNUC_UNUSED const char *reserved_chars_allowed,
+ G_GNUC_UNUSED gboolean allow_utf8)
+{
+ return g_strdup(unescaped);
+}
+
+#endif /* !2.16 */
+
+#endif
diff --git a/src/icy_metadata.c b/src/icy_metadata.c
index 69aa89092..009104b74 100644
--- a/src/icy_metadata.c
+++ b/src/icy_metadata.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "icy_metadata.h"
#include "tag.h"
@@ -95,7 +96,7 @@ icy_parse_tag_item(struct tag *tag, const char *item)
if (p[0] != NULL && p[1] != NULL) {
if (strcmp(p[0], "StreamTitle") == 0)
- icy_add_item(tag, TAG_ITEM_TITLE, p[1]);
+ icy_add_item(tag, TAG_TITLE, p[1]);
else
g_debug("unknown icy-tag: '%s'", p[0]);
}
diff --git a/src/icy_server.c b/src/icy_server.c
index 486c62c36..a9e6bc496 100644
--- a/src/icy_server.c
+++ b/src/icy_server.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "icy_server.h"
#include <glib.h>
diff --git a/src/idle.c b/src/idle.c
index 11b57376d..ea08e6a9b 100644
--- a/src/idle.c
+++ b/src/idle.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "idle.h"
#include "event_pipe.h"
@@ -40,6 +41,7 @@ static const char *const idle_names[] = {
"output",
"options",
"sticker",
+ "update",
NULL
};
diff --git a/src/idle.h b/src/idle.h
index a69acabb0..c8ed57f74 100644
--- a/src/idle.h
+++ b/src/idle.h
@@ -50,6 +50,9 @@ enum {
/** a sticker has been modified. */
IDLE_STICKER = 0x80,
+
+ /** a database update has started or finished. */
+ IDLE_UPDATE = 0x100,
};
/**
diff --git a/src/inotify_queue.c b/src/inotify_queue.c
new file mode 100644
index 000000000..d486e21db
--- /dev/null
+++ b/src/inotify_queue.c
@@ -0,0 +1,135 @@
+/*
+ * 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 "inotify_queue.h"
+#include "update.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ /**
+ * Wait this long after the last change before calling
+ * update_enqueue(). This increases the probability that
+ * updates can be bundled.
+ */
+ INOTIFY_UPDATE_DELAY_S = 5,
+};
+
+static GSList *inotify_queue;
+static guint queue_source_id;
+
+void
+mpd_inotify_queue_init(void)
+{
+}
+
+static void
+free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ g_free(data);
+}
+
+void
+mpd_inotify_queue_finish(void)
+{
+ if (queue_source_id != 0)
+ g_source_remove(queue_source_id);
+
+ g_slist_foreach(inotify_queue, free_callback, NULL);
+ g_slist_free(inotify_queue);
+}
+
+static gboolean
+mpd_inotify_run_update(G_GNUC_UNUSED gpointer data)
+{
+ unsigned id;
+
+ while (inotify_queue != NULL) {
+ char *uri_utf8 = inotify_queue->data;
+
+ id = update_enqueue(uri_utf8, false);
+ if (id == 0)
+ /* retry later */
+ return true;
+
+ g_debug("updating '%s' job=%u", uri_utf8, id);
+
+ g_free(uri_utf8);
+ inotify_queue = g_slist_delete_link(inotify_queue,
+ inotify_queue);
+ }
+
+ /* done, remove the timer event by returning false */
+ queue_source_id = 0;
+ return false;
+}
+
+static bool
+path_in(const char *path, const char *possible_parent)
+{
+ size_t length = strlen(possible_parent);
+
+ return path[0] == 0 ||
+ (memcmp(possible_parent, path, length) == 0 &&
+ (path[length] == 0 || path[length] == '/'));
+}
+
+void
+mpd_inotify_enqueue(char *uri_utf8)
+{
+ GSList *old_queue = inotify_queue;
+
+ if (queue_source_id != 0)
+ g_source_remove(queue_source_id);
+ queue_source_id = g_timeout_add_seconds(INOTIFY_UPDATE_DELAY_S,
+ mpd_inotify_run_update, NULL);
+
+ inotify_queue = NULL;
+ while (old_queue != NULL) {
+ char *current_uri = old_queue->data;
+
+ if (path_in(uri_utf8, current_uri)) {
+ /* already enqueued */
+ g_free(uri_utf8);
+ inotify_queue = g_slist_concat(inotify_queue,
+ old_queue);
+ return;
+ }
+
+ old_queue = g_slist_delete_link(old_queue, old_queue);
+
+ if (path_in(current_uri, uri_utf8))
+ /* existing path is a sub-path of the new
+ path; we can dequeue the existing path and
+ update the new path instead */
+ g_free(current_uri);
+ else
+ /* move the existing path to the new queue */
+ inotify_queue = g_slist_prepend(inotify_queue,
+ current_uri);
+ }
+
+ inotify_queue = g_slist_prepend(inotify_queue, uri_utf8);
+}
diff --git a/src/inotify_queue.h b/src/inotify_queue.h
new file mode 100644
index 000000000..6e12e9bda
--- /dev/null
+++ b/src/inotify_queue.h
@@ -0,0 +1,32 @@
+/*
+ * 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_INOTIFY_QUEUE_H
+#define MPD_INOTIFY_QUEUE_H
+
+void
+mpd_inotify_queue_init(void);
+
+void
+mpd_inotify_queue_finish(void);
+
+void
+mpd_inotify_enqueue(char *uri_utf8);
+
+#endif
diff --git a/src/inotify_source.c b/src/inotify_source.c
new file mode 100644
index 000000000..31dc1e7dc
--- /dev/null
+++ b/src/inotify_source.c
@@ -0,0 +1,165 @@
+/*
+ * 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 "inotify_source.h"
+#include "fifo_buffer.h"
+#include "fd_util.h"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+struct mpd_inotify_source {
+ int fd;
+
+ GIOChannel *channel;
+
+ /**
+ * The channel's source id in the GLib main loop.
+ */
+ guint id;
+
+ struct fifo_buffer *buffer;
+
+ mpd_inotify_callback_t callback;
+ void *callback_ctx;
+};
+
+/**
+ * A GQuark for GError instances.
+ */
+static inline GQuark
+mpd_inotify_quark(void)
+{
+ return g_quark_from_static_string("inotify");
+}
+
+static gboolean
+mpd_inotify_in_event(G_GNUC_UNUSED GIOChannel *_source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data)
+{
+ struct mpd_inotify_source *source = data;
+ void *dest;
+ size_t length;
+ ssize_t nbytes;
+ const struct inotify_event *event;
+
+ dest = fifo_buffer_write(source->buffer, &length);
+ if (dest == NULL)
+ g_error("buffer full");
+
+ nbytes = read(source->fd, dest, length);
+ if (nbytes < 0)
+ g_error("failed to read from inotify: %s", g_strerror(errno));
+ if (nbytes == 0)
+ g_error("end of file from inotify");
+
+ fifo_buffer_append(source->buffer, nbytes);
+
+ while (true) {
+ const char *name;
+
+ event = fifo_buffer_read(source->buffer, &length);
+ if (event == NULL || length < sizeof(*event) ||
+ length < sizeof(*event) + event->len)
+ break;
+
+ if (event->len > 0 && event->name[event->len - 1] == 0)
+ name = event->name;
+ else
+ name = NULL;
+
+ source->callback(event->wd, event->mask, name,
+ source->callback_ctx);
+ fifo_buffer_consume(source->buffer,
+ sizeof(*event) + event->len);
+ }
+
+ return true;
+}
+
+struct mpd_inotify_source *
+mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
+ GError **error_r)
+{
+ struct mpd_inotify_source *source =
+ g_new(struct mpd_inotify_source, 1);
+
+ source->fd = inotify_init_cloexec();
+ if (source->fd < 0) {
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_init() has failed: %s",
+ g_strerror(errno));
+ g_free(source);
+ return NULL;
+ }
+
+ source->buffer = fifo_buffer_new(4096);
+
+ source->channel = g_io_channel_unix_new(source->fd);
+ source->id = g_io_add_watch(source->channel, G_IO_IN,
+ mpd_inotify_in_event, source);
+
+ source->callback = callback;
+ source->callback_ctx = callback_ctx;
+
+ return source;
+}
+
+void
+mpd_inotify_source_free(struct mpd_inotify_source *source)
+{
+ g_source_remove(source->id);
+ g_io_channel_unref(source->channel);
+ fifo_buffer_free(source->buffer);
+ close(source->fd);
+ g_free(source);
+}
+
+int
+mpd_inotify_source_add(struct mpd_inotify_source *source,
+ const char *path_fs, unsigned mask,
+ GError **error_r)
+{
+ int wd = inotify_add_watch(source->fd, path_fs, mask);
+ if (wd < 0)
+ g_set_error(error_r, mpd_inotify_quark(), errno,
+ "inotify_add_watch() has failed: %s",
+ g_strerror(errno));
+
+ return wd;
+}
+
+void
+mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd)
+{
+ int ret = inotify_rm_watch(source->fd, wd);
+ if (ret < 0 && errno != EINVAL)
+ g_warning("inotify_rm_watch() has failed: %s",
+ g_strerror(errno));
+
+ /* EINVAL may happen here when the file has been deleted; the
+ kernel seems to auto-unregister deleted files */
+}
diff --git a/src/inotify_source.h b/src/inotify_source.h
new file mode 100644
index 000000000..eb609ccfc
--- /dev/null
+++ b/src/inotify_source.h
@@ -0,0 +1,61 @@
+/*
+ * 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_INOTIFY_SOURCE_H
+#define MPD_INOTIFY_SOURCE_H
+
+#include <glib.h>
+
+typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
+ const char *name, void *ctx);
+
+struct mpd_inotify_source;
+
+/**
+ * Creates a new inotify source and registers it in the GLib main
+ * loop.
+ *
+ * @param a callback invoked for events received from the kernel
+ */
+struct mpd_inotify_source *
+mpd_inotify_source_new(mpd_inotify_callback_t callback, void *callback_ctx,
+ GError **error_r);
+
+void
+mpd_inotify_source_free(struct mpd_inotify_source *source);
+
+/**
+ * Adds a path to the notify list.
+ *
+ * @return a watch descriptor or -1 on error
+ */
+int
+mpd_inotify_source_add(struct mpd_inotify_source *source,
+ const char *path_fs, unsigned mask,
+ GError **error_r);
+
+/**
+ * Removes a path from the notify list.
+ *
+ * @param wd the watch descriptor returned by mpd_inotify_source_add()
+ */
+void
+mpd_inotify_source_rm(struct mpd_inotify_source *source, unsigned wd);
+
+#endif
diff --git a/src/inotify_update.c b/src/inotify_update.c
new file mode 100644
index 000000000..53a7ff73c
--- /dev/null
+++ b/src/inotify_update.c
@@ -0,0 +1,351 @@
+/*
+ * 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 "inotify_update.h"
+#include "inotify_source.h"
+#include "inotify_queue.h"
+#include "database.h"
+#include "mapper.h"
+#include "path.h"
+
+#include <assert.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "inotify"
+
+enum {
+ IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
+ |IN_MOVE|IN_MOVE_SELF
+#ifdef IN_ONLYDIR
+ |IN_ONLYDIR
+#endif
+};
+
+struct watch_directory {
+ struct watch_directory *parent;
+
+ char *name;
+
+ int descriptor;
+
+ GList *children;
+};
+
+static struct mpd_inotify_source *inotify_source;
+
+static struct watch_directory inotify_root;
+static GTree *inotify_directories;
+
+static gint
+compare(gconstpointer a, gconstpointer b)
+{
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+tree_add_watch_directory(struct watch_directory *directory)
+{
+ g_tree_insert(inotify_directories,
+ GINT_TO_POINTER(directory->descriptor), directory);
+}
+
+static void
+tree_remove_watch_directory(struct watch_directory *directory)
+{
+ G_GNUC_UNUSED
+ bool found = g_tree_remove(inotify_directories,
+ GINT_TO_POINTER(directory->descriptor));
+ assert(found);
+}
+
+static struct watch_directory *
+tree_find_watch_directory(int wd)
+{
+ return g_tree_lookup(inotify_directories, GINT_TO_POINTER(wd));
+}
+
+static void
+remove_watch_directory(struct watch_directory *directory)
+{
+ assert(directory != NULL);
+ assert(directory->parent != NULL);
+ assert(directory->parent->children != NULL);
+
+ tree_remove_watch_directory(directory);
+
+ while (directory->children != NULL)
+ remove_watch_directory(directory->children->data);
+
+ directory->parent->children =
+ g_list_remove(directory->parent->children, directory);
+
+ mpd_inotify_source_rm(inotify_source, directory->descriptor);
+ g_free(directory->name);
+ g_slice_free(struct watch_directory, directory);
+}
+
+static char *
+watch_directory_get_uri_fs(const struct watch_directory *directory)
+{
+ char *parent_uri, *uri;
+
+ if (directory->parent == NULL)
+ return NULL;
+
+ parent_uri = watch_directory_get_uri_fs(directory->parent);
+ if (parent_uri == NULL)
+ return g_strdup(directory->name);
+
+ uri = g_strconcat(parent_uri, "/", directory->name, NULL);
+ g_free(parent_uri);
+
+ return uri;
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+static bool skip_path(const char *path)
+{
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+static void
+recursive_watch_subdirectories(struct watch_directory *directory,
+ const char *path_fs)
+{
+ GError *error = NULL;
+ DIR *dir;
+ struct dirent *ent;
+
+ assert(directory != NULL);
+ assert(path_fs != NULL);
+
+ dir = opendir(path_fs);
+ if (dir == NULL) {
+ g_warning("Failed to open directory %s: %s",
+ path_fs, g_strerror(errno));
+ return;
+ }
+
+ while ((ent = readdir(dir))) {
+ char *child_path_fs;
+ struct stat st;
+ int ret;
+ struct watch_directory *child;
+
+ if (skip_path(ent->d_name))
+ continue;
+
+ child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL);
+ /* XXX what about symlinks? */
+ ret = lstat(child_path_fs, &st);
+ if (ret < 0) {
+ g_warning("Failed to stat %s: %s",
+ child_path_fs, g_strerror(errno));
+ g_free(child_path_fs);
+ continue;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ g_free(child_path_fs);
+ continue;
+ }
+
+ ret = mpd_inotify_source_add(inotify_source, child_path_fs,
+ IN_MASK, &error);
+ if (ret < 0) {
+ g_warning("Failed to register %s: %s",
+ child_path_fs, error->message);
+ g_error_free(error);
+ error = NULL;
+ g_free(child_path_fs);
+ continue;
+ }
+
+ child = tree_find_watch_directory(ret);
+ if (child != NULL) {
+ /* already being watched */
+ g_free(child_path_fs);
+ continue;
+ }
+
+ child = g_slice_new(struct watch_directory);
+ child->parent = directory;
+ child->name = g_strdup(ent->d_name);
+ child->descriptor = ret;
+ child->children = NULL;
+
+ directory->children = g_list_prepend(directory->children,
+ child);
+
+ tree_add_watch_directory(child);
+
+ recursive_watch_subdirectories(child, child_path_fs);
+ g_free(child_path_fs);
+ }
+
+ closedir(dir);
+}
+
+static void
+mpd_inotify_callback(int wd, unsigned mask,
+ G_GNUC_UNUSED const char *name, G_GNUC_UNUSED void *ctx)
+{
+ struct watch_directory *directory;
+ char *uri_fs;
+
+ /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/
+
+ directory = tree_find_watch_directory(wd);
+ if (directory == NULL)
+ return;
+
+ uri_fs = watch_directory_get_uri_fs(directory);
+
+ if ((mask & (IN_DELETE_SELF|IN_MOVE_SELF)) != 0) {
+ g_free(uri_fs);
+ remove_watch_directory(directory);
+ return;
+ }
+
+ if ((mask & (IN_ATTRIB|IN_CREATE|IN_MOVE)) != 0 &&
+ (mask & IN_ISDIR) != 0) {
+ /* a sub directory was changed: register those in
+ inotify */
+ char *root = map_directory_fs(db_get_root());
+ char *path_fs;
+
+ if (uri_fs != NULL) {
+ path_fs = g_strconcat(root, "/", uri_fs, NULL);
+ g_free(root);
+ } else
+ path_fs = root;
+
+ recursive_watch_subdirectories(directory, path_fs);
+ g_free(path_fs);
+ }
+
+ if ((mask & (IN_CLOSE_WRITE|IN_MOVE|IN_DELETE)) != 0) {
+ /* a file was changed, or a direectory was
+ moved/deleted: queue a database update */
+ char *uri_utf8 = uri_fs != NULL
+ ? fs_charset_to_utf8(uri_fs)
+ : g_strdup("");
+
+ if (uri_utf8 != NULL)
+ /* this function will take care of freeing
+ uri_utf8 */
+ mpd_inotify_enqueue(uri_utf8);
+ }
+
+ g_free(uri_fs);
+}
+
+void
+mpd_inotify_init(void)
+{
+ struct directory *root;
+ char *path;
+ GError *error = NULL;
+
+ g_debug("initializing inotify");
+
+ root = db_get_root();
+ if (root == NULL) {
+ g_debug("no music directory configured");
+ return;
+ }
+
+ path = map_directory_fs(root);
+ if (path == NULL) {
+ g_warning("mapper has failed");
+ return;
+ }
+
+ inotify_source = mpd_inotify_source_new(mpd_inotify_callback, NULL,
+ &error);
+ if (inotify_source == NULL) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ g_free(path);
+ return;
+ }
+
+ inotify_root.name = path;
+ inotify_root.descriptor = mpd_inotify_source_add(inotify_source, path,
+ IN_MASK, &error);
+ if (inotify_root.descriptor < 0) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ mpd_inotify_source_free(inotify_source);
+ inotify_source = NULL;
+ g_free(path);
+ return;
+ }
+
+ inotify_directories = g_tree_new(compare);
+ tree_add_watch_directory(&inotify_root);
+
+ recursive_watch_subdirectories(&inotify_root, path);
+
+ mpd_inotify_queue_init();
+
+ g_debug("watching music directory");
+}
+
+static gboolean
+free_watch_directory(G_GNUC_UNUSED gpointer key, gpointer value,
+ G_GNUC_UNUSED gpointer data)
+{
+ struct watch_directory *directory = value;
+
+ g_free(directory->name);
+ g_list_free(directory->children);
+
+ if (directory != &inotify_root)
+ g_slice_free(struct watch_directory, directory);
+
+ return false;
+}
+
+void
+mpd_inotify_finish(void)
+{
+ if (inotify_source == NULL)
+ return;
+
+ mpd_inotify_queue_finish();
+ mpd_inotify_source_free(inotify_source);
+
+ g_tree_foreach(inotify_directories, free_watch_directory, NULL);
+ g_tree_destroy(inotify_directories);
+}
diff --git a/src/inotify_update.h b/src/inotify_update.h
new file mode 100644
index 000000000..f77e183a6
--- /dev/null
+++ b/src/inotify_update.h
@@ -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.
+ */
+
+#ifndef MPD_INOTIFY_UPDATE_H
+#define MPD_INOTIFY_UPDATE_H
+
+#include "check.h"
+
+#ifdef HAVE_INOTIFY_INIT
+
+void
+mpd_inotify_init(void);
+
+void
+mpd_inotify_finish(void);
+
+#else /* !HAVE_INOTIFY_INIT */
+
+static inline void
+mpd_inotify_init(void)
+{
+}
+
+static inline void
+mpd_inotify_finish(void)
+{
+}
+
+#endif /* !HAVE_INOTIFY_INIT */
+
+#endif
diff --git a/src/input/archive_input_plugin.c b/src/input/archive_input_plugin.c
index 8e897f0c2..0de715a82 100644
--- a/src/input/archive_input_plugin.c
+++ b/src/input/archive_input_plugin.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "input/archive_input_plugin.h"
#include "archive_api.h"
#include "archive_list.h"
diff --git a/src/input/curl_input_plugin.c b/src/input/curl_input_plugin.c
index 95d269ce5..69b19c371 100644
--- a/src/input/curl_input_plugin.c
+++ b/src/input/curl_input_plugin.c
@@ -17,12 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "input/curl_input_plugin.h"
#include "input_plugin.h"
#include "conf.h"
-#include "config.h"
#include "tag.h"
#include "icy_metadata.h"
+#include "glib_compat.h"
#include <assert.h>
@@ -42,7 +43,7 @@
#define G_LOG_DOMAIN "input_curl"
/** rewinding is possible after up to 64 kB */
-static const off_t max_rewind_size = 64 * 1024;
+static const goffset max_rewind_size = 64 * 1024;
/**
* Buffers created by input_curl_writefunction().
@@ -103,7 +104,8 @@ static const char *proxy, *proxy_user, *proxy_password;
static unsigned proxy_port;
static bool
-input_curl_init(const struct config_param *param)
+input_curl_init(const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
{
CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
if (code != CURLE_OK) {
@@ -150,11 +152,6 @@ buffer_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
g_free(data);
}
-/* g_queue_clear() was introduced in GLib 2.14 */
-#if !GLIB_CHECK_VERSION(2,14,0)
-#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
-#endif
-
/**
* Frees the current "libcurl easy" handle, and everything associated
* with it.
@@ -407,8 +404,8 @@ copy_icy_tag(struct input_curl *c)
if (c->tag != NULL)
tag_free(c->tag);
- if (c->meta_name != NULL && !tag_has_type(tag, TAG_ITEM_NAME))
- tag_add_item(tag, TAG_ITEM_NAME, c->meta_name);
+ if (c->meta_name != NULL && !tag_has_type(tag, TAG_NAME))
+ tag_add_item(tag, TAG_NAME, c->meta_name);
c->tag = tag;
}
@@ -425,7 +422,7 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
#ifndef NDEBUG
if (c->rewind != NULL &&
(!g_queue_is_empty(c->rewind) || is->offset == 0)) {
- off_t offset = 0;
+ goffset offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
@@ -474,11 +471,11 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
if (icy_defined(&c->icy_metadata))
copy_icy_tag(c);
- is->offset += (off_t)nbytes;
+ is->offset += (goffset)nbytes;
#ifndef NDEBUG
if (rewind_buffers != NULL) {
- off_t offset = 0;
+ goffset offset = 0;
struct buffer *buffer;
for (GList *list = g_queue_peek_head_link(c->rewind);
@@ -614,7 +611,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
tag_free(c->tag);
c->tag = tag_new();
- tag_add_item(c->tag, TAG_ITEM_NAME, c->meta_name);
+ tag_add_item(c->tag, TAG_NAME, c->meta_name);
} else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
char buffer[64];
size_t icy_metaint;
@@ -772,7 +769,7 @@ input_curl_can_rewind(struct input_stream *is)
/* rewind is possible if this is the very first buffer of the
resource */
buffer = (struct buffer*)g_queue_peek_head(c->buffers);
- return (off_t)buffer->consumed == is->offset;
+ return (goffset)buffer->consumed == is->offset;
}
static void
@@ -780,7 +777,7 @@ input_curl_rewind(struct input_stream *is)
{
struct input_curl *c = is->data;
#ifndef NDEBUG
- off_t offset = 0;
+ goffset offset = 0;
#endif
assert(c->rewind != NULL);
@@ -818,7 +815,7 @@ input_curl_rewind(struct input_stream *is)
}
static bool
-input_curl_seek(struct input_stream *is, off_t offset, int whence)
+input_curl_seek(struct input_stream *is, goffset offset, int whence)
{
struct input_curl *c = is->data;
bool ret;
@@ -884,7 +881,7 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
buffer = (struct buffer *)g_queue_pop_head(c->buffers);
length = buffer->size - buffer->consumed;
- if (offset - is->offset < (off_t)length)
+ if (offset - is->offset < (goffset)length)
length = offset - is->offset;
buffer = consume_buffer(buffer, length, rewind_buffers);
diff --git a/src/input/file_input_plugin.c b/src/input/file_input_plugin.c
index bda1777ac..73dcf4c0f 100644
--- a/src/input/file_input_plugin.c
+++ b/src/input/file_input_plugin.c
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "input/file_input_plugin.h"
#include "input_plugin.h"
+#include "fd_util.h"
#include <sys/stat.h>
#include <fcntl.h>
@@ -36,10 +38,10 @@ input_file_open(struct input_stream *is, const char *filename)
int fd, ret;
struct stat st;
- if (filename[0] != '/')
+ if (!g_path_is_absolute(filename))
return false;
- fd = open(filename, O_RDONLY);
+ fd = open_cloexec(filename, O_RDONLY, 0);
if (fd < 0) {
is->error = errno;
g_debug("Failed to open \"%s\": %s",
@@ -77,11 +79,11 @@ input_file_open(struct input_stream *is, const char *filename)
}
static bool
-input_file_seek(struct input_stream *is, off_t offset, int whence)
+input_file_seek(struct input_stream *is, goffset offset, int whence)
{
int fd = GPOINTER_TO_INT(is->data);
- offset = lseek(fd, offset, whence);
+ offset = (goffset)lseek(fd, (off_t)offset, whence);
if (offset < 0) {
is->error = errno;
return false;
diff --git a/src/input/lastfm_input_plugin.c b/src/input/lastfm_input_plugin.c
deleted file mode 100644
index 8e13a60a9..000000000
--- a/src/input/lastfm_input_plugin.c
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "input/lastfm_input_plugin.h"
-#include "input/curl_input_plugin.h"
-#include "input_plugin.h"
-#include "conf.h"
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_lastfm"
-
-static const char *lastfm_user, *lastfm_password;
-
-static bool
-lastfm_input_init(const struct config_param *param)
-{
- lastfm_user = config_get_block_string(param, "user", NULL);
- lastfm_password = config_get_block_string(param, "password", NULL);
-
- return lastfm_user != NULL && lastfm_password != NULL;
-}
-
-static char *
-lastfm_get(const char *url)
-{
- struct input_stream input_stream;
- bool success;
- int ret;
- char buffer[4096];
- size_t length = 0, nbytes;
-
- success = input_stream_open(&input_stream, url);
- if (!success)
- return NULL;
-
- while (!input_stream.ready) {
- ret = input_stream_buffer(&input_stream);
- if (ret < 0) {
- input_stream_close(&input_stream);
- return NULL;
- }
- }
-
- do {
- nbytes = input_stream_read(&input_stream, buffer + length,
- sizeof(buffer) - length);
- if (nbytes == 0) {
- if (input_stream_eof(&input_stream))
- break;
-
- /* I/O error */
- input_stream_close(&input_stream);
- return NULL;
- }
-
- length += nbytes;
- } while (length < sizeof(buffer));
-
- input_stream_close(&input_stream);
- return g_strndup(buffer, length);
-}
-
-static char *
-lastfm_find(const char *response, const char *name)
-{
- size_t name_length = strlen(name);
-
- while (true) {
- const char *eol = strchr(response, '\n');
- if (eol == NULL)
- return NULL;
-
- if (strncmp(response, name, name_length) == 0 &&
- response[name_length] == '=') {
- response += name_length + 1;
- return g_strndup(response, eol - response);
- }
-
- response = eol + 1;
- }
-}
-
-static bool
-lastfm_input_open(struct input_stream *is, const char *url)
-{
- char *md5, *p, *q, *response, *session, *stream_url;
- bool success;
-
- if (strncmp(url, "lastfm://", 9) != 0)
- return false;
-
- /* handshake */
-
-#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(lastfm_user, NULL, false);
-#else
- q = g_strdup(lastfm_user);
-#endif
-
-#if GLIB_CHECK_VERSION(2,16,0)
- if (strlen(lastfm_password) != 32)
- md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
- lastfm_password,
- strlen(lastfm_password));
- else
-#endif
- md5 = g_strdup(lastfm_password);
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
- "version=1.1.1&platform=linux&"
- "username=", q, "&"
- "passwordmd5=", md5, "&debug=0&partner=", NULL);
- g_free(q);
- g_free(md5);
-
- response = lastfm_get(p);
- g_free(p);
- if (response == NULL)
- return false;
-
- /* extract session id from response */
-
- session = lastfm_find(response, "session");
- stream_url = lastfm_find(response, "stream_url");
- g_free(response);
- if (session == NULL || stream_url == NULL) {
- g_free(session);
- g_free(stream_url);
- return false;
- }
-
-#if GLIB_CHECK_VERSION(2,16,0)
- q = g_uri_escape_string(session, NULL, false);
- g_free(session);
- session = q;
-#endif
-
- /* "adjust" last.fm radio */
-
- if (strlen(url) > 9) {
- char *escaped_url;
-
-#if GLIB_CHECK_VERSION(2,16,0)
- escaped_url = g_uri_escape_string(url, NULL, false);
-#else
- escaped_url = g_strdup(url);
-#endif
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
- "session=", session, "&url=", escaped_url, "&debug=0",
- NULL);
- g_free(escaped_url);
-
- response = lastfm_get(p);
- g_free(response);
- g_free(p);
-
- if (response == NULL) {
- g_free(session);
- g_free(stream_url);
- return false;
- }
- }
-
- /* load the last.fm playlist */
-
- p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
- "sk=", session, "&discovery=0&desktop=1.5.1.31879",
- NULL);
- g_free(session);
-
- response = lastfm_get(p);
- g_free(p);
-
- if (response == NULL) {
- g_free(stream_url);
- return false;
- }
-
- p = strstr(response, "<location>");
- if (p == NULL) {
- g_free(response);
- g_free(stream_url);
- return false;
- }
-
- p += 10;
- q = strchr(p, '<');
-
- if (q == NULL) {
- g_free(response);
- g_free(stream_url);
- return false;
- }
-
- g_free(stream_url);
- stream_url = g_strndup(p, q - p);
- g_free(response);
-
- /* now really open the last.fm radio stream */
-
- success = input_stream_open(is, stream_url);
- g_free(stream_url);
- return success;
-}
-
-const struct input_plugin lastfm_input_plugin = {
- .name = "lastfm",
- .init = lastfm_input_init,
- .open = lastfm_input_open,
-};
diff --git a/src/input/mms_input_plugin.c b/src/input/mms_input_plugin.c
index 2a3c53776..eb2665afb 100644
--- a/src/input/mms_input_plugin.c
+++ b/src/input/mms_input_plugin.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "input/mms_input_plugin.h"
#include "input_plugin.h"
@@ -110,7 +111,7 @@ input_mms_buffer(G_GNUC_UNUSED struct input_stream *is)
static bool
input_mms_seek(G_GNUC_UNUSED struct input_stream *is,
- G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
+ G_GNUC_UNUSED goffset offset, G_GNUC_UNUSED int whence)
{
return false;
}
diff --git a/src/input_init.c b/src/input_init.c
new file mode 100644
index 000000000..c4d015594
--- /dev/null
+++ b/src/input_init.c
@@ -0,0 +1,100 @@
+/*
+ * 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 "input_init.h"
+#include "input_plugin.h"
+#include "input_registry.h"
+#include "conf.h"
+#include "glib_compat.h"
+
+#include <string.h>
+
+static inline GQuark
+input_quark(void)
+{
+ return g_quark_from_static_string("input");
+}
+
+/**
+ * Find the "input" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the input plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+input_plugin_config(const char *plugin_name, GError **error_r)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "plugin", NULL);
+ if (name == NULL) {
+ g_set_error(error_r, input_quark(), 0,
+ "input configuration without 'plugin' name in line %d",
+ param->line);
+ return NULL;
+ }
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+bool
+input_stream_global_init(GError **error_r)
+{
+ GError *error = NULL;
+
+ for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
+ const struct input_plugin *plugin = input_plugins[i];
+ const struct config_param *param =
+ input_plugin_config(plugin->name, &error);
+ if (param == NULL && error != NULL) {
+ g_propagate_error(error_r, error);
+ return false;
+ }
+
+ if (!config_get_block_bool(param, "enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ if (plugin->init == NULL || plugin->init(param, &error))
+ input_plugins_enabled[i] = true;
+ else {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to initialize input plugin '%s': ",
+ plugin->name);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void input_stream_global_finish(void)
+{
+ for (unsigned i = 0; input_plugins[i] != NULL; ++i)
+ if (input_plugins_enabled[i] &&
+ input_plugins[i]->finish != NULL)
+ input_plugins[i]->finish();
+}
diff --git a/src/input_init.h b/src/input_init.h
new file mode 100644
index 000000000..8416de59e
--- /dev/null
+++ b/src/input_init.h
@@ -0,0 +1,42 @@
+/*
+ * 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_INPUT_INIT_H
+#define MPD_INPUT_INIT_H
+
+#include "check.h"
+
+#include <glib.h>
+#include <stdbool.h>
+
+/**
+ * Initializes this library and all input_stream implementations.
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ */
+bool
+input_stream_global_init(GError **error_r);
+
+/**
+ * Deinitializes this library and all input_stream implementations.
+ */
+void input_stream_global_finish(void);
+
+#endif
diff --git a/src/input_plugin.h b/src/input_plugin.h
index 8fe852bc6..b0ba37959 100644
--- a/src/input_plugin.h
+++ b/src/input_plugin.h
@@ -35,10 +35,12 @@ struct input_plugin {
/**
* Global initialization. This method is called when MPD starts.
*
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
* @return true on success, false if the plugin should be
* disabled
*/
- bool (*init)(const struct config_param *param);
+ bool (*init)(const struct config_param *param, GError **error_r);
/**
* Global deinitialization. Called once before MPD shuts
@@ -53,7 +55,7 @@ struct input_plugin {
int (*buffer)(struct input_stream *is);
size_t (*read)(struct input_stream *is, void *ptr, size_t size);
bool (*eof)(struct input_stream *is);
- bool (*seek)(struct input_stream *is, off_t offset, int whence);
+ bool (*seek)(struct input_stream *is, goffset offset, int whence);
};
#endif
diff --git a/src/input_registry.c b/src/input_registry.c
new file mode 100644
index 000000000..07266a856
--- /dev/null
+++ b/src/input_registry.c
@@ -0,0 +1,52 @@
+/*
+ * 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 "input_registry.h"
+#include "input/file_input_plugin.h"
+
+#ifdef ENABLE_ARCHIVE
+#include "input/archive_input_plugin.h"
+#endif
+
+#ifdef ENABLE_CURL
+#include "input/curl_input_plugin.h"
+#endif
+
+#ifdef ENABLE_MMS
+#include "input/mms_input_plugin.h"
+#endif
+
+#include <glib.h>
+
+const struct input_plugin *const input_plugins[] = {
+ &input_plugin_file,
+#ifdef ENABLE_ARCHIVE
+ &input_plugin_archive,
+#endif
+#ifdef ENABLE_CURL
+ &input_plugin_curl,
+#endif
+#ifdef ENABLE_MMS
+ &input_plugin_mms,
+#endif
+ NULL
+};
+
+bool input_plugins_enabled[G_N_ELEMENTS(input_plugins) - 1];
diff --git a/src/input_registry.h b/src/input_registry.h
new file mode 100644
index 000000000..0a9cd570f
--- /dev/null
+++ b/src/input_registry.h
@@ -0,0 +1,35 @@
+/*
+ * 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_INPUT_REGISTRY_H
+#define MPD_INPUT_REGISTRY_H
+
+#include "check.h"
+
+#include <stdbool.h>
+
+/**
+ * NULL terminated list of all input plugins which were enabled at
+ * compile time.
+ */
+extern const struct input_plugin *const input_plugins[];
+
+extern bool input_plugins_enabled[];
+
+#endif
diff --git a/src/input_stream.c b/src/input_stream.c
index 69dc644a2..54ea1c995 100644
--- a/src/input_stream.c
+++ b/src/input_stream.c
@@ -17,99 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "input_plugin.h"
#include "config.h"
-#include "conf.h"
-
-#include "input/file_input_plugin.h"
-
-#ifdef ENABLE_ARCHIVE
-#include "input/archive_input_plugin.h"
-#endif
-
-#ifdef HAVE_CURL
-#include "input/curl_input_plugin.h"
-#endif
-
-#include "input/lastfm_input_plugin.h"
-
-#ifdef ENABLE_MMS
-#include "input/mms_input_plugin.h"
-#endif
+#include "input_stream.h"
+#include "input_registry.h"
+#include "input_plugin.h"
#include <glib.h>
#include <assert.h>
-#include <string.h>
-
-static const struct input_plugin *const input_plugins[] = {
- &input_plugin_file,
-#ifdef ENABLE_ARCHIVE
- &input_plugin_archive,
-#endif
-#ifdef HAVE_CURL
- &input_plugin_curl,
-#endif
-#ifdef ENABLE_LASTFM
- &lastfm_input_plugin,
-#endif
-#ifdef ENABLE_MMS
- &input_plugin_mms,
-#endif
-};
-
-static bool input_plugins_enabled[G_N_ELEMENTS(input_plugins)];
-
-static const unsigned num_input_plugins =
- sizeof(input_plugins) / sizeof(input_plugins[0]);
-
-/**
- * Find the "input" configuration block for the specified plugin.
- *
- * @param plugin_name the name of the input plugin
- * @return the configuration block, or NULL if none was configured
- */
-static const struct config_param *
-input_plugin_config(const char *plugin_name)
-{
- const struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_INPUT, param)) != NULL) {
- const char *name =
- config_get_block_string(param, "plugin", NULL);
- if (name == NULL)
- g_error("input configuration without 'plugin' name in line %d",
- param->line);
-
- if (strcmp(name, plugin_name) == 0)
- return param;
- }
-
- return NULL;
-}
-
-void input_stream_global_init(void)
-{
- for (unsigned i = 0; i < num_input_plugins; ++i) {
- const struct input_plugin *plugin = input_plugins[i];
- const struct config_param *param =
- input_plugin_config(plugin->name);
-
- if (!config_get_block_bool(param, "enabled", true))
- /* the plugin is disabled in mpd.conf */
- continue;
-
- if (plugin->init == NULL || plugin->init(param))
- input_plugins_enabled[i] = true;
- }
-}
-
-void input_stream_global_finish(void)
-{
- for (unsigned i = 0; i < num_input_plugins; ++i)
- if (input_plugins_enabled[i] &&
- input_plugins[i]->finish != NULL)
- input_plugins[i]->finish();
-}
bool
input_stream_open(struct input_stream *is, const char *url)
@@ -121,7 +35,7 @@ input_stream_open(struct input_stream *is, const char *url)
is->error = 0;
is->mime = NULL;
- for (unsigned i = 0; i < num_input_plugins; ++i) {
+ for (unsigned i = 0; input_plugins[i] != NULL; ++i) {
const struct input_plugin *plugin = input_plugins[i];
if (input_plugins_enabled[i] && plugin->open(is, url)) {
@@ -139,7 +53,7 @@ input_stream_open(struct input_stream *is, const char *url)
}
bool
-input_stream_seek(struct input_stream *is, off_t offset, int whence)
+input_stream_seek(struct input_stream *is, goffset offset, int whence)
{
if (is->plugin->seek == NULL)
return false;
diff --git a/src/input_stream.h b/src/input_stream.h
index 35b0d44fd..63327c137 100644
--- a/src/input_stream.h
+++ b/src/input_stream.h
@@ -20,11 +20,17 @@
#ifndef MPD_INPUT_STREAM_H
#define MPD_INPUT_STREAM_H
+#include "check.h"
+
+#include <glib.h>
+
#include <stddef.h>
#include <stdbool.h>
#include <sys/types.h>
-struct input_stream;
+#if !GLIB_CHECK_VERSION(2,14,0)
+typedef gint64 goffset;
+#endif
struct input_stream {
/**
@@ -56,12 +62,12 @@ struct input_stream {
/**
* the size of the resource, or -1 if unknown
*/
- off_t size;
+ goffset size;
/**
* the current offset within the stream
*/
- off_t offset;
+ goffset offset;
/**
* the MIME content type of the resource, or NULL if unknown
@@ -70,16 +76,6 @@ struct input_stream {
};
/**
- * Initializes this library and all input_stream implementations.
- */
-void input_stream_global_init(void);
-
-/**
- * Deinitializes this library and all input_stream implementations.
- */
-void input_stream_global_finish(void);
-
-/**
* Opens a new input stream. You may not access it until the "ready"
* flag is set.
*
@@ -106,7 +102,7 @@ input_stream_close(struct input_stream *is);
* @param whence the base of the seek, one of SEEK_SET, SEEK_CUR, SEEK_END
*/
bool
-input_stream_seek(struct input_stream *is, off_t offset, int whence);
+input_stream_seek(struct input_stream *is, goffset offset, int whence);
/**
* Returns true if the stream has reached end-of-file.
diff --git a/src/listen.c b/src/listen.c
index 98108d9da..2b1ac7f5d 100644
--- a/src/listen.c
+++ b/src/listen.c
@@ -17,12 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "listen.h"
#include "socket_util.h"
#include "client.h"
#include "conf.h"
-#include "utils.h"
-#include "config.h"
+#include "fd_util.h"
+#include "glib_compat.h"
#include <sys/types.h>
#include <sys/stat.h>
@@ -347,7 +348,8 @@ listen_add_config_param(unsigned int port,
}
}
-void listen_global_init(void)
+bool
+listen_global_init(GError **error_r)
{
int port = config_get_positive(CONF_PORT, DEFAULT_PORT);
const struct config_param *param =
@@ -361,10 +363,12 @@ void listen_global_init(void)
do {
success = listen_add_config_param(port, param, &error);
- if (!success)
- g_error("Failed to listen on %s (line %i): %s",
- param->value, param->line,
- error->message);
+ if (!success) {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on %s (line %i): ",
+ param->value, param->line);
+ return false;
+ }
param = config_get_next_param(CONF_BIND_TO_ADDRESS,
param);
@@ -374,12 +378,16 @@ void listen_global_init(void)
configured port on all interfaces */
success = listen_add_port(port, &error);
- if (!success)
- g_error("Failed to listen on *:%d: %s",
- port, error->message);
+ if (!success) {
+ g_propagate_prefixed_error(error_r, error,
+ "Failed to listen on *:%d: ",
+ port);
+ return false;
+ }
}
listen_port = port;
+ return true;
}
void listen_global_finish(void)
@@ -419,12 +427,11 @@ listen_in_event(G_GNUC_UNUSED GIOChannel *source,
{
int listen_fd = GPOINTER_TO_INT(data), fd;
struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
+ size_t sa_length = sizeof(sa);
- fd = accept(listen_fd, (struct sockaddr*)&sa, &sa_length);
+ fd = accept_cloexec_nonblock(listen_fd, (struct sockaddr*)&sa,
+ &sa_length);
if (fd >= 0) {
- set_nonblocking(fd);
-
client_new(fd, (struct sockaddr*)&sa, sa_length,
get_remote_uid(fd));
} else if (fd < 0 && errno != EINTR) {
diff --git a/src/listen.h b/src/listen.h
index 63253fc53..9f55edb88 100644
--- a/src/listen.h
+++ b/src/listen.h
@@ -20,9 +20,14 @@
#ifndef MPD_LISTEN_H
#define MPD_LISTEN_H
+#include <glib.h>
+
+#include <stdbool.h>
+
extern int listen_port;
-void listen_global_init(void);
+bool
+listen_global_init(GError **error_r);
void listen_global_finish(void);
diff --git a/src/locate.c b/src/locate.c
index 175bca35a..7bc23db16 100644
--- a/src/locate.c
+++ b/src/locate.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "locate.h"
#include "path.h"
#include "tag.h"
@@ -42,9 +43,9 @@ locate_parse_type(const char *str)
if (0 == g_ascii_strcasecmp(str, LOCATE_TAG_ANY_KEY))
return LOCATE_TAG_ANY_TYPE;
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
- if (0 == g_ascii_strcasecmp(str, tag_item_names[i]))
- return i;
+ i = tag_name_parse_i(str);
+ if (i != TAG_NUM_OF_ITEM_TYPES)
+ return i;
return -1;
}
diff --git a/src/log.c b/src/log.c
index 94691ab64..20368c9b7 100644
--- a/src/log.c
+++ b/src/log.c
@@ -17,10 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "log.h"
#include "conf.h"
#include "utils.h"
-#include "config.h"
+#include "fd_util.h"
#include <assert.h>
#include <sys/types.h>
@@ -128,7 +129,7 @@ open_log_file(void)
{
assert(out_filename != NULL);
- return open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
+ return open_cloexec(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
}
static void
@@ -271,7 +272,10 @@ void setup_log_output(bool use_stdout)
{
fflush(NULL);
if (!use_stdout) {
- if (out_filename != NULL) {
+ if (out_filename == NULL)
+ out_fd = open("/dev/null", O_WRONLY);
+
+ if (out_fd >= 0) {
redirect_logs(out_fd);
close(out_fd);
}
diff --git a/src/ls.c b/src/ls.c
index fd8f22fd4..8caf8350f 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "ls.h"
#include "uri.h"
#include "client.h"
-#include "config.h"
#include <assert.h>
#include <string.h>
@@ -32,12 +32,9 @@
* connected by IPC socket.
*/
static const char *remoteUrlPrefixes[] = {
-#ifdef HAVE_CURL
+#ifdef ENABLE_CURL
"http://",
#endif
-#ifdef ENABLE_LASTFM
- "lastfm://",
-#endif
#ifdef ENABLE_MMS
"mms://",
"mmsh://",
diff --git a/src/main.c b/src/main.c
index 5035a4836..882664d49 100644
--- a/src/main.c
+++ b/src/main.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "main.h"
#include "daemon.h"
#include "client.h"
@@ -33,7 +34,6 @@
#include "path.h"
#include "mapper.h"
#include "chunk.h"
-#include "decoder_control.h"
#include "player_control.h"
#include "stats.h"
#include "sig_handlers.h"
@@ -44,11 +44,11 @@
#include "permission.h"
#include "replay_gain.h"
#include "decoder_list.h"
-#include "input_stream.h"
+#include "input_init.h"
+#include "playlist_list.h"
#include "state_file.h"
#include "tag.h"
#include "dbUtils.h"
-#include "config.h"
#include "normalize.h"
#include "zeroconf.h"
#include "event_pipe.h"
@@ -56,6 +56,10 @@
#include "songvec.h"
#include "tag_pool.h"
+#ifdef ENABLE_INOTIFY
+#include "inotify_update.h"
+#endif
+
#ifdef ENABLE_SQLITE
#include "sticker.h"
#endif
@@ -88,7 +92,34 @@ enum {
GThread *main_task;
GMainLoop *main_loop;
-struct notify main_notify;
+GCond *main_cond;
+
+static void
+glue_daemonize_init(const struct options *options)
+{
+ daemonize_init(config_get_string(CONF_USER, NULL),
+ config_get_string(CONF_GROUP, NULL),
+ config_get_path(CONF_PID_FILE));
+
+ if (options->kill)
+ daemonize_kill();
+}
+
+static void
+glue_mapper_init(void)
+{
+ const char *music_dir, *playlist_dir;
+
+ music_dir = config_get_path(CONF_MUSIC_DIR);
+#if GLIB_CHECK_VERSION(2,14,0)
+ if (music_dir == NULL)
+ music_dir = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
+#endif
+
+ playlist_dir = config_get_path(CONF_PLAYLIST_DIR);
+
+ mapper_init(music_dir, playlist_dir);
+}
/**
* Returns the database. If this function returns false, this has not
@@ -96,7 +127,7 @@ struct notify main_notify;
* process has been daemonized.
*/
static bool
-openDB(const Options *options)
+glue_db_init_and_load(void)
{
const char *path = config_get_path(CONF_DB_FILE);
bool ret;
@@ -115,19 +146,11 @@ openDB(const Options *options)
db_init(path);
- if (options->createDB > 0)
- /* don't attempt to load the old database */
- return false;
-
ret = db_load(&error);
if (!ret) {
g_warning("Failed to load database: %s", error->message);
g_error_free(error);
- if (options->createDB < 0)
- g_error("can't open db file and using "
- "\"--no-create-db\" command line option");
-
if (!db_check())
exit(EXIT_FAILURE);
@@ -141,11 +164,34 @@ openDB(const Options *options)
}
/**
+ * Configure and initialize the sticker subsystem.
+ */
+static void
+glue_sticker_init(void)
+{
+#ifdef ENABLE_SQLITE
+ bool success;
+ GError *error = NULL;
+
+ success = sticker_global_init(config_get_path(CONF_STICKER_FILE),
+ &error);
+ if (!success)
+ g_error("%s", error->message);
+#endif
+}
+
+static void
+glue_state_file_init(void)
+{
+ state_file_init(config_get_path(CONF_STATE_FILE));
+}
+
+/**
* Windows-only initialization of the Winsock2 library.
*/
-#ifdef WIN32
static void winsock_init(void)
{
+#ifdef WIN32
WSADATA sockinfo;
int retval;
@@ -161,9 +207,8 @@ static void winsock_init(void)
g_error("We use Winsock2 but your version is either too new or "
"old; please install Winsock 2.x\n");
}
-
-}
#endif
+}
/**
* Initialize the decoder and player core, including the music pipe.
@@ -210,7 +255,6 @@ initialize_decoder_and_player(void)
buffered_before_play = buffered_chunks;
pc_init(buffered_chunks, buffered_before_play);
- dc_init();
}
/**
@@ -228,9 +272,11 @@ idle_event_emitted(void)
int main(int argc, char *argv[])
{
- Options options;
+ struct options options;
clock_t start;
bool create_db;
+ GError *error = NULL;
+ bool success;
daemonize_close_stdin();
@@ -239,45 +285,51 @@ int main(int argc, char *argv[])
setlocale(LC_CTYPE,"");
#endif
+ g_set_application_name("Music Player Daemon");
+
/* enable GLib's thread safety code */
g_thread_init(NULL);
-#ifdef WIN32
winsock_init();
-#endif
idle_init();
dirvec_init();
songvec_init();
tag_pool_init();
config_global_init();
- parseOptions(argc, argv, &options);
-
- daemonize_init(config_get_string(CONF_USER, NULL),
- config_get_path(CONF_PID_FILE));
+ success = parse_cmdline(argc, argv, &options, &error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
- if (options.kill)
- daemonize_kill();
+ glue_daemonize_init(&options);
stats_global_init();
tag_lib_init();
- log_init(options.verbose, options.stdOutput);
+ log_init(options.verbose, options.log_stderr);
- listen_global_init();
+ success = listen_global_init(&error);
+ if (!success) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
daemonize_set_user();
main_task = g_thread_self();
main_loop = g_main_loop_new(NULL, FALSE);
- notify_init(&main_notify);
+ main_cond = g_cond_new();
event_pipe_init();
event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted);
path_global_init();
- mapper_init();
+ glue_mapper_init();
initPermissions();
- initPlaylist();
+ playlist_global_init();
spl_global_init();
#ifdef ENABLE_ARCHIVE
archive_plugin_init_all();
@@ -285,11 +337,9 @@ int main(int argc, char *argv[])
decoder_plugin_init_all();
update_global_init();
- create_db = !openDB(&options);
+ create_db = !glue_db_init_and_load();
-#ifdef ENABLE_SQLITE
- sticker_global_init(config_get_path(CONF_STICKER_FILE));
-#endif
+ glue_sticker_init();
command_init();
initialize_decoder_and_player();
@@ -299,11 +349,18 @@ int main(int argc, char *argv[])
client_manager_init();
replay_gain_global_init();
initNormalization();
- input_stream_global_init();
+
+ if (!input_stream_global_init(&error)) {
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ playlist_list_global_init();
daemonize(options.daemon);
- setup_log_output(options.stdOutput);
+ setup_log_output(options.log_stderr);
initSigHandlers();
@@ -312,15 +369,29 @@ int main(int argc, char *argv[])
player_create();
if (create_db) {
- /* the database failed to load, or MPD was started
- with --create-db: recreate a new database */
- unsigned job = directory_update_init(NULL);
+ /* the database failed to load: recreate the
+ database */
+ unsigned job = update_enqueue(NULL, true);
if (job == 0)
g_error("directory update failed");
}
+ glue_state_file_init();
- state_file_init(config_get_path(CONF_STATE_FILE));
+ success = config_get_bool(CONF_AUTO_UPDATE, false);
+#ifdef ENABLE_INOTIFY
+ if (success && mapper_has_music_directory())
+ mpd_inotify_init();
+#else
+ if (success)
+ g_warning("inotify: auto_update was disabled. enable during compilation phase");
+#endif
+
+ config_global_check();
+
+ /* enable all audio outputs (if not already done by
+ playlist_state_restore() */
+ pc_update_audio();
/* run the main loop */
@@ -330,12 +401,16 @@ int main(int argc, char *argv[])
g_main_loop_unref(main_loop);
+#ifdef ENABLE_INOTIFY
+ mpd_inotify_finish();
+#endif
+
state_file_finish();
- playerKill();
+ pc_kill();
finishZeroconf();
client_manager_deinit();
listen_global_finish();
- finishPlaylist();
+ playlist_global_finish();
start = clock();
db_finish();
@@ -346,18 +421,17 @@ int main(int argc, char *argv[])
sticker_global_finish();
#endif
- notify_deinit(&main_notify);
+ g_cond_free(main_cond);
event_pipe_deinit();
+ playlist_list_global_finish();
input_stream_global_finish();
finishNormalization();
audio_output_all_finish();
- finishAudioConfig();
volume_finish();
mapper_finish();
path_global_finish();
finishPermissions();
- dc_deinit();
pc_deinit();
command_finish();
update_global_finish();
diff --git a/src/main.h b/src/main.h
index 8ed02bf5d..de673829d 100644
--- a/src/main.h
+++ b/src/main.h
@@ -26,6 +26,6 @@ extern GThread *main_task;
extern GMainLoop *main_loop;
-extern struct notify main_notify;
+extern GCond *main_cond;
#endif
diff --git a/src/mapper.c b/src/mapper.c
index 5518cb79e..0c3348aa1 100644
--- a/src/mapper.c
+++ b/src/mapper.c
@@ -21,20 +21,16 @@
* Maps directory and song objects to file system paths.
*/
+#include "config.h"
#include "mapper.h"
#include "directory.h"
#include "song.h"
#include "path.h"
-#include "conf.h"
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
#include <string.h>
-#include <errno.h>
static char *music_dir;
static size_t music_dir_length;
@@ -58,17 +54,10 @@ strdup_chop_slash(const char *path_fs)
static void
mapper_set_music_dir(const char *path)
{
- int ret;
- struct stat st;
-
music_dir = strdup_chop_slash(path);
music_dir_length = strlen(music_dir);
- ret = stat(music_dir, &st);
- if (ret < 0)
- g_warning("failed to stat music directory \"%s\": %s",
- music_dir, g_strerror(errno));
- else if (!S_ISDIR(st.st_mode))
+ if (!g_file_test(music_dir, G_FILE_TEST_IS_DIR))
g_warning("music directory is not a directory: \"%s\"",
music_dir);
}
@@ -76,38 +65,20 @@ mapper_set_music_dir(const char *path)
static void
mapper_set_playlist_dir(const char *path)
{
- int ret;
- struct stat st;
-
playlist_dir = g_strdup(path);
- ret = stat(playlist_dir, &st);
- if (ret < 0)
- g_warning("failed to stat playlist directory \"%s\": %s",
- playlist_dir, g_strerror(errno));
- else if (!S_ISDIR(st.st_mode))
+ if (!g_file_test(playlist_dir, G_FILE_TEST_IS_DIR))
g_warning("playlist directory is not a directory: \"%s\"",
playlist_dir);
}
-void mapper_init(void)
+void mapper_init(const char *_music_dir, const char *_playlist_dir)
{
- const char *path;
-
- path = config_get_path(CONF_MUSIC_DIR);
- if (path != NULL)
- mapper_set_music_dir(path);
-#if GLIB_CHECK_VERSION(2,14,0)
- else {
- path = g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
- if (path != NULL)
- mapper_set_music_dir(path);
- }
-#endif
+ if (_music_dir != NULL)
+ mapper_set_music_dir(_music_dir);
- path = config_get_path(CONF_PLAYLIST_DIR);
- if (path != NULL)
- mapper_set_playlist_dir(path);
+ if (_playlist_dir != NULL)
+ mapper_set_playlist_dir(_playlist_dir);
}
void mapper_finish(void)
@@ -189,9 +160,9 @@ map_song_fs(const struct song *song)
assert(song_is_file(song));
if (song_in_database(song))
- return map_directory_child_fs(song->parent, song->url);
+ return map_directory_child_fs(song->parent, song->uri);
else
- return utf8_to_fs_charset(song->url);
+ return utf8_to_fs_charset(song->uri);
}
char *
@@ -199,10 +170,10 @@ map_fs_to_utf8(const char *path_fs)
{
if (music_dir != NULL &&
strncmp(path_fs, music_dir, music_dir_length) == 0 &&
- path_fs[music_dir_length] == '/')
+ G_IS_DIR_SEPARATOR(path_fs[music_dir_length]))
/* remove musicDir prefix */
path_fs += music_dir_length + 1;
- else if (path_fs[0] == '/')
+ else if (G_IS_DIR_SEPARATOR(path_fs[0]))
/* not within musicDir */
return NULL;
diff --git a/src/mapper.h b/src/mapper.h
index f109de0bd..12108cbeb 100644
--- a/src/mapper.h
+++ b/src/mapper.h
@@ -31,7 +31,7 @@
struct directory;
struct song;
-void mapper_init(void);
+void mapper_init(const char *_music_dir, const char *_playlist_dir);
void mapper_finish(void);
diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer_plugin.c
index 52e553cc5..6726f785a 100644
--- a/src/mixer/alsa_mixer.c
+++ b/src/mixer/alsa_mixer_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "config.h"
+#include "mixer_api.h"
+#include "output_api.h"
#include <glib.h>
#include <alsa/asoundlib.h>
@@ -42,12 +43,22 @@ struct alsa_mixer {
int volume_set;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+alsa_mixer_quark(void)
+{
+ return g_quark_from_static_string("alsa_mixer");
+}
+
static struct mixer *
-alsa_mixer_init(const struct config_param *param)
+alsa_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
{
struct alsa_mixer *am = g_new(struct alsa_mixer, 1);
- mixer_init(&am->base, &alsa_mixer);
+ mixer_init(&am->base, &alsa_mixer_plugin);
am->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_ALSA_DEFAULT);
@@ -81,7 +92,7 @@ alsa_mixer_close(struct mixer *data)
}
static bool
-alsa_mixer_open(struct mixer *data)
+alsa_mixer_open(struct mixer *data, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)data;
int err;
@@ -91,29 +102,33 @@ alsa_mixer_open(struct mixer *data)
err = snd_mixer_open(&am->handle, 0);
if (err < 0) {
- g_warning("problems opening alsa mixer: %s\n", snd_strerror(err));
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_open() failed: %s", snd_strerror(err));
return false;
}
if ((err = snd_mixer_attach(am->handle, am->device)) < 0) {
- g_warning("problems attaching alsa mixer: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to attach to %s: %s",
+ am->device, snd_strerror(err));
return false;
}
if ((err = snd_mixer_selem_register(am->handle, NULL,
NULL)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_selem_register() failed: %s",
+ snd_strerror(err));
return false;
}
if ((err = snd_mixer_load(am->handle)) < 0) {
- g_warning("problems snd_mixer_selem_register'ing: %s\n",
- snd_strerror(err));
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_load() failed: %s\n",
+ snd_strerror(err));
return false;
}
@@ -138,14 +153,14 @@ alsa_mixer_open(struct mixer *data)
return true;
}
- g_warning("can't find alsa mixer control \"%s\"\n", am->control);
-
alsa_mixer_close(data);
+ g_set_error(error_r, alsa_mixer_quark(), 0,
+ "no such mixer control: %s", am->control);
return false;
}
static int
-alsa_mixer_get_volume(struct mixer *mixer)
+alsa_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
int err;
@@ -156,8 +171,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
err = snd_mixer_handle_events(am->handle);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "handle_events");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "snd_mixer_handle_events() failed: %s",
+ snd_strerror(err));
return false;
}
@@ -165,8 +181,9 @@ alsa_mixer_get_volume(struct mixer *mixer)
SND_MIXER_SCHN_FRONT_LEFT,
&level);
if (err < 0) {
- g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
- snd_strerror(err), "selem_get_playback_volume");
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to read ALSA volume: %s",
+ snd_strerror(err));
return false;
}
@@ -183,7 +200,7 @@ alsa_mixer_get_volume(struct mixer *mixer)
}
static bool
-alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
+alsa_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct alsa_mixer *am = (struct alsa_mixer *)mixer;
float vol;
@@ -203,15 +220,16 @@ alsa_mixer_set_volume(struct mixer *mixer, unsigned volume)
err = snd_mixer_selem_set_playback_volume_all(am->elem, level);
if (err < 0) {
- g_warning("problems setting alsa volume: %s\n",
- snd_strerror(err));
+ g_set_error(error_r, alsa_mixer_quark(), err,
+ "failed to set ALSA volume: %s",
+ snd_strerror(err));
return false;
}
return true;
}
-const struct mixer_plugin alsa_mixer = {
+const struct mixer_plugin alsa_mixer_plugin = {
.init = alsa_mixer_init,
.finish = alsa_mixer_finish,
.open = alsa_mixer_open,
diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer_plugin.c
index f2db01ff4..6e75edd9b 100644
--- a/src/mixer/oss_mixer.c
+++ b/src/mixer/oss_mixer_plugin.c
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../mixer_api.h"
+#include "config.h"
+#include "mixer_api.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -49,6 +51,15 @@ struct oss_mixer {
int volume_control;
};
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+oss_mixer_quark(void)
+{
+ return g_quark_from_static_string("oss_mixer");
+}
+
static int
oss_find_mixer(const char *name)
{
@@ -65,11 +76,12 @@ oss_find_mixer(const char *name)
}
static struct mixer *
-oss_mixer_init(const struct config_param *param)
+oss_mixer_init(G_GNUC_UNUSED void *ao, const struct config_param *param,
+ GError **error_r)
{
struct oss_mixer *om = g_new(struct oss_mixer, 1);
- mixer_init(&om->base, &oss_mixer);
+ mixer_init(&om->base, &oss_mixer_plugin);
om->device = config_get_block_string(param, "mixer_device",
VOLUME_MIXER_OSS_DEFAULT);
@@ -78,9 +90,9 @@ oss_mixer_init(const struct config_param *param)
if (om->control != NULL) {
om->volume_control = oss_find_mixer(om->control);
if (om->volume_control < 0) {
- g_warning("mixer control \"%s\" not found",
- om->control);
g_free(om);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "no such mixer control: %s", om->control);
return NULL;
}
} else
@@ -108,13 +120,15 @@ oss_mixer_close(struct mixer *data)
}
static bool
-oss_mixer_open(struct mixer *data)
+oss_mixer_open(struct mixer *data, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *) data;
- om->device_fd = open(om->device, O_RDONLY);
+ om->device_fd = open_cloexec(om->device, O_RDONLY, 0);
if (om->device_fd < 0) {
- g_warning("Unable to open oss mixer \"%s\"\n", om->device);
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to open %s: %s",
+ om->device, g_strerror(errno));
return false;
}
@@ -122,14 +136,17 @@ oss_mixer_open(struct mixer *data)
int devmask = 0;
if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
- g_warning("errors getting read_devmask for oss mixer\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "READ_DEVMASK failed: %s",
+ g_strerror(errno));
oss_mixer_close(data);
return false;
}
if (((1 << om->volume_control) & devmask) == 0) {
- g_warning("mixer control \"%s\" not usable\n",
- om->control);
+ g_set_error(error_r, oss_mixer_quark(), 0,
+ "mixer control \"%s\" not usable",
+ om->control);
oss_mixer_close(data);
return false;
}
@@ -138,7 +155,7 @@ oss_mixer_open(struct mixer *data)
}
static int
-oss_mixer_get_volume(struct mixer *mixer)
+oss_mixer_get_volume(struct mixer *mixer, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int left, right, level;
@@ -148,7 +165,9 @@ oss_mixer_get_volume(struct mixer *mixer)
ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to read oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to read OSS volume: %s",
+ g_strerror(errno));
return false;
}
@@ -164,7 +183,7 @@ oss_mixer_get_volume(struct mixer *mixer)
}
static bool
-oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
+oss_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
struct oss_mixer *om = (struct oss_mixer *)mixer;
int level;
@@ -177,14 +196,16 @@ oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level);
if (ret < 0) {
- g_warning("unable to set oss volume\n");
+ g_set_error(error_r, oss_mixer_quark(), errno,
+ "failed to set OSS volume: %s",
+ g_strerror(errno));
return false;
}
return true;
}
-const struct mixer_plugin oss_mixer = {
+const struct mixer_plugin oss_mixer_plugin = {
.init = oss_mixer_init,
.finish = oss_mixer_finish,
.open = oss_mixer_open,
diff --git a/src/mixer/pulse_mixer.c b/src/mixer/pulse_mixer.c
deleted file mode 100644
index 5d9ce0475..000000000
--- a/src/mixer/pulse_mixer.c
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "mixer_api.h"
-#include "conf.h"
-
-#include <glib.h>
-#include <pulse/volume.h>
-#include <pulse/pulseaudio.h>
-
-#include <string.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pulse_mixer"
-
-struct pulse_mixer {
- struct mixer base;
-
- const char *server;
- const char *sink;
- const char *output_name;
-
- uint32_t index;
- bool online;
-
- struct pa_context *context;
- struct pa_threaded_mainloop *mainloop;
- struct pa_cvolume volume;
-
-};
-
-/**
- * \brief waits for a pulseaudio operation to finish, frees it and
- * unlocks the mainloop
- * \param operation the operation to wait for
- * \return true if operation has finished normally (DONE state),
- * false otherwise
- */
-static bool
-pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
- struct pa_operation *operation)
-{
- pa_operation_state_t state;
-
- assert(mainloop != NULL);
- assert(operation != NULL);
-
- state = pa_operation_get_state(operation);
- while (state == PA_OPERATION_RUNNING) {
- pa_threaded_mainloop_wait(mainloop);
- state = pa_operation_get_state(operation);
- }
-
- pa_operation_unref(operation);
-
- return state == PA_OPERATION_DONE;
-}
-
-static void
-sink_input_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- if (eol) {
- g_debug("eol error sink_input_cb");
- return;
- }
-
- if (i == NULL) {
- g_debug("Sink input callback failure");
- return;
- }
-
- g_debug("sink input cb %s, index %d ",i->name,i->index);
-
- if (strcmp(i->name,pm->output_name) == 0) {
- pm->index = i->index;
- pm->online = true;
- pm->volume = i->volume;
- } else
- g_debug("bad name");
-}
-
-static void
-sink_input_vol(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
- int eol, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- if (eol) {
- g_debug("eol error sink_input_vol");
- return;
- }
-
- if (i == NULL) {
- g_debug("Sink input callback failure");
- return;
- }
-
- g_debug("sink input vol %s, index %d ", i->name, i->index);
-
- pm->volume = i->volume;
-
- pa_threaded_mainloop_signal(pm->mainloop, 0);
-}
-
-static void
-subscribe_cb(pa_context *c, pa_subscription_event_type_t t,
- uint32_t idx, void *userdata)
-{
-
- struct pulse_mixer *pm = userdata;
-
- g_debug("subscribe call back");
-
- switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
- case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
- if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
- PA_SUBSCRIPTION_EVENT_REMOVE &&
- pm->index == idx)
- pm->online = false;
- else {
- pa_operation *o;
-
- o = pa_context_get_sink_input_info(c, idx,
- sink_input_cb, pm);
- if (o == NULL) {
- g_debug("pa_context_get_sink_input_info() failed");
- return;
- }
-
- pa_operation_unref(o);
- }
-
- break;
- }
-}
-
-static void
-context_state_cb(pa_context *context, void *userdata)
-{
- struct pulse_mixer *pm = userdata;
-
- switch (pa_context_get_state(context)) {
- case PA_CONTEXT_READY: {
- pa_operation *o;
-
- pa_context_set_subscribe_callback(context, subscribe_cb, pm);
-
- o = pa_context_subscribe(context,
- (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
- NULL, NULL);
- if (o == NULL) {
- g_debug("pa_context_subscribe() failed");
- return;
- }
-
- pa_operation_unref(o);
-
- o = pa_context_get_sink_input_info_list(context,
- sink_input_cb, pm);
- if (o == NULL) {
- g_debug("pa_context_get_sink_input_info_list() failed");
- return;
- }
-
- pa_operation_unref(o);
-
- pa_threaded_mainloop_signal(pm->mainloop, 0);
- break;
- }
-
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- break;
- case PA_CONTEXT_TERMINATED:
- case PA_CONTEXT_FAILED:
- pa_threaded_mainloop_signal(pm->mainloop, 0);
- break;
- }
-}
-
-
-static struct mixer *
-pulse_mixer_init(const struct config_param *param)
-{
- struct pulse_mixer *pm = g_new(struct pulse_mixer,1);
- mixer_init(&pm->base, &pulse_mixer);
-
- pm->online = false;
-
- pm->server = config_get_block_string(param, "server", NULL);
- pm->sink = config_get_block_string(param, "sink", NULL);
- pm->output_name = config_get_block_string(param, "name", NULL);
-
- return &pm->base;
-}
-
-static void
-pulse_mixer_finish(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- g_free(pm);
-}
-
-static bool
-pulse_mixer_setup(struct pulse_mixer *pm)
-{
- pa_context_set_state_callback(pm->context, context_state_cb, pm);
-
- if (pa_context_connect(pm->context, pm->server,
- (pa_context_flags_t)0, NULL) < 0) {
- g_debug("context server fail");
- return false;
- }
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (pa_threaded_mainloop_start(pm->mainloop) < 0) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error start mainloop");
- return false;
- }
-
- pa_threaded_mainloop_wait(pm->mainloop);
-
- if (pa_context_get_state(pm->context) != PA_CONTEXT_READY) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("error context not ready");
- return false;
- }
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return true;
-}
-
-static bool
-pulse_mixer_open(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- g_debug("pulse mixer open");
-
- pm->index = 0;
- pm->online = false;
-
- pm->mainloop = pa_threaded_mainloop_new();
- if (pm->mainloop == NULL) {
- g_debug("failed mainloop");
- return false;
- }
-
- pm->context = pa_context_new(pa_threaded_mainloop_get_api(pm->mainloop),
- "Mixer mpd");
- if (pm->context == NULL) {
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_threaded_mainloop_free(pm->mainloop);
- g_debug("failed context");
- return false;
- }
-
- if (!pulse_mixer_setup(pm)) {
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_context_disconnect(pm->context);
- pa_context_unref(pm->context);
- pa_threaded_mainloop_free(pm->mainloop);
- return false;
- }
-
- return true;
-}
-
-static void
-pulse_mixer_close(struct mixer *data)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) data;
-
- pa_threaded_mainloop_stop(pm->mainloop);
- pa_context_disconnect(pm->context);
- pa_context_unref(pm->context);
- pa_threaded_mainloop_free(pm->mainloop);
-
- pm->online = false;
-}
-
-static int
-pulse_mixer_get_volume(struct mixer *mixer)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- int ret;
- pa_operation *o;
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- o = pa_context_get_sink_input_info(pm->context, pm->index,
- sink_input_vol, pm);
- if (o == NULL) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- g_debug("pa_context_get_sink_input_info() failed");
- return false;
- }
-
- if (!pulse_wait_for_operation(pm->mainloop, o)) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- ret = pm->online
- ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
- : -1;
-
- pa_threaded_mainloop_unlock(pm->mainloop);
-
- return ret;
-}
-
-static bool
-pulse_mixer_set_volume(struct mixer *mixer, unsigned volume)
-{
- struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
- struct pa_cvolume cvolume;
- pa_operation *o;
-
- pa_threaded_mainloop_lock(pm->mainloop);
-
- if (!pm->online) {
- pa_threaded_mainloop_unlock(pm->mainloop);
- return false;
- }
-
- pa_cvolume_set(&cvolume, pm->volume.channels,
- (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
-
- o = pa_context_set_sink_input_volume(pm->context, pm->index,
- &cvolume, NULL, NULL);
- pa_threaded_mainloop_unlock(pm->mainloop);
- if (o == NULL) {
- g_debug("pa_context_set_sink_input_volume() failed");
- return false;
- }
-
- pa_operation_unref(o);
-
- return true;
-}
-
-const struct mixer_plugin pulse_mixer = {
- .init = pulse_mixer_init,
- .finish = pulse_mixer_finish,
- .open = pulse_mixer_open,
- .close = pulse_mixer_close,
- .get_volume = pulse_mixer_get_volume,
- .set_volume = pulse_mixer_set_volume,
-};
diff --git a/src/mixer/pulse_mixer_plugin.c b/src/mixer/pulse_mixer_plugin.c
new file mode 100644
index 000000000..5669e05c4
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.c
@@ -0,0 +1,234 @@
+/*
+ * 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 "pulse_mixer_plugin.h"
+#include "mixer_api.h"
+#include "output/pulse_output_plugin.h"
+#include "conf.h"
+#include "event_pipe.h"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/introspect.h>
+#include <pulse/stream.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pulse_mixer"
+
+struct pulse_mixer {
+ struct mixer base;
+
+ struct pulse_output *output;
+
+ bool online;
+ struct pa_cvolume volume;
+
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_mixer_quark(void)
+{
+ return g_quark_from_static_string("pulse_mixer");
+}
+
+static void
+pulse_mixer_offline(struct pulse_mixer *pm)
+{
+ if (!pm->online)
+ return;
+
+ pm->online = false;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+/**
+ * Callback invoked by pulse_mixer_update(). Receives the new mixer
+ * value.
+ */
+static void
+pulse_mixer_volume_cb(G_GNUC_UNUSED pa_context *context, const pa_sink_input_info *i,
+ int eol, void *userdata)
+{
+ struct pulse_mixer *pm = userdata;
+
+ if (eol)
+ return;
+
+ if (i == NULL) {
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pm->online = true;
+ pm->volume = i->volume;
+
+ event_pipe_emit(PIPE_EVENT_MIXER);
+}
+
+static void
+pulse_mixer_update(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+ assert(stream != NULL);
+ assert(pa_stream_get_state(stream) == PA_STREAM_READY);
+
+ o = pa_context_get_sink_input_info(context,
+ pa_stream_get_index(stream),
+ pulse_mixer_volume_cb, pm);
+ if (o == NULL) {
+ g_warning("pa_context_get_sink_input_info() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ pulse_mixer_offline(pm);
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_connect(G_GNUC_UNUSED struct pulse_mixer *pm,
+ struct pa_context *context)
+{
+ pa_operation *o;
+
+ assert(context != NULL);
+
+ o = pa_context_subscribe(context,
+ (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
+ NULL, NULL);
+ if (o == NULL) {
+ g_warning("pa_context_subscribe() failed: %s",
+ pa_strerror(pa_context_errno(context)));
+ return;
+ }
+
+ pa_operation_unref(o);
+}
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm)
+{
+ pulse_mixer_offline(pm);
+}
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream)
+{
+ pulse_mixer_update(pm, context, stream);
+}
+
+static struct mixer *
+pulse_mixer_init(void *ao, G_GNUC_UNUSED const struct config_param *param,
+ GError **error_r)
+{
+ struct pulse_mixer *pm;
+ struct pulse_output *po = ao;
+
+ if (ao == NULL) {
+ g_set_error(error_r, pulse_mixer_quark(), 0,
+ "The pulse mixer cannot work without the audio output");
+ return false;
+ }
+
+ pm = g_new(struct pulse_mixer,1);
+ mixer_init(&pm->base, &pulse_mixer_plugin);
+
+ pm->online = false;
+ pm->output = po;
+
+ pulse_output_set_mixer(po, pm);
+
+ return &pm->base;
+}
+
+static void
+pulse_mixer_finish(struct mixer *data)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) data;
+
+ pulse_output_clear_mixer(pm->output, pm);
+
+ /* free resources */
+
+ g_free(pm);
+}
+
+static int
+pulse_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
+ int ret;
+
+ pa_threaded_mainloop_lock(pm->output->mainloop);
+
+ ret = pm->online
+ ? (int)((100*(pa_cvolume_avg(&pm->volume)+1))/PA_VOLUME_NORM)
+ : -1;
+
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ return ret;
+}
+
+static bool
+pulse_mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
+{
+ struct pulse_mixer *pm = (struct pulse_mixer *) mixer;
+ struct pa_cvolume cvolume;
+ bool success;
+
+ pa_threaded_mainloop_lock(pm->output->mainloop);
+ if (!pm->online) {
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+ g_set_error(error_r, pulse_mixer_quark(), 0, "disconnected");
+ return false;
+ }
+
+ pa_cvolume_set(&cvolume, pm->volume.channels,
+ (pa_volume_t)volume * PA_VOLUME_NORM / 100 + 0.5);
+ success = pulse_output_set_volume(pm->output, &cvolume, error_r);
+ if (success)
+ pm->volume = cvolume;
+ pa_threaded_mainloop_unlock(pm->output->mainloop);
+
+ return success;
+}
+
+const struct mixer_plugin pulse_mixer_plugin = {
+ .init = pulse_mixer_init,
+ .finish = pulse_mixer_finish,
+ .get_volume = pulse_mixer_get_volume,
+ .set_volume = pulse_mixer_set_volume,
+};
diff --git a/src/mixer/pulse_mixer_plugin.h b/src/mixer/pulse_mixer_plugin.h
new file mode 100644
index 000000000..2318b94be
--- /dev/null
+++ b/src/mixer/pulse_mixer_plugin.h
@@ -0,0 +1,39 @@
+/*
+ * 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_PULSE_MIXER_PLUGIN_H
+#define MPD_PULSE_MIXER_PLUGIN_H
+
+#include <pulse/def.h>
+
+struct pulse_mixer;
+struct pa_context;
+struct pa_stream;
+
+void
+pulse_mixer_on_connect(struct pulse_mixer *pm, struct pa_context *context);
+
+void
+pulse_mixer_on_disconnect(struct pulse_mixer *pm);
+
+void
+pulse_mixer_on_change(struct pulse_mixer *pm,
+ struct pa_context *context, struct pa_stream *stream);
+
+#endif
diff --git a/src/mixer/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
new file mode 100644
index 000000000..30ae13013
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.c
@@ -0,0 +1,109 @@
+/*
+ * 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 "software_mixer_plugin.h"
+#include "mixer_api.h"
+#include "filter_plugin.h"
+#include "filter_registry.h"
+#include "filter/volume_filter_plugin.h"
+#include "pcm_volume.h"
+
+#include <assert.h>
+#include <math.h>
+
+struct software_mixer {
+ /** the base mixer class */
+ struct mixer base;
+
+ struct filter *filter;
+
+ unsigned volume;
+};
+
+static struct mixer *
+software_mixer_init(G_GNUC_UNUSED void *ao,
+ G_GNUC_UNUSED const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = g_new(struct software_mixer, 1);
+
+ mixer_init(&sm->base, &software_mixer_plugin);
+
+ sm->filter = filter_new(&volume_filter_plugin, NULL, NULL);
+ assert(sm->filter != NULL);
+
+ sm->volume = 100;
+
+ return &sm->base;
+}
+
+static void
+software_mixer_finish(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ g_free(sm);
+}
+
+static int
+software_mixer_get_volume(struct mixer *mixer, G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ return sm->volume;
+}
+
+static bool
+software_mixer_set_volume(struct mixer *mixer, unsigned volume,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(volume <= 100);
+
+ sm->volume = volume;
+
+ if (volume >= 100)
+ volume = PCM_VOLUME_1;
+ else if (volume > 0)
+ volume = pcm_float_to_volume((exp(volume / 25.0) - 1) /
+ (54.5981500331F - 1));
+
+ volume_filter_set(sm->filter, volume);
+ return true;
+}
+
+const struct mixer_plugin software_mixer_plugin = {
+ .init = software_mixer_init,
+ .finish = software_mixer_finish,
+ .get_volume = software_mixer_get_volume,
+ .set_volume = software_mixer_set_volume,
+ .global = true,
+};
+
+struct filter *
+software_mixer_get_filter(struct mixer *mixer)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ assert(sm->base.plugin == &software_mixer_plugin);
+
+ return sm->filter;
+}
diff --git a/src/mixer/software_mixer_plugin.h b/src/mixer/software_mixer_plugin.h
new file mode 100644
index 000000000..a59f7edeb
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.h
@@ -0,0 +1,33 @@
+/*
+ * 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 SOFTWARE_MIXER_PLUGIN_H
+#define SOFTWARE_MIXER_PLUGIN_H
+
+struct mixer;
+struct filter;
+
+/**
+ * Returns the (volume) filter associated with this mixer. All users
+ * of this mixer plugin should install this filter.
+ */
+struct filter *
+software_mixer_get_filter(struct mixer *mixer);
+
+#endif
diff --git a/src/mixer_all.c b/src/mixer_all.c
index 252cb61ab..71f5c3c95 100644
--- a/src/mixer_all.c
+++ b/src/mixer_all.c
@@ -17,11 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "mixer_all.h"
#include "mixer_control.h"
#include "output_all.h"
#include "output_plugin.h"
#include "output_internal.h"
+#include "pcm_volume.h"
+#include "mixer_api.h"
+#include "mixer_list.h"
#include <glib.h>
@@ -35,6 +39,8 @@ output_mixer_get_volume(unsigned i)
{
struct audio_output *output;
struct mixer *mixer;
+ int volume;
+ GError *error = NULL;
assert(i < audio_output_count());
@@ -46,7 +52,14 @@ output_mixer_get_volume(unsigned i)
if (mixer == NULL)
return -1;
- return mixer_get_volume(mixer);
+ volume = mixer_get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_warning("Failed to read mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
+ }
+
+ return volume;
}
int
@@ -70,12 +83,15 @@ mixer_all_get_volume(void)
}
static bool
-output_mixer_set_volume(unsigned i, int volume, bool relative)
+output_mixer_set_volume(unsigned i, unsigned volume)
{
struct audio_output *output;
struct mixer *mixer;
+ bool success;
+ GError *error = NULL;
assert(i < audio_output_count());
+ assert(volume <= 100);
output = audio_output_get(i);
if (!output->enabled)
@@ -85,31 +101,81 @@ output_mixer_set_volume(unsigned i, int volume, bool relative)
if (mixer == NULL)
return false;
- if (relative) {
- int prev = mixer_get_volume(mixer);
- if (prev < 0)
- return false;
-
- volume += prev;
+ success = mixer_set_volume(mixer, volume, &error);
+ if (!success && error != NULL) {
+ g_warning("Failed to set mixer for '%s': %s",
+ output->name, error->message);
+ g_error_free(error);
}
- if (volume > 100)
- volume = 100;
- else if (volume < 0)
- volume = 0;
-
- return mixer_set_volume(mixer, volume);
+ return success;
}
bool
-mixer_all_set_volume(int volume, bool relative)
+mixer_all_set_volume(unsigned volume)
{
bool success = false;
unsigned count = audio_output_count();
+ assert(volume <= 100);
+
for (unsigned i = 0; i < count; i++)
- success = output_mixer_set_volume(i, volume, relative)
+ success = output_mixer_set_volume(i, volume)
|| success;
return success;
}
+
+static int
+output_mixer_get_software_volume(unsigned i)
+{
+ struct audio_output *output;
+ struct mixer *mixer;
+
+ assert(i < audio_output_count());
+
+ output = audio_output_get(i);
+ if (!output->enabled)
+ return -1;
+
+ mixer = output->mixer;
+ if (mixer == NULL || mixer->plugin != &software_mixer_plugin)
+ return -1;
+
+ return mixer_get_volume(mixer, NULL);
+}
+
+int
+mixer_all_get_software_volume(void)
+{
+ unsigned count = audio_output_count(), ok = 0;
+ int volume, total = 0;
+
+ for (unsigned i = 0; i < count; i++) {
+ volume = output_mixer_get_software_volume(i);
+ if (volume >= 0) {
+ total += volume;
+ ++ok;
+ }
+ }
+
+ if (ok == 0)
+ return -1;
+
+ return total / ok;
+}
+
+void
+mixer_all_set_software_volume(unsigned volume)
+{
+ unsigned count = audio_output_count();
+
+ assert(volume <= PCM_VOLUME_1);
+
+ for (unsigned i = 0; i < count; i++) {
+ struct audio_output *output = audio_output_get(i);
+ if (output->mixer != NULL &&
+ output->mixer->plugin == &software_mixer_plugin)
+ mixer_set_volume(output->mixer, volume, NULL);
+ }
+}
diff --git a/src/mixer_all.h b/src/mixer_all.h
index 66c4988de..ebe8fed68 100644
--- a/src/mixer_all.h
+++ b/src/mixer_all.h
@@ -37,11 +37,26 @@ mixer_all_get_volume(void);
/**
* Sets the volume on all available mixers.
*
- * @param volume the volume (range 0..100 or -100..100 if #relative)
- * @param relative if true, then the #volume is added to the current value
+ * @param volume the volume (range 0..100)
* @return true on success, false on failure
*/
bool
-mixer_all_set_volume(int volume, bool relative);
+mixer_all_set_volume(unsigned volume);
+
+/**
+ * Similar to mixer_all_get_volume(), but gets the volume only for
+ * software mixers. See #software_mixer_plugin. This function fails
+ * if no software mixer is configured.
+ */
+int
+mixer_all_get_software_volume(void);
+
+/**
+ * Similar to mixer_all_set_volume(), but sets the volume only for
+ * software mixers. See #software_mixer_plugin. This function cannot
+ * fail, because the underlying software mixers cannot fail either.
+ */
+void
+mixer_all_set_software_volume(unsigned volume);
#endif
diff --git a/src/mixer_api.c b/src/mixer_api.c
index cff23a397..67b7037ef 100644
--- a/src/mixer_api.c
+++ b/src/mixer_api.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "mixer_api.h"
#undef G_LOG_DOMAIN
diff --git a/src/mixer_control.c b/src/mixer_control.c
index e19b82d65..af01600a1 100644
--- a/src/mixer_control.c
+++ b/src/mixer_control.c
@@ -17,38 +17,26 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "mixer_control.h"
#include "mixer_api.h"
-#include <glib.h>
-
#include <assert.h>
#include <stddef.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "mixer"
-static bool mixers_enabled = true;
-
-void
-mixer_disable_all(void)
-{
- g_debug("mixer api is disabled");
- mixers_enabled = false;
-}
-
struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param)
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const struct config_param *param,
+ GError **error_r)
{
struct mixer *mixer;
- //mixers are disabled (by using software volume)
- if (!mixers_enabled) {
- return NULL;
- }
assert(plugin != NULL);
- mixer = plugin->init(param);
+ mixer = plugin->init(ao, param, error_r);
assert(mixer == NULL || mixer->plugin == plugin);
@@ -72,7 +60,7 @@ mixer_free(struct mixer *mixer)
}
bool
-mixer_open(struct mixer *mixer)
+mixer_open(struct mixer *mixer, GError **error_r)
{
bool success;
@@ -83,8 +71,10 @@ mixer_open(struct mixer *mixer)
if (mixer->open)
success = true;
+ else if (mixer->plugin->open == NULL)
+ success = mixer->open = true;
else
- success = mixer->open = mixer->plugin->open(mixer);
+ success = mixer->open = mixer->plugin->open(mixer, error_r);
mixer->failed = !success;
@@ -100,7 +90,9 @@ mixer_close_internal(struct mixer *mixer)
assert(mixer->plugin != NULL);
assert(mixer->open);
- mixer->plugin->close(mixer);
+ if (mixer->plugin->close != NULL)
+ mixer->plugin->close(mixer);
+
mixer->open = false;
}
@@ -140,21 +132,26 @@ mixer_failed(struct mixer *mixer)
}
int
-mixer_get_volume(struct mixer *mixer)
+mixer_get_volume(struct mixer *mixer, GError **error_r)
{
int volume;
assert(mixer != NULL);
- if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
return -1;
g_mutex_lock(mixer->mutex);
if (mixer->open) {
- volume = mixer->plugin->get_volume(mixer);
- if (volume < 0)
+ GError *error = NULL;
+
+ volume = mixer->plugin->get_volume(mixer, &error);
+ if (volume < 0 && error != NULL) {
+ g_propagate_error(error_r, error);
mixer_failed(mixer);
+ }
} else
volume = -1;
@@ -164,22 +161,21 @@ mixer_get_volume(struct mixer *mixer)
}
bool
-mixer_set_volume(struct mixer *mixer, unsigned volume)
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r)
{
bool success;
assert(mixer != NULL);
assert(volume <= 100);
- if (mixer->plugin->global && !mixer->failed && !mixer_open(mixer))
+ if (mixer->plugin->global && !mixer->failed &&
+ !mixer_open(mixer, error_r))
return false;
g_mutex_lock(mixer->mutex);
if (mixer->open) {
- success = mixer->plugin->set_volume(mixer, volume);
- if (!success)
- mixer_failed(mixer);
+ success = mixer->plugin->set_volume(mixer, volume, error_r);
} else
success = false;
diff --git a/src/mixer_control.h b/src/mixer_control.h
index 0f73e8f75..a550e0864 100644
--- a/src/mixer_control.h
+++ b/src/mixer_control.h
@@ -25,23 +25,24 @@
#ifndef MPD_MIXER_CONTROL_H
#define MPD_MIXER_CONTROL_H
+#include <glib.h>
+
#include <stdbool.h>
struct mixer;
struct mixer_plugin;
struct config_param;
-void
-mixer_disable_all(void);
-
struct mixer *
-mixer_new(const struct mixer_plugin *plugin, const struct config_param *param);
+mixer_new(const struct mixer_plugin *plugin, void *ao,
+ const struct config_param *param,
+ GError **error_r);
void
mixer_free(struct mixer *mixer);
bool
-mixer_open(struct mixer *mixer);
+mixer_open(struct mixer *mixer, GError **error_r);
void
mixer_close(struct mixer *mixer);
@@ -54,9 +55,9 @@ void
mixer_auto_close(struct mixer *mixer);
int
-mixer_get_volume(struct mixer *mixer);
+mixer_get_volume(struct mixer *mixer, GError **error_r);
bool
-mixer_set_volume(struct mixer *mixer, unsigned volume);
+mixer_set_volume(struct mixer *mixer, unsigned volume, GError **error_r);
#endif
diff --git a/src/mixer_list.h b/src/mixer_list.h
index 7db4a00d8..2d9c773f6 100644
--- a/src/mixer_list.h
+++ b/src/mixer_list.h
@@ -25,8 +25,9 @@
#ifndef MPD_MIXER_LIST_H
#define MPD_MIXER_LIST_H
-extern const struct mixer_plugin alsa_mixer;
-extern const struct mixer_plugin oss_mixer;
-extern const struct mixer_plugin pulse_mixer;
+extern const struct mixer_plugin software_mixer_plugin;
+extern const struct mixer_plugin alsa_mixer_plugin;
+extern const struct mixer_plugin oss_mixer_plugin;
+extern const struct mixer_plugin pulse_mixer_plugin;
#endif
diff --git a/src/mixer_plugin.h b/src/mixer_plugin.h
index 2b9b440e5..b6e67e8ff 100644
--- a/src/mixer_plugin.h
+++ b/src/mixer_plugin.h
@@ -27,6 +27,8 @@
#ifndef MPD_MIXER_PLUGIN_H
#define MPD_MIXER_PLUGIN_H
+#include <glib.h>
+
#include <stdbool.h>
struct config_param;
@@ -35,8 +37,16 @@ struct mixer;
struct mixer_plugin {
/**
* Alocates and configures a mixer device.
+ *
+ * @param ao the pointer returned by audio_output_plugin.init
+ * @param param the configuration section, or NULL if there is
+ * no configuration
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return a mixer object, or NULL on error
*/
- struct mixer *(*init)(const struct config_param *param);
+ struct mixer *(*init)(void *ao, const struct config_param *param,
+ GError **error_r);
/**
* Finish and free mixer data
@@ -45,8 +55,12 @@ struct mixer_plugin {
/**
* Open mixer device
+ *
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
*/
- bool (*open)(struct mixer *data);
+ bool (*open)(struct mixer *data, GError **error_r);
/**
* Close mixer device
@@ -56,18 +70,23 @@ struct mixer_plugin {
/**
* Reads the current volume.
*
- * @return the current volume (0..100 including) or -1 on
- * error
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return the current volume (0..100 including) or -1 if
+ * unavailable or on error (error_r set, mixer will be closed)
*/
- int (*get_volume)(struct mixer *mixer);
+ int (*get_volume)(struct mixer *mixer, GError **error_r);
/**
* Sets the volume.
*
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
* @param volume the new volume (0..100 including)
- * @return true on success
+ * @return true on success, false on error
*/
- bool (*set_volume)(struct mixer *mixer, unsigned volume);
+ bool (*set_volume)(struct mixer *mixer, unsigned volume,
+ GError **error_r);
/**
* If true, then the mixer is automatically opened, even if
diff --git a/src/mixer_type.c b/src/mixer_type.c
new file mode 100644
index 000000000..804ecafef
--- /dev/null
+++ b/src/mixer_type.c
@@ -0,0 +1,39 @@
+/*
+ * 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 "mixer_type.h"
+
+#include <assert.h>
+#include <string.h>
+
+enum mixer_type
+mixer_type_parse(const char *input)
+{
+ assert(input != NULL);
+
+ if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0)
+ return MIXER_TYPE_NONE;
+ else if (strcmp(input, "hardware") == 0)
+ return MIXER_TYPE_HARDWARE;
+ else if (strcmp(input, "software") == 0)
+ return MIXER_TYPE_SOFTWARE;
+ else
+ return MIXER_TYPE_UNKNOWN;
+}
diff --git a/src/mixer_type.h b/src/mixer_type.h
new file mode 100644
index 000000000..ec3cbf9cc
--- /dev/null
+++ b/src/mixer_type.h
@@ -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.
+ */
+
+#ifndef MPD_MIXER_TYPE_H
+#define MPD_MIXER_TYPE_H
+
+enum mixer_type {
+ /** parser error */
+ MIXER_TYPE_UNKNOWN,
+
+ /** mixer disabled */
+ MIXER_TYPE_NONE,
+
+ /** software mixer with pcm_volume() */
+ MIXER_TYPE_SOFTWARE,
+
+ /** hardware mixer (output's plugin) */
+ MIXER_TYPE_HARDWARE,
+};
+
+/**
+ * Parses a "mixer_type" setting from the configuration file.
+ *
+ * @param input the configured string value; must not be NULL
+ * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could
+ * not be parsed
+ */
+enum mixer_type
+mixer_type_parse(const char *input);
+
+#endif
diff --git a/src/normalize.c b/src/normalize.c
index 63c0d15cb..1c8173def 100644
--- a/src/normalize.c
+++ b/src/normalize.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "normalize.h"
-#include "compress.h"
+#include "AudioCompress/compress.h"
#include "conf.h"
#include "audio_format.h"
@@ -26,24 +27,28 @@
int normalizationEnabled;
+static struct Compressor *compressor;
+
void initNormalization(void)
{
normalizationEnabled = config_get_bool(CONF_VOLUME_NORMALIZATION,
DEFAULT_VOLUME_NORMALIZATION);
if (normalizationEnabled)
- CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS);
+ compressor = Compressor_new(0);
}
void finishNormalization(void)
{
- if (normalizationEnabled) CompressFree();
+ if (normalizationEnabled)
+ Compressor_delete(compressor);
}
-void normalizeData(char *buffer, int bufferSize,
+void normalizeData(void *buffer, int bufferSize,
const struct audio_format *format)
{
- if ((format->bits != 16) || (format->channels != 2)) return;
+ if (format->format != SAMPLE_FORMAT_S16 || format->channels != 2)
+ return;
- CompressDo(buffer, bufferSize);
+ Compressor_Process_int16(compressor, buffer, bufferSize / 2);
}
diff --git a/src/normalize.h b/src/normalize.h
index a8144951d..2834f07fd 100644
--- a/src/normalize.h
+++ b/src/normalize.h
@@ -28,7 +28,7 @@ void initNormalization(void);
void finishNormalization(void);
-void normalizeData(char *buffer, int bufferSize,
+void normalizeData(void *buffer, int bufferSize,
const struct audio_format *format);
#endif /* !NORMALIZE_H */
diff --git a/src/notify.c b/src/notify.c
index 9168867d6..8954a8e61 100644
--- a/src/notify.c
+++ b/src/notify.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "notify.h"
void notify_init(struct notify *notify)
diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c
index 818c83ca2..b7325de07 100644
--- a/src/output/alsa_plugin.c
+++ b/src/output/alsa_plugin.c
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include "mixer_list.h"
#include <glib.h>
@@ -69,6 +70,16 @@ struct alsa_data {
/** the size of one audio frame */
size_t frame_size;
+
+ /**
+ * The size of one period, in number of frames.
+ */
+ snd_pcm_uframes_t period_frames;
+
+ /**
+ * The number of frames written in the current period.
+ */
+ snd_pcm_uframes_t period_position;
};
/**
@@ -174,15 +185,37 @@ alsa_test_default_device(void)
static snd_pcm_format_t
get_bitformat(const struct audio_format *af)
{
- switch (af->bits) {
- case 8: return SND_PCM_FORMAT_S8;
- case 16: return SND_PCM_FORMAT_S16;
- case 24: return SND_PCM_FORMAT_S24;
- case 32: return SND_PCM_FORMAT_S32;
+ switch (af->format) {
+ case SAMPLE_FORMAT_S8:
+ return SND_PCM_FORMAT_S8;
+
+ case SAMPLE_FORMAT_S16:
+ return SND_PCM_FORMAT_S16;
+
+ case SAMPLE_FORMAT_S24_P32:
+ return SND_PCM_FORMAT_S24;
+
+ case SAMPLE_FORMAT_S32:
+ return SND_PCM_FORMAT_S32;
+
+ default:
+ return SND_PCM_FORMAT_UNKNOWN;
}
- return SND_PCM_FORMAT_UNKNOWN;
}
+static snd_pcm_format_t
+byteswap_bitformat(snd_pcm_format_t fmt)
+{
+ switch(fmt) {
+ case SND_PCM_FORMAT_S16_LE: return SND_PCM_FORMAT_S16_BE;
+ case SND_PCM_FORMAT_S24_LE: return SND_PCM_FORMAT_S24_BE;
+ case SND_PCM_FORMAT_S32_LE: return SND_PCM_FORMAT_S32_BE;
+ case SND_PCM_FORMAT_S16_BE: return SND_PCM_FORMAT_S16_LE;
+ case SND_PCM_FORMAT_S24_BE: return SND_PCM_FORMAT_S24_LE;
+ case SND_PCM_FORMAT_S32_BE: return SND_PCM_FORMAT_S32_LE;
+ default: return SND_PCM_FORMAT_UNKNOWN;
+ }
+}
/**
* Set up the snd_pcm_t object which was opened by the caller. Set up
* the configured settings and the audio format.
@@ -208,7 +241,6 @@ alsa_setup(struct alsa_data *ad, struct audio_format *audio_format,
configure_hw:
/* configure HW params */
snd_pcm_hw_params_alloca(&hwparams);
-
cmd = "snd_pcm_hw_params_any";
err = snd_pcm_hw_params_any(ad->pcm, hwparams);
if (err < 0)
@@ -236,30 +268,72 @@ configure_hw:
}
err = snd_pcm_hw_params_set_format(ad->pcm, hwparams, bitformat);
- if (err == -EINVAL && (audio_format->bits == 24 ||
- audio_format->bits == 16)) {
+ if (err == -EINVAL &&
+ byteswap_bitformat(bitformat) != SND_PCM_FORMAT_UNKNOWN) {
+ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
+ byteswap_bitformat(bitformat));
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting format %s to reverse-endian",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format));
+ audio_format->reverse_endian = 1;
+ }
+ }
+ if (err == -EINVAL && (audio_format->format == SAMPLE_FORMAT_S24_P32 ||
+ audio_format->format == SAMPLE_FORMAT_S16)) {
/* fall back to 32 bit, let pcm_convert.c do the conversion */
err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
SND_PCM_FORMAT_S32);
- if (err == 0)
- audio_format->bits = 32;
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting format %s to 32 bit\n",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format));
+ audio_format->format = SAMPLE_FORMAT_S32;
+ }
+ }
+ if (err == -EINVAL && (audio_format->format == SAMPLE_FORMAT_S24_P32 ||
+ audio_format->format == SAMPLE_FORMAT_S16)) {
+ /* fall back to 32 bit, let pcm_convert.c do the conversion */
+ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
+ byteswap_bitformat(SND_PCM_FORMAT_S32));
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting format %s to 32 bit backward-endian\n",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format));
+ audio_format->format = SAMPLE_FORMAT_S32;
+ audio_format->reverse_endian = 1;
+ }
}
- if (err == -EINVAL && audio_format->bits != 16) {
+ if (err == -EINVAL && audio_format->format != SAMPLE_FORMAT_S16) {
/* fall back to 16 bit, let pcm_convert.c do the conversion */
err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
SND_PCM_FORMAT_S16);
if (err == 0) {
- g_debug("ALSA device \"%s\": converting %u bit to 16 bit\n",
- alsa_device(ad), audio_format->bits);
- audio_format->bits = 16;
+ g_debug("ALSA device \"%s\": converting format %s to 16 bit\n",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format));
+ audio_format->format = SAMPLE_FORMAT_S16;
+ }
+ }
+ if (err == -EINVAL && audio_format->format != SAMPLE_FORMAT_S16) {
+ /* fall back to 16 bit, let pcm_convert.c do the conversion */
+ err = snd_pcm_hw_params_set_format(ad->pcm, hwparams,
+ byteswap_bitformat(SND_PCM_FORMAT_S16));
+ if (err == 0) {
+ g_debug("ALSA device \"%s\": converting format %s to 16 bit backward-endian\n",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format));
+ audio_format->format = SAMPLE_FORMAT_S16;
+ audio_format->reverse_endian = 1;
}
}
if (err < 0) {
g_set_error(error, alsa_output_quark(), err,
- "ALSA device \"%s\" does not support %u bit audio: %s",
- alsa_device(ad), audio_format->bits,
+ "ALSA device \"%s\" does not support format %s: %s",
+ alsa_device(ad),
+ sample_format_to_string(audio_format->format),
snd_strerror(-err));
return false;
}
@@ -365,6 +439,9 @@ configure_hw:
g_debug("buffer_size=%u period_size=%u",
(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+ ad->period_frames = alsa_period_size;
+ ad->period_position = 0;
+
return true;
error:
@@ -387,7 +464,7 @@ alsa_open(void *data, struct audio_format *audio_format, GError **error)
/* sample format is not supported by this plugin -
fall back to 16 bit samples */
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
bitformat = SND_PCM_FORMAT_S16;
}
@@ -431,6 +508,7 @@ alsa_recover(struct alsa_data *ad, int err)
/* fall-through to snd_pcm_prepare: */
case SND_PCM_STATE_SETUP:
case SND_PCM_STATE_XRUN:
+ ad->period_position = 0;
err = snd_pcm_prepare(ad->pcm);
break;
case SND_PCM_STATE_DISCONNECTED:
@@ -448,11 +526,47 @@ alsa_recover(struct alsa_data *ad, int err)
}
static void
+alsa_drain(void *data)
+{
+ struct alsa_data *ad = data;
+
+ if (snd_pcm_state(ad->pcm) != SND_PCM_STATE_RUNNING)
+ return;
+
+ if (ad->period_position > 0) {
+ /* generate some silence to finish the partial
+ period */
+ snd_pcm_uframes_t nframes =
+ ad->period_frames - ad->period_position;
+ size_t nbytes = nframes * ad->frame_size;
+ void *buffer = g_malloc(nbytes);
+ snd_pcm_hw_params_t *params;
+ snd_pcm_format_t format;
+ unsigned channels;
+
+ snd_pcm_hw_params_alloca(&params);
+ snd_pcm_hw_params_current(ad->pcm, params);
+ snd_pcm_hw_params_get_format(params, &format);
+ snd_pcm_hw_params_get_channels(params, &channels);
+
+ snd_pcm_format_set_silence(format, buffer, nframes * channels);
+ ad->writei(ad->pcm, buffer, nframes);
+ g_free(buffer);
+ }
+
+ snd_pcm_drain(ad->pcm);
+
+ ad->period_position = 0;
+}
+
+static void
alsa_cancel(void *data)
{
struct alsa_data *ad = data;
- alsa_recover(ad, snd_pcm_drop(ad->pcm));
+ ad->period_position = 0;
+
+ snd_pcm_drop(ad->pcm);
}
static void
@@ -460,9 +574,6 @@ alsa_close(void *data)
{
struct alsa_data *ad = data;
- if (snd_pcm_state(ad->pcm) == SND_PCM_STATE_RUNNING)
- snd_pcm_drain(ad->pcm);
-
snd_pcm_close(ad->pcm);
}
@@ -475,8 +586,11 @@ alsa_play(void *data, const void *chunk, size_t size, GError **error)
while (true) {
snd_pcm_sframes_t ret = ad->writei(ad->pcm, chunk, size);
- if (ret > 0)
+ if (ret > 0) {
+ ad->period_position = (ad->period_position + ret)
+ % ad->period_frames;
return ret * ad->frame_size;
+ }
if (ret < 0 && ret != -EAGAIN && ret != -EINTR &&
alsa_recover(ad, ret) < 0) {
@@ -494,7 +608,9 @@ const struct audio_output_plugin alsaPlugin = {
.finish = alsa_finish,
.open = alsa_open,
.play = alsa_play,
+ .drain = alsa_drain,
.cancel = alsa_cancel,
.close = alsa_close,
- .mixer_plugin = &alsa_mixer,
+
+ .mixer_plugin = &alsa_mixer_plugin,
};
diff --git a/src/output/ao_plugin.c b/src/output/ao_plugin.c
index 12d2b7552..7afca0db2 100644
--- a/src/output/ao_plugin.c
+++ b/src/output/ao_plugin.c
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include <ao/ao.h>
#include <glib.h>
@@ -169,13 +170,24 @@ ao_output_open(void *data, struct audio_format *audio_format,
ao_sample_format format;
struct ao_data *ad = (struct ao_data *)data;
- /* support for 24 bit samples in libao is currently dubious,
- and until we have sorted that out, resample everything to
- 16 bit */
- if (audio_format->bits > 16)
- audio_format->bits = 16;
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ format.bits = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ format.bits = 16;
+ break;
+
+ default:
+ /* support for 24 bit samples in libao is currently
+ dubious, and until we have sorted that out,
+ convert everything to 16 bit */
+ audio_format->format = SAMPLE_FORMAT_S16;
+ format.bits = 16;
+ break;
+ }
- format.bits = audio_format->bits;
format.rate = audio_format->sample_rate;
format.byte_format = AO_FMT_NATIVE;
format.channels = audio_format->channels;
diff --git a/src/output/fifo_plugin.c b/src/output/fifo_output_plugin.c
index 76bbe8cfa..658c77340 100644
--- a/src/output/fifo_plugin.c
+++ b/src/output/fifo_output_plugin.c
@@ -17,9 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../utils.h"
-#include "../timer.h"
+#include "config.h"
+#include "output_api.h"
+#include "utils.h"
+#include "timer.h"
+#include "fd_util.h"
#include <glib.h>
@@ -152,7 +154,7 @@ fifo_open(struct fifo_data *fd, GError **error)
if (!fifo_check(fd, error))
return false;
- fd->input = open(fd->path, O_RDONLY|O_NONBLOCK);
+ fd->input = open_cloexec(fd->path, O_RDONLY|O_NONBLOCK, 0);
if (fd->input < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for reading: %s",
@@ -161,7 +163,7 @@ fifo_open(struct fifo_data *fd, GError **error)
return false;
}
- fd->output = open(fd->path, O_WRONLY|O_NONBLOCK);
+ fd->output = open_cloexec(fd->path, O_WRONLY|O_NONBLOCK, 0);
if (fd->output < 0) {
g_set_error(error, fifo_output_quark(), errno,
"Could not open FIFO \"%s\" for writing: %s",
diff --git a/src/output/httpd_client.c b/src/output/httpd_client.c
index 52a398e3b..83f08372e 100644
--- a/src/output/httpd_client.c
+++ b/src/output/httpd_client.c
@@ -17,11 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "httpd_client.h"
#include "httpd_internal.h"
#include "fifo_buffer.h"
#include "page.h"
#include "icy_server.h"
+#include "glib_compat.h"
#include <stdbool.h>
#include <assert.h>
@@ -482,11 +484,6 @@ httpd_client_queue_size(const struct httpd_client *client)
return size;
}
-/* g_queue_clear() was introduced in GLib 2.14 */
-#if !GLIB_CHECK_VERSION(2,14,0)
-#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
-#endif
-
void
httpd_client_cancel(struct httpd_client *client)
{
diff --git a/src/output/httpd_internal.h b/src/output/httpd_internal.h
index 2257e27a2..22155b7ba 100644
--- a/src/output/httpd_internal.h
+++ b/src/output/httpd_internal.h
@@ -30,11 +30,18 @@
#include <glib.h>
#include <sys/socket.h>
+#include <stdbool.h>
struct httpd_client;
struct httpd_output {
/**
+ * True if the audio output is open and accepts client
+ * connections.
+ */
+ bool open;
+
+ /**
* The configured encoder plugin.
*/
struct encoder *encoder;
@@ -97,6 +104,12 @@ struct httpd_output {
* function.
*/
char buffer[32768];
+
+ /**
+ * The maximum and current number of clients connected
+ * at the same time.
+ */
+ guint clients_max, clients_cnt;
};
/**
diff --git a/src/output/httpd_output_plugin.c b/src/output/httpd_output_plugin.c
index 9fdf46456..a54253351 100644
--- a/src/output/httpd_output_plugin.c
+++ b/src/output/httpd_output_plugin.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "httpd_internal.h"
#include "httpd_client.h"
#include "output_api.h"
@@ -25,6 +26,7 @@
#include "socket_util.h"
#include "page.h"
#include "icy_server.h"
+#include "fd_util.h"
#include <assert.h>
@@ -46,6 +48,52 @@ httpd_output_quark(void)
return g_quark_from_static_string("httpd_output");
}
+static gboolean
+httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
+ G_GNUC_UNUSED GIOCondition condition,
+ gpointer data);
+
+static bool
+httpd_output_bind(struct httpd_output *httpd, GError **error_r)
+{
+ GIOChannel *channel;
+
+ httpd->open = false;
+
+ /* create and set up listener socket */
+
+ httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
+ (struct sockaddr *)&httpd->address,
+ httpd->address_size,
+ 16, error_r);
+ if (httpd->fd < 0)
+ return false;
+
+ g_mutex_lock(httpd->mutex);
+
+ channel = g_io_channel_unix_new(httpd->fd);
+ httpd->source_id = g_io_add_watch(channel, G_IO_IN,
+ httpd_listen_in_event, httpd);
+ g_io_channel_unref(channel);
+
+ g_mutex_unlock(httpd->mutex);
+
+ return true;
+}
+
+static void
+httpd_output_unbind(struct httpd_output *httpd)
+{
+ assert(!httpd->open);
+
+ g_mutex_lock(httpd->mutex);
+
+ g_source_remove(httpd->source_id);
+ close(httpd->fd);
+
+ g_mutex_unlock(httpd->mutex);
+}
+
static void *
httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
const struct config_param *param,
@@ -69,12 +117,7 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
return NULL;
}
- if (strcmp(encoder_name, "vorbis") == 0)
- httpd->content_type = "application/x-ogg";
- else if (strcmp(encoder_name, "lame") == 0)
- httpd->content_type = "audio/mpeg";
- else
- httpd->content_type = "application/octet-stream";
+ httpd->clients_max = config_get_block_unsigned(param,"max_clients", 0);
/* initialize listen address */
@@ -94,6 +137,12 @@ httpd_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (httpd->encoder == NULL)
return NULL;
+ /* determine content type */
+ httpd->content_type = encoder_get_mime_type(httpd->encoder);
+ if (httpd->content_type == NULL) {
+ httpd->content_type = "application/octet-stream";
+ }
+
httpd->mutex = g_mutex_new();
return httpd;
@@ -124,6 +173,7 @@ httpd_client_add(struct httpd_output *httpd, int fd)
httpd->encoder->plugin->tag == NULL);
httpd->clients = g_list_prepend(httpd->clients, client);
+ httpd->clients_cnt++;
/* pass metadata to client */
if (httpd->metadata)
@@ -138,18 +188,26 @@ httpd_listen_in_event(G_GNUC_UNUSED GIOChannel *source,
struct httpd_output *httpd = data;
int fd;
struct sockaddr_storage sa;
- socklen_t sa_length = sizeof(sa);
+ size_t sa_length = sizeof(sa);
g_mutex_lock(httpd->mutex);
/* the listener socket has become readable - a client has
connected */
- fd = accept(httpd->fd, (struct sockaddr*)&sa, &sa_length);
- if (fd >= 0)
- httpd_client_add(httpd, fd);
- else if (fd < 0 && errno != EINTR)
+ fd = accept_cloexec_nonblock(httpd->fd, (struct sockaddr*)&sa,
+ &sa_length);
+ if (fd >= 0) {
+ /* can we allow additional client */
+ if (httpd->open &&
+ (httpd->clients_max == 0 ||
+ httpd->clients_cnt < httpd->clients_max))
+ httpd_client_add(httpd, fd);
+ else
+ close(fd);
+ } else if (fd < 0 && errno != EINTR) {
g_warning("accept() failed: %s", g_strerror(errno));
+ }
g_mutex_unlock(httpd->mutex);
@@ -199,31 +257,30 @@ httpd_output_encoder_open(struct httpd_output *httpd,
}
static bool
+httpd_output_enable(void *data, GError **error_r)
+{
+ struct httpd_output *httpd = data;
+
+ return httpd_output_bind(httpd, error_r);
+}
+
+static void
+httpd_output_disable(void *data)
+{
+ struct httpd_output *httpd = data;
+
+ httpd_output_unbind(httpd);
+}
+
+static bool
httpd_output_open(void *data, struct audio_format *audio_format,
GError **error)
{
struct httpd_output *httpd = data;
bool success;
- GIOChannel *channel;
g_mutex_lock(httpd->mutex);
- /* create and set up listener socket */
-
- httpd->fd = socket_bind_listen(PF_INET, SOCK_STREAM, 0,
- (struct sockaddr *)&httpd->address,
- httpd->address_size,
- 16, error);
- if (httpd->fd < 0) {
- g_mutex_unlock(httpd->mutex);
- return false;
- }
-
- channel = g_io_channel_unix_new(httpd->fd);
- httpd->source_id = g_io_add_watch(channel, G_IO_IN,
- httpd_listen_in_event, httpd);
- g_io_channel_unref(channel);
-
/* open the encoder */
success = httpd_output_encoder_open(httpd, audio_format, error);
@@ -237,8 +294,11 @@ httpd_output_open(void *data, struct audio_format *audio_format,
/* initialize other attributes */
httpd->clients = NULL;
+ httpd->clients_cnt = 0;
httpd->timer = timer_new(audio_format);
+ httpd->open = true;
+
g_mutex_unlock(httpd->mutex);
return true;
}
@@ -257,6 +317,8 @@ static void httpd_output_close(void *data)
g_mutex_lock(httpd->mutex);
+ httpd->open = false;
+
timer_free(httpd->timer);
g_list_foreach(httpd->clients, httpd_client_delete, NULL);
@@ -267,9 +329,6 @@ static void httpd_output_close(void *data)
encoder_close(httpd->encoder);
- g_source_remove(httpd->source_id);
- close(httpd->fd);
-
g_mutex_unlock(httpd->mutex);
}
@@ -281,6 +340,7 @@ httpd_output_remove_client(struct httpd_output *httpd,
assert(client != NULL);
httpd->clients = g_list_remove(httpd->clients, client);
+ httpd->clients_cnt--;
}
void
@@ -433,9 +493,8 @@ httpd_output_tag(void *data, const struct tag *tag)
page_unref (httpd->metadata);
httpd->metadata =
- icy_server_metadata_page(tag, TAG_ITEM_ALBUM,
- TAG_ITEM_ARTIST,
- TAG_ITEM_TITLE,
+ icy_server_metadata_page(tag, TAG_ALBUM,
+ TAG_ARTIST, TAG_TITLE,
TAG_NUM_OF_ITEM_TYPES);
if (httpd->metadata != NULL) {
g_mutex_lock(httpd->mutex);
@@ -468,6 +527,8 @@ const struct audio_output_plugin httpd_output_plugin = {
.name = "httpd",
.init = httpd_output_init,
.finish = httpd_output_finish,
+ .enable = httpd_output_enable,
+ .disable = httpd_output_disable,
.open = httpd_output_open,
.close = httpd_output_close,
.send_tag = httpd_output_tag,
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
new file mode 100644
index 000000000..f50bc37d0
--- /dev/null
+++ b/src/output/jack_output_plugin.c
@@ -0,0 +1,698 @@
+/*
+ * 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 "output_api.h"
+
+#include <assert.h>
+
+#include <glib.h>
+#include <jack/jack.h>
+#include <jack/types.h>
+#include <jack/ringbuffer.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "jack"
+
+enum {
+ MAX_PORTS = 16,
+};
+
+static const size_t sample_size = sizeof(jack_default_audio_sample_t);
+
+struct jack_data {
+ /**
+ * libjack options passed to jack_client_open().
+ */
+ jack_options_t options;
+
+ const char *name;
+
+ const char *server_name;
+
+ /* configuration */
+
+ char *source_ports[MAX_PORTS];
+ unsigned num_source_ports;
+
+ char *destination_ports[MAX_PORTS];
+ unsigned num_destination_ports;
+
+ size_t ringbuffer_size;
+
+ /* the current audio format */
+ struct audio_format audio_format;
+
+ /* jack library stuff */
+ jack_port_t *ports[MAX_PORTS];
+ jack_client_t *client;
+ jack_ringbuffer_t *ringbuffer[MAX_PORTS];
+
+ bool shutdown;
+
+ /**
+ * While this flag is set, the "process" callback generates
+ * silence.
+ */
+ bool pause;
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+jack_output_quark(void)
+{
+ return g_quark_from_static_string("jack_output");
+}
+
+static int
+mpd_jack_process(jack_nframes_t nframes, void *arg)
+{
+ struct jack_data *jd = (struct jack_data *) arg;
+ jack_default_audio_sample_t *out;
+ size_t available;
+
+ if (nframes <= 0)
+ return 0;
+
+ if (jd->pause) {
+ /* generate silence while MPD is paused */
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+ }
+
+ for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
+ assert(available % sample_size == 0);
+ available /= sample_size;
+ if (available > nframes)
+ available = nframes;
+
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+ jack_ringbuffer_read(jd->ringbuffer[i],
+ (char *)out, available * sample_size);
+
+ while (available < nframes)
+ /* ringbuffer underrun, fill with silence */
+ out[available++] = 0.0;
+ }
+
+ /* generate silence for the unused source ports */
+
+ for (unsigned i = jd->audio_format.channels;
+ i < jd->num_source_ports; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
+ return 0;
+}
+
+static void
+mpd_jack_shutdown(void *arg)
+{
+ struct jack_data *jd = (struct jack_data *) arg;
+ jd->shutdown = true;
+}
+
+static void
+set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
+{
+ audio_format->sample_rate = jack_get_sample_rate(jd->client);
+
+ if (jd->num_source_ports == 1)
+ audio_format->channels = 1;
+ else if (audio_format->channels > jd->num_source_ports)
+ audio_format->channels = 2;
+
+ if (audio_format->format != SAMPLE_FORMAT_S16 &&
+ audio_format->format != SAMPLE_FORMAT_S24_P32)
+ audio_format->format = SAMPLE_FORMAT_S24_P32;
+}
+
+static void
+mpd_jack_error(const char *msg)
+{
+ g_warning("%s", msg);
+}
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+static void
+mpd_jack_info(const char *msg)
+{
+ g_message("%s", msg);
+}
+#endif
+
+/**
+ * Disconnect the JACK client.
+ */
+static void
+mpd_jack_disconnect(struct jack_data *jd)
+{
+ assert(jd != NULL);
+ assert(jd->client != NULL);
+
+ jack_deactivate(jd->client);
+ jack_client_close(jd->client);
+ jd->client = NULL;
+}
+
+/**
+ * Connect the JACK client and performs some basic setup
+ * (e.g. register callbacks).
+ */
+static bool
+mpd_jack_connect(struct jack_data *jd, GError **error_r)
+{
+ jack_status_t status;
+
+ assert(jd != NULL);
+
+ jd->shutdown = false;
+
+ jd->client = jack_client_open(jd->name, jd->options, &status,
+ jd->server_name);
+ if (jd->client == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Failed to connect to JACK server, status=%d",
+ status);
+ return false;
+ }
+
+ jack_set_process_callback(jd->client, mpd_jack_process, jd);
+ jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ jd->ports[i] = jack_port_register(jd->client,
+ jd->source_ports[i],
+ JACK_DEFAULT_AUDIO_TYPE,
+ JackPortIsOutput, 0);
+ if (jd->ports[i] == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Cannot register output port \"%s\"",
+ jd->source_ports[i]);
+ mpd_jack_disconnect(jd);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool
+mpd_jack_test_default_device(void)
+{
+ return true;
+}
+
+static unsigned
+parse_port_list(int line, const char *source, char **dest, GError **error_r)
+{
+ char **list = g_strsplit(source, ",", 0);
+ unsigned n = 0;
+
+ for (n = 0; list[n] != NULL; ++n) {
+ if (n >= MAX_PORTS) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "too many port names in line %d",
+ line);
+ return 0;
+ }
+
+ dest[n] = list[n];
+ }
+
+ g_free(list);
+
+ if (n == 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "at least one port name expected in line %d",
+ line);
+ return 0;
+ }
+
+ return n;
+}
+
+static void *
+mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param, GError **error_r)
+{
+ struct jack_data *jd;
+ const char *value;
+
+ jd = g_new(struct jack_data, 1);
+ jd->options = JackNullOption;
+
+ jd->name = config_get_block_string(param, "client_name", NULL);
+ if (jd->name != NULL)
+ jd->options |= JackUseExactName;
+ else
+ /* if there's a no configured client name, we don't
+ care about the JackUseExactName option */
+ jd->name = "Music Player Daemon";
+
+ jd->server_name = config_get_block_string(param, "server_name", NULL);
+ if (jd->server_name != NULL)
+ jd->options |= JackServerName;
+
+ if (!config_get_block_bool(param, "autostart", false))
+ jd->options |= JackNoStartServer;
+
+ /* configure the source ports */
+
+ value = config_get_block_string(param, "source_ports", "left,right");
+ jd->num_source_ports = parse_port_list(param->line, value,
+ jd->source_ports, error_r);
+ if (jd->num_source_ports == 0)
+ return NULL;
+
+ /* configure the destination ports */
+
+ value = config_get_block_string(param, "destination_ports", NULL);
+ if (value == NULL) {
+ /* compatibility with MPD < 0.16 */
+ value = config_get_block_string(param, "ports", NULL);
+ if (value != NULL)
+ g_warning("deprecated option 'ports' in line %d",
+ param->line);
+ }
+
+ if (value != NULL) {
+ jd->num_destination_ports =
+ parse_port_list(param->line, value,
+ jd->destination_ports, error_r);
+ if (jd->num_destination_ports == 0)
+ return NULL;
+ } else {
+ jd->num_destination_ports = 0;
+ }
+
+ if (jd->num_destination_ports > 0 &&
+ jd->num_destination_ports != jd->num_source_ports)
+ g_warning("number of source ports (%u) mismatches the "
+ "number of destination ports (%u) in line %d",
+ jd->num_source_ports, jd->num_destination_ports,
+ param->line);
+
+ jd->ringbuffer_size =
+ config_get_block_unsigned(param, "ringbuffer_size", 32768);
+
+ jack_set_error_function(mpd_jack_error);
+
+#ifdef HAVE_JACK_SET_INFO_FUNCTION
+ jack_set_info_function(mpd_jack_info);
+#endif
+
+ return jd;
+}
+
+static void
+mpd_jack_finish(void *data)
+{
+ struct jack_data *jd = data;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ g_free(jd->source_ports[i]);
+
+ for (unsigned i = 0; i < jd->num_destination_ports; ++i)
+ g_free(jd->destination_ports[i]);
+
+ g_free(jd);
+}
+
+static bool
+mpd_jack_enable(void *data, GError **error_r)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
+ jd->ringbuffer[i] = NULL;
+
+ return mpd_jack_connect(jd, error_r);
+}
+
+static void
+mpd_jack_disable(void *data)
+{
+ struct jack_data *jd = (struct jack_data *)data;
+
+ if (jd->client != NULL)
+ mpd_jack_disconnect(jd);
+
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] != NULL) {
+ jack_ringbuffer_free(jd->ringbuffer[i]);
+ jd->ringbuffer[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Stops the playback on the JACK connection.
+ */
+static void
+mpd_jack_stop(struct jack_data *jd)
+{
+ assert(jd != NULL);
+
+ if (jd->client == NULL)
+ return;
+
+ if (jd->shutdown)
+ /* the connection has failed; close it */
+ mpd_jack_disconnect(jd);
+ else
+ /* the connection is alive: just stop playback */
+ jack_deactivate(jd->client);
+}
+
+static bool
+mpd_jack_start(struct jack_data *jd, GError **error_r)
+{
+ const char *destination_ports[MAX_PORTS], **jports;
+ const char *duplicate_port = NULL;
+ unsigned num_destination_ports;
+
+ assert(jd->client != NULL);
+ assert(jd->audio_format.channels <= jd->num_source_ports);
+
+ /* allocate the ring buffers on the first open(); these
+ persist until MPD exits. It's too unsafe to delete them
+ because we can never know when mpd_jack_process() gets
+ called */
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ if (jd->ringbuffer[i] == NULL)
+ jd->ringbuffer[i] =
+ jack_ringbuffer_create(jd->ringbuffer_size);
+
+ /* clear the ring buffer to be sure that data from
+ previous playbacks are gone */
+ jack_ringbuffer_reset(jd->ringbuffer[i]);
+ }
+
+ if ( jack_activate(jd->client) ) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "cannot activate client");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ if (jd->num_destination_ports == 0) {
+ /* no output ports were configured - ask libjack for
+ defaults */
+ jports = jack_get_ports(jd->client, NULL, NULL,
+ JackPortIsPhysical | JackPortIsInput);
+ if (jports == NULL) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "no ports found");
+ mpd_jack_stop(jd);
+ return false;
+ }
+
+ assert(*jports != NULL);
+
+ for (num_destination_ports = 0;
+ num_destination_ports < MAX_PORTS &&
+ jports[num_destination_ports] != NULL;
+ ++num_destination_ports) {
+ g_debug("destination_port[%u] = '%s'\n",
+ num_destination_ports,
+ jports[num_destination_ports]);
+ destination_ports[num_destination_ports] =
+ jports[num_destination_ports];
+ }
+ } else {
+ /* use the configured output ports */
+
+ num_destination_ports = jd->num_destination_ports;
+ memcpy(destination_ports, jd->destination_ports,
+ num_destination_ports * sizeof(*destination_ports));
+
+ jports = NULL;
+ }
+
+ assert(num_destination_ports > 0);
+
+ if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
+ /* mix stereo signal on one speaker */
+
+ while (num_destination_ports < jd->audio_format.channels)
+ destination_ports[num_destination_ports++] =
+ destination_ports[0];
+ } else if (num_destination_ports > jd->audio_format.channels) {
+ if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+ /* mono input file: connect the one source
+ channel to the both destination channels */
+ duplicate_port = destination_ports[1];
+ num_destination_ports = 1;
+ } else
+ /* connect only as many ports as we need */
+ num_destination_ports = jd->audio_format.channels;
+ }
+
+ assert(num_destination_ports <= jd->num_source_ports);
+
+ for (unsigned i = 0; i < num_destination_ports; ++i) {
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
+ destination_ports[i]);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ destination_ports[i]);
+
+ if (jports != NULL)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (duplicate_port != NULL) {
+ /* mono input file: connect the one source channel to
+ the both destination channels */
+ int ret;
+
+ ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
+ duplicate_port);
+ if (ret != 0) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Not a valid JACK port: %s",
+ duplicate_port);
+
+ if (jports != NULL)
+ free(jports);
+
+ mpd_jack_stop(jd);
+ return false;
+ }
+ }
+
+ if (jports != NULL)
+ free(jports);
+
+ return true;
+}
+
+static bool
+mpd_jack_open(void *data, struct audio_format *audio_format, GError **error_r)
+{
+ struct jack_data *jd = data;
+
+ assert(jd != NULL);
+
+ jd->pause = false;
+
+ if (jd->client == NULL && !mpd_jack_connect(jd, error_r))
+ return false;
+
+ set_audioformat(jd, audio_format);
+ jd->audio_format = *audio_format;
+
+ if (!mpd_jack_start(jd, error_r))
+ return false;
+
+ return true;
+}
+
+static void
+mpd_jack_close(G_GNUC_UNUSED void *data)
+{
+ struct jack_data *jd = data;
+
+ mpd_jack_stop(jd);
+}
+
+static inline jack_default_audio_sample_t
+sample_16_to_jack(int16_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
+}
+
+static void
+mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_16_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static inline jack_default_audio_sample_t
+sample_24_to_jack(int32_t sample)
+{
+ return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
+}
+
+static void
+mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
+ unsigned num_samples)
+{
+ jack_default_audio_sample_t sample;
+ unsigned i;
+
+ while (num_samples-- > 0) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
+ sample = sample_24_to_jack(*src++);
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
+ sizeof(sample));
+ }
+ }
+}
+
+static void
+mpd_jack_write_samples(struct jack_data *jd, const void *src,
+ unsigned num_samples)
+{
+ switch (jd->audio_format.format) {
+ case SAMPLE_FORMAT_S16:
+ mpd_jack_write_samples_16(jd, (const int16_t*)src,
+ num_samples);
+ break;
+
+ case SAMPLE_FORMAT_S24_P32:
+ mpd_jack_write_samples_24(jd, (const int32_t*)src,
+ num_samples);
+ break;
+
+ default:
+ assert(false);
+ }
+}
+
+static size_t
+mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct jack_data *jd = data;
+ const size_t frame_size = audio_format_frame_size(&jd->audio_format);
+ size_t space = 0, space1;
+
+ jd->pause = false;
+
+ assert(size % frame_size == 0);
+ size /= frame_size;
+
+ while (true) {
+ if (jd->shutdown) {
+ g_set_error(error_r, jack_output_quark(), 0,
+ "Refusing to play, because "
+ "there is no client thread");
+ return 0;
+ }
+
+ space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+ if (space > space1)
+ /* send data symmetrically */
+ space = space1;
+ }
+
+ if (space >= frame_size)
+ break;
+
+ /* XXX do something more intelligent to
+ synchronize */
+ g_usleep(1000);
+ }
+
+ space /= sample_size;
+ if (space < size)
+ size = space;
+
+ mpd_jack_write_samples(jd, chunk, size);
+ return size * frame_size;
+}
+
+static bool
+mpd_jack_pause(void *data)
+{
+ struct jack_data *jd = data;
+
+ if (jd->shutdown)
+ return false;
+
+ jd->pause = true;
+
+ /* due to a MPD API limitation, we have to sleep a little bit
+ here, to avoid hogging the CPU */
+ g_usleep(50000);
+
+ return true;
+}
+
+const struct audio_output_plugin jack_output_plugin = {
+ .name = "jack",
+ .test_default_device = mpd_jack_test_default_device,
+ .init = mpd_jack_init,
+ .finish = mpd_jack_finish,
+ .enable = mpd_jack_enable,
+ .disable = mpd_jack_disable,
+ .open = mpd_jack_open,
+ .play = mpd_jack_play,
+ .pause = mpd_jack_pause,
+ .close = mpd_jack_close,
+};
diff --git a/src/output/jack_plugin.c b/src/output/jack_plugin.c
deleted file mode 100644
index 5dc1eca24..000000000
--- a/src/output/jack_plugin.c
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "../output_api.h"
-#include "config.h"
-
-#include <assert.h>
-
-#include <glib.h>
-#include <jack/jack.h>
-#include <jack/types.h>
-#include <jack/ringbuffer.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "jack"
-
-static const size_t sample_size = sizeof(jack_default_audio_sample_t);
-
-static const char *const port_names[2] = {
- "left", "right",
-};
-
-struct jack_data {
- const char *name;
-
- /* configuration */
- char *output_ports[2];
- int ringbuffer_size;
-
- /* the current audio format */
- struct audio_format audio_format;
-
- /* jack library stuff */
- jack_port_t *ports[2];
- jack_client_t *client;
- jack_ringbuffer_t *ringbuffer[2];
-
- bool shutdown;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-jack_output_quark(void)
-{
- return g_quark_from_static_string("jack_output");
-}
-
-static void
-mpd_jack_client_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- if (jd->client != NULL) {
- jack_deactivate(jd->client);
- jack_client_close(jd->client);
- jd->client = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- if (jd->ringbuffer[i] != NULL) {
- jack_ringbuffer_free(jd->ringbuffer[i]);
- jd->ringbuffer[i] = NULL;
- }
- }
-}
-
-static void
-mpd_jack_free(struct jack_data *jd)
-{
- assert(jd != NULL);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->output_ports); ++i)
- g_free(jd->output_ports[i]);
-
- g_free(jd);
-}
-
-static void
-mpd_jack_finish(void *data)
-{
- struct jack_data *jd = data;
- mpd_jack_free(jd);
-}
-
-static int
-mpd_jack_process(jack_nframes_t nframes, void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jack_default_audio_sample_t *out;
- size_t available;
-
- if (nframes <= 0)
- return 0;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
- available = jack_ringbuffer_read_space(jd->ringbuffer[i]);
- assert(available % sample_size == 0);
- available /= sample_size;
- if (available > nframes)
- available = nframes;
-
- out = jack_port_get_buffer(jd->ports[i], nframes);
- jack_ringbuffer_read(jd->ringbuffer[i],
- (char *)out, available * sample_size);
-
- while (available < nframes)
- /* ringbuffer underrun, fill with silence */
- out[available++] = 0.0;
- }
-
- return 0;
-}
-
-static void
-mpd_jack_shutdown(void *arg)
-{
- struct jack_data *jd = (struct jack_data *) arg;
- jd->shutdown = true;
-}
-
-static void
-set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
-{
- audio_format->sample_rate = jack_get_sample_rate(jd->client);
- audio_format->channels = 2;
-
- if (audio_format->bits != 16 && audio_format->bits != 24)
- audio_format->bits = 24;
-}
-
-static void
-mpd_jack_error(const char *msg)
-{
- g_warning("%s", msg);
-}
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
-static void
-mpd_jack_info(const char *msg)
-{
- g_message("%s", msg);
-}
-#endif
-
-static void *
-mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, GError **error)
-{
- struct jack_data *jd;
- const char *value;
-
- jd = g_new(struct jack_data, 1);
- jd->name = config_get_block_string(param, "name", "mpd_jack");
-
- g_debug("mpd_jack_init (pid=%d)", getpid());
-
- value = config_get_block_string(param, "ports", NULL);
- if (value != NULL) {
- char **ports = g_strsplit(value, ",", 0);
-
- if (ports[0] == NULL || ports[1] == NULL || ports[2] != NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "two port names expected in line %d",
- param->line);
- return NULL;
- }
-
- jd->output_ports[0] = ports[0];
- jd->output_ports[1] = ports[1];
-
- g_free(ports);
- } else {
- jd->output_ports[0] = NULL;
- jd->output_ports[1] = NULL;
- }
-
- jd->ringbuffer_size =
- config_get_block_unsigned(param, "ringbuffer_size", 32768);
-
- jack_set_error_function(mpd_jack_error);
-
-#ifdef HAVE_JACK_SET_INFO_FUNCTION
- jack_set_info_function(mpd_jack_info);
-#endif
-
- return jd;
-}
-
-static bool
-mpd_jack_test_default_device(void)
-{
- return true;
-}
-
-static bool
-mpd_jack_connect(struct jack_data *jd, GError **error)
-{
- const char *output_ports[2], **jports;
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i)
- jd->ringbuffer[i] =
- jack_ringbuffer_create(jd->ringbuffer_size);
-
- jd->shutdown = false;
-
- if ((jd->client = jack_client_new(jd->name)) == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Failed to connect to JACK server");
- return false;
- }
-
- jack_set_process_callback(jd->client, mpd_jack_process, jd);
- jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- jd->ports[i] = jack_port_register(jd->client, port_names[i],
- JACK_DEFAULT_AUDIO_TYPE,
- JackPortIsOutput, 0);
- if (jd->ports[i] == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "Cannot register output port \"%s\"",
- port_names[i]);
- return false;
- }
- }
-
- if ( jack_activate(jd->client) ) {
- g_set_error(error, jack_output_quark(), 0,
- "cannot activate client");
- return false;
- }
-
- if (jd->output_ports[1] == NULL) {
- /* no output ports were configured - ask libjack for
- defaults */
- jports = jack_get_ports(jd->client, NULL, NULL,
- JackPortIsPhysical | JackPortIsInput);
- if (jports == NULL) {
- g_set_error(error, jack_output_quark(), 0,
- "no ports found");
- return false;
- }
-
- output_ports[0] = jports[0];
- output_ports[1] = jports[1] != NULL ? jports[1] : jports[0];
-
- g_debug("output_ports: %s %s", jports[0], jports[1]);
- } else {
- /* use the configured output ports */
-
- output_ports[0] = jd->output_ports[0];
- output_ports[1] = jd->output_ports[1];
-
- jports = NULL;
- }
-
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- int ret;
-
- ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
- output_ports[i]);
- if (ret != 0) {
- g_set_error(error, jack_output_quark(), 0,
- "Not a valid JACK port: %s",
- output_ports[i]);
-
- if (jports != NULL)
- free(jports);
-
- return false;
- }
- }
-
- if (jports != NULL)
- free(jports);
-
- return true;
-}
-
-static bool
-mpd_jack_open(void *data, struct audio_format *audio_format, GError **error)
-{
- struct jack_data *jd = data;
-
- assert(jd != NULL);
-
- if (!mpd_jack_connect(jd, error)) {
- mpd_jack_client_free(jd);
- return false;
- }
-
- set_audioformat(jd, audio_format);
- jd->audio_format = *audio_format;
-
- return true;
-}
-
-static void
-mpd_jack_close(G_GNUC_UNUSED void *data)
-{
- struct jack_data *jd = data;
-
- mpd_jack_client_free(jd);
-}
-
-static void
-mpd_jack_cancel (G_GNUC_UNUSED void *data)
-{
-}
-
-static inline jack_default_audio_sample_t
-sample_16_to_jack(int16_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (16 - 1));
-}
-
-static void
-mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
-
- while (num_samples-- > 0) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
- sizeof(sample));
- }
-}
-
-static inline jack_default_audio_sample_t
-sample_24_to_jack(int32_t sample)
-{
- return sample / (jack_default_audio_sample_t)(1 << (24 - 1));
-}
-
-static void
-mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
- unsigned num_samples)
-{
- jack_default_audio_sample_t sample;
-
- while (num_samples-- > 0) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
- sizeof(sample));
- }
-}
-
-static void
-mpd_jack_write_samples(struct jack_data *jd, const void *src,
- unsigned num_samples)
-{
- switch (jd->audio_format.bits) {
- case 16:
- mpd_jack_write_samples_16(jd, (const int16_t*)src,
- num_samples);
- break;
-
- case 24:
- mpd_jack_write_samples_24(jd, (const int32_t*)src,
- num_samples);
- break;
-
- default:
- assert(false);
- }
-}
-
-static size_t
-mpd_jack_play(void *data, const void *chunk, size_t size, GError **error)
-{
- struct jack_data *jd = data;
- const size_t frame_size = audio_format_frame_size(&jd->audio_format);
- size_t space = 0, space1;
-
- assert(size % frame_size == 0);
- size /= frame_size;
-
- while (true) {
- if (jd->shutdown) {
- g_set_error(error, jack_output_quark(), 0,
- "Refusing to play, because "
- "there is no client thread");
- return 0;
- }
-
- space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
-
- if (space >= frame_size)
- break;
-
- /* XXX do something more intelligent to
- synchronize */
- g_usleep(1000);
- }
-
- space /= sample_size;
- if (space < size)
- size = space;
-
- mpd_jack_write_samples(jd, chunk, size);
- return size * frame_size;
-}
-
-const struct audio_output_plugin jackPlugin = {
- .name = "jack",
- .test_default_device = mpd_jack_test_default_device,
- .init = mpd_jack_init,
- .finish = mpd_jack_finish,
- .open = mpd_jack_open,
- .play = mpd_jack_play,
- .cancel = mpd_jack_cancel,
- .close = mpd_jack_close,
-};
diff --git a/src/output/mvp_plugin.c b/src/output/mvp_plugin.c
index 96f9435a8..86f147e5a 100644
--- a/src/output/mvp_plugin.c
+++ b/src/output/mvp_plugin.c
@@ -22,7 +22,9 @@
* http://mvpmc.sourceforge.net/
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -115,7 +117,7 @@ mvp_output_test_default_device(void)
{
int fd;
- fd = open("/dev/adec_pcm", O_WRONLY);
+ fd = open_cloexec("/dev/adec_pcm", O_WRONLY, 0);
if (fd >= 0) {
close(fd);
@@ -170,19 +172,19 @@ mvp_set_pcm_params(struct mvp_data *md, struct audio_format *audio_format,
}
/* 0,1=24bit(24) , 2,3=16bit */
- switch (audio_format->bits) {
- case 16:
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S16:
mix[1] = 2;
break;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
mix[1] = 0;
break;
default:
- g_debug("unsupported sample format %u - falling back to stereo",
- audio_format->bits);
- audio_format->bits = 16;
+ g_debug("unsupported sample format %s - falling back to 16 bit",
+ sample_format_to_string(audio_format->format));
+ audio_format->format = SAMPLE_FORMAT_S16;
mix[1] = 2;
break;
}
@@ -230,7 +232,8 @@ mvp_output_open(void *data, struct audio_format *audio_format, GError **error)
int mix[5] = { 0, 2, 7, 1, 0 };
bool success;
- if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) {
+ md->fd = open_cloexec("/dev/adec_pcm", O_RDWR | O_NONBLOCK, 0);
+ if (md->fd < 0) {
g_set_error(error, mvp_output_quark(), errno,
"Error opening /dev/adec_pcm: %s",
strerror(errno));
diff --git a/src/output/null_plugin.c b/src/output/null_plugin.c
index e9731b019..495db656b 100644
--- a/src/output/null_plugin.c
+++ b/src/output/null_plugin.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
-#include "../timer.h"
+#include "config.h"
+#include "output_api.h"
+#include "timer.h"
#include <glib.h>
diff --git a/src/output/openal_plugin.c b/src/output/openal_plugin.c
new file mode 100644
index 000000000..0aded4d9a
--- /dev/null
+++ b/src/output/openal_plugin.c
@@ -0,0 +1,277 @@
+/*
+ * 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 "output_api.h"
+#include "timer.h"
+
+#include <glib.h>
+
+#ifndef HAVE_OSX
+#include <AL/al.h>
+#include <AL/alc.h>
+#else
+#include <OpenAL/al.h>
+#include <OpenAL/alc.h>
+#endif
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "openal"
+
+/* should be enough for buffer size = 2048 */
+#define NUM_BUFFERS 16
+
+struct openal_data {
+ const char *device_name;
+ ALCdevice *device;
+ ALCcontext *context;
+ Timer *timer;
+ ALuint buffers[NUM_BUFFERS];
+ int filled;
+ ALuint source;
+ ALenum format;
+ ALuint frequency;
+};
+
+static inline GQuark
+openal_output_quark(void)
+{
+ return g_quark_from_static_string("openal_output");
+}
+
+static ALenum
+openal_audio_format(struct audio_format *audio_format)
+{
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S16:
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO16;
+ break;
+
+ case SAMPLE_FORMAT_S8:
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO8;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO8;
+ break;
+
+ default:
+ /* fall back to 16 bit */
+ audio_format->format = SAMPLE_FORMAT_S16;
+ if (audio_format->channels == 2)
+ return AL_FORMAT_STEREO16;
+ if (audio_format->channels == 1)
+ return AL_FORMAT_MONO16;
+ break;
+ }
+
+ return 0;
+}
+
+static bool
+openal_setup_context(struct openal_data *od,
+ GError **error)
+{
+ od->device = alcOpenDevice(od->device_name);
+
+ if (od->device == NULL) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error opening OpenAL device \"%s\"\n",
+ od->device_name);
+ return false;
+ }
+
+ od->context = alcCreateContext(od->device, NULL);
+
+ if (od->context == NULL) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Error creating context for \"%s\"\n",
+ od->device_name);
+ alcCloseDevice(od->device);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+openal_unqueue_buffers(struct openal_data *od)
+{
+ ALint num;
+ ALuint buffer;
+
+ alGetSourcei(od->source, AL_BUFFERS_QUEUED, &num);
+
+ while (num--) {
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+}
+
+static void *
+openal_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ G_GNUC_UNUSED GError **error)
+{
+ const char *device_name = config_get_block_string(param, "device", NULL);
+ struct openal_data *od;
+
+ if (device_name == NULL) {
+ device_name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
+ }
+
+ od = g_new(struct openal_data, 1);
+ od->device_name = device_name;
+
+ return od;
+}
+
+static void
+openal_finish(void *data)
+{
+ struct openal_data *od = data;
+
+ g_free(od);
+}
+
+static bool
+openal_open(void *data, struct audio_format *audio_format,
+ GError **error)
+{
+ struct openal_data *od = data;
+
+ od->format = openal_audio_format(audio_format);
+
+ if (!od->format) {
+ struct audio_format_string s;
+ g_set_error(error, openal_output_quark(), 0,
+ "Unsupported audio format: %s",
+ audio_format_to_string(audio_format, &s));
+ return false;
+ }
+
+ if (!openal_setup_context(od, error)) {
+ return false;
+ }
+
+ alcMakeContextCurrent(od->context);
+ alGenBuffers(NUM_BUFFERS, od->buffers);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate buffers");
+ return false;
+ }
+
+ alGenSources(1, &od->source);
+
+ if (alGetError() != AL_NO_ERROR) {
+ g_set_error(error, openal_output_quark(), 0,
+ "Failed to generate source");
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ return false;
+ }
+
+ od->filled = 0;
+ od->timer = timer_new(audio_format);
+ od->frequency = audio_format->sample_rate;
+
+ return true;
+}
+
+static void
+openal_close(void *data)
+{
+ struct openal_data *od = data;
+
+ timer_free(od->timer);
+ alcMakeContextCurrent(od->context);
+ alDeleteSources(1, &od->source);
+ alDeleteBuffers(NUM_BUFFERS, od->buffers);
+ alcDestroyContext(od->context);
+ alcCloseDevice(od->device);
+}
+
+static size_t
+openal_play(void *data, const void *chunk, size_t size,
+ G_GNUC_UNUSED GError **error)
+{
+ struct openal_data *od = data;
+ ALuint buffer;
+ ALint num, state;
+
+ if (alcGetCurrentContext() != od->context) {
+ alcMakeContextCurrent(od->context);
+ }
+
+ alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
+
+ if (od->filled < NUM_BUFFERS) {
+ /* fill all buffers */
+ buffer = od->buffers[od->filled];
+ od->filled++;
+ } else {
+ /* wait for processed buffer */
+ while (num < 1) {
+ if (!od->timer->started) {
+ timer_start(od->timer);
+ } else {
+ timer_sync(od->timer);
+ }
+
+ timer_add(od->timer, size);
+
+ alGetSourcei(od->source, AL_BUFFERS_PROCESSED, &num);
+ }
+
+ alSourceUnqueueBuffers(od->source, 1, &buffer);
+ }
+
+ alBufferData(buffer, od->format, chunk, size, od->frequency);
+ alSourceQueueBuffers(od->source, 1, &buffer);
+ alGetSourcei(od->source, AL_SOURCE_STATE, &state);
+
+ if (state != AL_PLAYING) {
+ alSourcePlay(od->source);
+ }
+
+ return size;
+}
+
+static void
+openal_cancel(void *data)
+{
+ struct openal_data *od = data;
+
+ od->filled = 0;
+ alcMakeContextCurrent(od->context);
+ alSourceStop(od->source);
+ openal_unqueue_buffers(od);
+}
+
+const struct audio_output_plugin openal_output_plugin = {
+ .name = "openal",
+ .init = openal_init,
+ .finish = openal_finish,
+ .open = openal_open,
+ .close = openal_close,
+ .play = openal_play,
+ .cancel = openal_cancel,
+};
diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c
index a66bc0598..f16374e39 100644
--- a/src/output/oss_plugin.c
+++ b/src/output/oss_plugin.c
@@ -17,8 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include "mixer_list.h"
+#include "fd_util.h"
#include <glib.h>
@@ -343,7 +345,9 @@ oss_output_test_default_device(void)
int fd, i;
for (i = G_N_ELEMENTS(default_devices); --i >= 0; ) {
- if ((fd = open(default_devices[i], O_WRONLY)) >= 0) {
+ fd = open_cloexec(default_devices[i], O_WRONLY, 0);
+
+ if (fd >= 0) {
close(fd);
return true;
}
@@ -486,17 +490,18 @@ oss_setup(struct oss_data *od, GError **error)
}
od->audio_format.sample_rate = tmp;
- switch (od->audio_format.bits) {
- case 8:
+ switch (od->audio_format.format) {
+ case SAMPLE_FORMAT_S8:
tmp = AFMT_S8;
break;
- case 16:
+
+ case SAMPLE_FORMAT_S16:
tmp = AFMT_S16_MPD;
break;
default:
/* not supported by OSS - fall back to 16 bit */
- od->audio_format.bits = 16;
+ od->audio_format.format = SAMPLE_FORMAT_S16;
tmp = AFMT_S16_MPD;
break;
}
@@ -516,7 +521,8 @@ oss_open(struct oss_data *od, GError **error)
{
bool success;
- if ((od->fd = open(od->device, O_WRONLY)) < 0) {
+ od->fd = open_cloexec(od->device, O_WRONLY, 0);
+ if (od->fd < 0) {
g_set_error(error, oss_output_quark(), errno,
"Error opening OSS device \"%s\": %s",
od->device, strerror(errno));
@@ -601,5 +607,6 @@ const struct audio_output_plugin oss_output_plugin = {
.close = oss_output_close,
.play = oss_output_play,
.cancel = oss_output_cancel,
- .mixer_plugin = &oss_mixer,
+
+ .mixer_plugin = &oss_mixer_plugin,
};
diff --git a/src/output/osx_plugin.c b/src/output/osx_plugin.c
index 04173bf79..22b742ee5 100644
--- a/src/output/osx_plugin.c
+++ b/src/output/osx_plugin.c
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "../output_api.h"
+#include "config.h"
+#include "output_api.h"
#include <glib.h>
#include <AudioUnit/AudioUnit.h>
@@ -165,9 +166,6 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
OSStatus status;
ComponentResult result;
- if (audio_format->bits > 16)
- audio_format->bits = 16;
-
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
@@ -225,7 +223,21 @@ osx_output_open(void *data, struct audio_format *audio_format, GError **error)
stream_description.mFramesPerPacket = 1;
stream_description.mBytesPerFrame = stream_description.mBytesPerPacket;
stream_description.mChannelsPerFrame = audio_format->channels;
- stream_description.mBitsPerChannel = audio_format->bits;
+
+ switch (audio_format->format) {
+ case SAMPLE_FORMAT_S8:
+ stream_description.mBitsPerChannel = 8;
+ break;
+
+ case SAMPLE_FORMAT_S16:
+ stream_description.mBitsPerChannel = 16;
+ break;
+
+ default:
+ audio_format->format = SAMPLE_FORMAT_S16;
+ stream_description.mBitsPerChannel = 16;
+ break;
+ }
result = AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
diff --git a/src/output/pipe_output_plugin.c b/src/output/pipe_output_plugin.c
index 610ad9e8d..2a5841bae 100644
--- a/src/output/pipe_output_plugin.c
+++ b/src/output/pipe_output_plugin.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
#include <stdio.h>
diff --git a/src/output/pulse_output_plugin.c b/src/output/pulse_output_plugin.c
new file mode 100644
index 000000000..a64157920
--- /dev/null
+++ b/src/output/pulse_output_plugin.c
@@ -0,0 +1,824 @@
+/*
+ * 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 "pulse_output_plugin.h"
+#include "output_api.h"
+#include "mixer_list.h"
+#include "mixer/pulse_mixer_plugin.h"
+
+#include <glib.h>
+
+#include <pulse/thread-mainloop.h>
+#include <pulse/context.h>
+#include <pulse/stream.h>
+#include <pulse/introspect.h>
+#include <pulse/subscribe.h>
+#include <pulse/error.h>
+
+#include <assert.h>
+
+#define MPD_PULSE_NAME "Music Player Daemon"
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+pulse_output_quark(void)
+{
+ return g_quark_from_static_string("pulse_output");
+}
+
+void
+pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm)
+{
+ assert(po != NULL);
+ assert(po->mixer == NULL);
+ assert(pm != NULL);
+
+ po->mixer = pm;
+
+ if (po->mainloop == NULL)
+ return;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context != NULL &&
+ pa_context_get_state(po->context) == PA_CONTEXT_READY) {
+ pulse_mixer_on_connect(pm, po->context);
+
+ if (po->stream != NULL &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY)
+ pulse_mixer_on_change(pm, po->context, po->stream);
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+void
+pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm)
+{
+ assert(po != NULL);
+ assert(pm != NULL);
+ assert(po->mixer == pm);
+
+ po->mixer = NULL;
+}
+
+bool
+pulse_output_set_volume(struct pulse_output *po,
+ const struct pa_cvolume *volume, GError **error_r)
+{
+ pa_operation *o;
+
+ if (po->context == NULL || po->stream == NULL ||
+ pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0, "disconnected");
+ return false;
+ }
+
+ o = pa_context_set_sink_input_volume(po->context,
+ pa_stream_get_index(po->stream),
+ volume, NULL, NULL);
+ if (o == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to set PulseAudio volume: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ pa_operation_unref(o);
+ return true;
+}
+
+/**
+ * \brief waits for a pulseaudio operation to finish, frees it and
+ * unlocks the mainloop
+ * \param operation the operation to wait for
+ * \return true if operation has finished normally (DONE state),
+ * false otherwise
+ */
+static bool
+pulse_wait_for_operation(struct pa_threaded_mainloop *mainloop,
+ struct pa_operation *operation)
+{
+ pa_operation_state_t state;
+
+ assert(mainloop != NULL);
+ assert(operation != NULL);
+
+ state = pa_operation_get_state(operation);
+ while (state == PA_OPERATION_RUNNING) {
+ pa_threaded_mainloop_wait(mainloop);
+ state = pa_operation_get_state(operation);
+ }
+
+ pa_operation_unref(operation);
+
+ return state == PA_OPERATION_DONE;
+}
+
+/**
+ * Callback function for stream operation. It just sends a signal to
+ * the caller thread, to wake pulse_wait_for_operation() up.
+ */
+static void
+pulse_output_stream_success_cb(G_GNUC_UNUSED pa_stream *s,
+ G_GNUC_UNUSED int success, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static void
+pulse_output_context_state_cb(struct pa_context *context, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ switch (pa_context_get_state(context)) {
+ case PA_CONTEXT_READY:
+ if (po->mixer != NULL)
+ pulse_mixer_on_connect(po->mixer, context);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ if (po->mixer != NULL)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ /* the caller thread might be waiting for these
+ states */
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+}
+
+static void
+pulse_output_subscribe_cb(pa_context *context,
+ pa_subscription_event_type_t t,
+ uint32_t idx, void *userdata)
+{
+ struct pulse_output *po = userdata;
+ pa_subscription_event_type_t facility
+ = t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK;
+ pa_subscription_event_type_t type
+ = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
+
+ if (po->mixer != NULL &&
+ facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT &&
+ po->stream != NULL &&
+ pa_stream_get_state(po->stream) == PA_STREAM_READY &&
+ idx == pa_stream_get_index(po->stream) &&
+ (type == PA_SUBSCRIPTION_EVENT_NEW ||
+ type == PA_SUBSCRIPTION_EVENT_CHANGE))
+ pulse_mixer_on_change(po->mixer, context, po->stream);
+}
+
+/**
+ * Attempt to connect asynchronously to the PulseAudio server.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_connect(struct pulse_output *po, GError **error_r)
+{
+ int error;
+
+ error = pa_context_connect(po->context, po->server,
+ (pa_context_flags_t)0, NULL);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_connect() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Create, set up and connect a context.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_setup_context(struct pulse_output *po, GError **error_r)
+{
+ po->context = pa_context_new(pa_threaded_mainloop_get_api(po->mainloop),
+ MPD_PULSE_NAME);
+ if (po->context == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_context_new() has failed");
+ return false;
+ }
+
+ pa_context_set_state_callback(po->context,
+ pulse_output_context_state_cb, po);
+ pa_context_set_subscribe_callback(po->context,
+ pulse_output_subscribe_cb, po);
+
+ if (!pulse_output_connect(po, error_r)) {
+ pa_context_unref(po->context);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Frees and clears the context.
+ */
+static void
+pulse_output_delete_context(struct pulse_output *po)
+{
+ pa_context_disconnect(po->context);
+ pa_context_unref(po->context);
+ po->context = NULL;
+}
+
+static void *
+pulse_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param,
+ G_GNUC_UNUSED GError **error_r)
+{
+ struct pulse_output *po;
+
+ g_setenv("PULSE_PROP_media.role", "music", true);
+
+ po = g_new(struct pulse_output, 1);
+ po->name = config_get_block_string(param, "name", "mpd_pulse");
+ po->server = config_get_block_string(param, "server", NULL);
+ po->sink = config_get_block_string(param, "sink", NULL);
+
+ po->mixer = NULL;
+ po->mainloop = NULL;
+ po->context = NULL;
+ po->stream = NULL;
+
+ return po;
+}
+
+static void
+pulse_output_finish(void *data)
+{
+ struct pulse_output *po = data;
+
+ g_free(po);
+}
+
+static bool
+pulse_output_enable(void *data, GError **error_r)
+{
+ struct pulse_output *po = data;
+
+ assert(po->mainloop == NULL);
+ assert(po->context == NULL);
+
+ /* create the libpulse mainloop and start the thread */
+
+ po->mainloop = pa_threaded_mainloop_new();
+ if (po->mainloop == NULL) {
+ g_free(po);
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_new() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_threaded_mainloop_start(po->mainloop) < 0) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ g_free(po);
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_threaded_mainloop_start() has failed");
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ /* create the libpulse context and connect it */
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (!pulse_output_setup_context(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ pa_threaded_mainloop_stop(po->mainloop);
+ pa_threaded_mainloop_free(po->mainloop);
+ g_free(po);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static void
+pulse_output_disable(void *data)
+{
+ struct pulse_output *po = data;
+
+ pa_threaded_mainloop_stop(po->mainloop);
+ if (po->context != NULL)
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_free(po->mainloop);
+ po->mainloop = NULL;
+}
+
+/**
+ * Check if the context is (already) connected, and waits if not. If
+ * the context has been disconnected, retry to connect.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_connection(struct pulse_output *po, GError **error_r)
+{
+ pa_context_state_t state;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (po->context == NULL && !pulse_output_setup_context(po, error_r))
+ return false;
+
+ while (true) {
+ state = pa_context_get_state(po->context);
+ switch (state) {
+ case PA_CONTEXT_READY:
+ /* nothing to do */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return true;
+
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* failure */
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pulse_output_delete_context(po);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ /* wait some more */
+ pa_threaded_mainloop_wait(po->mainloop);
+ break;
+ }
+ }
+}
+
+static void
+pulse_output_stream_state_cb(pa_stream *stream, void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ switch (pa_stream_get_state(stream)) {
+ case PA_STREAM_READY:
+ if (po->mixer != NULL)
+ pulse_mixer_on_change(po->mixer, po->context, stream);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ if (po->mixer != NULL)
+ pulse_mixer_on_disconnect(po->mixer);
+
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+ break;
+
+ case PA_STREAM_UNCONNECTED:
+ case PA_STREAM_CREATING:
+ break;
+ }
+}
+
+static void
+pulse_output_stream_write_cb(G_GNUC_UNUSED pa_stream *stream, size_t nbytes,
+ void *userdata)
+{
+ struct pulse_output *po = userdata;
+
+ po->writable = nbytes;
+ pa_threaded_mainloop_signal(po->mainloop, 0);
+}
+
+static bool
+pulse_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct pulse_output *po = data;
+ pa_sample_spec ss;
+ int error;
+
+ if (po->context != NULL) {
+ switch (pa_context_get_state(po->context)) {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ /* the connection was closed meanwhile; delete
+ it, and pulse_output_wait_connection() will
+ reopen it */
+ pulse_output_delete_context(po);
+ break;
+
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+ }
+
+ if (!pulse_output_wait_connection(po, error_r))
+ return false;
+
+ /* MPD doesn't support the other pulseaudio sample formats, so
+ we just force MPD to send us everything as 16 bit */
+ audio_format->format = SAMPLE_FORMAT_S16;
+
+ ss.format = PA_SAMPLE_S16NE;
+ ss.rate = audio_format->sample_rate;
+ ss.channels = audio_format->channels;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* create a stream .. */
+
+ po->stream = pa_stream_new(po->context, po->name, &ss, NULL);
+ if (po->stream == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_new() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_stream_set_state_callback(po->stream,
+ pulse_output_stream_state_cb, po);
+ pa_stream_set_write_callback(po->stream,
+ pulse_output_stream_write_cb, po);
+
+ /* .. and connect it (asynchronously) */
+
+ error = pa_stream_connect_playback(po->stream, po->sink,
+ NULL, 0, NULL, NULL);
+ if (error < 0) {
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_connect_playback() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = false;
+#endif
+
+ return true;
+}
+
+static void
+pulse_output_close(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) == PA_STREAM_READY) {
+ o = pa_stream_drain(po->stream,
+ pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_warning("pa_stream_drain() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ } else
+ pulse_wait_for_operation(po->mainloop, o);
+ }
+
+ pa_stream_disconnect(po->stream);
+ pa_stream_unref(po->stream);
+ po->stream = NULL;
+
+ if (po->context != NULL &&
+ pa_context_get_state(po->context) != PA_CONTEXT_READY)
+ pulse_output_delete_context(po);
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+/**
+ * Check if the stream is (already) connected, and waits for a signal
+ * if not. The mainloop must be locked before calling this function.
+ *
+ * @return the current stream state
+ */
+static pa_stream_state_t
+pulse_output_check_stream(struct pulse_output *po)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ break;
+
+ case PA_STREAM_CREATING:
+ pa_threaded_mainloop_wait(po->mainloop);
+ state = pa_stream_get_state(po->stream);
+ break;
+ }
+
+ return state;
+}
+
+/**
+ * Check if the stream is (already) connected, and waits if not. The
+ * mainloop must be locked before calling this function.
+ *
+ * @return true on success, false on error
+ */
+static bool
+pulse_output_wait_stream(struct pulse_output *po, GError **error_r)
+{
+ pa_stream_state_t state = pa_stream_get_state(po->stream);
+
+ switch (state) {
+ case PA_STREAM_READY:
+ return true;
+
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ case PA_STREAM_UNCONNECTED:
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "disconnected");
+ return false;
+
+ case PA_STREAM_CREATING:
+ break;
+ }
+
+ do {
+ state = pulse_output_check_stream(po);
+ } while (state == PA_STREAM_CREATING);
+
+ if (state != PA_STREAM_READY) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "failed to connect the stream: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Determines whether the stream is paused. On libpulse older than
+ * 0.9.11, it uses a custom pause flag.
+ */
+static bool
+pulse_output_stream_is_paused(struct pulse_output *po)
+{
+ assert(po->stream != NULL);
+
+#if !defined(PA_CHECK_VERSION) || !PA_CHECK_VERSION(0,9,11)
+ return po->pause;
+#else
+ return pa_stream_is_corked(po->stream);
+#endif
+}
+
+/**
+ * Sets cork mode on the stream.
+ */
+static bool
+pulse_output_stream_pause(struct pulse_output *po, bool pause,
+ GError **error_r)
+{
+ pa_operation *o;
+
+ assert(po->stream != NULL);
+
+ o = pa_stream_cork(po->stream, pause,
+ pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+ if (!pulse_wait_for_operation(po->mainloop, o)) {
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "pa_stream_cork() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ return false;
+ }
+
+#if !PA_CHECK_VERSION(0,9,11)
+ po->pause = pause;
+#endif
+ return true;
+}
+
+static size_t
+pulse_output_play(void *data, const void *chunk, size_t size, GError **error_r)
+{
+ struct pulse_output *po = data;
+ int error;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already) connected */
+
+ if (!pulse_output_wait_stream(po, error_r)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return 0;
+ }
+
+ assert(po->context != NULL);
+
+ /* unpause if previously paused */
+
+ if (pulse_output_stream_is_paused(po) &&
+ !pulse_output_stream_pause(po, false, error_r))
+ return 0;
+
+ /* wait until the server allows us to write */
+
+ while (po->writable == 0) {
+ pa_threaded_mainloop_wait(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_set_error(error_r, pulse_output_quark(), 0,
+ "disconnected");
+ return false;
+ }
+ }
+
+ /* now write */
+
+ if (size > po->writable)
+ /* don't send more than possible */
+ size = po->writable;
+
+ po->writable -= size;
+
+ error = pa_stream_write(po->stream, chunk, size, NULL,
+ 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(po->mainloop);
+ if (error < 0) {
+ g_set_error(error_r, pulse_output_quark(), error,
+ "%s", pa_strerror(error));
+ return 0;
+ }
+
+ return size;
+}
+
+static void
+pulse_output_cancel(void *data)
+{
+ struct pulse_output *po = data;
+ pa_operation *o;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ if (pa_stream_get_state(po->stream) != PA_STREAM_READY) {
+ /* no need to flush when the stream isn't connected
+ yet */
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ assert(po->context != NULL);
+
+ o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
+ if (o == NULL) {
+ g_warning("pa_stream_flush() has failed: %s",
+ pa_strerror(pa_context_errno(po->context)));
+ pa_threaded_mainloop_unlock(po->mainloop);
+ return;
+ }
+
+ pulse_wait_for_operation(po->mainloop, o);
+ pa_threaded_mainloop_unlock(po->mainloop);
+}
+
+static bool
+pulse_output_pause(void *data)
+{
+ struct pulse_output *po = data;
+ GError *error = NULL;
+
+ assert(po->stream != NULL);
+
+ pa_threaded_mainloop_lock(po->mainloop);
+
+ /* check if the stream is (already/still) connected */
+
+ if (!pulse_output_wait_stream(po, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ assert(po->context != NULL);
+
+ /* cork the stream */
+
+ if (pulse_output_stream_is_paused(po)) {
+ /* already paused; due to a MPD API limitation, we
+ have to sleep a little bit here, to avoid hogging
+ the CPU */
+
+ g_usleep(50000);
+ } else if (!pulse_output_stream_pause(po, true, &error)) {
+ pa_threaded_mainloop_unlock(po->mainloop);
+ g_warning("%s", error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ pa_threaded_mainloop_unlock(po->mainloop);
+
+ return true;
+}
+
+static bool
+pulse_output_test_default_device(void)
+{
+ struct pulse_output *po;
+ bool success;
+
+ po = pulse_output_init(NULL, NULL, NULL);
+ if (po == NULL)
+ return false;
+
+ success = pulse_output_wait_connection(po, NULL);
+ pulse_output_finish(po);
+
+ return success;
+}
+
+const struct audio_output_plugin pulse_output_plugin = {
+ .name = "pulse",
+
+ .test_default_device = pulse_output_test_default_device,
+ .init = pulse_output_init,
+ .finish = pulse_output_finish,
+ .enable = pulse_output_enable,
+ .disable = pulse_output_disable,
+ .open = pulse_output_open,
+ .play = pulse_output_play,
+ .cancel = pulse_output_cancel,
+ .pause = pulse_output_pause,
+ .close = pulse_output_close,
+
+ .mixer_plugin = &pulse_mixer_plugin,
+};
diff --git a/src/output/pulse_output_plugin.h b/src/output/pulse_output_plugin.h
new file mode 100644
index 000000000..e6b37443f
--- /dev/null
+++ b/src/output/pulse_output_plugin.h
@@ -0,0 +1,72 @@
+/*
+ * 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_PULSE_OUTPUT_PLUGIN_H
+#define MPD_PULSE_OUTPUT_PLUGIN_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <glib.h>
+
+#include <pulse/version.h>
+
+#if !defined(PA_CHECK_VERSION)
+/**
+ * This macro was implemented in libpulse 0.9.16.
+ */
+#define PA_CHECK_VERSION(a,b,c) false
+#endif
+
+struct pa_operation;
+struct pa_cvolume;
+
+struct pulse_output {
+ const char *name;
+ const char *server;
+ const char *sink;
+
+ struct pulse_mixer *mixer;
+
+ struct pa_threaded_mainloop *mainloop;
+ struct pa_context *context;
+ struct pa_stream *stream;
+
+ size_t writable;
+
+#if !PA_CHECK_VERSION(0,9,11)
+ /**
+ * We need this variable because pa_stream_is_corked() wasn't
+ * added before 0.9.11.
+ */
+ bool pause;
+#endif
+};
+
+void
+pulse_output_set_mixer(struct pulse_output *po, struct pulse_mixer *pm);
+
+void
+pulse_output_clear_mixer(struct pulse_output *po, struct pulse_mixer *pm);
+
+bool
+pulse_output_set_volume(struct pulse_output *po,
+ const struct pa_cvolume *volume, GError **error_r);
+
+#endif
diff --git a/src/output/pulse_plugin.c b/src/output/pulse_plugin.c
deleted file mode 100644
index ffc7abc8b..000000000
--- a/src/output/pulse_plugin.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
- * http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-
-#include "../output_api.h"
-#include "mixer_list.h"
-
-#include <glib.h>
-#include <pulse/simple.h>
-#include <pulse/error.h>
-
-#define MPD_PULSE_NAME "mpd"
-
-struct pulse_data {
- const char *name;
- const char *server;
- const char *sink;
-
- pa_simple *s;
-};
-
-/**
- * The quark used for GError.domain.
- */
-static inline GQuark
-pulse_output_quark(void)
-{
- return g_quark_from_static_string("pulse_output");
-}
-
-static struct pulse_data *pulse_new_data(void)
-{
- struct pulse_data *ret;
-
- ret = g_new(struct pulse_data, 1);
-
- ret->server = NULL;
- ret->sink = NULL;
-
- return ret;
-}
-
-static void pulse_free_data(struct pulse_data *pd)
-{
- g_free(pd);
-}
-
-static void *
-pulse_init(G_GNUC_UNUSED const struct audio_format *audio_format,
- const struct config_param *param, G_GNUC_UNUSED GError **error)
-{
- struct pulse_data *pd;
-
- pd = pulse_new_data();
- pd->name = config_get_block_string(param, "name", "mpd_pulse");
- pd->server = config_get_block_string(param, "server", NULL);
- pd->sink = config_get_block_string(param, "sink", NULL);
-
- return pd;
-}
-
-static void pulse_finish(void *data)
-{
- struct pulse_data *pd = data;
-
- pulse_free_data(pd);
-}
-
-static bool pulse_test_default_device(void)
-{
- pa_simple *s;
- pa_sample_spec ss;
- int error;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = 44100;
- ss.channels = 2;
-
- s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL,
- MPD_PULSE_NAME, &ss, NULL, NULL, &error);
- if (!s) {
- g_message("Cannot connect to default PulseAudio server: %s\n",
- pa_strerror(error));
- return false;
- }
-
- pa_simple_free(s);
-
- return true;
-}
-
-static bool
-pulse_open(void *data, struct audio_format *audio_format, GError **error_r)
-{
- struct pulse_data *pd = data;
- pa_sample_spec ss;
- int error;
-
- /* MPD doesn't support the other pulseaudio sample formats, so
- we just force MPD to send us everything as 16 bit */
- audio_format->bits = 16;
-
- ss.format = PA_SAMPLE_S16NE;
- ss.rate = audio_format->sample_rate;
- ss.channels = audio_format->channels;
-
- pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK,
- pd->sink, pd->name,
- &ss, NULL, NULL,
- &error);
- if (!pd->s) {
- g_set_error(error_r, pulse_output_quark(), error,
- "Cannot connect to PulseAudio server: %s",
- pa_strerror(error));
- return false;
- }
-
- return true;
-}
-
-static void pulse_cancel(void *data)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_flush(pd->s, &error) < 0)
- g_warning("Flush failed in PulseAudio output \"%s\": %s\n",
- pd->name, pa_strerror(error));
-}
-
-static void pulse_close(void *data)
-{
- struct pulse_data *pd = data;
-
- pa_simple_drain(pd->s, NULL);
- pa_simple_free(pd->s);
-}
-
-static size_t
-pulse_play(void *data, const void *chunk, size_t size, GError **error_r)
-{
- struct pulse_data *pd = data;
- int error;
-
- if (pa_simple_write(pd->s, chunk, size, &error) < 0) {
- g_set_error(error_r, pulse_output_quark(), error,
- "%s", pa_strerror(error));
- return 0;
- }
-
- return size;
-}
-
-const struct audio_output_plugin pulse_plugin = {
- .name = "pulse",
- .test_default_device = pulse_test_default_device,
- .init = pulse_init,
- .finish = pulse_finish,
- .open = pulse_open,
- .play = pulse_play,
- .cancel = pulse_cancel,
- .close = pulse_close,
- .mixer_plugin = &pulse_mixer,
-};
diff --git a/src/output/recorder_output_plugin.c b/src/output/recorder_output_plugin.c
new file mode 100644
index 000000000..f56ec0328
--- /dev/null
+++ b/src/output/recorder_output_plugin.c
@@ -0,0 +1,217 @@
+/*
+ * 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 "output_api.h"
+#include "encoder_plugin.h"
+#include "encoder_list.h"
+#include "fd_util.h"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "recorder"
+
+struct recorder_output {
+ /**
+ * The configured encoder plugin.
+ */
+ struct encoder *encoder;
+
+ /**
+ * The destination file name.
+ */
+ const char *path;
+
+ /**
+ * The destination file descriptor.
+ */
+ int fd;
+
+ /**
+ * The buffer for encoder_read().
+ */
+ char buffer[32768];
+};
+
+/**
+ * The quark used for GError.domain.
+ */
+static inline GQuark
+recorder_output_quark(void)
+{
+ return g_quark_from_static_string("recorder_output");
+}
+
+static void *
+recorder_output_init(G_GNUC_UNUSED const struct audio_format *audio_format,
+ const struct config_param *param, GError **error_r)
+{
+ struct recorder_output *recorder = g_new(struct recorder_output, 1);
+ const char *encoder_name;
+ const struct encoder_plugin *encoder_plugin;
+
+ /* read configuration */
+
+ encoder_name = config_get_block_string(param, "encoder", "vorbis");
+ encoder_plugin = encoder_plugin_get(encoder_name);
+ if (encoder_plugin == NULL) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "No such encoder: %s", encoder_name);
+ return NULL;
+ }
+
+ recorder->path = config_get_block_string(param, "path", NULL);
+ if (recorder->path == NULL) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "'path' not configured");
+ return NULL;
+ }
+
+ /* initialize encoder */
+
+ recorder->encoder = encoder_init(encoder_plugin, param, error_r);
+ if (recorder->encoder == NULL)
+ return NULL;
+
+ return recorder;
+}
+
+static void
+recorder_output_finish(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ encoder_finish(recorder->encoder);
+ g_free(recorder);
+}
+
+/**
+ * Writes pending data from the encoder to the output file.
+ */
+static bool
+recorder_output_encoder_to_file(struct recorder_output *recorder,
+ GError **error_r)
+{
+ size_t size = 0, position, nbytes;
+
+ assert(recorder->fd >= 0);
+
+ /* read from the encoder */
+
+ size = encoder_read(recorder->encoder, recorder->buffer,
+ sizeof(recorder->buffer));
+ if (size == 0)
+ return true;
+
+ /* write everything into the file */
+
+ position = 0;
+ while (true) {
+ nbytes = write(recorder->fd, recorder->buffer + position,
+ size - position);
+ if (nbytes > 0) {
+ position += (size_t)nbytes;
+ if (position >= size)
+ return true;
+ } else if (nbytes == 0) {
+ /* shouldn't happen for files */
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "write() returned 0");
+ return false;
+ } else if (errno != EINTR) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to write to '%s': %s",
+ recorder->path, g_strerror(errno));
+ return false;
+ }
+ }
+}
+
+static bool
+recorder_output_open(void *data, struct audio_format *audio_format,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+ bool success;
+
+ /* create the output file */
+
+ recorder->fd = open_cloexec(recorder->path, O_CREAT|O_WRONLY|O_TRUNC,
+ 0666);
+ if (recorder->fd < 0) {
+ g_set_error(error_r, recorder_output_quark(), 0,
+ "Failed to create '%s': %s",
+ recorder->path, g_strerror(errno));
+ return false;
+ }
+
+ /* open the encoder */
+
+ success = encoder_open(recorder->encoder, audio_format, error_r);
+ if (!success) {
+ close(recorder->fd);
+ unlink(recorder->path);
+ return false;
+ }
+
+ return true;
+}
+
+static void
+recorder_output_close(void *data)
+{
+ struct recorder_output *recorder = data;
+
+ /* flush the encoder and write the rest to the file */
+
+ if (encoder_flush(recorder->encoder, NULL))
+ recorder_output_encoder_to_file(recorder, NULL);
+
+ /* now really close everything */
+
+ encoder_close(recorder->encoder);
+
+ close(recorder->fd);
+}
+
+static size_t
+recorder_output_play(void *data, const void *chunk, size_t size,
+ GError **error_r)
+{
+ struct recorder_output *recorder = data;
+
+ return encoder_write(recorder->encoder, chunk, size, error_r) &&
+ recorder_output_encoder_to_file(recorder, error_r)
+ ? size : 0;
+}
+
+const struct audio_output_plugin recorder_output_plugin = {
+ .name = "recorder",
+ .init = recorder_output_init,
+ .finish = recorder_output_finish,
+ .open = recorder_output_open,
+ .close = recorder_output_close,
+ .play = recorder_output_play,
+};
diff --git a/src/output/shout_plugin.c b/src/output/shout_plugin.c
index 4412d26ff..750b09191 100644
--- a/src/output/shout_plugin.c
+++ b/src/output/shout_plugin.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
#include "encoder_plugin.h"
#include "encoder_list.h"
@@ -126,6 +127,13 @@ my_shout_init_driver(const struct audio_format *audio_format,
struct block_param *block_param;
int public;
+ if (audio_format == NULL ||
+ !audio_format_fully_defined(audio_format)) {
+ g_set_error(error, shout_output_quark(), 0,
+ "Need full audio format specification");
+ return NULL;
+ }
+
sd = new_shout_data();
if (shout_init_count == 0)
@@ -191,8 +199,6 @@ my_shout_init_driver(const struct audio_format *audio_format,
}
}
- check_block_param("format");
-
encoding = config_get_block_string(param, "encoding", "ogg");
encoder_plugin = shout_encoder_plugin_get(encoding);
if (encoder_plugin == NULL) {
@@ -471,10 +477,10 @@ shout_tag_to_metadata(const struct tag *tag, char *dest, size_t size)
for (unsigned i = 0; i < tag->num_items; i++) {
switch (tag->items[i]->type) {
- case TAG_ITEM_ARTIST:
+ case TAG_ARTIST:
strncpy(artist, tag->items[i]->value, size);
break;
- case TAG_ITEM_TITLE:
+ case TAG_TITLE:
strncpy(title, tag->items[i]->value, size);
break;
diff --git a/src/output/solaris_output_plugin.c b/src/output/solaris_output_plugin.c
index 5febf0afc..fe84068f1 100644
--- a/src/output/solaris_output_plugin.c
+++ b/src/output/solaris_output_plugin.c
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_api.h"
+#include "fd_util.h"
#include <glib.h>
@@ -87,11 +89,11 @@ solaris_output_open(void *data, struct audio_format *audio_format,
/* support only 16 bit mono/stereo for now; nothing else has
been tested */
- audio_format->bits = 16;
+ audio_format->format = SAMPLE_FORMAT_S16;
/* open the device in non-blocking mode */
- so->fd = open(so->device, O_WRONLY|O_NONBLOCK);
+ so->fd = open_cloexec(so->device, O_WRONLY|O_NONBLOCK);
if (so->fd < 0) {
g_set_error(error, solaris_output_quark(), errno,
"Failed to open %s: %s",
@@ -117,7 +119,7 @@ solaris_output_open(void *data, struct audio_format *audio_format,
info.play.sample_rate = audio_format->sample_rate;
info.play.channels = audio_format->channels;
- info.play.precision = audio_format->bits;
+ info.play.precision = 16;
info.play.encoding = AUDIO_ENCODING_LINEAR;
ret = ioctl(so->fd, AUDIO_SETINFO, &info);
diff --git a/src/output_all.c b/src/output_all.c
index 4b5ba3a6f..194a65924 100644
--- a/src/output_all.c
+++ b/src/output_all.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_all.h"
#include "output_internal.h"
#include "output_control.h"
@@ -52,6 +53,11 @@ static struct music_buffer *g_music_buffer;
*/
static struct music_pipe *g_mp;
+/**
+ * The "elapsed_time" stamp of the most recently finished chunk.
+ */
+static float audio_output_all_elapsed_time = -1.0;
+
unsigned int audio_output_count(void)
{
return num_audio_outputs;
@@ -148,6 +154,25 @@ audio_output_all_finish(void)
notify_deinit(&audio_output_client_notify);
}
+void
+audio_output_all_enable_disable(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; i++) {
+ struct audio_output *ao = &audio_outputs[i];
+ bool enabled;
+
+ g_mutex_lock(ao->mutex);
+ enabled = ao->really_enabled;
+ g_mutex_unlock(ao->mutex);
+
+ if (ao->enabled != enabled) {
+ if (ao->enabled)
+ audio_output_enable(ao);
+ else
+ audio_output_disable(ao);
+ }
+ }
+}
/**
* Determine if all (active) outputs have finished the current
@@ -156,10 +181,18 @@ audio_output_all_finish(void)
static bool
audio_output_all_finished(void)
{
- for (unsigned i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]) &&
- !audio_output_command_is_finished(&audio_outputs[i]))
+ for (unsigned i = 0; i < num_audio_outputs; ++i) {
+ struct audio_output *ao = &audio_outputs[i];
+ bool not_finished;
+
+ g_mutex_lock(ao->mutex);
+ not_finished = audio_output_is_open(ao) &&
+ !audio_output_command_is_finished(ao);
+ g_mutex_unlock(ao->mutex);
+
+ if (not_finished)
return false;
+ }
return true;
}
@@ -170,6 +203,29 @@ static void audio_output_wait_all(void)
notify_wait(&audio_output_client_notify);
}
+/**
+ * Signals the audio output if it is open. This function locks the
+ * mutex.
+ */
+static void
+audio_output_lock_signal(struct audio_output *ao)
+{
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
+ g_mutex_unlock(ao->mutex);
+}
+
+/**
+ * Signals all audio outputs which are open.
+ */
+static void
+audio_output_signal_all(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_lock_signal(&audio_outputs[i]);
+}
+
static void
audio_output_reset_reopen(struct audio_output *ao)
{
@@ -237,8 +293,7 @@ audio_output_all_play(struct music_chunk *chunk)
music_pipe_push(g_mp, chunk);
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_play(&audio_outputs[i]);
+ audio_output_play(&audio_outputs[i]);
return true;
}
@@ -273,6 +328,7 @@ audio_output_all_open(const struct audio_format *audio_format,
input_audio_format = *audio_format;
audio_output_all_reset_reopen();
+ audio_output_all_enable_disable();
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i) {
@@ -385,6 +441,11 @@ audio_output_all_check(void)
this chunk */
return music_pipe_size(g_mp);
+ if (chunk->length > 0 && chunk->times >= 0.0)
+ /* only update elapsed_time if the chunk
+ provides a defined value */
+ audio_output_all_elapsed_time = chunk->times;
+
is_tail = chunk->next == NULL;
if (is_tail)
/* this is the tail of the pipe - clear the
@@ -412,10 +473,15 @@ audio_output_all_check(void)
bool
audio_output_all_wait(unsigned threshold)
{
- if (audio_output_all_check() < threshold)
+ player_lock();
+
+ if (audio_output_all_check() < threshold) {
+ player_unlock();
return true;
+ }
- notify_wait(&pc.notify);
+ player_wait();
+ player_unlock();
return audio_output_all_check() < threshold;
}
@@ -428,8 +494,16 @@ audio_output_all_pause(void)
audio_output_all_update();
for (i = 0; i < num_audio_outputs; ++i)
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_pause(&audio_outputs[i]);
+ audio_output_pause(&audio_outputs[i]);
+
+ audio_output_wait_all();
+}
+
+void
+audio_output_all_drain(void)
+{
+ for (unsigned i = 0; i < num_audio_outputs; ++i)
+ audio_output_drain_async(&audio_outputs[i]);
audio_output_wait_all();
}
@@ -441,10 +515,8 @@ audio_output_all_cancel(void)
/* send the cancel() command to all audio outputs */
- for (i = 0; i < num_audio_outputs; ++i) {
- if (audio_output_is_open(&audio_outputs[i]))
- audio_output_cancel(&audio_outputs[i]);
- }
+ for (i = 0; i < num_audio_outputs; ++i)
+ audio_output_cancel(&audio_outputs[i]);
audio_output_wait_all();
@@ -452,6 +524,15 @@ audio_output_all_cancel(void)
if (g_mp != NULL)
music_pipe_clear(g_mp, g_music_buffer);
+
+ /* the audio outputs are now waiting for a signal, to
+ synchronize the cleared music pipe */
+
+ audio_output_signal_all();
+
+ /* invalidate elapsed_time */
+
+ audio_output_all_elapsed_time = -1.0;
}
void
@@ -473,4 +554,12 @@ audio_output_all_close(void)
g_music_buffer = NULL;
audio_format_clear(&input_audio_format);
+
+ audio_output_all_elapsed_time = -1.0;
+}
+
+float
+audio_output_all_get_elapsed_time(void)
+{
+ return audio_output_all_elapsed_time;
}
diff --git a/src/output_all.h b/src/output_all.h
index 2a09514b2..6ff45fb79 100644
--- a/src/output_all.h
+++ b/src/output_all.h
@@ -66,6 +66,13 @@ struct audio_output *
audio_output_find(const char *name);
/**
+ * Checks the "enabled" flag of all audio outputs, and if one has
+ * changed, commit the change.
+ */
+void
+audio_output_all_enable_disable(void);
+
+/**
* Opens all audio outputs which are not disabled.
*
* @param audio_format the preferred audio format, or NULL to reuse
@@ -123,9 +130,23 @@ void
audio_output_all_pause(void);
/**
+ * Drain all audio outputs.
+ */
+void
+audio_output_all_drain(void);
+
+/**
* Try to cancel data which may still be in the device's buffers.
*/
void
audio_output_all_cancel(void);
+/**
+ * Returns the "elapsed_time" stamp of the most recently finished
+ * chunk. A negative value is returned when no chunk has been
+ * finished yet.
+ */
+float
+audio_output_all_get_elapsed_time(void);
+
#endif
diff --git a/src/output_command.c b/src/output_command.c
index 5da176dde..9a720904d 100644
--- a/src/output_command.c
+++ b/src/output_command.c
@@ -24,13 +24,17 @@
*
*/
+#include "config.h"
#include "output_command.h"
#include "output_all.h"
#include "output_internal.h"
#include "output_plugin.h"
#include "mixer_control.h"
+#include "player_control.h"
#include "idle.h"
+extern unsigned audio_output_state_version;
+
bool
audio_output_enable_index(unsigned idx)
{
@@ -40,10 +44,16 @@ audio_output_enable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (ao->enabled)
+ return true;
ao->enabled = true;
idle_add(IDLE_OUTPUT);
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
@@ -57,6 +67,8 @@ audio_output_disable_index(unsigned idx)
return false;
ao = audio_output_get(idx);
+ if (!ao->enabled)
+ return true;
ao->enabled = false;
idle_add(IDLE_OUTPUT);
@@ -67,5 +79,9 @@ audio_output_disable_index(unsigned idx)
idle_add(IDLE_MIXER);
}
+ pc_update_audio();
+
+ ++audio_output_state_version;
+
return true;
}
diff --git a/src/output_control.c b/src/output_control.c
index 16c0dbb75..5479263de 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -17,12 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_control.h"
#include "output_api.h"
#include "output_internal.h"
#include "output_thread.h"
#include "mixer_control.h"
#include "mixer_plugin.h"
+#include "filter_plugin.h"
+#include "notify.h"
#include <assert.h>
#include <stdlib.h>
@@ -38,8 +41,9 @@ struct notify audio_output_client_notify;
static void ao_command_wait(struct audio_output *ao)
{
while (ao->command != AO_COMMAND_NONE) {
- notify_signal(&ao->notify);
+ g_mutex_unlock(ao->mutex);
notify_wait(&audio_output_client_notify);
+ g_mutex_lock(ao->mutex);
}
}
@@ -47,6 +51,7 @@ static void ao_command(struct audio_output *ao, enum audio_output_command cmd)
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
+ g_cond_signal(ao->cond);
ao_command_wait(ao);
}
@@ -55,7 +60,46 @@ static void ao_command_async(struct audio_output *ao,
{
assert(ao->command == AO_COMMAND_NONE);
ao->command = cmd;
- notify_signal(&ao->notify);
+ g_cond_signal(ao->cond);
+}
+
+void
+audio_output_enable(struct audio_output *ao)
+{
+ if (ao->thread == NULL) {
+ if (ao->plugin->enable == NULL) {
+ /* don't bother to start the thread now if the
+ device doesn't even have a enable() method;
+ just assign the variable and we're done */
+ ao->really_enabled = true;
+ return;
+ }
+
+ audio_output_thread_start(ao);
+ }
+
+ g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_ENABLE);
+ g_mutex_unlock(ao->mutex);
+}
+
+void
+audio_output_disable(struct audio_output *ao)
+{
+ if (ao->thread == NULL) {
+ if (ao->plugin->disable == NULL)
+ ao->really_enabled = false;
+ else
+ /* if there's no thread yet, the device cannot
+ be enabled */
+ assert(!ao->really_enabled);
+
+ return;
+ }
+
+ g_mutex_lock(ao->mutex);
+ ao_command(ao, AO_COMMAND_DISABLE);
+ g_mutex_unlock(ao->mutex);
}
static bool
@@ -85,6 +129,10 @@ audio_output_open(struct audio_output *ao,
/* we're not using audio_output_cancel() here,
because that function is asynchronous */
ao_command(ao, AO_COMMAND_CANCEL);
+
+ /* the audio output is now waiting for a
+ signal; wake it up immediately */
+ g_cond_signal(ao->cond);
}
return true;
@@ -93,33 +141,47 @@ audio_output_open(struct audio_output *ao,
ao->in_audio_format = *audio_format;
ao->chunk = NULL;
- if (!ao->config_audio_format) {
- if (ao->open)
- audio_output_close(ao);
-
- /* no audio format is configured: copy in->out, let
- the output's open() method determine the effective
- out_audio_format */
- ao->out_audio_format = ao->in_audio_format;
- }
-
ao->pipe = mp;
if (ao->thread == NULL)
audio_output_thread_start(ao);
+ ao_command(ao, ao->open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN);
open = ao->open;
- if (!open) {
- ao_command(ao, AO_COMMAND_OPEN);
- open = ao->open;
- }
- if (open && ao->mixer != NULL)
- mixer_open(ao->mixer);
+ if (open && ao->mixer != NULL) {
+ GError *error = NULL;
+
+ if (!mixer_open(ao->mixer, &error)) {
+ g_warning("Failed to open mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+ }
return open;
}
+/**
+ * Same as audio_output_close(), but expects the lock to be held by
+ * the caller.
+ */
+static void
+audio_output_close_locked(struct audio_output *ao)
+{
+ if (ao->mixer != NULL)
+ mixer_auto_close(ao->mixer);
+
+ assert(!ao->open || ao->fail_timer == NULL);
+
+ if (ao->open)
+ ao_command(ao, AO_COMMAND_CLOSE);
+ else if (ao->fail_timer != NULL) {
+ g_timer_destroy(ao->fail_timer);
+ ao->fail_timer = NULL;
+ }
+}
+
bool
audio_output_update(struct audio_output *ao,
const struct audio_format *audio_format,
@@ -127,23 +189,31 @@ audio_output_update(struct audio_output *ao,
{
assert(mp != NULL);
- if (ao->enabled) {
+ g_mutex_lock(ao->mutex);
+
+ if (ao->enabled && ao->really_enabled) {
if (ao->fail_timer == NULL ||
- g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER)
- return audio_output_open(ao, audio_format, mp);
+ g_timer_elapsed(ao->fail_timer, NULL) > REOPEN_AFTER) {
+ bool success = audio_output_open(ao, audio_format, mp);
+ g_mutex_unlock(ao->mutex);
+ return success;
+ }
} else if (audio_output_is_open(ao))
- audio_output_close(ao);
+ audio_output_close_locked(ao);
+ g_mutex_unlock(ao->mutex);
return false;
}
void
audio_output_play(struct audio_output *ao)
{
- if (!ao->open)
- return;
+ g_mutex_lock(ao->mutex);
+
+ if (audio_output_is_open(ao))
+ g_cond_signal(ao->cond);
- notify_signal(&ao->notify);
+ g_mutex_unlock(ao->mutex);
}
void audio_output_pause(struct audio_output *ao)
@@ -154,27 +224,46 @@ void audio_output_pause(struct audio_output *ao)
mixer_auto_close()) */
mixer_auto_close(ao->mixer);
- ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_PAUSE);
+ g_mutex_unlock(ao->mutex);
+}
+
+void
+audio_output_drain_async(struct audio_output *ao)
+{
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_DRAIN);
+ g_mutex_unlock(ao->mutex);
}
void audio_output_cancel(struct audio_output *ao)
{
- ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_lock(ao->mutex);
+ if (audio_output_is_open(ao))
+ ao_command_async(ao, AO_COMMAND_CANCEL);
+ g_mutex_unlock(ao->mutex);
}
void audio_output_close(struct audio_output *ao)
{
- assert(!ao->open || ao->fail_timer == NULL);
-
if (ao->mixer != NULL)
mixer_auto_close(ao->mixer);
+ g_mutex_lock(ao->mutex);
+
+ assert(!ao->open || ao->fail_timer == NULL);
+
if (ao->open)
ao_command(ao, AO_COMMAND_CLOSE);
else if (ao->fail_timer != NULL) {
g_timer_destroy(ao->fail_timer);
ao->fail_timer = NULL;
}
+
+ g_mutex_unlock(ao->mutex);
}
void audio_output_finish(struct audio_output *ao)
@@ -184,7 +273,9 @@ void audio_output_finish(struct audio_output *ao)
assert(ao->fail_timer == NULL);
if (ao->thread != NULL) {
+ g_mutex_lock(ao->mutex);
ao_command(ao, AO_COMMAND_KILL);
+ g_mutex_unlock(ao->mutex);
g_thread_join(ao->thread);
}
@@ -193,6 +284,8 @@ void audio_output_finish(struct audio_output *ao)
ao_plugin_finish(ao->plugin, ao->data);
- notify_deinit(&ao->notify);
+ g_cond_free(ao->cond);
g_mutex_free(ao->mutex);
+
+ filter_free(ao->filter);
}
diff --git a/src/output_control.h b/src/output_control.h
index ce3abe3f6..692c11676 100644
--- a/src/output_control.h
+++ b/src/output_control.h
@@ -38,7 +38,19 @@ audio_output_quark(void)
bool
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error);
+ GError **error_r);
+
+/**
+ * Enables the device.
+ */
+void
+audio_output_enable(struct audio_output *ao);
+
+/**
+ * Disables the device.
+ */
+void
+audio_output_disable(struct audio_output *ao);
/**
* Opens or closes the device, depending on the "enabled" flag.
@@ -55,6 +67,9 @@ audio_output_play(struct audio_output *ao);
void audio_output_pause(struct audio_output *ao);
+void
+audio_output_drain_async(struct audio_output *ao);
+
void audio_output_cancel(struct audio_output *ao);
void audio_output_close(struct audio_output *ao);
void audio_output_finish(struct audio_output *ao);
diff --git a/src/output_init.c b/src/output_init.c
index 927424324..ab5257829 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -17,21 +17,32 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_control.h"
#include "output_api.h"
#include "output_internal.h"
#include "output_list.h"
#include "audio_parser.h"
#include "mixer_control.h"
+#include "mixer_type.h"
+#include "mixer_list.h"
+#include "mixer/software_mixer_plugin.h"
+#include "filter_plugin.h"
+#include "filter_registry.h"
+#include "filter_config.h"
+#include "filter/chain_filter_plugin.h"
#include <glib.h>
+#include <assert.h>
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "output"
#define AUDIO_OUTPUT_TYPE "type"
#define AUDIO_OUTPUT_NAME "name"
#define AUDIO_OUTPUT_FORMAT "format"
+#define AUDIO_FILTERS "filters"
static const struct audio_output_plugin *
audio_output_detect(GError **error)
@@ -56,46 +67,109 @@ audio_output_detect(GError **error)
return NULL;
}
+/**
+ * Determines the mixer type which should be used for the specified
+ * configuration block.
+ *
+ * This handles the deprecated options mixer_type (global) and
+ * mixer_enabled, if the mixer_type setting is not configured.
+ */
+static enum mixer_type
+audio_output_mixer_type(const struct config_param *param)
+{
+ /* read the local "mixer_type" setting */
+ const char *p = config_get_block_string(param, "mixer_type", NULL);
+ if (p != NULL)
+ return mixer_type_parse(p);
+
+ /* try the local "mixer_enabled" setting next (deprecated) */
+ if (!config_get_block_bool(param, "mixer_enabled", true))
+ return MIXER_TYPE_NONE;
+
+ /* fall back to the global "mixer_type" setting (also
+ deprecated) */
+ return mixer_type_parse(config_get_string("mixer_type", "hardware"));
+}
+
+static struct mixer *
+audio_output_load_mixer(void *ao, const struct config_param *param,
+ const struct mixer_plugin *plugin,
+ struct filter *filter_chain,
+ GError **error_r)
+{
+ struct mixer *mixer;
+
+ switch (audio_output_mixer_type(param)) {
+ case MIXER_TYPE_NONE:
+ case MIXER_TYPE_UNKNOWN:
+ return NULL;
+
+ case MIXER_TYPE_HARDWARE:
+ if (plugin == NULL)
+ return NULL;
+
+ return mixer_new(plugin, ao, param, error_r);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(&software_mixer_plugin, NULL, NULL, NULL);
+ assert(mixer != NULL);
+
+ filter_chain_append(filter_chain,
+ software_mixer_get_filter(mixer));
+ return mixer;
+ }
+
+ assert(false);
+ return NULL;
+}
+
bool
audio_output_init(struct audio_output *ao, const struct config_param *param,
- GError **error)
+ GError **error_r)
{
- const char *format;
const struct audio_output_plugin *plugin = NULL;
+ GError *error = NULL;
if (param) {
- const char *type = NULL;
+ const char *p;
- type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (type == NULL) {
- g_set_error(error, audio_output_quark(), 0,
+ p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
+ if (p == NULL) {
+ g_set_error(error_r, audio_output_quark(), 0,
"Missing \"type\" configuration");
return false;
}
- plugin = audio_output_plugin_get(type);
+ plugin = audio_output_plugin_get(p);
if (plugin == NULL) {
- g_set_error(error, audio_output_quark(), 0,
- "No such audio output plugin: %s",
- type);
+ g_set_error(error_r, audio_output_quark(), 0,
+ "No such audio output plugin: %s", p);
return false;
}
ao->name = config_get_block_string(param, AUDIO_OUTPUT_NAME,
NULL);
if (ao->name == NULL) {
- g_set_error(error, audio_output_quark(), 0,
+ g_set_error(error_r, audio_output_quark(), 0,
"Missing \"name\" configuration");
return false;
}
- format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
+ p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
NULL);
+ if (p != NULL) {
+ bool success =
+ audio_format_parse(&ao->config_audio_format,
+ p, true, error_r);
+ if (!success)
+ return false;
+ } else
+ audio_format_clear(&ao->config_audio_format);
} else {
g_warning("No \"%s\" defined in config file\n",
CONF_AUDIO_OUTPUT);
- plugin = audio_output_detect(error);
+ plugin = audio_output_detect(error_r);
if (plugin == NULL)
return false;
@@ -103,44 +177,62 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
plugin->name);
ao->name = "default detected output";
- format = NULL;
+
+ audio_format_clear(&ao->config_audio_format);
}
ao->plugin = plugin;
ao->enabled = config_get_block_bool(param, "enabled", true);
+ ao->really_enabled = false;
ao->open = false;
ao->pause = false;
ao->fail_timer = NULL;
- pcm_convert_init(&ao->convert_state);
-
- ao->config_audio_format = format != NULL;
- if (ao->config_audio_format) {
- bool ret;
-
- ret = audio_format_parse(&ao->out_audio_format, format,
- error);
- if (!ret)
- return false;
+ /* set up the filter chain */
+
+ ao->filter = filter_chain_new();
+ assert(ao->filter != NULL);
+ filter_chain_parse(ao->filter,
+ config_get_block_string(param, AUDIO_FILTERS, ""),
+ &error
+ );
+
+ // It's not really fatal - Part of the filter chain has been set up already
+ // and even an empty one will work (if only with unexpected behaviour)
+ if (error != NULL) {
+ g_warning("Failed to initialize filter chain for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
}
ao->thread = NULL;
- notify_init(&ao->notify);
ao->command = AO_COMMAND_NONE;
ao->mutex = g_mutex_new();
+ ao->cond = g_cond_new();
ao->data = ao_plugin_init(plugin,
- ao->config_audio_format
- ? &ao->out_audio_format : NULL,
- param, error);
+ &ao->config_audio_format,
+ param, error_r);
if (ao->data == NULL)
return false;
- if (plugin->mixer_plugin != NULL &&
- config_get_block_bool(param, "mixer_enabled", true))
- ao->mixer = mixer_new(plugin->mixer_plugin, param);
- else
- ao->mixer = NULL;
+ ao->mixer = audio_output_load_mixer(ao->data, param,
+ plugin->mixer_plugin,
+ ao->filter, &error);
+ if (ao->mixer == NULL && error != NULL) {
+ g_warning("Failed to initialize hardware mixer for '%s': %s",
+ ao->name, error->message);
+ g_error_free(error);
+ }
+
+ /* the "convert" filter must be the last one in the chain */
+
+ ao->convert_filter = filter_new(&convert_filter_plugin, NULL, NULL);
+ assert(ao->convert_filter != NULL);
+
+ filter_chain_append(ao->filter, ao->convert_filter);
+
+ /* done */
return true;
}
diff --git a/src/output_internal.h b/src/output_internal.h
index 72596c1c3..de1b15c29 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -21,16 +21,32 @@
#define MPD_OUTPUT_INTERNAL_H
#include "audio_format.h"
-#include "pcm_convert.h"
-#include "notify.h"
+
+#include <glib.h>
#include <time.h>
enum audio_output_command {
AO_COMMAND_NONE = 0,
+ AO_COMMAND_ENABLE,
+ AO_COMMAND_DISABLE,
AO_COMMAND_OPEN,
+
+ /**
+ * This command is invoked when the input audio format
+ * changes.
+ */
+ AO_COMMAND_REOPEN,
+
AO_COMMAND_CLOSE,
AO_COMMAND_PAUSE,
+
+ /**
+ * Drains the internal (hardware) buffers of the device. This
+ * operation may take a while to complete.
+ */
+ AO_COMMAND_DRAIN,
+
AO_COMMAND_CANCEL,
AO_COMMAND_KILL
};
@@ -60,15 +76,15 @@ struct audio_output {
struct mixer *mixer;
/**
- * This flag is true, when the audio_format of this device is
- * configured in mpd.conf.
+ * Has the user enabled this device?
*/
- bool config_audio_format;
+ bool enabled;
/**
- * Has the user enabled this device?
+ * Is this device actually enabled, i.e. the "enable" method
+ * has succeeded?
*/
- bool enabled;
+ bool really_enabled;
/**
* Is the device (already) open and functional?
@@ -94,6 +110,11 @@ struct audio_output {
GTimer *fail_timer;
/**
+ * The configured audio format.
+ */
+ struct audio_format config_audio_format;
+
+ /**
* The audio_format in which audio data is received from the
* player thread (which in turn receives it from the decoder).
*/
@@ -107,18 +128,25 @@ struct audio_output {
*/
struct audio_format out_audio_format;
- struct pcm_convert_state convert_state;
+ /**
+ * The filter object of this audio output. This is an
+ * instance of chain_filter_plugin.
+ */
+ struct filter *filter;
/**
- * The thread handle, or NULL if the output thread isn't
- * running.
+ * The convert_filter_plugin instance of this audio output.
+ * It is the last item in the filter chain, and is responsible
+ * for converting the input data into the appropriate format
+ * for this audio output.
*/
- GThread *thread;
+ struct filter *convert_filter;
/**
- * Notify object for the thread.
+ * The thread handle, or NULL if the output thread isn't
+ * running.
*/
- struct notify notify;
+ GThread *thread;
/**
* The next command to be performed by the output thread.
@@ -136,6 +164,12 @@ struct audio_output {
GMutex *mutex;
/**
+ * This condition object wakes up the output thread after
+ * #command has been set.
+ */
+ GCond *cond;
+
+ /**
* The #music_chunk which is currently being played. All
* chunks before this one may be returned to the
* #music_buffer, because they are not going to be used by
diff --git a/src/output_list.c b/src/output_list.c
index 81de16649..71a294407 100644
--- a/src/output_list.c
+++ b/src/output_list.c
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_list.h"
#include "output_api.h"
-#include "config.h"
extern const struct audio_output_plugin shoutPlugin;
extern const struct audio_output_plugin null_output_plugin;
@@ -28,12 +28,14 @@ extern const struct audio_output_plugin pipe_output_plugin;
extern const struct audio_output_plugin alsaPlugin;
extern const struct audio_output_plugin ao_output_plugin;
extern const struct audio_output_plugin oss_output_plugin;
+extern const struct audio_output_plugin openal_output_plugin;
extern const struct audio_output_plugin osxPlugin;
extern const struct audio_output_plugin solaris_output_plugin;
-extern const struct audio_output_plugin pulse_plugin;
+extern const struct audio_output_plugin pulse_output_plugin;
extern const struct audio_output_plugin mvp_output_plugin;
-extern const struct audio_output_plugin jackPlugin;
+extern const struct audio_output_plugin jack_output_plugin;
extern const struct audio_output_plugin httpd_output_plugin;
+extern const struct audio_output_plugin recorder_output_plugin;
const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -55,6 +57,9 @@ const struct audio_output_plugin *audio_output_plugins[] = {
#ifdef HAVE_OSS
&oss_output_plugin,
#endif
+#ifdef HAVE_OPENAL
+ &openal_output_plugin,
+#endif
#ifdef HAVE_OSX
&osxPlugin,
#endif
@@ -62,17 +67,20 @@ const struct audio_output_plugin *audio_output_plugins[] = {
&solaris_output_plugin,
#endif
#ifdef HAVE_PULSE
- &pulse_plugin,
+ &pulse_output_plugin,
#endif
#ifdef HAVE_MVP
&mvp_output_plugin,
#endif
#ifdef HAVE_JACK
- &jackPlugin,
+ &jack_output_plugin,
#endif
#ifdef ENABLE_HTTPD_OUTPUT
&httpd_output_plugin,
#endif
+#ifdef ENABLE_RECORDER_OUTPUT
+ &recorder_output_plugin,
+#endif
NULL
};
diff --git a/src/output_plugin.h b/src/output_plugin.h
index 13dba0d0b..ee7f7c73d 100644
--- a/src/output_plugin.h
+++ b/src/output_plugin.h
@@ -67,6 +67,24 @@ struct audio_output_plugin {
void (*finish)(void *data);
/**
+ * Enable the device. This may allocate resources, preparing
+ * for the device to be opened. Enabling a device cannot
+ * fail: if an error occurs during that, it should be reported
+ * by the open() method.
+ *
+ * @param error_r location to store the error occuring, or
+ * NULL to ignore errors
+ * @return true on success, false on error
+ */
+ bool (*enable)(void *data, GError **error_r);
+
+ /**
+ * Disables the device. It is closed before this method is
+ * called.
+ */
+ void (*disable)(void *data);
+
+ /**
* Really open the device.
*
* @param audio_format the audio format in which data is going
@@ -99,6 +117,11 @@ struct audio_output_plugin {
GError **error);
/**
+ * Wait until the device has finished playing.
+ */
+ void (*drain)(void *data);
+
+ /**
* Try to cancel data which may still be in the device's
* buffers.
*/
@@ -150,6 +173,22 @@ ao_plugin_finish(const struct audio_output_plugin *plugin, void *data)
}
static inline bool
+ao_plugin_enable(const struct audio_output_plugin *plugin, void *data,
+ GError **error_r)
+{
+ return plugin->enable != NULL
+ ? plugin->enable(data, error_r)
+ : true;
+}
+
+static inline void
+ao_plugin_disable(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->disable != NULL)
+ plugin->disable(data);
+}
+
+static inline bool
ao_plugin_open(const struct audio_output_plugin *plugin,
void *data, struct audio_format *audio_format,
GError **error)
@@ -180,6 +219,13 @@ ao_plugin_play(const struct audio_output_plugin *plugin,
}
static inline void
+ao_plugin_drain(const struct audio_output_plugin *plugin, void *data)
+{
+ if (plugin->drain != NULL)
+ plugin->drain(data);
+}
+
+static inline void
ao_plugin_cancel(const struct audio_output_plugin *plugin, void *data)
{
if (plugin->cancel != NULL)
diff --git a/src/output_print.c b/src/output_print.c
index 11e53c32c..9cbf75c9d 100644
--- a/src/output_print.c
+++ b/src/output_print.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "output_print.h"
#include "output_internal.h"
#include "output_all.h"
diff --git a/src/output_state.c b/src/output_state.c
index c7e6c8579..81e3b0120 100644
--- a/src/output_state.c
+++ b/src/output_state.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "output_state.h"
#include "output_internal.h"
#include "output_all.h"
@@ -34,8 +35,10 @@
#define AUDIO_DEVICE_STATE "audio_device_state:"
+unsigned audio_output_state_version;
+
void
-saveAudioDevicesState(FILE *fp)
+audio_output_state_save(FILE *fp)
{
unsigned n = audio_output_count();
@@ -49,35 +52,40 @@ saveAudioDevicesState(FILE *fp)
}
}
-void
-readAudioDevicesState(FILE *fp)
+bool
+audio_output_state_read(const char *line)
{
- char buffer[1024];
+ long value;
+ char *endptr;
+ const char *name;
+ struct audio_output *ao;
- while (fgets(buffer, sizeof(buffer), fp)) {
- char *c, *name;
- struct audio_output *ao;
+ if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ return false;
- g_strchomp(buffer);
+ line += sizeof(AUDIO_DEVICE_STATE) - 1;
- if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
- continue;
+ value = strtol(line, &endptr, 10);
+ if (*endptr != ':' || (value != 0 && value != 1))
+ return false;
- c = strchr(buffer, ':');
- if (!c || !(++c))
- goto errline;
+ if (value != 0)
+ /* state is "enabled": no-op */
+ return true;
- name = strchr(c, ':');
- if (!name || !(++name))
- goto errline;
+ name = endptr + 1;
+ ao = audio_output_find(name);
+ if (ao == NULL) {
+ g_debug("Ignoring device state for '%s'", name);
+ return true;
+ }
- ao = audio_output_find(name);
- if (ao != NULL && atoi(c) == 0)
- ao->enabled = false;
+ ao->enabled = false;
+ return true;
+}
- continue;
-errline:
- /* nonfatal */
- g_warning("invalid line in state_file: %s\n", buffer);
- }
+unsigned
+audio_output_state_get_version(void)
+{
+ return audio_output_state_version;
}
diff --git a/src/output_state.h b/src/output_state.h
index 8592574ab..3b865f5fe 100644
--- a/src/output_state.h
+++ b/src/output_state.h
@@ -25,12 +25,21 @@
#ifndef OUTPUT_STATE_H
#define OUTPUT_STATE_H
+#include <stdbool.h>
#include <stdio.h>
-void
-readAudioDevicesState(FILE *fp);
+bool
+audio_output_state_read(const char *line);
void
-saveAudioDevicesState(FILE *fp);
+audio_output_state_save(FILE *fp);
+
+/**
+ * Generates a version number for the current state of the audio
+ * outputs. This is used by timer_save_state_file() to determine
+ * whether the state has changed and the state file should be saved.
+ */
+unsigned
+audio_output_state_get_version(void);
#endif
diff --git a/src/output_thread.c b/src/output_thread.c
index 770b377e8..fccbad5eb 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -17,12 +17,15 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "output_thread.h"
#include "output_api.h"
#include "output_internal.h"
#include "chunk.h"
#include "pipe.h"
#include "player_control.h"
+#include "filter_plugin.h"
+#include "filter/convert_filter_plugin.h"
#include <glib.h>
@@ -37,27 +40,208 @@ static void ao_command_finished(struct audio_output *ao)
{
assert(ao->command != AO_COMMAND_NONE);
ao->command = AO_COMMAND_NONE;
+
+ g_mutex_unlock(ao->mutex);
notify_signal(&audio_output_client_notify);
+ g_mutex_lock(ao->mutex);
+}
+
+static bool
+ao_enable(struct audio_output *ao)
+{
+ GError *error = NULL;
+ bool success;
+
+ if (ao->really_enabled)
+ return true;
+
+ g_mutex_unlock(ao->mutex);
+ success = ao_plugin_enable(ao->plugin, ao->data, &error);
+ g_mutex_lock(ao->mutex);
+ if (!success) {
+ g_warning("Failed to enable \"%s\" [%s]: %s\n",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+ return false;
+ }
+
+ ao->really_enabled = true;
+ return true;
+}
+
+static void
+ao_close(struct audio_output *ao, bool drain);
+
+static void
+ao_disable(struct audio_output *ao)
+{
+ if (ao->open)
+ ao_close(ao, false);
+
+ if (ao->really_enabled) {
+ ao->really_enabled = false;
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_disable(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+ }
+}
+
+static void
+ao_open(struct audio_output *ao)
+{
+ bool success;
+ GError *error = NULL;
+ const struct audio_format *filter_audio_format;
+ struct audio_format_string af_string;
+
+ assert(!ao->open);
+ assert(ao->fail_timer == NULL);
+ assert(ao->pipe != NULL);
+ assert(ao->chunk == NULL);
+
+ /* enable the device (just in case the last enable has failed) */
+
+ if (!ao_enable(ao))
+ /* still no luck */
+ return;
+
+ /* open the filter */
+
+ filter_audio_format = filter_open(ao->filter, &ao->in_audio_format,
+ &error);
+ if (filter_audio_format == NULL) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ ao->out_audio_format = *filter_audio_format;
+ audio_format_mask_apply(&ao->out_audio_format,
+ &ao->config_audio_format);
+
+ g_mutex_unlock(ao->mutex);
+ success = ao_plugin_open(ao->plugin, ao->data,
+ &ao->out_audio_format,
+ &error);
+ g_mutex_lock(ao->mutex);
+
+ assert(!ao->open);
+
+ if (!success) {
+ g_warning("Failed to open \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ filter_close(ao->filter);
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+
+ ao->open = true;
+
+ g_debug("opened plugin=%s name=\"%s\" "
+ "audio_format=%s",
+ ao->plugin->name, ao->name,
+ audio_format_to_string(&ao->out_audio_format, &af_string));
+
+ if (!audio_format_equals(&ao->in_audio_format,
+ &ao->out_audio_format))
+ g_debug("converting from %s",
+ audio_format_to_string(&ao->in_audio_format,
+ &af_string));
}
static void
-ao_close(struct audio_output *ao)
+ao_close(struct audio_output *ao, bool drain)
{
assert(ao->open);
ao->pipe = NULL;
- g_mutex_lock(ao->mutex);
ao->chunk = NULL;
ao->open = false;
+
g_mutex_unlock(ao->mutex);
+ if (drain)
+ ao_plugin_drain(ao->plugin, ao->data);
+ else
+ ao_plugin_cancel(ao->plugin, ao->data);
+
ao_plugin_close(ao->plugin, ao->data);
- pcm_convert_deinit(&ao->convert_state);
+ filter_close(ao->filter);
+
+ g_mutex_lock(ao->mutex);
g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
}
+static void
+ao_reopen_filter(struct audio_output *ao)
+{
+ const struct audio_format *filter_audio_format;
+ GError *error = NULL;
+
+ filter_close(ao->filter);
+ filter_audio_format = filter_open(ao->filter, &ao->in_audio_format,
+ &error);
+ if (filter_audio_format == NULL) {
+ g_warning("Failed to open filter for \"%s\" [%s]: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ /* this is a little code duplication fro ao_close(),
+ but we cannot call this function because we must
+ not call filter_close(ao->filter) again */
+
+ ao->pipe = NULL;
+
+ ao->chunk = NULL;
+ ao->open = false;
+ ao->fail_timer = g_timer_new();
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_close(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+}
+
+static void
+ao_reopen(struct audio_output *ao)
+{
+ if (!audio_format_fully_defined(&ao->config_audio_format)) {
+ if (ao->open) {
+ const struct music_pipe *mp = ao->pipe;
+ ao_close(ao, true);
+ ao->pipe = mp;
+ }
+
+ /* no audio format is configured: copy in->out, let
+ the output's open() method determine the effective
+ out_audio_format */
+ ao->out_audio_format = ao->in_audio_format;
+ audio_format_mask_apply(&ao->out_audio_format,
+ &ao->config_audio_format);
+ }
+
+ if (ao->open)
+ /* the audio format has changed, and all filters have
+ to be reconfigured */
+ ao_reopen_filter(ao);
+ else
+ ao_open(ao);
+}
+
static bool
ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
{
@@ -65,43 +249,49 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
size_t size = chunk->length;
GError *error = NULL;
+ assert(ao != NULL);
+ assert(ao->filter != NULL);
assert(!music_chunk_is_empty(chunk));
assert(music_chunk_check_format(chunk, &ao->in_audio_format));
assert(size % audio_format_frame_size(&ao->in_audio_format) == 0);
- if (chunk->tag != NULL)
+ if (chunk->tag != NULL) {
+ g_mutex_unlock(ao->mutex);
ao_plugin_send_tag(ao->plugin, ao->data, chunk->tag);
+ g_mutex_lock(ao->mutex);
+ }
if (size == 0)
return true;
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format)) {
- data = pcm_convert(&ao->convert_state,
- &ao->in_audio_format, data, size,
- &ao->out_audio_format, &size);
-
- /* under certain circumstances, pcm_convert() may
- return an empty buffer - this condition should be
- investigated further, but for now, do this check as
- a workaround: */
- if (data == NULL)
- return true;
+ data = filter_filter(ao->filter, data, size, &size, &error);
+ if (data == NULL) {
+ g_warning("\"%s\" [%s] failed to filter: %s",
+ ao->name, ao->plugin->name, error->message);
+ g_error_free(error);
+
+ ao_close(ao, false);
+
+ /* don't automatically reopen this device for 10
+ seconds */
+ ao->fail_timer = g_timer_new();
+ return false;
}
while (size > 0 && ao->command == AO_COMMAND_NONE) {
size_t nbytes;
+ g_mutex_unlock(ao->mutex);
nbytes = ao_plugin_play(ao->plugin, ao->data, data, size,
&error);
+ g_mutex_lock(ao->mutex);
if (nbytes == 0) {
/* play()==0 means failure */
g_warning("\"%s\" [%s] failed to play: %s",
ao->name, ao->plugin->name, error->message);
g_error_free(error);
- ao_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
/* don't automatically reopen this device for
10 seconds */
@@ -119,32 +309,45 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
return true;
}
-static void ao_play(struct audio_output *ao)
+static const struct music_chunk *
+ao_next_chunk(struct audio_output *ao)
+{
+ return ao->chunk != NULL
+ /* continue the previous play() call */
+ ? ao->chunk->next
+ /* get the first chunk from the pipe */
+ : music_pipe_peek(ao->pipe);
+}
+
+/**
+ * Plays all remaining chunks, until the tail of the pipe has been
+ * reached (and no more chunks are queued), or until a command is
+ * received.
+ *
+ * @return true if at least one chunk has been available, false if the
+ * tail of the pipe was already reached
+ */
+static bool
+ao_play(struct audio_output *ao)
{
bool success;
const struct music_chunk *chunk;
assert(ao->pipe != NULL);
- g_mutex_lock(ao->mutex);
- chunk = ao->chunk;
- if (chunk != NULL)
- /* continue the previous play() call */
- chunk = chunk->next;
- else
- chunk = music_pipe_peek(ao->pipe);
+ chunk = ao_next_chunk(ao);
+ if (chunk == NULL)
+ /* no chunk available */
+ return false;
+
ao->chunk_finished = false;
while (chunk != NULL && ao->command == AO_COMMAND_NONE) {
assert(!ao->chunk_finished);
ao->chunk = chunk;
- g_mutex_unlock(ao->mutex);
success = ao_play_chunk(ao, chunk);
-
- g_mutex_lock(ao->mutex);
-
if (!success) {
assert(ao->chunk == NULL);
break;
@@ -155,23 +358,32 @@ static void ao_play(struct audio_output *ao)
}
ao->chunk_finished = true;
+
g_mutex_unlock(ao->mutex);
+ player_lock_signal();
+ g_mutex_lock(ao->mutex);
- notify_signal(&pc.notify);
+ return true;
}
static void ao_pause(struct audio_output *ao)
{
bool ret;
+ g_mutex_unlock(ao->mutex);
ao_plugin_cancel(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
ao->pause = true;
ao_command_finished(ao);
do {
+ g_mutex_unlock(ao->mutex);
ret = ao_plugin_pause(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+
if (!ret) {
- ao_close(ao);
+ ao_close(ao, false);
break;
}
} while (ao->command == AO_COMMAND_NONE);
@@ -182,56 +394,31 @@ static void ao_pause(struct audio_output *ao)
static gpointer audio_output_task(gpointer arg)
{
struct audio_output *ao = arg;
- bool ret;
- GError *error;
+
+ g_mutex_lock(ao->mutex);
while (1) {
switch (ao->command) {
case AO_COMMAND_NONE:
break;
- case AO_COMMAND_OPEN:
- assert(!ao->open);
- assert(ao->fail_timer == NULL);
- assert(ao->pipe != NULL);
- assert(ao->chunk == NULL);
-
- error = NULL;
- ret = ao_plugin_open(ao->plugin, ao->data,
- &ao->out_audio_format,
- &error);
-
- assert(!ao->open);
- if (ret) {
- pcm_convert_init(&ao->convert_state);
+ case AO_COMMAND_ENABLE:
+ ao_enable(ao);
+ ao_command_finished(ao);
+ break;
- g_mutex_lock(ao->mutex);
- ao->open = true;
- g_mutex_unlock(ao->mutex);
+ case AO_COMMAND_DISABLE:
+ ao_disable(ao);
+ ao_command_finished(ao);
+ break;
- g_debug("opened plugin=%s name=\"%s\" "
- "audio_format=%u:%u:%u",
- ao->plugin->name,
- ao->name,
- ao->out_audio_format.sample_rate,
- ao->out_audio_format.bits,
- ao->out_audio_format.channels);
-
- if (!audio_format_equals(&ao->in_audio_format,
- &ao->out_audio_format))
- g_debug("converting from %u:%u:%u",
- ao->in_audio_format.sample_rate,
- ao->in_audio_format.bits,
- ao->in_audio_format.channels);
- } else {
- g_warning("Failed to open \"%s\" [%s]: %s",
- ao->name, ao->plugin->name,
- error->message);
- g_error_free(error);
-
- ao->fail_timer = g_timer_new();
- }
+ case AO_COMMAND_OPEN:
+ ao_open(ao);
+ ao_command_finished(ao);
+ break;
+ case AO_COMMAND_REOPEN:
+ ao_reopen(ao);
ao_command_finished(ao);
break;
@@ -239,11 +426,7 @@ static gpointer audio_output_task(gpointer arg)
assert(ao->open);
assert(ao->pipe != NULL);
- ao->pipe = NULL;
- ao->chunk = NULL;
-
- ao_plugin_cancel(ao->plugin, ao->data);
- ao_close(ao);
+ ao_close(ao, false);
ao_command_finished(ao);
break;
@@ -264,6 +447,19 @@ static gpointer audio_output_task(gpointer arg)
the new command first */
continue;
+ case AO_COMMAND_DRAIN:
+ if (ao->open) {
+ assert(ao->chunk == NULL);
+ assert(music_pipe_peek(ao->pipe) == NULL);
+
+ g_mutex_unlock(ao->mutex);
+ ao_plugin_drain(ao->plugin, ao->data);
+ g_mutex_lock(ao->mutex);
+ }
+
+ ao_command_finished(ao);
+ continue;
+
case AO_COMMAND_CANCEL:
ao->chunk = NULL;
if (ao->open)
@@ -273,19 +469,24 @@ static gpointer audio_output_task(gpointer arg)
/* the player thread will now clear our music
pipe - wait for a notify, to give it some
time */
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
continue;
case AO_COMMAND_KILL:
ao->chunk = NULL;
ao_command_finished(ao);
+ g_mutex_unlock(ao->mutex);
return NULL;
}
- if (ao->open)
- ao_play(ao);
+ if (ao->open && ao_play(ao))
+ /* don't wait for an event if there are more
+ chunks in the pipe */
+ continue;
- notify_wait(&ao->notify);
+ if (ao->command == AO_COMMAND_NONE)
+ g_cond_wait(ao->cond, ao->mutex);
}
}
diff --git a/src/page.c b/src/page.c
index 5ea03cd02..537137697 100644
--- a/src/page.c
+++ b/src/page.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "page.h"
#include <glib.h>
diff --git a/src/path.c b/src/path.c
index fc73ee7c9..62732fcb4 100644
--- a/src/path.c
+++ b/src/path.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "path.h"
#include "conf.h"
diff --git a/src/pcm_byteswap.c b/src/pcm_byteswap.c
new file mode 100644
index 000000000..5bd23398d
--- /dev/null
+++ b/src/pcm_byteswap.c
@@ -0,0 +1,70 @@
+/*
+ * 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 "pcm_byteswap.h"
+#include "pcm_buffer.h"
+
+#include <glib.h>
+
+#include <assert.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "pcm"
+
+static inline uint16_t swab16(uint16_t x)
+{
+ return (x << 8) | (x >> 8);
+}
+
+const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
+ const int16_t *src, size_t len)
+{
+ unsigned i;
+ int16_t *buf = pcm_buffer_get(buffer, len);
+
+ assert(buf != NULL);
+
+ for (i = 0; i < len / 2; i++)
+ buf[i] = swab16(src[i]);
+
+ return buf;
+}
+
+static inline uint32_t swab32(uint32_t x)
+{
+ return (x << 24) |
+ ((x & 0xff00) << 8) |
+ ((x & 0xff0000) >> 8) |
+ (x >> 24);
+}
+
+const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
+ const int32_t *src, size_t len)
+{
+ unsigned i;
+ int32_t *buf = pcm_buffer_get(buffer, len);
+
+ assert(buf != NULL);
+
+ for (i = 0; i < len / 4; i++)
+ buf[i] = swab32(src[i]);
+
+ return buf;
+}
diff --git a/src/pcm_byteswap.h b/src/pcm_byteswap.h
new file mode 100644
index 000000000..e1196d9b2
--- /dev/null
+++ b/src/pcm_byteswap.h
@@ -0,0 +1,50 @@
+/*
+ * 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_PCM_BYTESWAP_H
+#define MPD_PCM_BYTESWAP_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct pcm_buffer;
+
+/**
+ * Changes the endianness of 16 bit PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @return the destination buffer
+ */
+const int16_t *pcm_byteswap_16(struct pcm_buffer *buffer,
+ const int16_t *src, size_t len);
+
+/**
+ * Changes the endianness of 32-bit (or 24-bit) PCM data.
+ *
+ * @param buffer the destination pcm_buffer object
+ * @param src the source PCM buffer
+ * @param src_size the number of bytes in #src
+ * @return the destination buffer
+ */
+const int32_t *pcm_byteswap_32(struct pcm_buffer *buffer,
+ const int32_t *src, size_t len);
+
+#endif
diff --git a/src/pcm_channels.c b/src/pcm_channels.c
index 969ddff32..d82e46a67 100644
--- a/src/pcm_channels.c
+++ b/src/pcm_channels.c
@@ -17,16 +17,12 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_channels.h"
#include "pcm_buffer.h"
-#include <glib.h>
-
#include <assert.h>
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
static void
pcm_convert_channels_16_1_to_2(int16_t *dest, const int16_t *src,
unsigned num_frames)
@@ -75,8 +71,8 @@ pcm_convert_channels_16_n_to_2(int16_t *dest,
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int16_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -92,11 +88,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_16_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
@@ -149,8 +142,8 @@ pcm_convert_channels_24_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -166,11 +159,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_24_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
@@ -218,8 +208,8 @@ pcm_convert_channels_32_n_to_2(int32_t *dest,
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_frames = src_size / src_channels / sizeof(*src);
@@ -235,11 +225,8 @@ pcm_convert_channels_32(struct pcm_buffer *buffer,
else if (dest_channels == 2)
pcm_convert_channels_32_n_to_2(dest, src_channels, src,
num_frames);
- else {
- g_warning("conversion %u->%u channels is not supported",
- src_channels, dest_channels);
+ else
return NULL;
- }
return dest;
}
diff --git a/src/pcm_channels.h b/src/pcm_channels.h
index accf4b07b..13b7ffbf3 100644
--- a/src/pcm_channels.h
+++ b/src/pcm_channels.h
@@ -38,8 +38,8 @@ struct pcm_buffer;
*/
const int16_t *
pcm_convert_channels_16(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int16_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int16_t *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -56,8 +56,8 @@ pcm_convert_channels_16(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_24(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -73,8 +73,8 @@ pcm_convert_channels_24(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_channels_32(struct pcm_buffer *buffer,
- int8_t dest_channels,
- int8_t src_channels, const int32_t *src,
+ uint8_t dest_channels,
+ uint8_t src_channels, const int32_t *src,
size_t src_size, size_t *dest_size_r);
#endif
diff --git a/src/pcm_convert.c b/src/pcm_convert.c
index ebb4adff5..8d529dd5f 100644
--- a/src/pcm_convert.c
+++ b/src/pcm_convert.c
@@ -17,9 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_convert.h"
#include "pcm_channels.h"
#include "pcm_format.h"
+#include "pcm_byteswap.h"
#include "audio_format.h"
#include <assert.h>
@@ -39,6 +41,7 @@ void pcm_convert_init(struct pcm_convert_state *state)
pcm_buffer_init(&state->format_buffer);
pcm_buffer_init(&state->channels_buffer);
+ pcm_buffer_init(&state->byteswap_buffer);
}
void pcm_convert_deinit(struct pcm_convert_state *state)
@@ -47,41 +50,60 @@ void pcm_convert_deinit(struct pcm_convert_state *state)
pcm_buffer_deinit(&state->format_buffer);
pcm_buffer_deinit(&state->channels_buffer);
+ pcm_buffer_deinit(&state->byteswap_buffer);
}
static const int16_t *
pcm_convert_16(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int16_t *buf;
size_t len;
- assert(dest_format->bits == 16);
+ assert(dest_format->format == SAMPLE_FORMAT_S16);
buf = pcm_convert_to_16(&state->format_buffer, &state->dither,
- src_format->bits, src_buffer, src_size,
+ src_format->format, src_buffer, src_size,
&len);
- if (!buf)
- g_error("pcm_convert_to_16() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 16 bit is not implemented",
+ sample_format_to_string(src_format->format));
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_16(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_16() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format->channels,
+ dest_format->channels);
+ return NULL;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_16(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ if (dest_format->reverse_endian) {
+ buf = pcm_byteswap_16(&state->byteswap_buffer, buf, len);
+ assert(buf != NULL);
+ }
*dest_size_r = len;
return buf;
@@ -91,34 +113,52 @@ static const int32_t *
pcm_convert_24(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int32_t *buf;
size_t len;
- assert(dest_format->bits == 24);
+ assert(dest_format->format == SAMPLE_FORMAT_S24_P32);
- buf = pcm_convert_to_24(&state->format_buffer, src_format->bits,
+ buf = pcm_convert_to_24(&state->format_buffer, src_format->format,
src_buffer, src_size, &len);
- if (!buf)
- g_error("pcm_convert_to_24() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 24 bit is not implemented",
+ sample_format_to_string(src_format->format));
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_24(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_24() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format->channels,
+ dest_format->channels);
+ return NULL;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_24(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ return NULL;
+ }
+
+ if (dest_format->reverse_endian) {
+ buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len);
+ assert(buf != NULL);
+ }
*dest_size_r = len;
return buf;
@@ -128,34 +168,52 @@ static const int32_t *
pcm_convert_32(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src_buffer, size_t src_size,
- const struct audio_format *dest_format,
- size_t *dest_size_r)
+ const struct audio_format *dest_format, size_t *dest_size_r,
+ GError **error_r)
{
const int32_t *buf;
size_t len;
- assert(dest_format->bits == 32);
+ assert(dest_format->format == SAMPLE_FORMAT_S32);
- buf = pcm_convert_to_32(&state->format_buffer, src_format->bits,
+ buf = pcm_convert_to_32(&state->format_buffer, src_format->format,
src_buffer, src_size, &len);
- if (!buf)
- g_error("pcm_convert_to_32() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %s to 24 bit is not implemented",
+ sample_format_to_string(src_format->format));
+ return NULL;
+ }
if (src_format->channels != dest_format->channels) {
buf = pcm_convert_channels_32(&state->channels_buffer,
dest_format->channels,
src_format->channels,
buf, len, &len);
- if (!buf)
- g_error("pcm_convert_channels_32() failed");
+ if (buf == NULL) {
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "Conversion from %u to %u channels "
+ "is not implemented",
+ src_format->channels,
+ dest_format->channels);
+ return NULL;
+ }
}
- if (src_format->sample_rate != dest_format->sample_rate)
+ if (src_format->sample_rate != dest_format->sample_rate) {
buf = pcm_resample_32(&state->resample,
dest_format->channels,
src_format->sample_rate, buf, len,
- dest_format->sample_rate,
- &len);
+ dest_format->sample_rate, &len,
+ error_r);
+ if (buf == NULL)
+ return buf;
+ }
+
+ if (dest_format->reverse_endian) {
+ buf = pcm_byteswap_32(&state->byteswap_buffer, buf, len);
+ assert(buf != NULL);
+ }
*dest_size_r = len;
return buf;
@@ -166,26 +224,32 @@ pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src, size_t src_size,
const struct audio_format *dest_format,
- size_t *dest_size_r)
+ size_t *dest_size_r,
+ GError **error_r)
{
- switch (dest_format->bits) {
- case 16:
+ switch (dest_format->format) {
+ case SAMPLE_FORMAT_S16:
return pcm_convert_16(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
return pcm_convert_24(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
- case 32:
+ case SAMPLE_FORMAT_S32:
return pcm_convert_32(state,
src_format, src, src_size,
- dest_format, dest_size_r);
+ dest_format, dest_size_r,
+ error_r);
default:
- g_error("cannot convert to %u bit\n", dest_format->bits);
+ g_set_error(error_r, pcm_convert_quark(), 0,
+ "PCM conversion to %s is not implemented",
+ sample_format_to_string(dest_format->format));
return NULL;
}
}
diff --git a/src/pcm_convert.h b/src/pcm_convert.h
index be08ad8a8..7ef0782df 100644
--- a/src/pcm_convert.h
+++ b/src/pcm_convert.h
@@ -41,8 +41,17 @@ struct pcm_convert_state {
/** the buffer for converting the channel count */
struct pcm_buffer channels_buffer;
+
+ /** the buffer for swapping the byte order */
+ struct pcm_buffer byteswap_buffer;
};
+static inline GQuark
+pcm_convert_quark(void)
+{
+ return g_quark_from_static_string("pcm_convert");
+}
+
/**
* Initializes a pcm_convert_state object.
*/
@@ -63,13 +72,16 @@ void pcm_convert_deinit(struct pcm_convert_state *state);
* @param src_size the size of #src in bytes
* @param dest_format the requested destination audio format
* @param dest_size_r returns the number of bytes of the destination buffer
- * @return the destination buffer
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return the destination buffer, or NULL on error
*/
const void *
pcm_convert(struct pcm_convert_state *state,
const struct audio_format *src_format,
const void *src, size_t src_size,
const struct audio_format *dest_format,
- size_t *dest_size_r);
+ size_t *dest_size_r,
+ GError **error_r);
#endif
diff --git a/src/pcm_dither.c b/src/pcm_dither.c
index 45c11790c..0d1c7e004 100644
--- a/src/pcm_dither.c
+++ b/src/pcm_dither.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_dither.h"
#include "pcm_prng.h"
diff --git a/src/pcm_format.c b/src/pcm_format.c
index 0e686e17c..b0dad2ba3 100644
--- a/src/pcm_format.c
+++ b/src/pcm_format.c
@@ -17,12 +17,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_format.h"
#include "pcm_dither.h"
#include "pcm_buffer.h"
-#include <glib.h>
-
static void
pcm_convert_8_to_16(int16_t *out, const int8_t *in,
unsigned num_samples)
@@ -51,14 +50,17 @@ pcm_convert_32_to_16(struct pcm_dither *dither,
const int16_t *
pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int16_t *dest;
- switch (bits) {
- case 8:
+ switch (src_format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+
+ case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -68,11 +70,11 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
num_samples);
return dest;
- case 16:
+ case SAMPLE_FORMAT_S16:
*dest_size_r = src_size;
return src;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -82,7 +84,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
num_samples);
return dest;
- case 32:
+ case SAMPLE_FORMAT_S32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -93,7 +95,6 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
return dest;
}
- g_warning("only 8 or 16 bits are supported for conversion!\n");
return NULL;
}
@@ -129,14 +130,17 @@ pcm_convert_32_to_24(int32_t *out, const int16_t *in,
const int32_t *
pcm_convert_to_24(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int32_t *dest;
- switch (bits) {
- case 8:
+ switch (src_format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+
+ case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -145,7 +149,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 16:
+ case SAMPLE_FORMAT_S16:
num_samples = src_size / 2;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -154,11 +158,11 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
*dest_size_r = src_size;
return src;
- case 32:
+ case SAMPLE_FORMAT_S32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -168,7 +172,6 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
return dest;
}
- g_warning("only 8 or 24 bits are supported for conversion!\n");
return NULL;
}
@@ -204,14 +207,17 @@ pcm_convert_24_to_32(int32_t *out, const int32_t *in,
const int32_t *
pcm_convert_to_32(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r)
{
unsigned num_samples;
int32_t *dest;
- switch (bits) {
- case 8:
+ switch (src_format) {
+ case SAMPLE_FORMAT_UNDEFINED:
+ break;
+
+ case SAMPLE_FORMAT_S8:
num_samples = src_size;
*dest_size_r = src_size * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -220,7 +226,7 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 16:
+ case SAMPLE_FORMAT_S16:
num_samples = src_size / 2;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -229,7 +235,7 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
num_samples = src_size / 4;
*dest_size_r = num_samples * sizeof(*dest);
dest = pcm_buffer_get(buffer, *dest_size_r);
@@ -238,11 +244,10 @@ pcm_convert_to_32(struct pcm_buffer *buffer,
num_samples);
return dest;
- case 32:
+ case SAMPLE_FORMAT_S32:
*dest_size_r = src_size;
return src;
}
- g_warning("only 8 or 32 bits are supported for conversion!\n");
return NULL;
}
diff --git a/src/pcm_format.h b/src/pcm_format.h
index 350566827..6ea5573bd 100644
--- a/src/pcm_format.h
+++ b/src/pcm_format.h
@@ -20,6 +20,8 @@
#ifndef PCM_FORMAT_H
#define PCM_FORMAT_H
+#include "audio_format.h"
+
#include <stdint.h>
#include <stddef.h>
@@ -40,7 +42,7 @@ struct pcm_dither;
*/
const int16_t *
pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -55,7 +57,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither,
*/
const int32_t *
pcm_convert_to_24(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
/**
@@ -70,7 +72,7 @@ pcm_convert_to_24(struct pcm_buffer *buffer,
*/
const int32_t *
pcm_convert_to_32(struct pcm_buffer *buffer,
- uint8_t bits, const void *src,
+ enum sample_format src_format, const void *src,
size_t src_size, size_t *dest_size_r);
#endif
diff --git a/src/pcm_mix.c b/src/pcm_mix.c
index d1e716731..f5a8eb90e 100644
--- a/src/pcm_mix.c
+++ b/src/pcm_mix.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_mix.h"
#include "pcm_volume.h"
#include "pcm_utils.h"
@@ -81,29 +82,53 @@ pcm_add_24(int32_t *buffer1, const int32_t *buffer2,
}
static void
+pcm_add_32(int32_t *buffer1, const int32_t *buffer2,
+ unsigned num_samples, unsigned volume1, unsigned volume2)
+{
+ while (num_samples > 0) {
+ int64_t sample1 = *buffer1;
+ int64_t sample2 = *buffer2++;
+
+ sample1 = ((sample1 * volume1 + sample2 * volume2) +
+ pcm_volume_dither() + PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+
+ *buffer1++ = pcm_range_64(sample1, 32);
+ --num_samples;
+ }
+}
+
+static void
pcm_add(void *buffer1, const void *buffer2, size_t size,
int vol1, int vol2,
const struct audio_format *format)
{
- switch (format->bits) {
- case 8:
+ switch (format->format) {
+ case SAMPLE_FORMAT_S8:
pcm_add_8((int8_t *)buffer1, (const int8_t *)buffer2,
size, vol1, vol2);
break;
- case 16:
+ case SAMPLE_FORMAT_S16:
pcm_add_16((int16_t *)buffer1, (const int16_t *)buffer2,
size / 2, vol1, vol2);
break;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
pcm_add_24((int32_t*)buffer1,
(const int32_t*)buffer2,
size / 4, vol1, vol2);
break;
+ case SAMPLE_FORMAT_S32:
+ pcm_add_32((int32_t*)buffer1,
+ (const int32_t*)buffer2,
+ size / 4, vol1, vol2);
+ break;
+
default:
- g_error("%u bits not supported by pcm_add!\n", format->bits);
+ g_error("format %s not supported by pcm_add",
+ sample_format_to_string(format->format));
}
}
diff --git a/src/pcm_resample.c b/src/pcm_resample.c
index d1360d02a..fea499e07 100644
--- a/src/pcm_resample.c
+++ b/src/pcm_resample.c
@@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "pcm_resample_internal.h"
#include "config.h"
+#include "pcm_resample_internal.h"
#ifdef HAVE_LIBSAMPLERATE
#include "conf.h"
@@ -62,16 +62,18 @@ void pcm_resample_deinit(struct pcm_resample_state *state)
const int16_t *
pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
- unsigned src_rate,
- const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned src_rate, const int16_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
#ifdef HAVE_LIBSAMPLERATE
if (pcm_resample_lsr_enabled())
return pcm_resample_lsr_16(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
#endif
return pcm_resample_fallback_16(state, channels,
@@ -82,16 +84,18 @@ pcm_resample_16(struct pcm_resample_state *state,
const int32_t *
pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
- unsigned src_rate,
- const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned src_rate, const int32_t *src_buffer, size_t src_size,
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
#ifdef HAVE_LIBSAMPLERATE
if (pcm_resample_lsr_enabled())
return pcm_resample_lsr_32(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r,
+ error_r);
+#else
+ (void)error_r;
#endif
return pcm_resample_fallback_32(state, channels,
diff --git a/src/pcm_resample.h b/src/pcm_resample.h
index 44720f7b2..a17b12d8b 100644
--- a/src/pcm_resample.h
+++ b/src/pcm_resample.h
@@ -20,8 +20,8 @@
#ifndef MPD_PCM_RESAMPLE_H
#define MPD_PCM_RESAMPLE_H
+#include "check.h"
#include "pcm_buffer.h"
-#include "config.h"
#include <stdint.h>
#include <stddef.h>
@@ -48,7 +48,7 @@ struct pcm_resample_state {
uint8_t channels;
} prev;
- bool error;
+ int error;
#endif
struct pcm_buffer buffer;
@@ -82,8 +82,8 @@ pcm_resample_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
/**
* Resamples 32 bit PCM data.
@@ -102,8 +102,8 @@ pcm_resample_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
/**
* Resamples 24 bit PCM data.
@@ -122,14 +122,14 @@ pcm_resample_24(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
/* reuse the 32 bit code - the resampler code doesn't care if
the upper 8 bits are actually used */
return pcm_resample_32(state, channels,
src_rate, src_buffer, src_size,
- dest_rate, dest_size_r);
+ dest_rate, dest_size_r, error_r);
}
#endif
diff --git a/src/pcm_resample_fallback.c b/src/pcm_resample_fallback.c
index 36af51ad0..fcc97d9cd 100644
--- a/src/pcm_resample_fallback.c
+++ b/src/pcm_resample_fallback.c
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_resample_internal.h"
#include <assert.h>
-#include <glib.h>
void
pcm_resample_fallback_deinit(struct pcm_resample_state *state)
@@ -74,8 +74,7 @@ const int32_t *
pcm_resample_fallback_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
- const int32_t *src_buffer,
- G_GNUC_UNUSED size_t src_size,
+ const int32_t *src_buffer, size_t src_size,
unsigned dest_rate,
size_t *dest_size_r)
{
diff --git a/src/pcm_resample_internal.h b/src/pcm_resample_internal.h
index a10ba08cd..cdb3c9a2f 100644
--- a/src/pcm_resample_internal.h
+++ b/src/pcm_resample_internal.h
@@ -27,8 +27,8 @@
#ifndef MPD_PCM_RESAMPLE_INTERNAL_H
#define MPD_PCM_RESAMPLE_INTERNAL_H
+#include "check.h"
#include "pcm_resample.h"
-#include "config.h"
#ifdef HAVE_LIBSAMPLERATE
@@ -40,8 +40,8 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
const int32_t *
pcm_resample_lsr_32(struct pcm_resample_state *state,
@@ -49,8 +49,8 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
unsigned src_rate,
const int32_t *src_buffer,
G_GNUC_UNUSED size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r);
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r);
#endif
diff --git a/src/pcm_resample_libsamplerate.c b/src/pcm_resample_libsamplerate.c
index 6d019e892..71b76b86d 100644
--- a/src/pcm_resample_libsamplerate.c
+++ b/src/pcm_resample_libsamplerate.c
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_resample_internal.h"
#include "conf.h"
-#include "config.h"
#include <glib.h>
@@ -30,6 +30,12 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "pcm"
+static inline GQuark
+libsamplerate_quark(void)
+{
+ return g_quark_from_static_string("libsamplerate");
+}
+
void
pcm_resample_lsr_deinit(struct pcm_resample_state *state)
{
@@ -77,9 +83,10 @@ out:
return convalgo;
}
-static void
+static bool
pcm_resample_set(struct pcm_resample_state *state,
- uint8_t channels, unsigned src_rate, unsigned dest_rate)
+ uint8_t channels, unsigned src_rate, unsigned dest_rate,
+ GError **error_r)
{
static int convalgo = -1;
int error;
@@ -92,9 +99,9 @@ pcm_resample_set(struct pcm_resample_state *state,
if (channels == state->prev.channels &&
src_rate == state->prev.src_rate &&
dest_rate == state->prev.dest_rate)
- return;
+ return true;
- state->error = false;
+ state->error = 0;
state->prev.channels = channels;
state->prev.src_rate = src_rate;
state->prev.dest_rate = dest_rate;
@@ -104,16 +111,18 @@ pcm_resample_set(struct pcm_resample_state *state,
state->state = src_new(convalgo, channels, &error);
if (!state->state) {
- g_warning("cannot create new libsamplerate state: %s",
- src_strerror(error));
- state->error = true;
- return;
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate initialization has failed: %s",
+ src_strerror(error));
+ return false;
}
data->src_ratio = (double)dest_rate / (double)src_rate;
g_debug("setting samplerate conversion ratio to %.2lf",
data->src_ratio);
src_set_ratio(state->state, data->src_ratio);
+
+ return true;
}
const int16_t *
@@ -121,9 +130,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int16_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
+ bool success;
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
@@ -132,11 +142,18 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
- pcm_resample_set(state, channels, src_rate, dest_rate);
+ success = pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r);
+ if (!success)
+ return NULL;
/* there was an error previously, and nothing has changed */
- if (state->error)
+ if (state->error) {
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate has failed: %s",
+ src_strerror(state->error));
return NULL;
+ }
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
@@ -151,9 +168,10 @@ pcm_resample_lsr_16(struct pcm_resample_state *state,
error = src_process(state->state, data);
if (error) {
- g_warning("error processing samples with libsamplerate: %s",
- src_strerror(error));
- state->error = true;
+ g_set_error(error_r, libsamplerate_quark(), error,
+ "libsamplerate has failed: %s",
+ src_strerror(error));
+ state->error = error;
return NULL;
}
@@ -191,9 +209,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
uint8_t channels,
unsigned src_rate,
const int32_t *src_buffer, size_t src_size,
- unsigned dest_rate,
- size_t *dest_size_r)
+ unsigned dest_rate, size_t *dest_size_r,
+ GError **error_r)
{
+ bool success;
SRC_DATA *data = &state->data;
size_t data_in_size;
size_t data_out_size;
@@ -202,11 +221,18 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
assert((src_size % (sizeof(*src_buffer) * channels)) == 0);
- pcm_resample_set(state, channels, src_rate, dest_rate);
+ success = pcm_resample_set(state, channels, src_rate, dest_rate,
+ error_r);
+ if (!success)
+ return NULL;
/* there was an error previously, and nothing has changed */
- if (state->error)
+ if (state->error) {
+ g_set_error(error_r, libsamplerate_quark(), state->error,
+ "libsamplerate has failed: %s",
+ src_strerror(state->error));
return NULL;
+ }
data->input_frames = src_size / sizeof(*src_buffer) / channels;
data_in_size = data->input_frames * sizeof(float) * channels;
@@ -221,9 +247,10 @@ pcm_resample_lsr_32(struct pcm_resample_state *state,
error = src_process(state->state, data);
if (error) {
- g_warning("error processing samples with libsamplerate: %s",
- src_strerror(error));
- state->error = true;
+ g_set_error(error_r, libsamplerate_quark(), error,
+ "libsamplerate has failed: %s",
+ src_strerror(error));
+ state->error = error;
return NULL;
}
diff --git a/src/pcm_utils.h b/src/pcm_utils.h
index 93f414231..8b2259655 100644
--- a/src/pcm_utils.h
+++ b/src/pcm_utils.h
@@ -38,4 +38,18 @@ pcm_range(int32_t sample, unsigned bits)
return sample;
}
+/**
+ * Check if the value is within the range of the provided bit size,
+ * and caps it if necessary.
+ */
+static inline int64_t
+pcm_range_64(int64_t sample, unsigned bits)
+{
+ if (G_UNLIKELY(sample < ((int64_t)-1 << (bits - 1))))
+ return (int64_t)-1 << (bits - 1);
+ if (G_UNLIKELY(sample >= ((int64_t)1 << (bits - 1))))
+ return ((int64_t)1 << (bits - 1)) - 1;
+ return sample;
+}
+
#endif
diff --git a/src/pcm_volume.c b/src/pcm_volume.c
index 2a94c1890..34981118d 100644
--- a/src/pcm_volume.c
+++ b/src/pcm_volume.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pcm_volume.h"
#include "pcm_utils.h"
#include "audio_format.h"
@@ -113,6 +114,29 @@ pcm_volume_change_24(int32_t *buffer, unsigned num_samples, int volume)
}
}
+static void
+pcm_volume_change_32(int32_t *buffer, unsigned num_samples, int volume)
+{
+ while (num_samples > 0) {
+#ifdef __i386__
+ /* assembly version for i386 */
+ int32_t sample = *buffer;
+
+ *buffer++ = pcm_volume_sample_24(sample, volume, 0);
+#else
+ /* portable version */
+ int64_t sample = *buffer;
+
+ sample = (sample * volume + pcm_volume_dither() +
+ PCM_VOLUME_1 / 2)
+ / PCM_VOLUME_1;
+ *buffer++ = pcm_range_64(sample, 32);
+#endif
+
+ --num_samples;
+ }
+}
+
bool
pcm_volume(void *buffer, int length,
const struct audio_format *format,
@@ -126,21 +150,26 @@ pcm_volume(void *buffer, int length,
return true;
}
- switch (format->bits) {
- case 8:
+ switch (format->format) {
+ case SAMPLE_FORMAT_S8:
pcm_volume_change_8((int8_t *)buffer, length, volume);
return true;
- case 16:
+ case SAMPLE_FORMAT_S16:
pcm_volume_change_16((int16_t *)buffer, length / 2,
volume);
return true;
- case 24:
+ case SAMPLE_FORMAT_S24_P32:
pcm_volume_change_24((int32_t*)buffer, length / 4,
volume);
return true;
+ case SAMPLE_FORMAT_S32:
+ pcm_volume_change_32((int32_t*)buffer, length / 4,
+ volume);
+ return true;
+
default:
return false;
}
diff --git a/src/permission.c b/src/permission.c
index 7df4e27fc..94aca70cf 100644
--- a/src/permission.c
+++ b/src/permission.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "permission.h"
#include "conf.h"
@@ -111,7 +112,7 @@ void initPermissions(void)
permission_default = parsePermissions(param->value);
}
-int getPermissionFromPassword(char *password, unsigned *permission)
+int getPermissionFromPassword(char const* password, unsigned* permission)
{
bool found;
gpointer key, value;
diff --git a/src/permission.h b/src/permission.h
index bad26aa3c..2a5d999ce 100644
--- a/src/permission.h
+++ b/src/permission.h
@@ -27,7 +27,7 @@
#define PERMISSION_ADMIN 8
-int getPermissionFromPassword(char *password, unsigned *permission);
+int getPermissionFromPassword(char const* password, unsigned* permission);
void finishPermissions(void);
diff --git a/src/pipe.c b/src/pipe.c
index c9f0d159c..7cce06075 100644
--- a/src/pipe.c
+++ b/src/pipe.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "pipe.h"
#include "buffer.h"
#include "chunk.h"
diff --git a/src/player_control.c b/src/player_control.c
index ac4b006dd..b7e802b9d 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -17,7 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "player_control.h"
+#include "decoder_control.h"
#include "path.h"
#include "log.h"
#include "tag.h"
@@ -35,17 +37,28 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
{
pc.buffer_chunks = buffer_chunks;
pc.buffered_before_play = buffered_before_play;
- notify_init(&pc.notify);
+
+ pc.mutex = g_mutex_new();
+ pc.cond = g_cond_new();
+
pc.command = PLAYER_COMMAND_NONE;
pc.error = PLAYER_ERROR_NOERROR;
pc.state = PLAYER_STATE_STOP;
pc.cross_fade_seconds = 0;
- pc.software_volume = PCM_VOLUME_1;
}
void pc_deinit(void)
{
- notify_deinit(&pc.notify);
+ g_cond_free(pc.cond);
+ g_mutex_free(pc.mutex);
+}
+
+void
+player_wait_decoder(struct decoder_control *dc)
+{
+ /* during this function, the decoder lock is held, because
+ we're waiting for the decoder thread */
+ g_cond_wait(pc.cond, dc->mutex);
}
void
@@ -57,27 +70,44 @@ pc_song_deleted(const struct song *song)
}
}
-static void player_command(enum player_command cmd)
+static void
+player_command_wait_locked(void)
+{
+ while (pc.command != PLAYER_COMMAND_NONE)
+ g_cond_wait(main_cond, pc.mutex);
+}
+
+static void
+player_command_locked(enum player_command cmd)
{
assert(pc.command == PLAYER_COMMAND_NONE);
pc.command = cmd;
- while (pc.command != PLAYER_COMMAND_NONE) {
- notify_signal(&pc.notify);
- notify_wait(&main_notify);
- }
+ player_signal();
+ player_command_wait_locked();
+}
+
+static void
+player_command(enum player_command cmd)
+{
+ player_lock();
+ player_command_locked(cmd);
+ player_unlock();
}
void
-playerPlay(struct song *song)
+pc_play(struct song *song)
{
assert(song != NULL);
if (pc.state != PLAYER_STATE_STOP)
player_command(PLAYER_COMMAND_STOP);
- pc.next_song = song;
- player_command(PLAYER_COMMAND_PLAY);
+ assert(pc.next_song == NULL);
+
+ pc_enqueue_song(song);
+
+ assert(pc.next_song == NULL);
idle_add(IDLE_PLAYER);
}
@@ -85,16 +115,26 @@ playerPlay(struct song *song)
void pc_cancel(void)
{
player_command(PLAYER_COMMAND_CANCEL);
+ assert(pc.next_song == NULL);
}
-void playerWait(void)
+void
+pc_stop(void)
{
player_command(PLAYER_COMMAND_CLOSE_AUDIO);
+ assert(pc.next_song == NULL);
idle_add(IDLE_PLAYER);
}
-void playerKill(void)
+void
+pc_update_audio(void)
+{
+ player_command(PLAYER_COMMAND_UPDATE_AUDIO);
+}
+
+void
+pc_kill(void)
{
assert(pc.thread != NULL);
@@ -105,7 +145,8 @@ void playerKill(void)
idle_add(IDLE_PLAYER);
}
-void playerPause(void)
+void
+pc_pause(void)
{
if (pc.state != PLAYER_STATE_STOP) {
player_command(PLAYER_COMMAND_PAUSE);
@@ -113,7 +154,8 @@ void playerPause(void)
}
}
-void playerSetPause(int pause_flag)
+void
+pc_set_pause(bool pause_flag)
{
switch (pc.state) {
case PLAYER_STATE_STOP:
@@ -121,41 +163,49 @@ void playerSetPause(int pause_flag)
case PLAYER_STATE_PLAY:
if (pause_flag)
- playerPause();
+ pc_pause();
break;
+
case PLAYER_STATE_PAUSE:
if (!pause_flag)
- playerPause();
+ pc_pause();
break;
}
}
-int getPlayerElapsedTime(void)
+void
+pc_get_status(struct player_status *status)
{
- return (int)(pc.elapsed_time + 0.5);
-}
+ player_lock();
+ player_command_locked(PLAYER_COMMAND_REFRESH);
-unsigned long getPlayerBitRate(void)
-{
- return pc.bit_rate;
-}
+ status->state = pc.state;
-int getPlayerTotalTime(void)
-{
- return (int)(pc.total_time + 0.5);
+ if (pc.state != PLAYER_STATE_STOP) {
+ status->bit_rate = pc.bit_rate;
+ status->audio_format = pc.audio_format;
+ status->total_time = pc.total_time;
+ status->elapsed_time = pc.elapsed_time;
+ }
+
+ player_unlock();
}
-enum player_state getPlayerState(void)
+enum player_state
+pc_get_state(void)
{
return pc.state;
}
-void clearPlayerError(void)
+void
+pc_clear_error(void)
{
- pc.error = 0;
+ pc.error = PLAYER_ERROR_NOERROR;
+ pc.errored_song = NULL;
}
-enum player_error getPlayerError(void)
+enum player_error
+pc_get_error(void)
{
return pc.error;
}
@@ -166,58 +216,56 @@ pc_errored_song_uri(void)
return song_get_uri(pc.errored_song);
}
-char *getPlayerErrorStr(void)
+char *
+pc_get_error_message(void)
{
- /* static OK here, only one user in main task */
- static char error[MPD_PATH_MAX + 64]; /* still too much */
- static const size_t errorlen = sizeof(error);
+ char *error;
char *uri;
- *error = '\0'; /* likely */
-
switch (pc.error) {
case PLAYER_ERROR_NOERROR:
- break;
+ return NULL;
case PLAYER_ERROR_FILENOTFOUND:
uri = pc_errored_song_uri();
- snprintf(error, errorlen,
- "file \"%s\" does not exist or is inaccessible", uri);
+ error = g_strdup_printf("file \"%s\" does not exist or is inaccessible", uri);
g_free(uri);
- break;
+ return error;
case PLAYER_ERROR_FILE:
uri = pc_errored_song_uri();
- snprintf(error, errorlen, "problems decoding \"%s\"", uri);
+ error = g_strdup_printf("problems decoding \"%s\"", uri);
g_free(uri);
- break;
+ return error;
case PLAYER_ERROR_AUDIO:
- strcpy(error, "problems opening audio device");
- break;
+ return g_strdup("problems opening audio device");
case PLAYER_ERROR_SYSTEM:
- strcpy(error, "system error occured");
- break;
+ return g_strdup("system error occured");
case PLAYER_ERROR_UNKTYPE:
uri = pc_errored_song_uri();
- snprintf(error, errorlen,
- "file type of \"%s\" is unknown", uri);
+ error = g_strdup_printf("file type of \"%s\" is unknown", uri);
g_free(uri);
- break;
+ return error;
}
- return *error ? error : NULL;
+
+ assert(false);
+ return NULL;
}
void
-queueSong(struct song *song)
+pc_enqueue_song(struct song *song)
{
assert(song != NULL);
+
+ player_lock();
assert(pc.next_song == NULL);
pc.next_song = song;
- player_command(PLAYER_COMMAND_QUEUE);
+ player_command_locked(PLAYER_COMMAND_QUEUE);
+ player_unlock();
}
bool
@@ -228,9 +276,11 @@ pc_seek(struct song *song, float seek_time)
if (pc.state == PLAYER_STATE_STOP)
return false;
+ player_lock();
pc.next_song = song;
pc.seek_where = seek_time;
- player_command(PLAYER_COMMAND_SEEK);
+ player_command_locked(PLAYER_COMMAND_SEEK);
+ player_unlock();
assert(pc.next_song == NULL);
@@ -239,31 +289,24 @@ pc_seek(struct song *song, float seek_time)
return true;
}
-float getPlayerCrossFade(void)
+float
+pc_get_cross_fade(void)
{
return pc.cross_fade_seconds;
}
-void setPlayerCrossFade(float crossFadeInSeconds)
+void
+pc_set_cross_fade(float cross_fade_seconds)
{
- if (crossFadeInSeconds < 0)
- crossFadeInSeconds = 0;
- pc.cross_fade_seconds = crossFadeInSeconds;
+ if (cross_fade_seconds < 0)
+ cross_fade_seconds = 0;
+ pc.cross_fade_seconds = cross_fade_seconds;
idle_add(IDLE_OPTIONS);
}
-void setPlayerSoftwareVolume(int volume)
-{
- if (volume > PCM_VOLUME_1)
- volume = PCM_VOLUME_1;
- else if (volume < 0)
- volume = 0;
-
- pc.software_volume = volume;
-}
-
-double getPlayerTotalPlayTime(void)
+double
+pc_get_total_play_time(void)
{
return pc.total_play_time;
}
diff --git a/src/player_control.h b/src/player_control.h
index b1f7481cd..e279067d5 100644
--- a/src/player_control.h
+++ b/src/player_control.h
@@ -25,6 +25,8 @@
#include <stdint.h>
+struct decoder_control;
+
enum player_state {
PLAYER_STATE_STOP = 0,
PLAYER_STATE_PAUSE,
@@ -35,11 +37,16 @@ enum player_command {
PLAYER_COMMAND_NONE = 0,
PLAYER_COMMAND_EXIT,
PLAYER_COMMAND_STOP,
- PLAYER_COMMAND_PLAY,
PLAYER_COMMAND_PAUSE,
PLAYER_COMMAND_SEEK,
PLAYER_COMMAND_CLOSE_AUDIO,
+ /**
+ * At least one audio_output.enabled flag has been modified;
+ * commit those changes to the output threads.
+ */
+ PLAYER_COMMAND_UPDATE_AUDIO,
+
/** player_control.next_song has been updated */
PLAYER_COMMAND_QUEUE,
@@ -49,6 +56,12 @@ enum player_command {
* stop
*/
PLAYER_COMMAND_CANCEL,
+
+ /**
+ * Refresh status information in the #player_control struct,
+ * e.g. elapsed_time.
+ */
+ PLAYER_COMMAND_REFRESH,
};
enum player_error {
@@ -60,6 +73,14 @@ enum player_error {
PLAYER_ERROR_FILENOTFOUND,
};
+struct player_status {
+ enum player_state state;
+ uint16_t bit_rate;
+ struct audio_format audio_format;
+ float total_time;
+ float elapsed_time;
+};
+
struct player_control {
unsigned buffer_chunks;
@@ -69,19 +90,27 @@ struct player_control {
thread isn't running */
GThread *thread;
- struct notify notify;
- volatile enum player_command command;
- volatile enum player_state state;
- volatile enum player_error error;
+ /**
+ * This lock protects #command, #state, #error.
+ */
+ GMutex *mutex;
+
+ /**
+ * Trigger this object after you have modified #command.
+ */
+ GCond *cond;
+
+ enum player_command command;
+ enum player_state state;
+ enum player_error error;
uint16_t bit_rate;
struct audio_format audio_format;
float total_time;
float elapsed_time;
- struct song *volatile next_song;
- struct song *errored_song;
- volatile double seek_where;
+ struct song *next_song;
+ const struct song *errored_song;
+ double seek_where;
float cross_fade_seconds;
- uint16_t software_volume;
double total_play_time;
};
@@ -92,6 +121,67 @@ void pc_init(unsigned buffer_chunks, unsigned buffered_before_play);
void pc_deinit(void);
/**
+ * Locks the #player_control object.
+ */
+static inline void
+player_lock(void)
+{
+ g_mutex_lock(pc.mutex);
+}
+
+/**
+ * Unlocks the #player_control object.
+ */
+static inline void
+player_unlock(void)
+{
+ g_mutex_unlock(pc.mutex);
+}
+
+/**
+ * Waits for a signal on the #player_control object. This function is
+ * only valid in the player thread. The object must be locked prior
+ * to calling this function.
+ */
+static inline void
+player_wait(void)
+{
+ g_cond_wait(pc.cond, pc.mutex);
+}
+
+/**
+ * Waits for a signal on the #player_control object. This function is
+ * only valid in the player thread. The #decoder_control object must
+ * be locked prior to calling this function.
+ *
+ * Note the small difference to the player_wait() function!
+ */
+void
+player_wait_decoder(struct decoder_control *dc);
+
+/**
+ * Signals the #player_control object. The object should be locked
+ * prior to calling this function.
+ */
+static inline void
+player_signal(void)
+{
+ g_cond_signal(pc.cond);
+}
+
+/**
+ * Signals the #player_control object. The object is temporarily
+ * locked by this function.
+ */
+static inline void
+player_lock_signal(void)
+{
+ player_lock();
+ player_signal();
+ player_unlock();
+}
+
+/**
* Call this function when the specified song pointer is about to be
* invalidated. This makes sure that player_control.errored_song does
* not point to an invalid pointer.
@@ -100,37 +190,50 @@ void
pc_song_deleted(const struct song *song);
void
-playerPlay(struct song *song);
+pc_play(struct song *song);
/**
* see PLAYER_COMMAND_CANCEL
*/
void pc_cancel(void);
-void playerSetPause(int pause_flag);
-
-void playerPause(void);
+void
+pc_set_pause(bool pause_flag);
-void playerKill(void);
+void
+pc_pause(void);
-int getPlayerTotalTime(void);
+void
+pc_kill(void);
-int getPlayerElapsedTime(void);
+void
+pc_get_status(struct player_status *status);
-unsigned long getPlayerBitRate(void);
+enum player_state
+pc_get_state(void);
-enum player_state getPlayerState(void);
+void
+pc_clear_error(void);
-void clearPlayerError(void);
+/**
+ * Returns the human-readable message describing the last error during
+ * playback, NULL if no error occurred. The caller has to free the
+ * returned string.
+ */
+char *
+pc_get_error_message(void);
-char *getPlayerErrorStr(void);
+enum player_error
+pc_get_error(void);
-enum player_error getPlayerError(void);
+void
+pc_stop(void);
-void playerWait(void);
+void
+pc_update_audio(void);
void
-queueSong(struct song *song);
+pc_enqueue_song(struct song *song);
/**
* Makes the player thread seek the specified song to a position.
@@ -141,20 +244,13 @@ queueSong(struct song *song);
bool
pc_seek(struct song *song, float seek_time);
-void setPlayerCrossFade(float crossFadeInSeconds);
-
-float getPlayerCrossFade(void);
-
-void setPlayerSoftwareVolume(int volume);
-
-double getPlayerTotalPlayTime(void);
+void
+pc_set_cross_fade(float cross_fade_seconds);
-static inline const struct audio_format *
-player_get_audio_format(void)
-{
- return &pc.audio_format;
-}
+float
+pc_get_cross_fade(void);
-void playerInit(void);
+double
+pc_get_total_play_time(void);
#endif
diff --git a/src/player_thread.c b/src/player_thread.c
index 7fc55d3d1..83f348d19 100644
--- a/src/player_thread.c
+++ b/src/player_thread.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "player_thread.h"
#include "player_control.h"
#include "decoder_control.h"
@@ -46,6 +47,8 @@ enum xfade_state {
};
struct player {
+ struct decoder_control *dc;
+
struct music_pipe *pipe;
/**
@@ -95,74 +98,129 @@ struct player {
struct audio_format play_audio_format;
/**
- * Coefficient for converting a PCM buffer size into a time
- * span.
+ * The time stamp of the chunk most recently sent to the
+ * output thread. This attribute is only used if
+ * audio_output_all_get_elapsed_time() didn't return a usable
+ * value; the output thread can estimate the elapsed time more
+ * precisly.
*/
- double size_to_time;
+ float elapsed_time;
};
static struct music_buffer *player_buffer;
-static void player_command_finished(void)
+static void player_command_finished_locked(void)
{
assert(pc.command != PLAYER_COMMAND_NONE);
pc.command = PLAYER_COMMAND_NONE;
- notify_signal(&main_notify);
+ g_cond_signal(main_cond);
+}
+
+static void player_command_finished(void)
+{
+ player_lock();
+ player_command_finished_locked();
+ player_unlock();
+}
+
+/**
+ * Start the decoder.
+ *
+ * Player lock is not held.
+ */
+static void
+player_dc_start(struct player *player, struct music_pipe *pipe)
+{
+ struct decoder_control *dc = player->dc;
+
+ assert(player->queued || pc.command == PLAYER_COMMAND_SEEK);
+ assert(pc.next_song != NULL);
+
+ dc_start(dc, pc.next_song, player_buffer, pipe);
}
/**
* Stop the decoder and clears (and frees) its music pipe.
+ *
+ * Player lock is not held.
*/
static void
player_dc_stop(struct player *player)
{
- dc_stop(&pc.notify);
+ struct decoder_control *dc = player->dc;
- if (dc.pipe != NULL) {
+ dc_stop(dc);
+
+ if (dc->pipe != NULL) {
/* clear and free the decoder pipe */
- music_pipe_clear(dc.pipe, player_buffer);
+ music_pipe_clear(dc->pipe, player_buffer);
- if (dc.pipe != player->pipe)
- music_pipe_free(dc.pipe);
+ if (dc->pipe != player->pipe)
+ music_pipe_free(dc->pipe);
- dc.pipe = NULL;
+ dc->pipe = NULL;
}
}
/**
+ * Returns true if the decoder is decoding the next song (or has begun
+ * decoding it, or has finished doing it), and the player hasn't
+ * switched to that song yet.
+ */
+static bool
+decoding_next_song(const struct player *player)
+{
+ return player->dc->pipe != NULL && player->dc->pipe != player->pipe;
+}
+
+/**
* After the decoder has been started asynchronously, wait for the
* "START" command to finish. The decoder may not be initialized yet,
* i.e. there is no audio_format information yet.
+ *
+ * The player lock is not held.
*/
static bool
player_wait_for_decoder(struct player *player)
{
- dc_command_wait(&pc.notify);
+ struct decoder_control *dc = player->dc;
+
+ assert(player->queued || pc.command == PLAYER_COMMAND_SEEK);
+ assert(pc.next_song != NULL);
+
+ player->queued = false;
- if (decoder_has_failed()) {
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
+ if (decoder_lock_has_failed(dc)) {
+ player_lock();
+ pc.errored_song = dc->song;
pc.error = PLAYER_ERROR_FILE;
pc.next_song = NULL;
- player->queued = false;
+ player_unlock();
+
return false;
}
+ player->song = pc.next_song;
+ player->elapsed_time = 0.0;
+
+ /* set the "starting" flag, which will be cleared by
+ player_check_decoder_startup() */
+ player->decoder_starting = true;
+
+ player_lock();
+
+ /* update player_control's song information */
pc.total_time = pc.next_song->tag != NULL
? pc.next_song->tag->time : 0;
pc.bit_rate = 0;
audio_format_clear(&pc.audio_format);
- player->song = pc.next_song;
+ /* clear the queued song */
pc.next_song = NULL;
- pc.elapsed_time = 0;
- player->queued = false;
- /* set the "starting" flag, which will be cleared by
- player_check_decoder_startup() */
- player->decoder_starting = true;
+ player_unlock();
/* call syncPlaylistWithQueue() in the main thread */
event_pipe_emit(PIPE_EVENT_PLAYLIST);
@@ -174,51 +232,63 @@ player_wait_for_decoder(struct player *player)
* The decoder has acknowledged the "START" command (see
* player_wait_for_decoder()). This function checks if the decoder
* initialization has completed yet.
+ *
+ * The player lock is not held.
*/
static bool
player_check_decoder_startup(struct player *player)
{
+ struct decoder_control *dc = player->dc;
+
assert(player->decoder_starting);
- if (decoder_has_failed()) {
+ decoder_lock(dc);
+
+ if (decoder_has_failed(dc)) {
/* the decoder failed */
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
+ decoder_unlock(dc);
- pc.errored_song = dc.next_song;
+ player_lock();
+ pc.errored_song = dc->song;
pc.error = PLAYER_ERROR_FILE;
+ player_unlock();
return false;
- } else if (!decoder_is_starting()) {
+ } else if (!decoder_is_starting(dc)) {
/* the decoder is ready and ok */
+ decoder_unlock(dc);
+
if (audio_format_defined(&player->play_audio_format) &&
!audio_output_all_wait(1))
/* the output devices havn't finished playing
all chunks yet - wait for that */
return true;
- pc.total_time = dc.total_time;
- pc.audio_format = dc.in_audio_format;
- player->play_audio_format = dc.out_audio_format;
- player->size_to_time =
- audioFormatSizeToTime(&dc.out_audio_format);
+ player_lock();
+ pc.total_time = dc->total_time;
+ pc.audio_format = dc->in_audio_format;
+ player_unlock();
+
+ player->play_audio_format = dc->out_audio_format;
player->decoder_starting = false;
if (!player->paused &&
- !audio_output_all_open(&dc.out_audio_format,
+ !audio_output_all_open(&dc->out_audio_format,
player_buffer)) {
- char *uri = song_get_uri(dc.next_song);
+ char *uri = song_get_uri(dc->song);
g_warning("problems opening audio device "
"while playing \"%s\"", uri);
g_free(uri);
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
+ player_lock();
pc.error = PLAYER_ERROR_AUDIO;
/* pause: the user may resume playback as soon
as an audio output becomes available */
pc.state = PLAYER_STATE_PAUSE;
+ player_unlock();
+
player->paused = true;
return true;
}
@@ -227,7 +297,8 @@ player_check_decoder_startup(struct player *player)
} else {
/* the decoder is not yet ready; wait
some more */
- notify_wait(&pc.notify);
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
return true;
}
@@ -237,6 +308,8 @@ player_check_decoder_startup(struct player *player)
* Sends a chunk of silence to the audio outputs. This is called when
* there is not enough decoded data in the pipe yet, to prevent
* underruns in the hardware buffers.
+ *
+ * The player lock is not held.
*/
static bool
player_send_silence(struct player *player)
@@ -260,6 +333,7 @@ player_send_silence(struct player *player)
chunk->audio_format = player->play_audio_format;
#endif
+ chunk->times = -1.0; /* undefined time stamp */
chunk->length = num_frames * frame_size;
memset(chunk->data, 0, chunk->length);
@@ -273,15 +347,18 @@ player_send_silence(struct player *player)
/**
* This is the handler for the #PLAYER_COMMAND_SEEK command.
+ *
+ * The player lock is not held.
*/
static bool player_seek_decoder(struct player *player)
{
+ struct decoder_control *dc = player->dc;
double where;
bool ret;
assert(pc.next_song != NULL);
- if (decoder_current_song() != pc.next_song) {
+ if (decoder_current_song(dc) != pc.next_song) {
/* the decoder is already decoding the "next" song -
stop it and start the previous song again */
@@ -290,10 +367,9 @@ static bool player_seek_decoder(struct player *player)
/* clear music chunks which might still reside in the
pipe */
music_pipe_clear(player->pipe, player_buffer);
- dc.pipe = player->pipe;
/* re-start the decoder */
- dc_start_async(pc.next_song);
+ player_dc_start(player, player->pipe);
ret = player_wait_for_decoder(player);
if (!ret) {
/* decoder failure */
@@ -324,14 +400,15 @@ static bool player_seek_decoder(struct player *player)
if (where < 0.0)
where = 0.0;
- ret = dc_seek(&pc.notify, where);
+ ret = dc_seek(dc, where);
if (!ret) {
/* decoder failure */
player_command_finished();
return false;
}
- pc.elapsed_time = where;
+ player->elapsed_time = where;
+
player_command_finished();
player->xfade = XFADE_UNKNOWN;
@@ -344,53 +421,73 @@ static bool player_seek_decoder(struct player *player)
return true;
}
+/**
+ * Player lock must be held before calling.
+ */
static void player_process_command(struct player *player)
{
+ G_GNUC_UNUSED struct decoder_control *dc = player->dc;
+
switch (pc.command) {
case PLAYER_COMMAND_NONE:
- case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_STOP:
case PLAYER_COMMAND_EXIT:
case PLAYER_COMMAND_CLOSE_AUDIO:
break;
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ player_unlock();
+ audio_output_all_enable_disable();
+ player_lock();
+ player_command_finished_locked();
+ break;
+
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
assert(!player->queued);
- assert(dc.pipe == NULL || dc.pipe == player->pipe);
+ assert(dc->pipe == NULL || dc->pipe == player->pipe);
player->queued = true;
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_PAUSE:
+ player_unlock();
+
player->paused = !player->paused;
if (player->paused) {
audio_output_all_pause();
+ player_lock();
+
pc.state = PLAYER_STATE_PAUSE;
} else if (!audio_format_defined(&player->play_audio_format)) {
/* the decoder hasn't provided an audio format
yet - don't open the audio device yet */
+ player_lock();
pc.state = PLAYER_STATE_PLAY;
} else if (audio_output_all_open(&player->play_audio_format, player_buffer)) {
/* unpaused, continue playing */
+ player_lock();
+
pc.state = PLAYER_STATE_PLAY;
} else {
/* the audio device has failed - rollback to
pause mode */
- assert(dc.next_song == NULL || dc.next_song->url != NULL);
- pc.errored_song = dc.next_song;
pc.error = PLAYER_ERROR_AUDIO;
player->paused = true;
+
+ player_lock();
}
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_SEEK:
+ player_unlock();
player_seek_decoder(player);
+ player_lock();
break;
case PLAYER_COMMAND_CANCEL:
@@ -402,80 +499,91 @@ static void player_process_command(struct player *player)
return;
}
- if (dc.pipe != NULL && dc.pipe != player->pipe)
+ if (decoding_next_song(player)) {
/* the decoder is already decoding the song -
stop it and reset the position */
+ player_unlock();
player_dc_stop(player);
+ player_lock();
+ }
pc.next_song = NULL;
player->queued = false;
- player_command_finished();
+ player_command_finished_locked();
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ if (audio_format_defined(&player->play_audio_format) &&
+ !player->paused) {
+ player_unlock();
+ audio_output_all_check();
+ player_lock();
+ }
+
+ pc.elapsed_time = audio_output_all_get_elapsed_time();
+ if (pc.elapsed_time < 0.0)
+ pc.elapsed_time = player->elapsed_time;
+
+ player_command_finished_locked();
break;
}
}
+static void
+update_song_tag(struct song *song, const struct tag *new_tag)
+{
+ struct tag *old_tag;
+
+ if (song_is_file(song))
+ /* don't update tags of local files, only remote
+ streams may change tags dynamically */
+ return;
+
+ old_tag = song->tag;
+ song->tag = tag_dup(new_tag);
+
+ if (old_tag != NULL)
+ tag_free(old_tag);
+
+ /* the main thread will update the playlist version when he
+ receives this event */
+ event_pipe_emit(PIPE_EVENT_TAG);
+
+ /* notify all clients that the tag of the current song has
+ changed */
+ idle_add(IDLE_PLAYER);
+}
+
/**
* Plays a #music_chunk object (after applying software volume). If
* it contains a (stream) tag, copy it to the current song, so MPD's
* playlist reflects the new stream tag.
+ *
+ * Player lock is not held.
*/
static bool
play_chunk(struct song *song, struct music_chunk *chunk,
- const struct audio_format *format, double sizeToTime)
+ const struct audio_format *format)
{
- bool success;
-
assert(music_chunk_check_format(chunk, format));
- if (chunk->tag != NULL) {
- if (!song_is_file(song)) {
- /* always update the tag of remote streams */
- struct tag *old_tag = song->tag;
-
- song->tag = tag_dup(chunk->tag);
-
- if (old_tag != NULL)
- tag_free(old_tag);
-
- /* the main thread will update the playlist
- version when he receives this event */
- event_pipe_emit(PIPE_EVENT_TAG);
-
- /* notify all clients that the tag of the
- current song has changed */
- idle_add(IDLE_PLAYER);
- }
- }
+ if (chunk->tag != NULL)
+ update_song_tag(song, chunk->tag);
if (chunk->length == 0) {
music_buffer_return(player_buffer, chunk);
return true;
}
- pc.elapsed_time = chunk->times;
pc.bit_rate = chunk->bit_rate;
- /* apply software volume */
-
- success = pcm_volume(chunk->data, chunk->length,
- format, pc.software_volume);
- if (!success) {
- g_warning("pcm_volume() failed on %u:%u:%u",
- format->sample_rate, format->bits, format->channels);
- pc.errored_song = dc.current_song;
- pc.error = PLAYER_ERROR_AUDIO;
- return false;
- }
-
/* send the chunk to the audio outputs */
- if (!audio_output_all_play(chunk)) {
- pc.errored_song = dc.current_song;
- pc.error = PLAYER_ERROR_AUDIO;
+ if (!audio_output_all_play(chunk))
return false;
- }
- pc.total_play_time += sizeToTime * chunk->length;
+ pc.total_play_time += (double)chunk->length /
+ audio_format_time_to_size(format);
return true;
}
@@ -488,6 +596,7 @@ play_chunk(struct song *song, struct music_chunk *chunk,
static bool
play_next_chunk(struct player *player)
{
+ struct decoder_control *dc = player->dc;
struct music_chunk *chunk = NULL;
unsigned cross_fade_position;
bool success;
@@ -498,12 +607,12 @@ play_next_chunk(struct player *player)
return true;
if (player->xfade == XFADE_ENABLED &&
- dc.pipe != NULL && dc.pipe != player->pipe &&
+ decoding_next_song(player) &&
(cross_fade_position = music_pipe_size(player->pipe))
<= player->cross_fade_chunks) {
/* perform cross fade */
struct music_chunk *other_chunk =
- music_pipe_shift(dc.pipe);
+ music_pipe_shift(dc->pipe);
if (!player->cross_fading) {
/* beginning of the cross fade - adjust
@@ -519,20 +628,26 @@ play_next_chunk(struct player *player)
assert(chunk != NULL);
cross_fade_apply(chunk, other_chunk,
- &dc.out_audio_format,
+ &dc->out_audio_format,
cross_fade_position,
player->cross_fade_chunks);
music_buffer_return(player_buffer, other_chunk);
} else {
/* there are not enough decoded chunks yet */
- if (decoder_is_idle()) {
+
+ decoder_lock(dc);
+
+ if (decoder_is_idle(dc)) {
/* the decoder isn't running, abort
cross fading */
+ decoder_unlock(dc);
+
player->xfade = XFADE_DISABLED;
} else {
/* wait for the decoder */
- notify_signal(&dc.notify);
- notify_wait(&pc.notify);
+ decoder_signal(dc);
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
return true;
}
@@ -546,27 +661,34 @@ play_next_chunk(struct player *player)
/* play the current chunk */
- success = play_chunk(player->song, chunk, &player->play_audio_format,
- player->size_to_time);
+ success = play_chunk(player->song, chunk, &player->play_audio_format);
if (!success) {
music_buffer_return(player_buffer, chunk);
+ player_lock();
+
+ pc.error = PLAYER_ERROR_AUDIO;
+
/* pause: the user may resume playback as soon as an
audio output becomes available */
pc.state = PLAYER_STATE_PAUSE;
player->paused = true;
+ player_unlock();
+
return false;
}
/* this formula should prevent that the decoder gets woken up
with each chunk; it is more efficient to make it decode a
larger block at a time */
- if (!decoder_is_idle() &&
- music_pipe_size(dc.pipe) <= (pc.buffered_before_play +
+ decoder_lock(dc);
+ if (!decoder_is_idle(dc) &&
+ music_pipe_size(dc->pipe) <= (pc.buffered_before_play +
music_buffer_size(player_buffer) * 3) / 4)
- notify_signal(&dc.notify);
+ decoder_signal(dc);
+ decoder_unlock(dc);
return true;
}
@@ -576,15 +698,23 @@ play_next_chunk(struct player *player)
* has consumed all chunks of the current song, and we should start
* sending chunks from the next one.
*
+ * The player lock is not held.
+ *
* @return true on success, false on error (playback will be stopped)
*/
static bool
player_song_border(struct player *player)
{
+ char *uri;
+
player->xfade = XFADE_UNKNOWN;
+ uri = song_get_uri(player->song);
+ g_message("played \"%s\"", uri);
+ g_free(uri);
+
music_pipe_free(player->pipe);
- player->pipe = dc.pipe;
+ player->pipe = player->dc->pipe;
if (!player_wait_for_decoder(player))
return false;
@@ -597,53 +727,58 @@ player_song_border(struct player *player)
* basically a state machine, which multiplexes data between the
* decoder thread and the output threads.
*/
-static void do_play(void)
+static void do_play(struct decoder_control *dc)
{
struct player player = {
+ .dc = dc,
.buffering = true,
.decoder_starting = false,
.paused = false,
- .queued = false,
+ .queued = true,
.song = NULL,
.xfade = XFADE_UNKNOWN,
.cross_fading = false,
.cross_fade_chunks = 0,
- .size_to_time = 0.0,
+ .elapsed_time = 0.0,
};
+ player_unlock();
+
player.pipe = music_pipe_new();
- dc.buffer = player_buffer;
- dc.pipe = player.pipe;
- dc_start(&pc.notify, pc.next_song);
+ player_dc_start(&player, player.pipe);
if (!player_wait_for_decoder(&player)) {
player_dc_stop(&player);
player_command_finished();
music_pipe_free(player.pipe);
event_pipe_emit(PIPE_EVENT_PLAYLIST);
+ player_lock();
return;
}
- pc.elapsed_time = 0;
+ player_lock();
pc.state = PLAYER_STATE_PLAY;
- player_command_finished();
+ player_command_finished_locked();
while (true) {
player_process_command(&player);
if (pc.command == PLAYER_COMMAND_STOP ||
pc.command == PLAYER_COMMAND_EXIT ||
pc.command == PLAYER_COMMAND_CLOSE_AUDIO) {
+ player_unlock();
audio_output_all_cancel();
break;
}
+ player_unlock();
+
if (player.buffering) {
/* buffering at the start of the song - wait
until the buffer is large enough, to
prevent stuttering on slow machines */
if (music_pipe_size(player.pipe) < pc.buffered_before_play &&
- !decoder_is_idle()) {
+ !decoder_lock_is_idle(dc)) {
/* not enough decoded buffer space yet */
if (!player.paused &&
@@ -652,7 +787,11 @@ static void do_play(void)
!player_send_silence(&player))
break;
- notify_wait(&pc.notify);
+ decoder_lock(dc);
+ /* XXX race condition: check decoder again */
+ player_wait_decoder(dc);
+ decoder_unlock(dc);
+ player_lock();
continue;
} else {
/* buffering is complete */
@@ -667,6 +806,8 @@ static void do_play(void)
success = player_check_decoder_startup(&player);
if (!success)
break;
+
+ player_lock();
continue;
}
@@ -674,30 +815,28 @@ static void do_play(void)
/*
music_pipe_check_format(&play_audio_format,
player.next_song_chunk,
- &dc.out_audio_format);
+ &dc->out_audio_format);
*/
#endif
- if (decoder_is_idle() && player.queued) {
+ if (decoder_lock_is_idle(dc) && player.queued &&
+ dc->pipe == player.pipe) {
/* the decoder has finished the current song;
make it decode the next song */
- assert(pc.next_song != NULL);
- assert(dc.pipe == NULL || dc.pipe == player.pipe);
+ assert(dc->pipe == NULL || dc->pipe == player.pipe);
- player.queued = false;
- dc.pipe = music_pipe_new();
- dc_start_async(pc.next_song);
+ player_dc_start(&player, music_pipe_new());
}
- if (dc.pipe != NULL && dc.pipe != player.pipe &&
+ if (decoding_next_song(&player) &&
player.xfade == XFADE_UNKNOWN &&
- !decoder_is_starting()) {
+ !decoder_lock_is_starting(dc)) {
/* enable cross fading in this song? if yes,
calculate how many chunks will be required
for it */
player.cross_fade_chunks =
- cross_fade_calc(pc.cross_fade_seconds, dc.total_time,
- &dc.out_audio_format,
+ cross_fade_calc(pc.cross_fade_seconds, dc->total_time,
+ &dc->out_audio_format,
&player.play_audio_format,
music_buffer_size(player_buffer) -
pc.buffered_before_play);
@@ -710,9 +849,13 @@ static void do_play(void)
player.xfade = XFADE_DISABLED;
}
- if (player.paused)
- notify_wait(&pc.notify);
- else if (music_pipe_size(player.pipe) > 0) {
+ if (player.paused) {
+ player_lock();
+
+ if (pc.command == PLAYER_COMMAND_NONE)
+ player_wait();
+ continue;
+ } else if (music_pipe_size(player.pipe) > 0) {
/* at least one music chunk is ready - send it
to the audio output */
@@ -724,17 +867,21 @@ static void do_play(void)
/* XXX synchronize in a better way */
g_usleep(10000);
- } else if (dc.pipe != NULL && dc.pipe != player.pipe) {
+ } else if (decoding_next_song(&player)) {
/* at the beginning of a new song */
if (!player_song_border(&player))
break;
- } else if (decoder_is_idle()) {
+ } else if (decoder_lock_is_idle(dc)) {
/* check the size of the pipe again, because
the decoder thread may have added something
since we last checked */
- if (music_pipe_size(player.pipe) == 0)
+ if (music_pipe_size(player.pipe) == 0) {
+ /* wait for the hardware to finish
+ playback */
+ audio_output_all_drain();
break;
+ }
} else {
/* the decoder is too busy and hasn't provided
new PCM data in time: send silence (if the
@@ -742,11 +889,8 @@ static void do_play(void)
if (!player_send_silence(&player))
break;
}
- }
- if (player.queued) {
- assert(pc.next_song != NULL);
- pc.next_song = NULL;
+ player_lock();
}
player_dc_stop(&player);
@@ -754,38 +898,61 @@ static void do_play(void)
music_pipe_clear(player.pipe, player_buffer);
music_pipe_free(player.pipe);
+ player_lock();
+
+ if (player.queued) {
+ assert(pc.next_song != NULL);
+ pc.next_song = NULL;
+ }
+
pc.state = PLAYER_STATE_STOP;
+
+ player_unlock();
+
event_pipe_emit(PIPE_EVENT_PLAYLIST);
+
+ player_lock();
}
static gpointer player_task(G_GNUC_UNUSED gpointer arg)
{
- decoder_thread_start();
+ struct decoder_control dc;
+
+ dc_init(&dc);
+ decoder_thread_start(&dc);
player_buffer = music_buffer_new(pc.buffer_chunks);
+ player_lock();
+
while (1) {
switch (pc.command) {
- case PLAYER_COMMAND_PLAY:
case PLAYER_COMMAND_QUEUE:
assert(pc.next_song != NULL);
- do_play();
+ do_play(&dc);
break;
case PLAYER_COMMAND_STOP:
+ player_unlock();
audio_output_all_cancel();
+ player_lock();
+
/* fall through */
case PLAYER_COMMAND_SEEK:
case PLAYER_COMMAND_PAUSE:
pc.next_song = NULL;
- player_command_finished();
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_CLOSE_AUDIO:
+ player_unlock();
+
audio_output_all_close();
- player_command_finished();
+
+ player_lock();
+ player_command_finished_locked();
#ifndef NDEBUG
/* in the DEBUG build, check for leaked
@@ -797,25 +964,39 @@ static gpointer player_task(G_GNUC_UNUSED gpointer arg)
break;
+ case PLAYER_COMMAND_UPDATE_AUDIO:
+ player_unlock();
+ audio_output_all_enable_disable();
+ player_lock();
+ player_command_finished_locked();
+ break;
+
case PLAYER_COMMAND_EXIT:
- dc_quit();
+ player_unlock();
+
+ dc_quit(&dc);
+ dc_deinit(&dc);
audio_output_all_close();
music_buffer_free(player_buffer);
+
player_command_finished();
- g_thread_exit(NULL);
- break;
+ return NULL;
case PLAYER_COMMAND_CANCEL:
pc.next_song = NULL;
- player_command_finished();
+ player_command_finished_locked();
+ break;
+
+ case PLAYER_COMMAND_REFRESH:
+ /* no-op when not playing */
+ player_command_finished_locked();
break;
case PLAYER_COMMAND_NONE:
- notify_wait(&pc.notify);
+ player_wait();
break;
}
}
- return NULL;
}
void player_create(void)
diff --git a/src/playlist.c b/src/playlist.c
index 35c09329a..691fe5d26 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_internal.h"
#include "playlist_save.h"
#include "player_control.h"
@@ -34,7 +35,8 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-void playlistVersionChange(struct playlist *playlist)
+void
+playlist_increment_version_all(struct playlist *playlist)
{
queue_modify_all(&playlist->queue);
idle_add(IDLE_PLAYLIST);
@@ -61,16 +63,12 @@ playlist_init(struct playlist *playlist)
playlist->queued = -1;
playlist->current = -1;
-
- playlist->prev_elapsed = g_timer_new();
}
void
playlist_finish(struct playlist *playlist)
{
queue_finish(&playlist->queue);
-
- g_timer_destroy(playlist->prev_elapsed);
}
/**
@@ -91,14 +89,15 @@ playlist_queue_song_order(struct playlist *playlist, unsigned order)
g_debug("queue song %i:\"%s\"", playlist->queued, uri);
g_free(uri);
- queueSong(song);
+ pc_enqueue_song(song);
}
/**
* Check if the player thread has already started playing the "queued"
* song.
*/
-static void syncPlaylistWithQueue(struct playlist *playlist)
+static void
+playlist_sync_with_queue(struct playlist *playlist)
{
if (pc.next_song == NULL && playlist->queued != -1) {
/* queued song has started: copy queued to current,
@@ -109,7 +108,7 @@ static void syncPlaylistWithQueue(struct playlist *playlist)
playlist->queued = -1;
if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
idle_add(IDLE_PLAYER);
}
@@ -178,7 +177,7 @@ playlist_update_queued_song(struct playlist *playlist, const struct song *prev)
}
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
+playlist_play_order(struct playlist *playlist, int orderNum)
{
struct song *song;
char *uri;
@@ -192,34 +191,35 @@ playPlaylistOrderNumber(struct playlist *playlist, int orderNum)
g_debug("play %i:\"%s\"", orderNum, uri);
g_free(uri);
- playerPlay(song);
+ pc_play(song);
playlist->current = orderNum;
}
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist);
+playlist_resume_playback(struct playlist *playlist);
/**
* This is the "PLAYLIST" event handler. It is invoked by the player
* thread whenever it requests a new queued song, or when it exits.
*/
-void syncPlayerAndPlaylist(struct playlist *playlist)
+void
+playlist_sync(struct playlist *playlist)
{
if (!playlist->playing)
/* this event has reached us out of sync: we aren't
playing anymore; ignore the event */
return;
- if (getPlayerState() == PLAYER_STATE_STOP)
+ if (pc_get_state() == PLAYER_STATE_STOP)
/* the player thread has stopped: check if playback
should be restarted with the next song. That can
happen if the playlist isn't filling the queue fast
enough */
- playPlaylistIfPlayerStopped(playlist);
+ playlist_resume_playback(playlist);
else {
/* check if the player thread has already started
playing the queued song */
- syncPlaylistWithQueue(playlist);
+ playlist_sync_with_queue(playlist);
/* make sure the queued song is always set (if
possible) */
@@ -233,14 +233,14 @@ void syncPlayerAndPlaylist(struct playlist *playlist)
* decide whether to re-start playback
*/
static void
-playPlaylistIfPlayerStopped(struct playlist *playlist)
+playlist_resume_playback(struct playlist *playlist)
{
enum player_error error;
assert(playlist->playing);
- assert(getPlayerState() == PLAYER_STATE_STOP);
+ assert(pc_get_state() == PLAYER_STATE_STOP);
- error = getPlayerError();
+ error = pc_get_error();
if (error == PLAYER_ERROR_NOERROR)
playlist->error_count = 0;
else
@@ -251,37 +251,38 @@ playPlaylistIfPlayerStopped(struct playlist *playlist)
playlist->error_count >= queue_length(&playlist->queue))
/* too many errors, or critical error: stop
playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
else
/* continue playback at the next song */
- nextSongInPlaylist(playlist);
+ playlist_next(playlist);
}
bool
-getPlaylistRepeatStatus(const struct playlist *playlist)
+playlist_get_repeat(const struct playlist *playlist)
{
return playlist->queue.repeat;
}
bool
-getPlaylistRandomStatus(const struct playlist *playlist)
+playlist_get_random(const struct playlist *playlist)
{
return playlist->queue.random;
}
bool
-getPlaylistSingleStatus(const struct playlist *playlist)
+playlist_get_single(const struct playlist *playlist)
{
return playlist->queue.single;
}
bool
-getPlaylistConsumeStatus(const struct playlist *playlist)
+playlist_get_consume(const struct playlist *playlist)
{
return playlist->queue.consume;
}
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
+void
+playlist_set_repeat(struct playlist *playlist, bool status)
{
if (status == playlist->queue.repeat)
return;
@@ -296,7 +297,8 @@ void setPlaylistRepeatStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-static void orderPlaylist(struct playlist *playlist)
+static void
+playlist_order(struct playlist *playlist)
{
if (playlist->current >= 0)
/* update playlist.current, order==position now */
@@ -306,7 +308,8 @@ static void orderPlaylist(struct playlist *playlist)
queue_restore_order(&playlist->queue);
}
-void setPlaylistSingleStatus(struct playlist *playlist, bool status)
+void
+playlist_set_single(struct playlist *playlist, bool status)
{
if (status == playlist->queue.single)
return;
@@ -321,7 +324,8 @@ void setPlaylistSingleStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
+void
+playlist_set_consume(struct playlist *playlist, bool status)
{
if (status == playlist->queue.consume)
return;
@@ -330,7 +334,8 @@ void setPlaylistConsumeStatus(struct playlist *playlist, bool status)
idle_add(IDLE_OPTIONS);
}
-void setPlaylistRandomStatus(struct playlist *playlist, bool status)
+void
+playlist_set_random(struct playlist *playlist, bool status)
{
const struct song *queued;
@@ -365,14 +370,15 @@ void setPlaylistRandomStatus(struct playlist *playlist, bool status)
} else
playlist->current = -1;
} else
- orderPlaylist(playlist);
+ playlist_order(playlist);
playlist_update_queued_song(playlist, queued);
idle_add(IDLE_OPTIONS);
}
-int getPlaylistCurrentSong(const struct playlist *playlist)
+int
+playlist_get_current_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
return queue_order_to_position(&playlist->queue,
@@ -381,7 +387,8 @@ int getPlaylistCurrentSong(const struct playlist *playlist)
return -1;
}
-int getPlaylistNextSong(const struct playlist *playlist)
+int
+playlist_get_next_song(const struct playlist *playlist)
{
if (playlist->current >= 0)
{
@@ -404,19 +411,19 @@ int getPlaylistNextSong(const struct playlist *playlist)
}
unsigned long
-getPlaylistVersion(const struct playlist *playlist)
+playlist_get_version(const struct playlist *playlist)
{
return playlist->queue.version;
}
int
-getPlaylistLength(const struct playlist *playlist)
+playlist_get_length(const struct playlist *playlist)
{
return queue_length(&playlist->queue);
}
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song)
+playlist_get_song_id(const struct playlist *playlist, unsigned song)
{
return queue_position_to_id(&playlist->queue, song);
}
diff --git a/src/playlist.h b/src/playlist.h
index 57b2450fa..f8f5bd942 100644
--- a/src/playlist.h
+++ b/src/playlist.h
@@ -23,7 +23,6 @@
#include "queue.h"
#include <stdbool.h>
-#include <stdio.h>
#define PLAYLIST_COMMENT '#'
@@ -82,21 +81,16 @@ struct playlist {
* This variable is only valid if #playing is true.
*/
int queued;
-
- /**
- * This timer tracks the time elapsed since the last "prev"
- * command. If that is less than one second ago, "prev" jumps
- * to the previous song instead of rewinding the current song.
- */
- GTimer *prev_elapsed;
};
/** the global playlist object */
extern struct playlist g_playlist;
-void initPlaylist(void);
+void
+playlist_global_init(void);
-void finishPlaylist(void);
+void
+playlist_global_finish(void);
void
playlist_init(struct playlist *playlist);
@@ -116,11 +110,8 @@ playlist_get_queue(const struct playlist *playlist)
return &playlist->queue;
}
-void readPlaylistState(FILE *);
-
-void savePlaylistState(FILE *);
-
-void clearPlaylist(struct playlist *playlist);
+void
+playlist_clear(struct playlist *playlist);
#ifndef WIN32
/**
@@ -133,90 +124,111 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
#endif
enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *file, unsigned *added_id);
+playlist_append_uri(struct playlist *playlist, const char *file,
+ unsigned *added_id);
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id);
enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song);
+playlist_delete(struct playlist *playlist, unsigned song);
+
+/**
+ * Deletes a range of songs from the playlist.
+ *
+ * @param start the position of the first song to delete
+ * @param end the position after the last song to delete
+ */
+enum playlist_result
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end);
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned song);
+playlist_delete_id(struct playlist *playlist, unsigned song);
-void stopPlaylist(struct playlist *playlist);
+void
+playlist_stop(struct playlist *playlist);
enum playlist_result
-playPlaylist(struct playlist *playlist, int song);
+playlist_play(struct playlist *playlist, int song);
enum playlist_result
-playPlaylistById(struct playlist *playlist, int song);
+playlist_play_id(struct playlist *playlist, int song);
-void nextSongInPlaylist(struct playlist *playlist);
+void
+playlist_next(struct playlist *playlist);
-void syncPlayerAndPlaylist(struct playlist *playlist);
+void
+playlist_sync(struct playlist *playlist);
-void previousSongInPlaylist(struct playlist *playlist);
+void
+playlist_previous(struct playlist *playlist);
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end);
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end);
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song);
+playlist_delete_song(struct playlist *playlist, const struct song *song);
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to);
+playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to);
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to);
+playlist_move_id(struct playlist *playlist, unsigned id, int to);
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2);
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2);
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2);
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2);
bool
-getPlaylistRepeatStatus(const struct playlist *playlist);
+playlist_get_repeat(const struct playlist *playlist);
-void setPlaylistRepeatStatus(struct playlist *playlist, bool status);
+void
+playlist_set_repeat(struct playlist *playlist, bool status);
bool
-getPlaylistRandomStatus(const struct playlist *playlist);
+playlist_get_random(const struct playlist *playlist);
-void setPlaylistRandomStatus(struct playlist *playlist, bool status);
+void
+playlist_set_random(struct playlist *playlist, bool status);
bool
-getPlaylistSingleStatus(const struct playlist *playlist);
+playlist_get_single(const struct playlist *playlist);
-void setPlaylistSingleStatus(struct playlist *playlist, bool status);
+void
+playlist_set_single(struct playlist *playlist, bool status);
bool
-getPlaylistConsumeStatus(const struct playlist *playlist);
+playlist_get_consume(const struct playlist *playlist);
-void setPlaylistConsumeStatus(struct playlist *playlist, bool status);
+void
+playlist_set_consume(struct playlist *playlist, bool status);
-int getPlaylistCurrentSong(const struct playlist *playlist);
+int
+playlist_get_current_song(const struct playlist *playlist);
-int getPlaylistNextSong(const struct playlist *playlist);
+int
+playlist_get_next_song(const struct playlist *playlist);
unsigned
-getPlaylistSongId(const struct playlist *playlist, unsigned song);
+playlist_get_song_id(const struct playlist *playlist, unsigned song);
-int getPlaylistLength(const struct playlist *playlist);
+int
+playlist_get_length(const struct playlist *playlist);
unsigned long
-getPlaylistVersion(const struct playlist *playlist);
+playlist_get_version(const struct playlist *playlist);
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time);
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time);
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist,
+playlist_seek_song_id(struct playlist *playlist,
unsigned id, float seek_time);
-void playlistVersionChange(struct playlist *playlist);
-
-int is_valid_playlist_name(const char *utf8path);
+void
+playlist_increment_version_all(struct playlist *playlist);
#endif
diff --git a/src/playlist/asx_playlist_plugin.c b/src/playlist/asx_playlist_plugin.c
new file mode 100644
index 000000000..901212f90
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.c
@@ -0,0 +1,314 @@
+/*
+ * 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 "playlist/asx_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "asx"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct asx_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, ENTRY,
+ } state;
+
+ /**
+ * The current tag within the "entry" element. This is only
+ * valid if state==ENTRY. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static const gchar *
+get_attribute(const gchar **attribute_names, const gchar **attribute_values,
+ const gchar *name)
+{
+ for (unsigned i = 0; attribute_names[i] != NULL; ++i)
+ if (g_ascii_strcasecmp(attribute_names[i], name) == 0)
+ return attribute_values[i];
+
+ return NULL;
+}
+
+static void
+asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ parser->state = ENTRY;
+ parser->song = song_remote_new("asx:");
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "ref") == 0) {
+ const gchar *href = get_attribute(attribute_names,
+ attribute_values,
+ "href");
+ if (href != NULL) {
+ /* create new song object, and copy
+ the existing tag over; we cannot
+ replace the existing song's URI,
+ because that attribute is
+ immutable */
+ struct song *song = song_remote_new(href);
+
+ if (parser->song != NULL) {
+ song->tag = parser->song->tag;
+ parser->song->tag = NULL;
+ song_free(parser->song);
+ }
+
+ parser->song = song;
+ }
+ } else if (g_ascii_strcasecmp(element_name, "author") == 0)
+ /* is that correct? or should it be COMPOSER
+ or PERFORMER? */
+ parser->tag = TAG_ARTIST;
+ else if (g_ascii_strcasecmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+
+ break;
+ }
+}
+
+static void
+asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (g_ascii_strcasecmp(element_name, "entry") == 0) {
+ if (strcmp(parser->song->uri, "asx:") != 0)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+ else
+ song_free(parser->song);
+
+ parser->state = ROOT;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+ }
+}
+
+static void
+asx_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct asx_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case ENTRY:
+ if (parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser asx_parser = {
+ .start_element = asx_start_element,
+ .end_element = asx_end_element,
+ .text = asx_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+asx_parser_destroy(gpointer data)
+{
+ struct asx_parser *parser = data;
+
+ if (parser->state >= ENTRY)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct asx_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+asx_open_stream(struct input_stream *is)
+{
+ struct asx_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct asx_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the ASX XML file */
+
+ context = g_markup_parse_context_new(&asx_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, asx_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer));
+ if (nbytes == 0)
+ break;
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #asx_playlist object from the parsed song list */
+
+ playlist = g_new(struct asx_playlist, 1);
+ playlist_provider_init(&playlist->base, &asx_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+asx_close(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+asx_read(struct playlist_provider *_playlist)
+{
+ struct asx_playlist *playlist = (struct asx_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const asx_suffixes[] = {
+ "asx",
+ NULL
+};
+
+static const char *const asx_mime_types[] = {
+ "video/x-ms-asf",
+ NULL
+};
+
+const struct playlist_plugin asx_playlist_plugin = {
+ .name = "asx",
+
+ .open_stream = asx_open_stream,
+ .close = asx_close,
+ .read = asx_read,
+
+ .suffixes = asx_suffixes,
+ .mime_types = asx_mime_types,
+};
diff --git a/src/playlist/asx_playlist_plugin.h b/src/playlist/asx_playlist_plugin.h
new file mode 100644
index 000000000..021e97f56
--- /dev/null
+++ b/src/playlist/asx_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_ASX_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin asx_playlist_plugin;
+
+#endif
diff --git a/src/playlist/extm3u_playlist_plugin.c b/src/playlist/extm3u_playlist_plugin.c
new file mode 100644
index 000000000..bd81ff9fb
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.c
@@ -0,0 +1,161 @@
+/*
+ * 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 "playlist/extm3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+struct extm3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+extm3u_open_stream(struct input_stream *is)
+{
+ struct extm3u_playlist *playlist;
+ const char *line;
+
+ playlist = g_new(struct extm3u_playlist, 1);
+ playlist->tis = text_input_stream_new(is);
+
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL || strcmp(line, "#EXTM3U") != 0) {
+ /* no EXTM3U header: fall back to the plain m3u
+ plugin */
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+ return NULL;
+ }
+
+ playlist_provider_init(&playlist->base, &extm3u_playlist_plugin);
+ return &playlist->base;
+}
+
+static void
+extm3u_close(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+/**
+ * Parse a EXTINF line.
+ *
+ * @param line the rest of the input line after the colon
+ */
+static struct tag *
+extm3u_parse_tag(const char *line)
+{
+ long duration;
+ char *endptr;
+ const char *name;
+ struct tag *tag;
+
+ duration = strtol(line, &endptr, 10);
+ if (endptr[0] != ',')
+ /* malformed line */
+ return NULL;
+
+ if (duration < 0)
+ /* 0 means unknown duration */
+ duration = 0;
+
+ name = g_strchug(endptr + 1);
+ if (*name == 0 && duration == 0)
+ /* no information available; don't allocate a tag
+ object */
+ return NULL;
+
+ tag = tag_new();
+ tag->time = duration;
+
+ /* unfortunately, there is no real specification for the
+ EXTM3U format, so we must assume that the string after the
+ comma is opaque, and is just the song name*/
+ if (*name != 0)
+ tag_add_item(tag, TAG_NAME, name);
+
+ return tag;
+}
+
+static struct song *
+extm3u_read(struct playlist_provider *_playlist)
+{
+ struct extm3u_playlist *playlist = (struct extm3u_playlist *)_playlist;
+ struct tag *tag = NULL;
+ const char *line;
+ struct song *song;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL) {
+ if (tag != NULL)
+ tag_free(tag);
+ return NULL;
+ }
+
+ if (g_str_has_prefix(line, "#EXTINF:")) {
+ if (tag != NULL)
+ tag_free(tag);
+ tag = extm3u_parse_tag(line + 8);
+ continue;
+ }
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ song = song_remote_new(line);
+ song->tag = tag;
+ return song;
+}
+
+static const char *const extm3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const extm3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin extm3u_playlist_plugin = {
+ .name = "extm3u",
+
+ .open_stream = extm3u_open_stream,
+ .close = extm3u_close,
+ .read = extm3u_read,
+
+ .suffixes = extm3u_suffixes,
+ .mime_types = extm3u_mime_types,
+};
diff --git a/src/playlist/extm3u_playlist_plugin.h b/src/playlist/extm3u_playlist_plugin.h
new file mode 100644
index 000000000..07f18eafa
--- /dev/null
+++ b/src/playlist/extm3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_EXTM3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin extm3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/lastfm_playlist_plugin.c b/src/playlist/lastfm_playlist_plugin.c
new file mode 100644
index 000000000..c776d25ab
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.c
@@ -0,0 +1,292 @@
+/*
+ * 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 "playlist/lastfm_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "playlist_list.h"
+#include "conf.h"
+#include "uri.h"
+#include "song.h"
+#include "input_stream.h"
+#include "glib_compat.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+struct lastfm_playlist {
+ struct playlist_provider base;
+
+ struct input_stream is;
+
+ struct playlist_provider *xspf;
+};
+
+static struct {
+ char *user;
+ char *md5;
+} lastfm_config;
+
+static bool
+lastfm_init(const struct config_param *param)
+{
+ const char *user = config_get_block_string(param, "user", NULL);
+ const char *passwd = config_get_block_string(param, "password", NULL);
+
+ if (user == NULL || passwd == NULL) {
+ g_debug("disabling the last.fm playlist plugin "
+ "because account is not configured");
+ return false;
+ }
+
+ lastfm_config.user = g_uri_escape_string(user, NULL, false);
+
+#if GLIB_CHECK_VERSION(2,16,0)
+ if (strlen(passwd) != 32)
+ lastfm_config.md5 = g_compute_checksum_for_string(G_CHECKSUM_MD5,
+ passwd, strlen(passwd));
+ else
+#endif
+ lastfm_config.md5 = g_strdup(passwd);
+
+ return true;
+}
+
+static void
+lastfm_finish(void)
+{
+ g_free(lastfm_config.user);
+ g_free(lastfm_config.md5);
+}
+
+/**
+ * Simple data fetcher.
+ * @param url path or url of data to fetch.
+ * @return data fetched, or NULL on error. Must be freed with g_free.
+ */
+static char *
+lastfm_get(const char *url)
+{
+ struct input_stream input_stream;
+ bool success;
+ int ret;
+ char buffer[4096];
+ size_t length = 0, nbytes;
+
+ success = input_stream_open(&input_stream, url);
+ if (!success)
+ return NULL;
+
+ while (!input_stream.ready) {
+ ret = input_stream_buffer(&input_stream);
+ if (ret < 0) {
+ input_stream_close(&input_stream);
+ return NULL;
+ }
+ }
+
+ do {
+ nbytes = input_stream_read(&input_stream, buffer + length,
+ sizeof(buffer) - length);
+ if (nbytes == 0) {
+ if (input_stream_eof(&input_stream))
+ break;
+
+ /* I/O error */
+ input_stream_close(&input_stream);
+ return NULL;
+ }
+
+ length += nbytes;
+ } while (length < sizeof(buffer));
+
+ input_stream_close(&input_stream);
+ return g_strndup(buffer, length);
+}
+
+/**
+ * Ini-style value fetcher.
+ * @param response data through which to search.
+ * @param name name of value to search for.
+ * @return value for param name in param reponse or NULL on error. Free with g_free.
+ */
+static char *
+lastfm_find(const char *response, const char *name)
+{
+ size_t name_length = strlen(name);
+
+ while (true) {
+ const char *eol = strchr(response, '\n');
+ if (eol == NULL)
+ return NULL;
+
+ if (strncmp(response, name, name_length) == 0 &&
+ response[name_length] == '=') {
+ response += name_length + 1;
+ return g_strndup(response, eol - response);
+ }
+
+ response = eol + 1;
+ }
+}
+
+static struct playlist_provider *
+lastfm_open_uri(const char *uri)
+{
+ struct lastfm_playlist *playlist;
+ char *p, *q, *response, *session;
+ bool success;
+
+ /* handshake */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/handshake.php?"
+ "version=1.1.1&platform=linux&"
+ "username=", lastfm_config.user, "&"
+ "passwordmd5=", lastfm_config.md5, "&"
+ "debug=0&partner=", NULL);
+ response = lastfm_get(p);
+ g_free(p);
+ if (response == NULL)
+ return NULL;
+
+ /* extract session id from response */
+
+ session = lastfm_find(response, "session");
+ g_free(response);
+ if (session == NULL) {
+ g_warning("last.fm handshake failed");
+ return NULL;
+ }
+
+ q = g_uri_escape_string(session, NULL, false);
+ g_free(session);
+ session = q;
+
+ g_debug("session='%s'", session);
+
+ /* "adjust" last.fm radio */
+
+ if (strlen(uri) > 9) {
+ char *escaped_uri;
+
+ escaped_uri = g_uri_escape_string(uri, NULL, false);
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/adjust.php?"
+ "session=", session, "&url=", escaped_uri, "&debug=0",
+ NULL);
+ g_free(escaped_uri);
+
+ response = lastfm_get(p);
+ g_free(response);
+ g_free(p);
+
+ if (response == NULL) {
+ g_free(session);
+ return NULL;
+ }
+ }
+
+ /* create the playlist object */
+
+ playlist = g_new(struct lastfm_playlist, 1);
+ playlist_provider_init(&playlist->base, &lastfm_playlist_plugin);
+
+ /* open the last.fm playlist */
+
+ p = g_strconcat("http://ws.audioscrobbler.com/radio/xspf.php?"
+ "sk=", session, "&discovery=0&desktop=1.5.1.31879",
+ NULL);
+ g_free(session);
+
+ success = input_stream_open(&playlist->is, p);
+ g_free(p);
+
+ if (!success) {
+ g_warning("Failed to load XSPF playlist");
+ g_free(playlist);
+ return NULL;
+ }
+
+ while (!playlist->is.ready) {
+ int ret = input_stream_buffer(&playlist->is);
+ if (ret < 0) {
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+ return NULL;
+ }
+
+ if (ret == 0)
+ /* nothing was buffered - wait */
+ g_usleep(10000);
+ }
+
+ /* last.fm does not send a MIME type, we have to fake it here
+ :-( */
+ g_free(playlist->is.mime);
+ playlist->is.mime = g_strdup("application/xspf+xml");
+
+ /* parse the XSPF playlist */
+
+ playlist->xspf = playlist_list_open_stream(&playlist->is, NULL);
+ if (playlist->xspf == NULL) {
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+ g_warning("Failed to parse XSPF playlist");
+ return NULL;
+ }
+
+ return &playlist->base;
+}
+
+static void
+lastfm_close(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ playlist_plugin_close(playlist->xspf);
+ input_stream_close(&playlist->is);
+ g_free(playlist);
+}
+
+static struct song *
+lastfm_read(struct playlist_provider *_playlist)
+{
+ struct lastfm_playlist *playlist = (struct lastfm_playlist *)_playlist;
+
+ return playlist_plugin_read(playlist->xspf);
+}
+
+static const char *const lastfm_schemes[] = {
+ "lastfm",
+ NULL
+};
+
+const struct playlist_plugin lastfm_playlist_plugin = {
+ .name = "lastfm",
+
+ .init = lastfm_init,
+ .finish = lastfm_finish,
+ .open_uri = lastfm_open_uri,
+ .close = lastfm_close,
+ .read = lastfm_read,
+
+ .schemes = lastfm_schemes,
+};
diff --git a/src/playlist/lastfm_playlist_plugin.h b/src/playlist/lastfm_playlist_plugin.h
new file mode 100644
index 000000000..871a4a043
--- /dev/null
+++ b/src/playlist/lastfm_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_LASTFM_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin lastfm_playlist_plugin;
+
+#endif
diff --git a/src/playlist/m3u_playlist_plugin.c b/src/playlist/m3u_playlist_plugin.c
new file mode 100644
index 000000000..dbabea2e6
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.c
@@ -0,0 +1,92 @@
+/*
+ * 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 "playlist/m3u_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "text_input_stream.h"
+#include "uri.h"
+#include "song.h"
+
+#include <glib.h>
+
+struct m3u_playlist {
+ struct playlist_provider base;
+
+ struct text_input_stream *tis;
+};
+
+static struct playlist_provider *
+m3u_open_stream(struct input_stream *is)
+{
+ struct m3u_playlist *playlist = g_new(struct m3u_playlist, 1);
+
+ playlist_provider_init(&playlist->base, &m3u_playlist_plugin);
+ playlist->tis = text_input_stream_new(is);
+
+ return &playlist->base;
+}
+
+static void
+m3u_close(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+
+ text_input_stream_free(playlist->tis);
+ g_free(playlist);
+}
+
+static struct song *
+m3u_read(struct playlist_provider *_playlist)
+{
+ struct m3u_playlist *playlist = (struct m3u_playlist *)_playlist;
+ const char *line;
+
+ do {
+ line = text_input_stream_read(playlist->tis);
+ if (line == NULL)
+ return NULL;
+
+ while (*line != 0 && g_ascii_isspace(*line))
+ ++line;
+ } while (line[0] == '#' || *line == 0);
+
+ return song_remote_new(line);
+}
+
+static const char *const m3u_suffixes[] = {
+ "m3u",
+ NULL
+};
+
+static const char *const m3u_mime_types[] = {
+ "audio/x-mpegurl",
+ NULL
+};
+
+const struct playlist_plugin m3u_playlist_plugin = {
+ .name = "m3u",
+
+ .open_stream = m3u_open_stream,
+ .close = m3u_close,
+ .read = m3u_read,
+
+ .suffixes = m3u_suffixes,
+ .mime_types = m3u_mime_types,
+};
diff --git a/src/playlist/m3u_playlist_plugin.h b/src/playlist/m3u_playlist_plugin.h
new file mode 100644
index 000000000..3cb4b8874
--- /dev/null
+++ b/src/playlist/m3u_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_M3U_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin m3u_playlist_plugin;
+
+#endif
diff --git a/src/playlist/pls_playlist_plugin.c b/src/playlist/pls_playlist_plugin.c
new file mode 100644
index 000000000..5308b7160
--- /dev/null
+++ b/src/playlist/pls_playlist_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"
+#include "playlist/pls_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+#include <glib.h>
+
+struct pls_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static void pls_parser(GKeyFile *keyfile, struct pls_playlist *playlist)
+{
+ gchar *key;
+ gchar *value;
+ int length;
+ GError *error = NULL;
+ int num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "NumberOfEntries", &error);
+ if (error) {
+ g_debug("Invalid PLS file: '%s'", error->message);
+ g_error_free(error);
+ error = NULL;
+
+ /* Hack to work around shoutcast failure to comform to spec */
+ num_entries = g_key_file_get_integer(keyfile, "playlist",
+ "numberofentries", &error);
+ if (error) {
+ g_error_free(error);
+ error = NULL;
+ }
+ }
+
+ while (num_entries > 0) {
+ struct song *song;
+ key = g_strdup_printf("File%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ if(error) {
+ g_debug("Invalid PLS entry %s: '%s'",key, error->message);
+ g_error_free(error);
+ g_free(key);
+ return;
+ }
+ g_free(key);
+
+ song = song_remote_new(value);
+ g_free(value);
+
+ key = g_strdup_printf("Title%i", num_entries);
+ value = g_key_file_get_string(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && value){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ tag_add_item(song->tag,TAG_TITLE, value);
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+ g_free(value);
+
+ key = g_strdup_printf("Length%i", num_entries);
+ length = g_key_file_get_integer(keyfile, "playlist", key,
+ &error);
+ g_free(key);
+ if(error == NULL && length > 0){
+ if (song->tag == NULL)
+ song->tag = tag_new();
+ song->tag->time = length;
+ }
+ /* Ignore errors? Most likely value not present */
+ if(error) g_error_free(error);
+ error = NULL;
+
+ playlist->songs = g_slist_prepend(playlist->songs, song);
+ num_entries--;
+ }
+
+}
+
+static struct playlist_provider *
+pls_open_stream(struct input_stream *is)
+{
+ GError *error = NULL;
+ size_t nbytes;
+ char buffer[1024];
+ bool success;
+ GKeyFile *keyfile;
+ struct pls_playlist *playlist;
+ GString *kf_data = g_string_new("");
+
+ do {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer));
+ if(nbytes ==0)
+ break;
+ kf_data = g_string_append_len(kf_data, buffer,nbytes);
+ /* Limit to 64k */
+ } while(kf_data->len < 65536);
+
+ if (kf_data->len == 0) {
+ g_warning("KeyFile parser failed: No Data");
+ g_string_free(kf_data, TRUE);
+ return NULL;
+ }
+
+ keyfile = g_key_file_new();
+ success = g_key_file_load_from_data(keyfile,
+ kf_data->str, kf_data->len,
+ G_KEY_FILE_NONE, &error);
+
+ g_string_free(kf_data, TRUE);
+
+ if (!success) {
+ g_warning("KeyFile parser failed: %s", error->message);
+ g_error_free(error);
+ g_key_file_free(keyfile);
+ return NULL;
+ }
+
+ playlist = g_new(struct pls_playlist, 1);
+ playlist_provider_init(&playlist->base, &pls_playlist_plugin);
+ playlist->songs = NULL;
+
+ pls_parser(keyfile, playlist);
+
+ g_key_file_free(keyfile);
+ return &playlist->base;
+}
+
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+pls_close(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+
+ g_free(playlist);
+
+}
+
+static struct song *
+pls_read(struct playlist_provider *_playlist)
+{
+ struct pls_playlist *playlist = (struct pls_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const pls_suffixes[] = {
+ "pls",
+ NULL
+};
+
+static const char *const pls_mime_types[] = {
+ "audio/x-scpls",
+ NULL
+};
+
+const struct playlist_plugin pls_playlist_plugin = {
+ .name = "pls",
+
+ .open_stream = pls_open_stream,
+ .close = pls_close,
+ .read = pls_read,
+
+ .suffixes = pls_suffixes,
+ .mime_types = pls_mime_types,
+};
diff --git a/src/playlist/pls_playlist_plugin.h b/src/playlist/pls_playlist_plugin.h
new file mode 100644
index 000000000..7bf1951b8
--- /dev/null
+++ b/src/playlist/pls_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_PLS_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin pls_playlist_plugin;
+
+#endif
diff --git a/src/playlist/xspf_playlist_plugin.c b/src/playlist/xspf_playlist_plugin.c
new file mode 100644
index 000000000..687765b3a
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.c
@@ -0,0 +1,334 @@
+/*
+ * 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 "playlist/xspf_playlist_plugin.h"
+#include "playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "song.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "xspf"
+
+/**
+ * This is the state object for the GLib XML parser.
+ */
+struct xspf_parser {
+ /**
+ * The list of songs (in reverse order because that's faster
+ * while adding).
+ */
+ GSList *songs;
+
+ /**
+ * The current position in the XML file.
+ */
+ enum {
+ ROOT, PLAYLIST, TRACKLIST, TRACK,
+ LOCATION,
+ } state;
+
+ /**
+ * The current tag within the "track" element. This is only
+ * valid if state==TRACK. TAG_NUM_OF_ITEM_TYPES means there
+ * is no (known) tag.
+ */
+ enum tag_type tag;
+
+ /**
+ * The current song. It is allocated after the "location"
+ * element.
+ */
+ struct song *song;
+};
+
+static void
+xspf_start_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ G_GNUC_UNUSED const gchar **attribute_names,
+ G_GNUC_UNUSED const gchar **attribute_values,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "trackList") == 0)
+ parser->state = TRACKLIST;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "track") == 0) {
+ parser->state = TRACK;
+ parser->song = NULL;
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+ }
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "location") == 0)
+ parser->state = LOCATION;
+ else if (strcmp(element_name, "title") == 0)
+ parser->tag = TAG_TITLE;
+ else if (strcmp(element_name, "creator") == 0)
+ /* TAG_COMPOSER would be more correct
+ according to the XSPF spec */
+ parser->tag = TAG_ARTIST;
+ else if (strcmp(element_name, "annotation") == 0)
+ parser->tag = TAG_COMMENT;
+ else if (strcmp(element_name, "album") == 0)
+ parser->tag = TAG_ALBUM;
+ else if (strcmp(element_name, "trackNum") == 0)
+ parser->tag = TAG_TRACK;
+
+ break;
+
+ case LOCATION:
+ break;
+ }
+}
+
+static void
+xspf_end_element(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ break;
+
+ case PLAYLIST:
+ if (strcmp(element_name, "playlist") == 0)
+ parser->state = ROOT;
+
+ break;
+
+ case TRACKLIST:
+ if (strcmp(element_name, "tracklist") == 0)
+ parser->state = PLAYLIST;
+
+ break;
+
+ case TRACK:
+ if (strcmp(element_name, "track") == 0) {
+ if (parser->song != NULL)
+ parser->songs = g_slist_prepend(parser->songs,
+ parser->song);
+
+ parser->state = TRACKLIST;
+ } else
+ parser->tag = TAG_NUM_OF_ITEM_TYPES;
+
+ break;
+
+ case LOCATION:
+ parser->state = TRACK;
+ break;
+ }
+}
+
+static void
+xspf_text(G_GNUC_UNUSED GMarkupParseContext *context,
+ const gchar *text, gsize text_len,
+ gpointer user_data, G_GNUC_UNUSED GError **error)
+{
+ struct xspf_parser *parser = user_data;
+
+ switch (parser->state) {
+ case ROOT:
+ case PLAYLIST:
+ case TRACKLIST:
+ break;
+
+ case TRACK:
+ if (parser->song != NULL &&
+ parser->tag != TAG_NUM_OF_ITEM_TYPES) {
+ if (parser->song->tag == NULL)
+ parser->song->tag = tag_new();
+ tag_add_item_n(parser->song->tag, parser->tag,
+ text, text_len);
+ }
+
+ break;
+
+ case LOCATION:
+ if (parser->song == NULL) {
+ char *uri = g_strndup(text, text_len);
+ parser->song = song_remote_new(uri);
+ g_free(uri);
+ }
+
+ break;
+ }
+}
+
+static const GMarkupParser xspf_parser = {
+ .start_element = xspf_start_element,
+ .end_element = xspf_end_element,
+ .text = xspf_text,
+};
+
+static void
+song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
+{
+ struct song *song = data;
+
+ song_free(song);
+}
+
+static void
+xspf_parser_destroy(gpointer data)
+{
+ struct xspf_parser *parser = data;
+
+ if (parser->state >= TRACK && parser->song != NULL)
+ song_free(parser->song);
+
+ g_slist_foreach(parser->songs, song_free_callback, NULL);
+ g_slist_free(parser->songs);
+}
+
+/*
+ * The playlist object
+ *
+ */
+
+struct xspf_playlist {
+ struct playlist_provider base;
+
+ GSList *songs;
+};
+
+static struct playlist_provider *
+xspf_open_stream(struct input_stream *is)
+{
+ struct xspf_parser parser = {
+ .songs = NULL,
+ .state = ROOT,
+ };
+ struct xspf_playlist *playlist;
+ GMarkupParseContext *context;
+ char buffer[1024];
+ size_t nbytes;
+ bool success;
+ GError *error = NULL;
+
+ /* parse the XSPF XML file */
+
+ context = g_markup_parse_context_new(&xspf_parser,
+ G_MARKUP_TREAT_CDATA_AS_TEXT,
+ &parser, xspf_parser_destroy);
+
+ while (true) {
+ nbytes = input_stream_read(is, buffer, sizeof(buffer));
+ if (nbytes == 0)
+ break;
+
+ success = g_markup_parse_context_parse(context, buffer, nbytes,
+ &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+ }
+
+ success = g_markup_parse_context_end_parse(context, &error);
+ if (!success) {
+ g_warning("XML parser failed: %s", error->message);
+ g_error_free(error);
+ g_markup_parse_context_free(context);
+ return NULL;
+ }
+
+ /* create a #xspf_playlist object from the parsed song list */
+
+ playlist = g_new(struct xspf_playlist, 1);
+ playlist_provider_init(&playlist->base, &xspf_playlist_plugin);
+ playlist->songs = g_slist_reverse(parser.songs);
+ parser.songs = NULL;
+
+ g_markup_parse_context_free(context);
+
+ return &playlist->base;
+}
+
+static void
+xspf_close(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+
+ g_slist_foreach(playlist->songs, song_free_callback, NULL);
+ g_slist_free(playlist->songs);
+ g_free(playlist);
+}
+
+static struct song *
+xspf_read(struct playlist_provider *_playlist)
+{
+ struct xspf_playlist *playlist = (struct xspf_playlist *)_playlist;
+ struct song *song;
+
+ if (playlist->songs == NULL)
+ return NULL;
+
+ song = playlist->songs->data;
+ playlist->songs = g_slist_remove(playlist->songs, song);
+
+ return song;
+}
+
+static const char *const xspf_suffixes[] = {
+ "xspf",
+ NULL
+};
+
+static const char *const xspf_mime_types[] = {
+ "application/xspf+xml",
+ NULL
+};
+
+const struct playlist_plugin xspf_playlist_plugin = {
+ .name = "xspf",
+
+ .open_stream = xspf_open_stream,
+ .close = xspf_close,
+ .read = xspf_read,
+
+ .suffixes = xspf_suffixes,
+ .mime_types = xspf_mime_types,
+};
diff --git a/src/playlist/xspf_playlist_plugin.h b/src/playlist/xspf_playlist_plugin.h
new file mode 100644
index 000000000..c2c36fbed
--- /dev/null
+++ b/src/playlist/xspf_playlist_plugin.h
@@ -0,0 +1,25 @@
+/*
+ * 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_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_XSPF_PLAYLIST_PLUGIN_H
+
+extern const struct playlist_plugin xspf_playlist_plugin;
+
+#endif
diff --git a/src/playlist_control.c b/src/playlist_control.c
index 4359611fd..2f75b504f 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist_internal.h"
#include "player_control.h"
@@ -30,15 +31,7 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"
-enum {
- /**
- * When the "prev" command is received, rewind the current
- * track if this number of seconds has already elapsed.
- */
- PLAYLIST_PREV_UNLESS_ELAPSED = 10,
-};
-
-void stopPlaylist(struct playlist *playlist)
+void playlist_stop(struct playlist *playlist)
{
if (!playlist->playing)
return;
@@ -46,7 +39,7 @@ void stopPlaylist(struct playlist *playlist)
assert(playlist->current >= 0);
g_debug("stop");
- playerWait();
+ pc_stop();
playlist->queued = -1;
playlist->playing = false;
@@ -68,11 +61,11 @@ void stopPlaylist(struct playlist *playlist)
}
}
-enum playlist_result playPlaylist(struct playlist *playlist, int song)
+enum playlist_result playlist_play(struct playlist *playlist, int song)
{
unsigned i = song;
- clearPlayerError();
+ pc_clear_error();
if (song == -1) {
/* play any song ("current" song, or the first song */
@@ -83,7 +76,7 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
if (playlist->playing) {
/* already playing: unpause playback, just in
case it was paused, and return */
- playerSetPause(0);
+ pc_set_pause(false);
return PLAYLIST_RESULT_SUCCESS;
}
@@ -115,28 +108,28 @@ enum playlist_result playPlaylist(struct playlist *playlist, int song)
playlist->stop_on_error = false;
playlist->error_count = 0;
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-playPlaylistById(struct playlist *playlist, int id)
+playlist_play_id(struct playlist *playlist, int id)
{
int song;
if (id == -1) {
- return playPlaylist(playlist, id);
+ return playlist_play(playlist, id);
}
song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return playPlaylist(playlist, song);
+ return playlist_play(playlist, song);
}
void
-nextSongInPlaylist(struct playlist *playlist)
+playlist_next(struct playlist *playlist)
{
int next_order;
int current;
@@ -157,7 +150,7 @@ nextSongInPlaylist(struct playlist *playlist)
/* cancel single */
playlist->queue.single = false;
/* no song after this one: stop playback */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* reset "current song" */
playlist->current = -1;
@@ -174,50 +167,42 @@ nextSongInPlaylist(struct playlist *playlist)
queue_shuffle_order(&playlist->queue);
/* note that playlist->current and playlist->queued are
- now invalid, but playPlaylistOrderNumber() will
+ now invalid, but playlist_play_order() will
discard them anyway */
}
- playPlaylistOrderNumber(playlist, next_order);
+ playlist_play_order(playlist, next_order);
}
/* Consume mode removes each played songs. */
if(playlist->queue.consume)
- deleteFromPlaylist(playlist, queue_order_to_position(&playlist->queue, current));
+ playlist_delete(playlist, queue_order_to_position(&playlist->queue, current));
}
-void previousSongInPlaylist(struct playlist *playlist)
+void playlist_previous(struct playlist *playlist)
{
if (!playlist->playing)
return;
- if (g_timer_elapsed(playlist->prev_elapsed, NULL) >= 1.0 &&
- getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) {
- /* re-start playing the current song (just like the
- "prev" button on CD players) */
+ assert(queue_length(&playlist->queue) > 0);
- playPlaylistOrderNumber(playlist, playlist->current);
+ if (playlist->current > 0) {
+ /* play the preceding song */
+ playlist_play_order(playlist,
+ playlist->current - 1);
+ } else if (playlist->queue.repeat) {
+ /* play the last song in "repeat" mode */
+ playlist_play_order(playlist,
+ queue_length(&playlist->queue) - 1);
} else {
- if (playlist->current > 0) {
- /* play the preceding song */
- playPlaylistOrderNumber(playlist,
- playlist->current - 1);
- } else if (playlist->queue.repeat) {
- /* play the last song in "repeat" mode */
- playPlaylistOrderNumber(playlist,
- queue_length(&playlist->queue) - 1);
- } else {
- /* re-start playing the current song if it's
- the first one */
- playPlaylistOrderNumber(playlist, playlist->current);
- }
+ /* re-start playing the current song if it's
+ the first one */
+ playlist_play_order(playlist, playlist->current);
}
-
- g_timer_start(playlist->prev_elapsed);
}
enum playlist_result
-seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
+playlist_seek_song(struct playlist *playlist, unsigned song, float seek_time)
{
const struct song *queued;
unsigned i;
@@ -233,7 +218,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
else
i = song;
- clearPlayerError();
+ pc_clear_error();
playlist->stop_on_error = true;
playlist->error_count = 0;
@@ -241,7 +226,7 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
/* seeking is not within the current song - first
start playing the new song */
- playPlaylistOrderNumber(playlist, i);
+ playlist_play_order(playlist, i);
queued = NULL;
}
@@ -259,11 +244,11 @@ seekSongInPlaylist(struct playlist *playlist, unsigned song, float seek_time)
}
enum playlist_result
-seekSongInPlaylistById(struct playlist *playlist, unsigned id, float seek_time)
+playlist_seek_song_id(struct playlist *playlist, unsigned id, float seek_time)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return seekSongInPlaylist(playlist, song, seek_time);
+ return playlist_seek_song(playlist, song, seek_time);
}
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
index b83dc0933..956c33d8e 100644
--- a/src/playlist_edit.c
+++ b/src/playlist_edit.c
@@ -23,6 +23,7 @@
*
*/
+#include "config.h"
#include "playlist_internal.h"
#include "player_control.h"
#include "database.h"
@@ -35,16 +36,16 @@
#include <unistd.h>
#include <stdlib.h>
-static void incrPlaylistVersion(struct playlist *playlist)
+static void playlist_increment_version(struct playlist *playlist)
{
queue_increment_version(&playlist->queue);
idle_add(IDLE_PLAYLIST);
}
-void clearPlaylist(struct playlist *playlist)
+void playlist_clear(struct playlist *playlist)
{
- stopPlaylist(playlist);
+ playlist_stop(playlist);
/* make sure there are no references to allocated songs
anymore */
@@ -58,7 +59,7 @@ void clearPlaylist(struct playlist *playlist)
playlist->current = -1;
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
}
#ifndef WIN32
@@ -86,41 +87,12 @@ playlist_append_file(struct playlist *playlist, const char *path, int uid,
if (song == NULL)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return addSongToPlaylist(playlist, song, added_id);
+ return playlist_append_song(playlist, song, added_id);
}
#endif
-static struct song *
-song_by_url(const char *url)
-{
- struct song *song;
-
- song = db_get_song(url);
- if (song != NULL)
- return song;
-
- if (uri_has_scheme(url))
- return song_remote_new(url);
-
- return NULL;
-}
-
-enum playlist_result
-addToPlaylist(struct playlist *playlist, const char *url, unsigned *added_id)
-{
- struct song *song;
-
- g_debug("add to playlist: %s", url);
-
- song = song_by_url(url);
- if (song == NULL)
- return PLAYLIST_RESULT_NO_SUCH_SONG;
-
- return addSongToPlaylist(playlist, song, added_id);
-}
-
enum playlist_result
-addSongToPlaylist(struct playlist *playlist,
+playlist_append_song(struct playlist *playlist,
struct song *song, unsigned *added_id)
{
const struct song *queued;
@@ -147,7 +119,7 @@ addSongToPlaylist(struct playlist *playlist,
queue_length(&playlist->queue));
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -157,8 +129,38 @@ addSongToPlaylist(struct playlist *playlist,
return PLAYLIST_RESULT_SUCCESS;
}
+static struct song *
+song_by_uri(const char *uri)
+{
+ struct song *song;
+
+ song = db_get_song(uri);
+ if (song != NULL)
+ return song;
+
+ if (uri_has_scheme(uri))
+ return song_remote_new(uri);
+
+ return NULL;
+}
+
+enum playlist_result
+playlist_append_uri(struct playlist *playlist, const char *uri,
+ unsigned *added_id)
+{
+ struct song *song;
+
+ g_debug("add to playlist: %s", uri);
+
+ song = song_by_uri(uri);
+ if (song == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_SONG;
+
+ return playlist_append_song(playlist, song, added_id);
+}
+
enum playlist_result
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2)
{
const struct song *queued;
@@ -188,7 +190,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
playlist->current = song1;
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -196,7 +198,7 @@ swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
}
enum playlist_result
-swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
+playlist_swap_songs_id(struct playlist *playlist, unsigned id1, unsigned id2)
{
int song1 = queue_id_to_position(&playlist->queue, id1);
int song2 = queue_id_to_position(&playlist->queue, id2);
@@ -204,28 +206,25 @@ swapSongsInPlaylistById(struct playlist *playlist, unsigned id1, unsigned id2)
if (song1 < 0 || song2 < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return swapSongsInPlaylist(playlist, song1, song2);
+ return playlist_swap_songs(playlist, song1, song2);
}
-enum playlist_result
-deleteFromPlaylist(struct playlist *playlist, unsigned song)
+static void
+playlist_delete_internal(struct playlist *playlist, unsigned song,
+ const struct song **queued_p)
{
- const struct song *queued;
unsigned songOrder;
- if (song >= queue_length(&playlist->queue))
- return PLAYLIST_RESULT_BAD_RANGE;
-
- queued = playlist_get_queued_song(playlist);
+ assert(song < queue_length(&playlist->queue));
songOrder = queue_position_to_order(&playlist->queue, song);
if (playlist->playing && playlist->current == (int)songOrder) {
- bool paused = getPlayerState() == PLAYER_STATE_PAUSE;
+ bool paused = pc_get_state() == PLAYER_STATE_PAUSE;
/* the current song is going to be deleted: stop the player */
- playerWait();
+ pc_stop();
playlist->playing = false;
/* see which song is going to be played instead */
@@ -237,13 +236,13 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
if (playlist->current >= 0 && !paused)
/* play the song after the deleted one */
- playPlaylistOrderNumber(playlist, playlist->current);
+ playlist_play_order(playlist, playlist->current);
else
/* no songs left to play, stop playback
completely */
- stopPlaylist(playlist);
+ playlist_stop(playlist);
- queued = NULL;
+ *queued_p = NULL;
} else if (playlist->current == (int)songOrder)
/* there's a "current song" but we're not playing
currently - clear "current" */
@@ -256,41 +255,80 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
queue_delete(&playlist->queue, song);
- incrPlaylistVersion(playlist);
-
/* update the "current" and "queued" variables */
if (playlist->current > (int)songOrder) {
playlist->current--;
}
+}
+
+enum playlist_result
+playlist_delete(struct playlist *playlist, unsigned song)
+{
+ const struct song *queued;
+
+ if (song >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ queued = playlist_get_queued_song(playlist);
+
+ playlist_delete_internal(playlist, song, &queued);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
return PLAYLIST_RESULT_SUCCESS;
}
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned id)
+playlist_delete_range(struct playlist *playlist, unsigned start, unsigned end)
+{
+ const struct song *queued;
+
+ if (start >= queue_length(&playlist->queue))
+ return PLAYLIST_RESULT_BAD_RANGE;
+
+ if (end > queue_length(&playlist->queue))
+ end = queue_length(&playlist->queue);
+
+ if (start >= end)
+ return PLAYLIST_RESULT_SUCCESS;
+
+ queued = playlist_get_queued_song(playlist);
+
+ do {
+ playlist_delete_internal(playlist, --end, &queued);
+ } while (end != start);
+
+ playlist_increment_version(playlist);
+ playlist_update_queued_song(playlist, queued);
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+enum playlist_result
+playlist_delete_id(struct playlist *playlist, unsigned id)
{
int song = queue_id_to_position(&playlist->queue, id);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return deleteFromPlaylist(playlist, song);
+ return playlist_delete(playlist, song);
}
void
-deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
+playlist_delete_song(struct playlist *playlist, const struct song *song)
{
for (int i = queue_length(&playlist->queue) - 1; i >= 0; --i)
if (song == queue_get(&playlist->queue, i))
- deleteFromPlaylist(playlist, i);
+ playlist_delete(playlist, i);
pc_song_deleted(song);
}
enum playlist_result
-moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to)
+playlist_move_range(struct playlist *playlist,
+ unsigned start, unsigned end, int to)
{
const struct song *queued;
int currentSong;
@@ -342,7 +380,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -350,16 +388,17 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
enum playlist_result
-moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
+playlist_move_id(struct playlist *playlist, unsigned id1, int to)
{
int song = queue_id_to_position(&playlist->queue, id1);
if (song < 0)
return PLAYLIST_RESULT_NO_SUCH_SONG;
- return moveSongRangeInPlaylist(playlist, song, song+1, to);
+ return playlist_move_range(playlist, song, song+1, to);
}
-void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
+void
+playlist_shuffle(struct playlist *playlist, unsigned start, unsigned end)
{
const struct song *queued;
@@ -399,7 +438,7 @@ void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
queue_shuffle_range(&playlist->queue, start, end);
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
}
diff --git a/src/playlist_global.c b/src/playlist_global.c
index fa810bbc3..8cfbf2c5d 100644
--- a/src/playlist_global.c
+++ b/src/playlist_global.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist.h"
#include "playlist_state.h"
#include "event_pipe.h"
@@ -37,10 +38,11 @@ playlist_tag_event(void)
static void
playlist_event(void)
{
- syncPlayerAndPlaylist(&g_playlist);
+ playlist_sync(&g_playlist);
}
-void initPlaylist(void)
+void
+playlist_global_init(void)
{
playlist_init(&g_playlist);
@@ -48,17 +50,8 @@ void initPlaylist(void)
event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event);
}
-void finishPlaylist(void)
+void
+playlist_global_finish(void)
{
playlist_finish(&g_playlist);
}
-
-void savePlaylistState(FILE *fp)
-{
- playlist_state_save(fp, &g_playlist);
-}
-
-void readPlaylistState(FILE *fp)
-{
- playlist_state_restore(fp, &g_playlist);
-}
diff --git a/src/playlist_internal.h b/src/playlist_internal.h
index af880691b..8b2f780cc 100644
--- a/src/playlist_internal.h
+++ b/src/playlist_internal.h
@@ -47,6 +47,6 @@ playlist_update_queued_song(struct playlist *playlist,
const struct song *prev);
void
-playPlaylistOrderNumber(struct playlist *playlist, int orderNum);
+playlist_play_order(struct playlist *playlist, int orderNum);
#endif
diff --git a/src/playlist_list.c b/src/playlist_list.c
new file mode 100644
index 000000000..2ea174a2f
--- /dev/null
+++ b/src/playlist_list.c
@@ -0,0 +1,255 @@
+/*
+ * 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 "playlist_list.h"
+#include "playlist_plugin.h"
+#include "playlist/extm3u_playlist_plugin.h"
+#include "playlist/m3u_playlist_plugin.h"
+#include "playlist/xspf_playlist_plugin.h"
+#include "playlist/lastfm_playlist_plugin.h"
+#include "playlist/pls_playlist_plugin.h"
+#include "playlist/asx_playlist_plugin.h"
+#include "input_stream.h"
+#include "uri.h"
+#include "utils.h"
+#include "conf.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+static const struct playlist_plugin *const playlist_plugins[] = {
+ &extm3u_playlist_plugin,
+ &m3u_playlist_plugin,
+ &xspf_playlist_plugin,
+ &pls_playlist_plugin,
+ &asx_playlist_plugin,
+#ifdef ENABLE_LASTFM
+ &lastfm_playlist_plugin,
+#endif
+ NULL
+};
+
+/** which plugins have been initialized successfully? */
+static bool playlist_plugins_enabled[G_N_ELEMENTS(playlist_plugins)];
+
+/**
+ * Find the "playlist" configuration block for the specified plugin.
+ *
+ * @param plugin_name the name of the playlist plugin
+ * @return the configuration block, or NULL if none was configured
+ */
+static const struct config_param *
+playlist_plugin_config(const char *plugin_name)
+{
+ const struct config_param *param = NULL;
+
+ assert(plugin_name != NULL);
+
+ while ((param = config_get_next_param(CONF_PLAYLIST_PLUGIN, param)) != NULL) {
+ const char *name =
+ config_get_block_string(param, "name", NULL);
+ if (name == NULL)
+ g_error("playlist configuration without 'plugin' name in line %d",
+ param->line);
+
+ if (strcmp(name, plugin_name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+void
+playlist_list_global_init(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+ const struct config_param *param =
+ playlist_plugin_config(plugin->name);
+
+ if (!config_get_block_bool(param, "enabled", true))
+ /* the plugin is disabled in mpd.conf */
+ continue;
+
+ playlist_plugins_enabled[i] =
+ playlist_plugin_init(playlist_plugins[i], param);
+ }
+}
+
+void
+playlist_list_global_finish(void)
+{
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i)
+ if (playlist_plugins_enabled[i])
+ playlist_plugin_finish(playlist_plugins[i]);
+}
+
+/* g_uri_parse_scheme() was introduced in GLib 2.16 */
+#if !GLIB_CHECK_VERSION(2,16,0)
+static char *
+g_uri_parse_scheme(const char *uri)
+{
+ const char *end = strstr(uri, "://");
+ if (end == NULL)
+ return NULL;
+ return g_strndup(uri, end - uri);
+}
+#endif
+
+struct playlist_provider *
+playlist_list_open_uri(const char *uri)
+{
+ char *scheme;
+ struct playlist_provider *playlist = NULL;
+
+ assert(uri != NULL);
+
+ scheme = g_uri_parse_scheme(uri);
+ if (scheme == NULL)
+ return NULL;
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->schemes != NULL &&
+ string_array_contains(plugin->schemes, scheme)) {
+ playlist = playlist_plugin_open_uri(plugin, uri);
+ if (playlist != NULL)
+ break;
+ }
+ }
+
+ g_free(scheme);
+ return playlist;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_mime(struct input_stream *is)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(is->mime != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] &&
+ plugin->mime_types != NULL &&
+ string_array_contains(plugin->mime_types, is->mime)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+static struct playlist_provider *
+playlist_list_open_stream_suffix(struct input_stream *is, const char *suffix)
+{
+ struct playlist_provider *playlist;
+
+ assert(is != NULL);
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix)) {
+ /* rewind the stream, so each plugin gets a
+ fresh start */
+ input_stream_seek(is, 0, SEEK_SET);
+
+ playlist = playlist_plugin_open_stream(plugin, is);
+ if (playlist != NULL)
+ return playlist;
+ }
+ }
+
+ return NULL;
+}
+
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri)
+{
+ const char *suffix;
+ struct playlist_provider *playlist;
+
+ if (is->mime != NULL) {
+ playlist = playlist_list_open_stream_mime(is);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ suffix = uri != NULL ? uri_get_suffix(uri) : NULL;
+ if (suffix != NULL) {
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist != NULL)
+ return playlist;
+ }
+
+ return NULL;
+}
+
+static bool
+playlist_suffix_supported(const char *suffix)
+{
+ assert(suffix != NULL);
+
+ for (unsigned i = 0; playlist_plugins[i] != NULL; ++i) {
+ const struct playlist_plugin *plugin = playlist_plugins[i];
+
+ if (playlist_plugins_enabled[i] && plugin->suffixes != NULL &&
+ string_array_contains(plugin->suffixes, suffix))
+ return true;
+ }
+
+ return false;
+}
+
+struct playlist_provider *
+playlist_list_open_path(struct input_stream *is, const char *path_fs)
+{
+ const char *suffix;
+ struct playlist_provider *playlist;
+
+ assert(path_fs != NULL);
+
+ suffix = uri_get_suffix(path_fs);
+ if (suffix == NULL || !playlist_suffix_supported(suffix) ||
+ !input_stream_open(is, path_fs))
+ return NULL;
+
+ playlist = playlist_list_open_stream_suffix(is, suffix);
+ if (playlist == NULL)
+ input_stream_close(is);
+
+ return playlist;
+}
diff --git a/src/playlist_list.h b/src/playlist_list.h
new file mode 100644
index 000000000..ebfc5c509
--- /dev/null
+++ b/src/playlist_list.h
@@ -0,0 +1,65 @@
+/*
+ * 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_PLAYLIST_LIST_H
+#define MPD_PLAYLIST_LIST_H
+
+struct playlist_provider;
+struct input_stream;
+
+/**
+ * Initializes all playlist plugins.
+ */
+void
+playlist_list_global_init(void);
+
+/**
+ * Deinitializes all playlist plugins.
+ */
+void
+playlist_list_global_finish(void);
+
+/**
+ * Opens a playlist by its URI.
+ */
+struct playlist_provider *
+playlist_list_open_uri(const char *uri);
+
+/**
+ * Opens a playlist from an input stream.
+ *
+ * @param is an #input_stream object which is open and ready
+ * @param uri optional URI which was used to open the stream; may be
+ * used to select the appropriate playlist plugin
+ */
+struct playlist_provider *
+playlist_list_open_stream(struct input_stream *is, const char *uri);
+
+/**
+ * Opens a playlist from a local file.
+ *
+ * @param is an uninitialized #input_stream object (must be closed
+ * with input_stream_close() if this function succeeds)
+ * @param path_fs the path of the playlist file
+ * @return a playlist, or NULL on error
+ */
+struct playlist_provider *
+playlist_list_open_path(struct input_stream *is, const char *path_fs);
+
+#endif
diff --git a/src/playlist_plugin.h b/src/playlist_plugin.h
new file mode 100644
index 000000000..3515af109
--- /dev/null
+++ b/src/playlist_plugin.h
@@ -0,0 +1,137 @@
+/*
+ * 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_PLAYLIST_PLUGIN_H
+#define MPD_PLAYLIST_PLUGIN_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct config_param;
+struct input_stream;
+struct tag;
+
+/**
+ * An object which provides the contents of a playlist.
+ */
+struct playlist_provider {
+ const struct playlist_plugin *plugin;
+};
+
+static inline void
+playlist_provider_init(struct playlist_provider *playlist,
+ const struct playlist_plugin *plugin)
+{
+ playlist->plugin = plugin;
+}
+
+struct playlist_plugin {
+ const char *name;
+
+ /**
+ * Initialize the plugin. Optional method.
+ *
+ * @param param a configuration block for this plugin, or NULL
+ * if none is configured
+ * @return true if the plugin was initialized successfully,
+ * false if the plugin is not available
+ */
+ bool (*init)(const struct config_param *param);
+
+ /**
+ * Deinitialize a plugin which was initialized successfully.
+ * Optional method.
+ */
+ void (*finish)(void);
+
+ /**
+ * Opens the playlist on the specified URI. This URI has
+ * either matched one of the schemes or one of the suffixes.
+ */
+ struct playlist_provider *(*open_uri)(const char *uri);
+
+ /**
+ * Opens the playlist in the specified input stream. It has
+ * either matched one of the suffixes or one of the MIME
+ * types.
+ */
+ struct playlist_provider *(*open_stream)(struct input_stream *is);
+
+ void (*close)(struct playlist_provider *playlist);
+
+ struct song *(*read)(struct playlist_provider *playlist);
+
+ const char *const*schemes;
+ const char *const*suffixes;
+ const char *const*mime_types;
+};
+
+/**
+ * Initialize a plugin.
+ *
+ * @param param a configuration block for this plugin, or NULL if none
+ * is configured
+ * @return true if the plugin was initialized successfully, false if
+ * the plugin is not available
+ */
+static inline bool
+playlist_plugin_init(const struct playlist_plugin *plugin,
+ const struct config_param *param)
+{
+ return plugin->init != NULL
+ ? plugin->init(param)
+ : true;
+}
+
+/**
+ * Deinitialize a plugin which was initialized successfully.
+ */
+static inline void
+playlist_plugin_finish(const struct playlist_plugin *plugin)
+{
+ if (plugin->finish != NULL)
+ plugin->finish();
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_uri(const struct playlist_plugin *plugin, const char *uri)
+{
+ return plugin->open_uri(uri);
+}
+
+static inline struct playlist_provider *
+playlist_plugin_open_stream(const struct playlist_plugin *plugin,
+ struct input_stream *is)
+{
+ return plugin->open_stream(is);
+}
+
+static inline void
+playlist_plugin_close(struct playlist_provider *playlist)
+{
+ playlist->plugin->close(playlist);
+}
+
+static inline struct song *
+playlist_plugin_read(struct playlist_provider *playlist)
+{
+ return playlist->plugin->read(playlist);
+}
+
+#endif
diff --git a/src/playlist_print.c b/src/playlist_print.c
index fd61ab62c..f414ee0ac 100644
--- a/src/playlist_print.c
+++ b/src/playlist_print.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_print.h"
#include "queue_print.h"
#include "stored_playlist.h"
@@ -69,7 +70,7 @@ playlist_print_id(struct client *client, const struct playlist *playlist,
bool
playlist_print_current(struct client *client, const struct playlist *playlist)
{
- int current_position = getPlaylistCurrentSong(playlist);
+ int current_position = playlist_get_current_song(playlist);
if (current_position < 0)
return false;
diff --git a/src/playlist_queue.c b/src/playlist_queue.c
new file mode 100644
index 000000000..0b4231f59
--- /dev/null
+++ b/src/playlist_queue.c
@@ -0,0 +1,135 @@
+/*
+ * 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 "playlist_queue.h"
+#include "playlist_list.h"
+#include "playlist_plugin.h"
+#include "stored_playlist.h"
+#include "mapper.h"
+#include "song.h"
+#include "uri.h"
+#include "ls.h"
+#include "input_stream.h"
+
+/**
+ * Determins if it's allowed to add this song to the playlist. For
+ * safety reasons, we disallow local files.
+ */
+static inline bool
+accept_song(const struct song *song)
+{
+ return !song_is_file(song) && uri_has_scheme(song->uri) &&
+ uri_supported_scheme(song->uri);
+}
+
+enum playlist_result
+playlist_load_into_queue(struct playlist_provider *source,
+ struct playlist *dest)
+{
+ enum playlist_result result;
+ struct song *song;
+
+ while ((song = playlist_plugin_read(source)) != NULL) {
+ if (!accept_song(song)) {
+ song_free(song);
+ continue;
+ }
+
+ result = playlist_append_song(dest, song, NULL);
+ if (result != PLAYLIST_RESULT_SUCCESS) {
+ song_free(song);
+ return result;
+ }
+ }
+
+ return PLAYLIST_RESULT_SUCCESS;
+}
+
+static enum playlist_result
+playlist_open_remote_into_queue(const char *uri, struct playlist *dest)
+{
+ struct playlist_provider *playlist;
+ bool stream = false;
+ struct input_stream is;
+ enum playlist_result result;
+
+ assert(uri_has_scheme(uri));
+
+ playlist = playlist_list_open_uri(uri);
+ if (playlist == NULL) {
+ stream = input_stream_open(&is, uri);
+ if (!stream)
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ playlist = playlist_list_open_stream(&is, uri);
+ if (playlist == NULL) {
+ input_stream_close(&is);
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+ }
+ }
+
+ result = playlist_load_into_queue(playlist, dest);
+ playlist_plugin_close(playlist);
+
+ if (stream)
+ input_stream_close(&is);
+
+ return result;
+}
+
+static enum playlist_result
+playlist_open_local_into_queue(const char *uri, struct playlist *dest)
+{
+ struct playlist_provider *playlist;
+ const char *playlist_directory_fs;
+ char *path_fs;
+ struct input_stream is;
+ enum playlist_result result;
+
+ assert(spl_valid_name(uri));
+
+ playlist_directory_fs = map_spl_path();
+ if (playlist_directory_fs == NULL)
+ return PLAYLIST_RESULT_DISABLED;
+
+ path_fs = g_build_filename(playlist_directory_fs, uri, NULL);
+ playlist = playlist_list_open_path(&is, path_fs);
+ g_free(path_fs);
+ if (playlist == NULL)
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+
+ result = playlist_load_into_queue(playlist, dest);
+ playlist_plugin_close(playlist);
+
+ input_stream_close(&is);
+
+ return result;
+}
+
+enum playlist_result
+playlist_open_into_queue(const char *uri, struct playlist *dest)
+{
+ if (uri_has_scheme(uri))
+ return playlist_open_remote_into_queue(uri, dest);
+ else if (spl_valid_name(uri))
+ return playlist_open_local_into_queue(uri, dest);
+ else
+ return PLAYLIST_RESULT_NO_SUCH_LIST;
+}
diff --git a/src/playlist_queue.h b/src/playlist_queue.h
new file mode 100644
index 000000000..b571cd63a
--- /dev/null
+++ b/src/playlist_queue.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/*! \file
+ * \brief Glue between playlist plugin and the play queue
+ */
+
+#ifndef MPD_PLAYLIST_QUEUE_H
+#define MPD_PLAYLIST_QUEUE_H
+
+#include "playlist.h"
+
+struct playlist_provider;
+struct playlist;
+
+/**
+ * Loads the contents of a playlist and append it to the specified
+ * play queue.
+ */
+enum playlist_result
+playlist_load_into_queue(struct playlist_provider *source,
+ struct playlist *dest);
+
+/**
+ * Opens a playlist with a playlist plugin and append to the specified
+ * play queue.
+ */
+enum playlist_result
+playlist_open_into_queue(const char *uri, struct playlist *dest);
+
+#endif
+
diff --git a/src/playlist_save.c b/src/playlist_save.c
index 13dbc721d..235842ae8 100644
--- a/src/playlist_save.c
+++ b/src/playlist_save.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "playlist_save.h"
#include "stored_playlist.h"
#include "song.h"
@@ -54,7 +55,7 @@ playlist_print_uri(FILE *file, const char *uri)
char *s;
if (playlist_saveAbsolutePaths && !uri_has_scheme(uri) &&
- uri[0] != '/')
+ !g_path_is_absolute(uri))
s = map_uri_fs(uri);
else
s = utf8_to_fs_charset(uri);
@@ -118,7 +119,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
for (unsigned i = 0; i < list->len; ++i) {
const char *temp = g_ptr_array_index(list, i);
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
/* for windows compatibility, convert slashes */
char *temp2 = g_strdup(temp);
char *p = temp2;
@@ -127,7 +128,7 @@ playlist_load_spl(struct playlist *playlist, const char *name_utf8)
*p = '/';
p++;
}
- if ((addToPlaylist(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
+ if ((playlist_append_uri(playlist, temp, NULL)) != PLAYLIST_RESULT_SUCCESS) {
g_warning("can't add file \"%s\"", temp2);
}
g_free(temp2);
diff --git a/src/playlist_state.c b/src/playlist_state.c
index af0f7982b..ea8b7e4f9 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -22,6 +22,7 @@
*
*/
+#include "config.h"
#include "playlist_state.h"
#include "playlist.h"
#include "player_control.h"
@@ -51,10 +52,14 @@
void
playlist_state_save(FILE *fp, const struct playlist *playlist)
{
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE);
if (playlist->playing) {
- switch (getPlayerState()) {
+ switch (player_status.state) {
case PLAYER_STATE_PAUSE:
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE);
break;
@@ -65,10 +70,16 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
queue_order_to_position(&playlist->queue,
playlist->current));
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
- getPlayerElapsedTime());
- } else
+ (int)player_status.elapsed_time);
+ } else {
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP);
+ if (playlist->current >= 0)
+ fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT,
+ queue_order_to_position(&playlist->queue,
+ playlist->current));
+ }
+
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM,
playlist->queue.random);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT,
@@ -78,7 +89,7 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CONSUME,
playlist->queue.consume);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
- (int)(getPlayerCrossFade()));
+ (int)(pc_get_cross_fade()));
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
queue_save(fp, &playlist->queue);
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
@@ -109,8 +120,8 @@ playlist_state_load(FILE *fp, struct playlist *playlist, char *buffer)
queue_increment_version(&playlist->queue);
}
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist)
+bool
+playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist)
{
int current = -1;
int seek_time = 0;
@@ -118,50 +129,45 @@ playlist_state_restore(FILE *fp, struct playlist *playlist)
char buffer[PLAYLIST_BUFFER_SIZE];
bool random_mode = false;
+ if (!g_str_has_prefix(line, PLAYLIST_STATE_FILE_STATE))
+ return false;
+
+ line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1;
+
+ if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
+ state = PLAYER_STATE_PLAY;
+ else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
+ state = PLAYER_STATE_PAUSE;
+
while (fgets(buffer, sizeof(buffer), fp)) {
g_strchomp(buffer);
- if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_STATE)) {
- if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PLAY) == 0) {
- state = PLAYER_STATE_PLAY;
- } else
- if (strcmp
- (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]),
- PLAYLIST_STATE_FILE_STATE_PAUSE)
- == 0) {
- state = PLAYER_STATE_PAUSE;
- }
- } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
+ if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_TIME)) {
seek_time =
atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)]));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_REPEAT)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]),
"1") == 0) {
- setPlaylistRepeatStatus(playlist, true);
+ playlist_set_repeat(playlist, true);
} else
- setPlaylistRepeatStatus(playlist, false);
+ playlist_set_repeat(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_SINGLE)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_SINGLE)]),
"1") == 0) {
- setPlaylistSingleStatus(playlist, true);
+ playlist_set_single(playlist, true);
} else
- setPlaylistSingleStatus(playlist, false);
+ playlist_set_single(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CONSUME)) {
if (strcmp
(&(buffer[strlen(PLAYLIST_STATE_FILE_CONSUME)]),
"1") == 0) {
- setPlaylistConsumeStatus(playlist, true);
+ playlist_set_consume(playlist, true);
} else
- setPlaylistConsumeStatus(playlist, false);
+ playlist_set_consume(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) {
- setPlayerCrossFade(atoi
- (&
- (buffer
- [strlen
- (PLAYLIST_STATE_FILE_CROSSFADE)])));
+ pc_set_cross_fade(atoi(buffer + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM),
@@ -172,24 +178,56 @@ playlist_state_restore(FILE *fp, struct playlist *playlist)
(PLAYLIST_STATE_FILE_CURRENT)]));
} else if (g_str_has_prefix(buffer,
PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
- if (state == PLAYER_STATE_STOP)
- current = -1;
playlist_state_load(fp, playlist, buffer);
}
}
- setPlaylistRandomStatus(playlist, random_mode);
+ playlist_set_random(playlist, random_mode);
- if (state != PLAYER_STATE_STOP && !queue_is_empty(&playlist->queue)) {
+ if (!queue_is_empty(&playlist->queue)) {
if (!queue_valid_position(&playlist->queue, current))
current = 0;
- if (seek_time == 0)
- playPlaylist(playlist, current);
+ /* enable all devices for the first time; this must be
+ called here, after the audio output states were
+ restored, before playback begins */
+ if (state != PLAYER_STATE_STOP)
+ pc_update_audio();
+
+ if (state == PLAYER_STATE_STOP /* && config_option */)
+ playlist->current = current;
+ else if (seek_time == 0)
+ playlist_play(playlist, current);
else
- seekSongInPlaylist(playlist, current, seek_time);
+ playlist_seek_song(playlist, current, seek_time);
if (state == PLAYER_STATE_PAUSE)
- playerPause();
+ pc_pause();
}
+
+ return true;
+}
+
+unsigned
+playlist_state_get_hash(const struct playlist *playlist)
+{
+ struct player_status player_status;
+
+ pc_get_status(&player_status);
+
+ return playlist->queue.version ^
+ (player_status.state != PLAYER_STATE_STOP
+ ? ((int)player_status.elapsed_time << 8)
+ : 0) ^
+ (playlist->current >= 0
+ ? (queue_order_to_position(&playlist->queue,
+ playlist->current) << 16)
+ : 0) ^
+ ((int)pc_get_cross_fade() << 20) ^
+ (player_status.state << 24) ^
+ (playlist->queue.random << 27) ^
+ (playlist->queue.repeat << 28) ^
+ (playlist->queue.single << 29) ^
+ (playlist->queue.consume << 30) ^
+ (playlist->queue.random << 31);
}
diff --git a/src/playlist_state.h b/src/playlist_state.h
index 989430264..d116aaeb1 100644
--- a/src/playlist_state.h
+++ b/src/playlist_state.h
@@ -25,6 +25,7 @@
#ifndef PLAYLIST_STATE_H
#define PLAYLIST_STATE_H
+#include <stdbool.h>
#include <stdio.h>
struct playlist;
@@ -32,7 +33,16 @@ struct playlist;
void
playlist_state_save(FILE *fp, const struct playlist *playlist);
-void
-playlist_state_restore(FILE *fp, struct playlist *playlist);
+bool
+playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist);
+
+/**
+ * Generates a hash number for the current state of the playlist and
+ * the playback options. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+playlist_state_get_hash(const struct playlist *playlist);
#endif
diff --git a/src/poison.h b/src/poison.h
index 5919c3cbe..ca6d73937 100644
--- a/src/poison.h
+++ b/src/poison.h
@@ -20,8 +20,9 @@
#ifndef MPD_POISON_H
#define MPD_POISON_H
+#include "check.h"
+
#ifndef NDEBUG
-#include "config.h"
#ifdef HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
diff --git a/src/queue.c b/src/queue.c
index 141222a80..bd89544ca 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "queue.h"
#include "song.h"
@@ -33,7 +34,7 @@ queue_generate_id(const struct queue *queue)
if (cur >= queue->max_length * QUEUE_HASH_MULT)
cur = 0;
- } while (queue->idToPosition[cur] != -1);
+ } while (queue->id_to_position[cur] != -1);
return cur;
}
@@ -111,7 +112,7 @@ queue_append(struct queue *queue, struct song *song)
};
queue->order[queue->length] = queue->length;
- queue->idToPosition[id] = queue->length;
+ queue->id_to_position[id] = queue->length;
++queue->length;
@@ -132,8 +133,8 @@ queue_swap(struct queue *queue, unsigned position1, unsigned position2)
queue->items[position1].version = queue->version;
queue->items[position2].version = queue->version;
- queue->idToPosition[id1] = position2;
- queue->idToPosition[id2] = position1;
+ queue->id_to_position[id1] = position2;
+ queue->id_to_position[id2] = position1;
}
static void
@@ -143,7 +144,7 @@ queue_move_song_to(struct queue *queue, unsigned from, unsigned to)
queue->items[to] = queue->items[from];
queue->items[to].version = queue->version;
- queue->idToPosition[from_id] = to;
+ queue->id_to_position[from_id] = to;
}
void
@@ -163,7 +164,7 @@ queue_move(struct queue *queue, unsigned from, unsigned to)
/* put song at _to_ */
- queue->idToPosition[item.id] = to;
+ queue->id_to_position[item.id] = to;
queue->items[to] = item;
queue->items[to].version = queue->version;
@@ -203,7 +204,7 @@ queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to)
// Copy the original block back in, starting at to.
for (unsigned i = start; i< end; i++)
{
- queue->idToPosition[items[i-start].id] = to + i - start;
+ queue->id_to_position[items[i-start].id] = to + i - start;
queue->items[to + i - start] = items[i-start];
queue->items[to + i - start].version = queue->version;
}
@@ -243,7 +244,7 @@ queue_delete(struct queue *queue, unsigned position)
/* release the song id */
- queue->idToPosition[id] = -1;
+ queue->id_to_position[id] = -1;
/* delete song from songs array */
@@ -271,7 +272,7 @@ queue_clear(struct queue *queue)
if (!song_in_database(item->song))
song_free(item->song);
- queue->idToPosition[item->id] = -1;
+ queue->id_to_position[item->id] = -1;
}
queue->length = 0;
@@ -291,11 +292,11 @@ queue_init(struct queue *queue, unsigned max_length)
queue->items = g_new(struct queue_item, max_length);
queue->order = g_malloc(sizeof(queue->order[0]) *
max_length);
- queue->idToPosition = g_malloc(sizeof(queue->idToPosition[0]) *
+ queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) *
max_length * QUEUE_HASH_MULT);
for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i)
- queue->idToPosition[i] = -1;
+ queue->id_to_position[i] = -1;
queue->rand = g_rand_new();
}
@@ -307,7 +308,7 @@ queue_finish(struct queue *queue)
g_free(queue->items);
g_free(queue->order);
- g_free(queue->idToPosition);
+ g_free(queue->id_to_position);
g_rand_free(queue->rand);
}
diff --git a/src/queue.h b/src/queue.h
index 9c7228fd8..032a812c2 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -74,8 +74,8 @@ struct queue {
/** map order numbers to positions */
unsigned *order;
- /** map song ids to posiitons */
- int *idToPosition;
+ /** map song ids to positions */
+ int *id_to_position;
/** repeat playback when the end of the queue has been
reached? */
@@ -146,10 +146,10 @@ queue_id_to_position(const struct queue *queue, unsigned id)
if (id >= queue->max_length * QUEUE_HASH_MULT)
return -1;
- assert(queue->idToPosition[id] >= -1);
- assert(queue->idToPosition[id] < (int)queue->length);
+ assert(queue->id_to_position[id] >= -1);
+ assert(queue->id_to_position[id] < (int)queue->length);
- return queue->idToPosition[id];
+ return queue->id_to_position[id];
}
static inline int
diff --git a/src/queue_print.c b/src/queue_print.c
index 2ca9ccc34..4bc600aec 100644
--- a/src/queue_print.c
+++ b/src/queue_print.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "queue_print.h"
#include "queue.h"
#include "song.h"
diff --git a/src/queue_save.c b/src/queue_save.c
index 9a5a0e30f..71b6a1526 100644
--- a/src/queue_save.c
+++ b/src/queue_save.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "queue_save.h"
#include "queue.h"
#include "song.h"
diff --git a/src/replay_gain.c b/src/replay_gain.c
index bcb501e54..805d1e5c8 100644
--- a/src/replay_gain.c
+++ b/src/replay_gain.c
@@ -20,12 +20,16 @@
* (c)2004 replayGain code by AliasMrJones
*/
+#include "config.h"
#include "replay_gain.h"
#include "conf.h"
#include "audio_format.h"
#include "pcm_volume.h"
+#include "idle.h"
#include <glib.h>
+
+#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
@@ -38,19 +42,51 @@ static const char *const replay_gain_mode_names[] = {
enum replay_gain_mode replay_gain_mode = REPLAY_GAIN_OFF;
static float replay_gain_preamp = 1.0;
+static float replay_gain_missing_preamp = 1.0;
-void replay_gain_global_init(void)
+const char *
+replay_gain_get_mode_string(void)
{
- const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
+ switch (replay_gain_mode) {
+ case REPLAY_GAIN_OFF:
+ return "off";
- if (!param)
- return;
+ case REPLAY_GAIN_TRACK:
+ return "track";
+
+ case REPLAY_GAIN_ALBUM:
+ return "album";
+ }
+
+ /* unreachable */
+ assert(false);
+ return "off";
+}
+
+bool
+replay_gain_set_mode_string(const char *p)
+{
+ assert(p != NULL);
- if (strcmp(param->value, "track") == 0) {
+ if (strcmp(p, "off") == 0)
+ replay_gain_mode = REPLAY_GAIN_OFF;
+ else if (strcmp(p, "track") == 0)
replay_gain_mode = REPLAY_GAIN_TRACK;
- } else if (strcmp(param->value, "album") == 0) {
+ else if (strcmp(p, "album") == 0)
replay_gain_mode = REPLAY_GAIN_ALBUM;
- } else {
+ else
+ return false;
+
+ idle_add(IDLE_OPTIONS);
+
+ return true;
+}
+
+void replay_gain_global_init(void)
+{
+ const struct config_param *param = config_get_param(CONF_REPLAYGAIN);
+
+ if (param != NULL && !replay_gain_set_mode_string(param->value)) {
g_error("replaygain value \"%s\" at line %i is invalid\n",
param->value, param->line);
}
@@ -73,6 +109,25 @@ void replay_gain_global_init(void)
replay_gain_preamp = pow(10, f / 20.0);
}
+
+ param = config_get_param(CONF_REPLAYGAIN_MISSING_PREAMP);
+
+ if (param) {
+ char *test;
+ float f = strtod(param->value, &test);
+
+ if (*test != '\0') {
+ g_error("Replaygain missing preamp \"%s\" is not a number at "
+ "line %i\n", param->value, param->line);
+ }
+
+ if (f < -15 || f > 15) {
+ g_error("Replaygain missing preamp \"%s\" is not between -15 and"
+ "15 at line %i\n", param->value, param->line);
+ }
+
+ replay_gain_missing_preamp = pow(10, f / 20.0);
+ }
}
static float calc_replay_gain_scale(float gain, float peak)
@@ -116,19 +171,28 @@ void
replay_gain_apply(struct replay_gain_info *info, char *buffer, int size,
const struct audio_format *format)
{
- if (replay_gain_mode == REPLAY_GAIN_OFF || !info)
+ float scale;
+
+ if (replay_gain_mode == REPLAY_GAIN_OFF)
return;
- if (info->scale < 0) {
- const struct replay_gain_tuple *tuple =
- &info->tuples[replay_gain_mode];
+ if (info) {
+ if (info->scale < 0) {
+ const struct replay_gain_tuple *tuple =
+ &info->tuples[replay_gain_mode];
- g_debug("computing ReplayGain %s scale with gain %f, peak %f\n",
- replay_gain_mode_names[replay_gain_mode],
- tuple->gain, tuple->peak);
+ g_debug("computing ReplayGain %s scale with gain %f, peak %f\n",
+ replay_gain_mode_names[replay_gain_mode],
+ tuple->gain, tuple->peak);
- info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak);
+ info->scale = calc_replay_gain_scale(tuple->gain, tuple->peak);
+ }
+ scale = info->scale;
+ }
+ else {
+ scale = replay_gain_missing_preamp;
+ g_debug("ReplayGain is missing, computing scale %f\n", scale);
}
- pcm_volume(buffer, size, format, pcm_float_to_volume(info->scale));
+ pcm_volume(buffer, size, format, pcm_float_to_volume(scale));
}
diff --git a/src/replay_gain.h b/src/replay_gain.h
index aa48f3f14..5d0492ad8 100644
--- a/src/replay_gain.h
+++ b/src/replay_gain.h
@@ -23,6 +23,8 @@
#ifndef MPD_REPLAY_GAIN_H
#define MPD_REPLAY_GAIN_H
+#include <stdbool.h>
+
enum replay_gain_mode {
REPLAY_GAIN_OFF = -1,
REPLAY_GAIN_ALBUM,
@@ -52,6 +54,20 @@ void replay_gain_info_free(struct replay_gain_info *info);
void replay_gain_global_init(void);
+/**
+ * Returns the current replay gain mode as a machine-readable string.
+ */
+const char *
+replay_gain_get_mode_string(void);
+
+/**
+ * Sets the replay gain mode, parsed from a string.
+ *
+ * @return true on success, false if the string could not be parsed
+ */
+bool
+replay_gain_set_mode_string(const char *p);
+
void
replay_gain_apply(struct replay_gain_info *info, char *buffer, int bufferSize,
const struct audio_format *format);
diff --git a/src/riff.c b/src/riff.c
index a8ea9dd42..787a63cc5 100644
--- a/src/riff.c
+++ b/src/riff.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h" /* must be first for large file support */
#include "riff.h"
#include <glib.h>
diff --git a/src/sig_handlers.c b/src/sig_handlers.c
index e70e1a159..028cd4038 100644
--- a/src/sig_handlers.c
+++ b/src/sig_handlers.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "sig_handlers.h"
#ifndef WIN32
diff --git a/src/socket_util.c b/src/socket_util.c
index da4e414b6..bf8fe0f07 100644
--- a/src/socket_util.c
+++ b/src/socket_util.c
@@ -17,8 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include "socket_util.h"
#include "config.h"
+#include "socket_util.h"
+#include "fd_util.h"
#include <errno.h>
#include <unistd.h>
@@ -102,7 +103,7 @@ socket_bind_listen(int domain, int type, int protocol,
int passcred = 1;
#endif
- fd = socket(domain, type, protocol);
+ fd = socket_cloexec_nonblock(domain, type, protocol);
if (fd < 0) {
g_set_error(error, listen_quark(), errno,
"Failed to create socket: %s", g_strerror(errno));
diff --git a/src/song.c b/src/song.c
index 76c25f44f..faaa208ca 100644
--- a/src/song.c
+++ b/src/song.c
@@ -17,37 +17,29 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song.h"
#include "uri.h"
#include "directory.h"
-#include "mapper.h"
-#include "decoder_list.h"
-#include "decoder_plugin.h"
-#include "tag_ape.h"
-#include "tag_id3.h"
#include "tag.h"
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <string.h>
static struct song *
-song_alloc(const char *url, struct directory *parent)
+song_alloc(const char *uri, struct directory *parent)
{
- size_t urllen;
+ size_t uri_length;
struct song *song;
- assert(url);
- urllen = strlen(url);
- assert(urllen);
- song = g_malloc(sizeof(*song) - sizeof(song->url) + urllen + 1);
+ assert(uri);
+ uri_length = strlen(uri);
+ assert(uri_length);
+ song = g_malloc(sizeof(*song) - sizeof(song->uri) + uri_length + 1);
song->tag = NULL;
- memcpy(song->url, url, urllen + 1);
+ memcpy(song->uri, uri, uri_length + 1);
song->parent = parent;
song->mtime = 0;
@@ -55,9 +47,9 @@ song_alloc(const char *url, struct directory *parent)
}
struct song *
-song_remote_new(const char *url)
+song_remote_new(const char *uri)
{
- return song_alloc(url, NULL);
+ return song_alloc(uri, NULL);
}
struct song *
@@ -68,32 +60,6 @@ song_file_new(const char *path, struct directory *parent)
return song_alloc(path, parent);
}
-struct song *
-song_file_load(const char *path, struct directory *parent)
-{
- struct song *song;
- bool ret;
-
- assert((parent == NULL) == (*path == '/'));
- assert(!uri_has_scheme(path));
- assert(strchr(path, '\n') == NULL);
-
- song = song_file_new(path, parent);
-
- //in archive ?
- if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
- ret = song_file_update_inarchive(song);
- } else {
- ret = song_file_update(song);
- }
- if (!ret) {
- song_free(song);
- return NULL;
- }
-
- return song;
-}
-
void
song_free(struct song *song)
{
@@ -102,127 +68,15 @@ song_free(struct song *song)
g_free(song);
}
-/**
- * Attempts to load APE or ID3 tags from the specified file.
- */
-static struct tag *
-tag_load_fallback(const char *path)
-{
- struct tag *tag = tag_ape_load(path);
- if (tag == NULL)
- tag = tag_id3_load(path);
- return tag;
-}
-
-/**
- * The decoder plugin failed to load any tags: fall back to the APE or
- * ID3 tag loader.
- */
-static struct tag *
-tag_fallback(const char *path, struct tag *tag)
-{
- struct tag *fallback = tag_load_fallback(path);
-
- if (fallback != NULL) {
- /* tag was successfully loaded: copy the song
- duration, and destroy the old (empty) tag */
- fallback->time = tag->time;
- tag_free(tag);
- return fallback;
- } else
- /* no APE/ID3 tag found: return the empty tag */
- return tag;
-}
-
-bool
-song_file_update(struct song *song)
-{
- const char *suffix;
- char *path_fs;
- const struct decoder_plugin *plugin;
- struct stat st;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->url);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, false);
- if (plugin == NULL)
- return false;
-
- path_fs = map_song_fs(song);
- if (path_fs == NULL)
- return false;
-
- if (song->tag != NULL) {
- tag_free(song->tag);
- song->tag = NULL;
- }
-
- if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
- g_free(path_fs);
- return false;
- }
-
- song->mtime = st.st_mtime;
-
- do {
- song->tag = plugin->tag_dup(path_fs);
- if (song->tag != NULL)
- break;
-
- plugin = decoder_plugin_from_suffix(suffix, true);
- } while (plugin != NULL);
-
- if (song->tag != NULL && tag_is_empty(song->tag))
- song->tag = tag_fallback(path_fs, song->tag);
-
- g_free(path_fs);
- return song->tag != NULL;
-}
-
-bool
-song_file_update_inarchive(struct song *song)
-{
- const char *suffix;
- const struct decoder_plugin *plugin;
-
- assert(song_is_file(song));
-
- /* check if there's a suffix and a plugin */
-
- suffix = uri_get_suffix(song->url);
- if (suffix == NULL)
- return false;
-
- plugin = decoder_plugin_from_suffix(suffix, false);
- if (plugin == NULL)
- return false;
-
- if (song->tag != NULL)
- tag_free(song->tag);
-
- //accept every file that has music suffix
- //because we dont support tag reading throught
- //input streams
- song->tag = tag_new();
-
- return true;
-}
-
char *
song_get_uri(const struct song *song)
{
assert(song != NULL);
- assert(*song->url);
+ assert(*song->uri);
if (!song_in_database(song) || directory_is_root(song->parent))
- return g_strdup(song->url);
+ return g_strdup(song->uri);
else
return g_strconcat(directory_get_path(song->parent),
- "/", song->url, NULL);
+ "/", song->uri, NULL);
}
diff --git a/src/song.h b/src/song.h
index 3044e910f..c8e80b0d1 100644
--- a/src/song.h
+++ b/src/song.h
@@ -24,9 +24,6 @@
#include <stdbool.h>
#include <sys/time.h>
-#define SONG_BEGIN "songList begin"
-#define SONG_END "songList end"
-
#define SONG_FILE "file: "
#define SONG_TIME "Time: "
@@ -34,12 +31,12 @@ struct song {
struct tag *tag;
struct directory *parent;
time_t mtime;
- char url[sizeof(int)];
+ char uri[sizeof(int)];
};
/** allocate a new song with a remote URL */
struct song *
-song_remote_new(const char *url);
+song_remote_new(const char *uri);
/** allocate a new song with a local file name */
struct song *
@@ -81,7 +78,7 @@ song_in_database(const struct song *song)
static inline bool
song_is_file(const struct song *song)
{
- return song_in_database(song) || song->url[0] == '/';
+ return song_in_database(song) || song->uri[0] == '/';
}
#endif
diff --git a/src/song_print.c b/src/song_print.c
index 64ab9f6b1..3e6252ed6 100644
--- a/src/song_print.c
+++ b/src/song_print.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song_print.h"
#include "song.h"
#include "songvec.h"
@@ -26,18 +27,18 @@
#include "uri.h"
void
-song_print_url(struct client *client, struct song *song)
+song_print_uri(struct client *client, struct song *song)
{
if (song_in_database(song) && !directory_is_root(song->parent)) {
client_printf(client, "%s%s/%s\n", SONG_FILE,
- directory_get_path(song->parent), song->url);
+ directory_get_path(song->parent), song->uri);
} else {
char *allocated;
const char *uri;
- uri = allocated = uri_remove_auth(song->url);
+ uri = allocated = uri_remove_auth(song->uri);
if (uri == NULL)
- uri = song->url;
+ uri = song->uri;
client_printf(client, "%s%s\n", SONG_FILE, uri);
@@ -48,7 +49,28 @@ song_print_url(struct client *client, struct song *song)
int
song_print_info(struct client *client, struct song *song)
{
- song_print_url(client, song);
+ song_print_uri(client, song);
+
+ if (song->mtime > 0) {
+#ifndef G_OS_WIN32
+ struct tm tm;
+#endif
+ const struct tm *tm2;
+
+#ifdef G_OS_WIN32
+ tm2 = gmtime(&song->mtime);
+#else
+ tm2 = gmtime_r(&song->mtime, &tm);
+#endif
+
+ if (tm2 != NULL) {
+ char timestamp[32];
+
+ strftime(timestamp, sizeof(timestamp), "%FT%TZ", tm2);
+ client_printf(client, "Last-Modified: %s\n",
+ timestamp);
+ }
+ }
if (song->tag)
tag_print(client, song->tag);
diff --git a/src/song_print.h b/src/song_print.h
index 291fd81c8..c16bc7387 100644
--- a/src/song_print.h
+++ b/src/song_print.h
@@ -30,6 +30,6 @@ song_print_info(struct client *client, struct song *song);
int songvec_print(struct client *client, const struct songvec *sv);
void
-song_print_url(struct client *client, struct song *song);
+song_print_uri(struct client *client, struct song *song);
#endif
diff --git a/src/song_save.c b/src/song_save.c
index 2d6297f3e..37df5067d 100644
--- a/src/song_save.c
+++ b/src/song_save.c
@@ -17,11 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song_save.h"
#include "song.h"
#include "tag_save.h"
#include "directory.h"
#include "tag.h"
+#include "text_file.h"
#include <glib.h>
@@ -30,18 +32,13 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "song"
-#define SONG_KEY "key: "
-#define SONG_MTIME "mtime: "
+#define SONG_MTIME "mtime"
+#define SONG_END "song_end"
-static void
-song_save_url(FILE *fp, struct song *song)
+static GQuark
+song_save_quark(void)
{
- if (song->parent != NULL && song->parent->path != NULL)
- fprintf(fp, SONG_FILE "%s/%s\n",
- directory_get_path(song->parent), song->url);
- else
- fprintf(fp, SONG_FILE "%s\n",
- song->url);
+ return g_quark_from_static_string("song_save");
}
static int
@@ -49,117 +46,76 @@ song_save(struct song *song, void *data)
{
FILE *fp = data;
- fprintf(fp, SONG_KEY "%s\n", song->url);
-
- song_save_url(fp, song);
+ fprintf(fp, SONG_BEGIN "%s\n", song->uri);
if (song->tag != NULL)
tag_save(fp, song->tag);
- fprintf(fp, SONG_MTIME "%li\n", (long)song->mtime);
+ fprintf(fp, SONG_MTIME ": %li\n", (long)song->mtime);
+ fprintf(fp, SONG_END "\n");
return 0;
}
void songvec_save(FILE *fp, struct songvec *sv)
{
- fprintf(fp, "%s\n", SONG_BEGIN);
songvec_for_each(sv, song_save, fp);
- fprintf(fp, "%s\n", SONG_END);
}
-static void
-insertSongIntoList(struct songvec *sv, struct song *newsong)
+struct song *
+song_load(FILE *fp, struct directory *parent, const char *uri,
+ GString *buffer, GError **error_r)
{
- struct song *existing = songvec_find(sv, newsong->url);
-
- if (!existing) {
- songvec_add(sv, newsong);
- if (newsong->tag)
- tag_end_add(newsong->tag);
- } else { /* prevent dupes, just update the existing song info */
- if (existing->mtime != newsong->mtime) {
- if (existing->tag != NULL)
- tag_free(existing->tag);
- if (newsong->tag)
- tag_end_add(newsong->tag);
- existing->tag = newsong->tag;
- existing->mtime = newsong->mtime;
- newsong->tag = NULL;
- }
- song_free(newsong);
- }
-}
-
-static char *
-matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
-{
- int i;
-
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
- size_t len = strlen(tag_item_names[i]);
+ struct song *song = song_file_new(uri, parent);
+ char *line, *colon;
+ enum tag_type type;
+ const char *value;
- if (0 == strncmp(tag_item_names[i], buffer, len) &&
- buffer[len] == ':') {
- *itemType = i;
- return g_strchug(buffer + len + 1);
+ while ((line = read_text_line(fp, buffer)) != NULL &&
+ strcmp(line, SONG_END) != 0) {
+ colon = strchr(line, ':');
+ if (colon == NULL || colon == line) {
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
+ song_free(song);
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
}
- }
- return NULL;
-}
+ *colon++ = 0;
+ value = g_strchug(colon);
-void readSongInfoIntoList(FILE *fp, struct songvec *sv,
- struct directory *parent)
-{
- enum {
- buffer_size = 32768,
- };
- char *buffer = g_malloc(buffer_size);
- struct song *song = NULL;
- enum tag_type itemType;
- const char *value;
-
- while (fgets(buffer, buffer_size, fp) &&
- !g_str_has_prefix(buffer, SONG_END)) {
- g_strchomp(buffer);
-
- if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) {
- if (song)
- insertSongIntoList(sv, song);
-
- song = song_file_new(buffer + strlen(SONG_KEY),
- parent);
- } else if (*buffer == 0) {
- /* ignore empty lines (starting with '\0') */
- } else if (song == NULL) {
- g_error("Problems reading song info");
- } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) {
- /* we don't need this info anymore */
- } else if ((value = matchesAnMpdTagItemKey(buffer,
- &itemType)) != NULL) {
+ if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- tag_add_item(song->tag, itemType, value);
- } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) {
+ tag_add_item(song->tag, type, value);
+ } else if (strcmp(line, "Time") == 0) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- song->tag->time = atoi(&(buffer[strlen(SONG_TIME)]));
- } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) {
- song->mtime = atoi(&(buffer[strlen(SONG_MTIME)]));
+ song->tag->time = atoi(value);
+ } else if (strcmp(line, SONG_MTIME) == 0) {
+ song->mtime = atoi(value);
+ } else {
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
+ song_free(song);
+
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", line);
+ return false;
}
- else
- g_error("unknown line in db: %s", buffer);
}
- g_free(buffer);
+ if (song->tag != NULL)
+ tag_end_add(song->tag);
- if (song)
- insertSongIntoList(sv, song);
+ return song;
}
diff --git a/src/song_save.h b/src/song_save.h
index 370e42730..70ddb249e 100644
--- a/src/song_save.h
+++ b/src/song_save.h
@@ -20,14 +20,28 @@
#ifndef MPD_SONG_SAVE_H
#define MPD_SONG_SAVE_H
+#include <glib.h>
+
+#include <stdbool.h>
#include <stdio.h>
+#define SONG_BEGIN "song_begin: "
+
struct songvec;
struct directory;
void songvec_save(FILE *fp, struct songvec *sv);
-void readSongInfoIntoList(FILE * fp, struct songvec *sv,
- struct directory *parent);
+/**
+ * Loads a song from the input file. Reading stops after the
+ * "song_end" line.
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
+ */
+struct song *
+song_load(FILE *fp, struct directory *parent, const char *uri,
+ GString *buffer, GError **error_r);
#endif
diff --git a/src/song_sticker.c b/src/song_sticker.c
index 2758ff534..83131ba00 100644
--- a/src/song_sticker.c
+++ b/src/song_sticker.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "song_sticker.h"
#include "song.h"
#include "directory.h"
diff --git a/src/song_update.c b/src/song_update.c
new file mode 100644
index 000000000..ab366b0c7
--- /dev/null
+++ b/src/song_update.c
@@ -0,0 +1,173 @@
+/*
+ * 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 "song.h"
+#include "uri.h"
+#include "directory.h"
+#include "mapper.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "tag_ape.h"
+#include "tag_id3.h"
+#include "tag.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+struct song *
+song_file_load(const char *path, struct directory *parent)
+{
+ struct song *song;
+ bool ret;
+
+ assert((parent == NULL) == g_path_is_absolute(path));
+ assert(!uri_has_scheme(path));
+ assert(strchr(path, '\n') == NULL);
+
+ song = song_file_new(path, parent);
+
+ //in archive ?
+ if (parent != NULL && parent->device == DEVICE_INARCHIVE) {
+ ret = song_file_update_inarchive(song);
+ } else {
+ ret = song_file_update(song);
+ }
+ if (!ret) {
+ song_free(song);
+ return NULL;
+ }
+
+ return song;
+}
+
+/**
+ * Attempts to load APE or ID3 tags from the specified file.
+ */
+static struct tag *
+tag_load_fallback(const char *path)
+{
+ struct tag *tag = tag_ape_load(path);
+ if (tag == NULL)
+ tag = tag_id3_load(path);
+ return tag;
+}
+
+/**
+ * The decoder plugin failed to load any tags: fall back to the APE or
+ * ID3 tag loader.
+ */
+static struct tag *
+tag_fallback(const char *path, struct tag *tag)
+{
+ struct tag *fallback = tag_load_fallback(path);
+
+ if (fallback != NULL) {
+ /* tag was successfully loaded: copy the song
+ duration, and destroy the old (empty) tag */
+ fallback->time = tag->time;
+ tag_free(tag);
+ return fallback;
+ } else
+ /* no APE/ID3 tag found: return the empty tag */
+ return tag;
+}
+
+bool
+song_file_update(struct song *song)
+{
+ const char *suffix;
+ char *path_fs;
+ const struct decoder_plugin *plugin;
+ struct stat st;
+
+ assert(song_is_file(song));
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(song->uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, NULL);
+ if (plugin == NULL)
+ return false;
+
+ path_fs = map_song_fs(song);
+ if (path_fs == NULL)
+ return false;
+
+ if (song->tag != NULL) {
+ tag_free(song->tag);
+ song->tag = NULL;
+ }
+
+ if (stat(path_fs, &st) < 0 || !S_ISREG(st.st_mode)) {
+ g_free(path_fs);
+ return false;
+ }
+
+ song->mtime = st.st_mtime;
+
+ do {
+ song->tag = plugin->tag_dup(path_fs);
+ if (song->tag != NULL)
+ break;
+
+ plugin = decoder_plugin_from_suffix(suffix, plugin);
+ } while (plugin != NULL);
+
+ if (song->tag != NULL && tag_is_empty(song->tag))
+ song->tag = tag_fallback(path_fs, song->tag);
+
+ g_free(path_fs);
+ return song->tag != NULL;
+}
+
+bool
+song_file_update_inarchive(struct song *song)
+{
+ const char *suffix;
+ const struct decoder_plugin *plugin;
+
+ assert(song_is_file(song));
+
+ /* check if there's a suffix and a plugin */
+
+ suffix = uri_get_suffix(song->uri);
+ if (suffix == NULL)
+ return false;
+
+ plugin = decoder_plugin_from_suffix(suffix, false);
+ if (plugin == NULL)
+ return false;
+
+ if (song->tag != NULL)
+ tag_free(song->tag);
+
+ //accept every file that has music suffix
+ //because we dont support tag reading throught
+ //input streams
+ song->tag = tag_new();
+
+ return true;
+}
diff --git a/src/songvec.c b/src/songvec.c
index efef02216..bdc90da32 100644
--- a/src/songvec.c
+++ b/src/songvec.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "songvec.h"
#include "song.h"
#include "tag.h"
@@ -29,6 +30,38 @@
static GMutex *nr_lock = NULL;
+static const char *
+tag_get_value_checked(const struct tag *tag, enum tag_type type)
+{
+ return tag != NULL
+ ? tag_get_value(tag, type)
+ : NULL;
+}
+
+static int
+compare_utf8_string(const char *a, const char *b)
+{
+ if (a == NULL)
+ return b == NULL ? 0 : -1;
+
+ if (b == NULL)
+ return 1;
+
+ return g_utf8_collate(a, b);
+}
+
+/**
+ * Compare two string tag values, ignoring case. Either one may be
+ * NULL.
+ */
+static int
+compare_string_tag_item(const struct tag *a, const struct tag *b,
+ enum tag_type type)
+{
+ return compare_utf8_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
+}
+
/**
* Compare two tag values which should contain an integer value
* (e.g. disc or track number). Either one may be NULL.
@@ -51,14 +84,8 @@ compare_number_string(const char *a, const char *b)
static int
compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type)
{
- if (a == NULL)
- return b == NULL ? 0 : -1;
-
- if (b == NULL)
- return 1;
-
- return compare_number_string(tag_get_value(a, type),
- tag_get_value(b, type));
+ return compare_number_string(tag_get_value_checked(a, type),
+ tag_get_value_checked(b, type));
}
/* Only used for sorting/searchin a songvec, not general purpose compares */
@@ -68,18 +95,23 @@ static int songvec_cmp(const void *s1, const void *s2)
const struct song *b = ((const struct song * const *)s2)[0];
int ret;
- /* first sort by disc */
- ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_DISC);
+ /* first sort by album */
+ ret = compare_string_tag_item(a->tag, b->tag, TAG_ALBUM);
+ if (ret != 0)
+ return ret;
+
+ /* then sort by disc */
+ ret = compare_tag_item(a->tag, b->tag, TAG_DISC);
if (ret != 0)
return ret;
/* then by track number */
- ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_TRACK);
+ ret = compare_tag_item(a->tag, b->tag, TAG_TRACK);
if (ret != 0)
return ret;
/* still no difference? compare file name */
- return g_utf8_collate(a->url, b->url);
+ return g_utf8_collate(a->uri, b->uri);
}
static size_t sv_size(const struct songvec *sv)
@@ -108,14 +140,14 @@ void songvec_sort(struct songvec *sv)
}
struct song *
-songvec_find(const struct songvec *sv, const char *url)
+songvec_find(const struct songvec *sv, const char *uri)
{
int i;
struct song *ret = NULL;
g_mutex_lock(nr_lock);
for (i = sv->nr; --i >= 0; ) {
- if (strcmp(sv->base[i]->url, url))
+ if (strcmp(sv->base[i]->uri, uri))
continue;
ret = sv->base[i];
break;
@@ -182,7 +214,7 @@ songvec_for_each(const struct songvec *sv,
struct song *song = sv->base[i];
assert(song);
- assert(*song->url);
+ assert(*song->uri);
prev_nr = sv->nr;
g_mutex_unlock(nr_lock); /* fn() may block */
diff --git a/src/songvec.h b/src/songvec.h
index 0fd207ed0..1d07dd6e9 100644
--- a/src/songvec.h
+++ b/src/songvec.h
@@ -34,7 +34,7 @@ void songvec_deinit(void);
void songvec_sort(struct songvec *sv);
struct song *
-songvec_find(const struct songvec *sv, const char *url);
+songvec_find(const struct songvec *sv, const char *uri);
int
songvec_delete(struct songvec *sv, const struct song *del);
diff --git a/src/state_file.c b/src/state_file.c
index 9c6475cc8..fd9832313 100644
--- a/src/state_file.c
+++ b/src/state_file.c
@@ -17,10 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "state_file.h"
#include "output_state.h"
#include "playlist.h"
+#include "playlist_state.h"
#include "volume.h"
+#include "glib_compat.h"
#include <glib.h>
#include <assert.h>
@@ -30,28 +33,26 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "state_file"
-static struct _sf_cb {
- void (*reader)(FILE *);
- void (*writer)(FILE *);
-} sf_callbacks [] = {
- { read_sw_volume_state, save_sw_volume_state },
- { readAudioDevicesState, saveAudioDevicesState },
- { readPlaylistState, savePlaylistState },
-};
-
static char *state_file_path;
/** the GLib source id for the save timer */
static guint save_state_source_id;
+/**
+ * These version numbers determine whether we need to save the state
+ * file. If nothing has changed, we won't let the hard drive spin up.
+ */
+static unsigned prev_volume_version, prev_output_version,
+ prev_playlist_version;
+
static void
state_file_write(void)
{
- unsigned int i;
FILE *fp;
- if (state_file_path == NULL)
- return;
+ assert(state_file_path != NULL);
+
+ g_debug("Saving state file %s", state_file_path);
fp = fopen(state_file_path, "w");
if (G_UNLIKELY(!fp)) {
@@ -60,21 +61,27 @@ state_file_write(void)
return;
}
- for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++)
- sf_callbacks[i].writer(fp);
+ save_sw_volume_state(fp);
+ audio_output_state_save(fp);
+ playlist_state_save(fp, &g_playlist);
while(fclose(fp) && errno == EINTR) /* nothing */;
+
+ prev_volume_version = sw_volume_state_get_hash();
+ prev_output_version = audio_output_state_get_version();
+ prev_playlist_version = playlist_state_get_hash(&g_playlist);
}
static void
state_file_read(void)
{
- unsigned int i;
FILE *fp;
+ char line[1024];
+ bool success;
assert(state_file_path != NULL);
- g_debug("Saving state file");
+ g_debug("Loading state file %s", state_file_path);
fp = fopen(state_file_path, "r");
if (G_UNLIKELY(!fp)) {
@@ -82,12 +89,22 @@ state_file_read(void)
state_file_path, strerror(errno));
return;
}
- for (i = 0; i < G_N_ELEMENTS(sf_callbacks); i++) {
- sf_callbacks[i].reader(fp);
- rewind(fp);
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ g_strchomp(line);
+
+ success = read_sw_volume_state(line) ||
+ audio_output_state_read(line) ||
+ playlist_state_restore(line, fp, &g_playlist);
+ if (!success)
+ g_warning("Unrecognized line in state file: %s", line);
}
while(fclose(fp) && errno == EINTR) /* nothing */;
+
+ prev_volume_version = sw_volume_state_get_hash();
+ prev_output_version = audio_output_state_get_version();
+ prev_playlist_version = playlist_state_get_hash(&g_playlist);
}
/**
@@ -97,6 +114,13 @@ state_file_read(void)
static gboolean
timer_save_state_file(G_GNUC_UNUSED gpointer data)
{
+ if (prev_volume_version == sw_volume_state_get_hash() &&
+ prev_output_version == audio_output_state_get_version() &&
+ prev_playlist_version == playlist_state_get_hash(&g_playlist))
+ /* nothing has changed - don't save the state file,
+ don't spin up the hard disk */
+ return true;
+
state_file_write();
return true;
}
@@ -112,18 +136,22 @@ state_file_init(const char *path)
state_file_path = g_strdup(path);
state_file_read();
- save_state_source_id = g_timeout_add(5 * 60 * 1000,
- timer_save_state_file, NULL);
+ save_state_source_id = g_timeout_add_seconds(5 * 60,
+ timer_save_state_file,
+ NULL);
}
void
state_file_finish(void)
{
+ if (state_file_path == NULL)
+ /* no state file configured, no cleanup required */
+ return;
+
if (save_state_source_id != 0)
g_source_remove(save_state_source_id);
- if (state_file_path != NULL)
- state_file_write();
+ state_file_write();
g_free(state_file_path);
}
diff --git a/src/stats.c b/src/stats.c
index 01f6761f3..93e492387 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "stats.h"
#include "database.h"
#include "tag.h"
@@ -52,11 +53,11 @@ visit_tag(struct visit_data *data, const struct tag *tag)
const struct tag_item *item = tag->items[i];
switch (item->type) {
- case TAG_ITEM_ARTIST:
+ case TAG_ARTIST:
strset_add(data->artists, item->value);
break;
- case TAG_ITEM_ALBUM:
+ case TAG_ALBUM:
strset_add(data->albums, item->value);
break;
@@ -113,7 +114,7 @@ int stats_print(struct client *client)
stats.album_count,
stats.song_count,
(long)g_timer_elapsed(stats.timer, NULL),
- (long)(getPlayerTotalPlayTime() + 0.5),
+ (long)(pc_get_total_play_time() + 0.5),
stats.song_duration,
db_get_mtime());
return 0;
diff --git a/src/sticker.c b/src/sticker.c
index cded09fca..222ae05bc 100644
--- a/src/sticker.c
+++ b/src/sticker.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "sticker.h"
#include "idle.h"
@@ -76,50 +77,69 @@ static const char sticker_sql_create[] =
static sqlite3 *sticker_db;
static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)];
+static GQuark
+sticker_quark(void)
+{
+ return g_quark_from_static_string("sticker");
+}
+
static sqlite3_stmt *
-sticker_prepare(const char *sql)
+sticker_prepare(const char *sql, GError **error_r)
{
int ret;
sqlite3_stmt *stmt;
ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, NULL);
- if (ret != SQLITE_OK)
- g_error("sqlite3_prepare_v2() failed: %s",
- sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "sqlite3_prepare_v2() failed: %s",
+ sqlite3_errmsg(sticker_db));
+ return NULL;
+ }
return stmt;
}
-void
-sticker_global_init(const char *path)
+bool
+sticker_global_init(const char *path, GError **error_r)
{
int ret;
if (path == NULL)
/* not configured */
- return;
+ return true;
/* open/create the sqlite database */
ret = sqlite3_open(path, &sticker_db);
- if (ret != SQLITE_OK)
- g_error("Failed to open sqlite database '%s': %s",
- path, sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to open sqlite database '%s': %s",
+ path, sqlite3_errmsg(sticker_db));
+ return false;
+ }
/* create the table and index */
ret = sqlite3_exec(sticker_db, sticker_sql_create, NULL, NULL, NULL);
- if (ret != SQLITE_OK)
- g_error("Failed to create sticker table: %s",
- sqlite3_errmsg(sticker_db));
+ if (ret != SQLITE_OK) {
+ g_set_error(error_r, sticker_quark(), ret,
+ "Failed to create sticker table: %s",
+ sqlite3_errmsg(sticker_db));
+ return false;
+ }
/* prepare the statements we're going to use */
for (unsigned i = 0; i < G_N_ELEMENTS(sticker_sql); ++i) {
assert(sticker_sql[i] != NULL);
- sticker_stmt[i] = sticker_prepare(sticker_sql[i]);
+ sticker_stmt[i] = sticker_prepare(sticker_sql[i], error_r);
+ if (sticker_stmt[i] == NULL)
+ return false;
}
+
+ return true;
}
void
diff --git a/src/sticker.h b/src/sticker.h
index 8e6410914..30d85fa18 100644
--- a/src/sticker.h
+++ b/src/sticker.h
@@ -50,9 +50,13 @@ struct sticker;
/**
* Opens the sticker database (if path is not NULL).
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
*/
-void
-sticker_global_init(const char *path);
+bool
+sticker_global_init(const char *path, GError **error_r);
/**
* Close the sticker database.
diff --git a/src/sticker_print.c b/src/sticker_print.c
index 12dafd3f7..6bcc41d77 100644
--- a/src/sticker_print.c
+++ b/src/sticker_print.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "sticker_print.h"
#include "sticker.h"
#include "client.h"
diff --git a/src/stored_playlist.c b/src/stored_playlist.c
index 5ed7182f6..1007da6f8 100644
--- a/src/stored_playlist.c
+++ b/src/stored_playlist.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "stored_playlist.h"
#include "playlist_save.h"
#include "song.h"
diff --git a/src/strset.c b/src/strset.c
index 474dd6642..0c911d5ac 100644
--- a/src/strset.c
+++ b/src/strset.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "strset.h"
#include <assert.h>
diff --git a/src/tag.c b/src/tag.c
index 34205d20d..94fff530e 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag.h"
#include "tag_internal.h"
#include "tag_pool.h"
@@ -42,18 +43,20 @@ static struct {
} bulk;
const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
- "Artist",
- "Album",
- "AlbumArtist",
- "Title",
- "Track",
- "Name",
- "Genre",
- "Date",
- "Composer",
- "Performer",
- "Comment",
- "Disc",
+ [TAG_ARTIST] = "Artist",
+ [TAG_ARTIST_SORT] = "ArtistSort",
+ [TAG_ALBUM] = "Album",
+ [TAG_ALBUM_ARTIST] = "AlbumArtist",
+ [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
+ [TAG_TITLE] = "Title",
+ [TAG_TRACK] = "Track",
+ [TAG_NAME] = "Name",
+ [TAG_GENRE] = "Genre",
+ [TAG_DATE] = "Date",
+ [TAG_COMPOSER] = "Composer",
+ [TAG_PERFORMER] = "Performer",
+ [TAG_COMMENT] = "Comment",
+ [TAG_DISC] = "Disc",
/* MusicBrainz tags from http://musicbrainz.org/doc/MusicBrainzTag */
[TAG_MUSICBRAINZ_ARTISTID] = "MUSICBRAINZ_ARTISTID",
@@ -64,6 +67,36 @@ const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
bool ignore_tag_items[TAG_NUM_OF_ITEM_TYPES];
+enum tag_type
+tag_name_parse(const char *name)
+{
+ assert(name != NULL);
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ assert(tag_item_names[i] != NULL);
+
+ if (strcmp(name, tag_item_names[i]) == 0)
+ return (enum tag_type)i;
+ }
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
+enum tag_type
+tag_name_parse_i(const char *name)
+{
+ assert(name != NULL);
+
+ for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
+ assert(tag_item_names[i] != NULL);
+
+ if (g_ascii_strcasecmp(name, tag_item_names[i]) == 0)
+ return (enum tag_type)i;
+ }
+
+ return TAG_NUM_OF_ITEM_TYPES;
+}
+
static size_t items_size(const struct tag *tag)
{
return tag->num_items * sizeof(struct tag_item *);
@@ -76,12 +109,12 @@ void tag_lib_init(void)
char *temp;
char *s;
char *c;
- int i;
+ enum tag_type type;
/* parse the "metadata_to_use" config parameter below */
/* ignore comments by default */
- ignore_tag_items[TAG_ITEM_COMMENT] = true;
+ ignore_tag_items[TAG_COMMENT] = true;
value = config_get_string(CONF_METADATA_TO_USE, NULL);
if (value == NULL)
@@ -98,16 +131,18 @@ void tag_lib_init(void)
if (*s == '\0')
quit = 1;
*s = '\0';
- for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
- if (g_ascii_strcasecmp(c, tag_item_names[i]) == 0) {
- ignore_tag_items[i] = false;
- break;
- }
- }
- if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) {
+
+ c = g_strstrip(c);
+ if (*c == 0)
+ continue;
+
+ type = tag_name_parse_i(c);
+ if (type == TAG_NUM_OF_ITEM_TYPES)
g_error("error parsing metadata item \"%s\"",
c);
- }
+
+ ignore_tag_items[type] = false;
+
s++;
c = s;
}
diff --git a/src/tag.h b/src/tag.h
index 4b72dd187..8cd09003e 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -31,18 +31,20 @@
* Codes for the type of a tag item.
*/
enum tag_type {
- TAG_ITEM_ARTIST,
- TAG_ITEM_ALBUM,
- TAG_ITEM_ALBUM_ARTIST,
- TAG_ITEM_TITLE,
- TAG_ITEM_TRACK,
- TAG_ITEM_NAME,
- TAG_ITEM_GENRE,
- TAG_ITEM_DATE,
- TAG_ITEM_COMPOSER,
- TAG_ITEM_PERFORMER,
- TAG_ITEM_COMMENT,
- TAG_ITEM_DISC,
+ TAG_ARTIST,
+ TAG_ARTIST_SORT,
+ TAG_ALBUM,
+ TAG_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST_SORT,
+ TAG_TITLE,
+ TAG_TRACK,
+ TAG_NAME,
+ TAG_GENRE,
+ TAG_DATE,
+ TAG_COMPOSER,
+ TAG_PERFORMER,
+ TAG_COMMENT,
+ TAG_DISC,
TAG_MUSICBRAINZ_ARTISTID,
TAG_MUSICBRAINZ_ALBUMID,
@@ -94,6 +96,22 @@ struct tag {
};
/**
+ * Parse the string, and convert it into a #tag_type. Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ */
+enum tag_type
+tag_name_parse(const char *name);
+
+/**
+ * Parse the string, and convert it into a #tag_type. Returns
+ * #TAG_NUM_OF_ITEM_TYPES if the string could not be recognized.
+ *
+ * Case does not matter.
+ */
+enum tag_type
+tag_name_parse_i(const char *name);
+
+/**
* Creates an empty #tag.
*/
struct tag *tag_new(void);
diff --git a/src/tag_ape.c b/src/tag_ape.c
index 7cbf32208..d18cc84ee 100644
--- a/src/tag_ape.c
+++ b/src/tag_ape.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_ape.h"
#include "tag.h"
@@ -25,6 +26,36 @@
#include <assert.h>
#include <stdio.h>
+static const char *const ape_tag_names[] = {
+ [TAG_TITLE] = "title",
+ [TAG_ARTIST] = "artist",
+ [TAG_ALBUM] = "album",
+ [TAG_COMMENT] = "comment",
+ [TAG_GENRE] = "genre",
+ [TAG_TRACK] = "track",
+ [TAG_DATE] = "year"
+};
+
+static struct tag *
+tag_ape_import_item(struct tag *tag, unsigned long flags,
+ const char *key, const char *value, size_t value_length)
+{
+ /* we only care about utf-8 text tags */
+ if ((flags & (0x3 << 1)) != 0)
+ return tag;
+
+ for (unsigned i = 0; i < G_N_ELEMENTS(ape_tag_names); i++) {
+ if (ape_tag_names[i] != NULL &&
+ g_ascii_strcasecmp(key, ape_tag_names[i]) == 0) {
+ if (tag == NULL)
+ tag = tag_new();
+ tag_add_item_n(tag, i, value, value_length);
+ }
+ }
+
+ return tag;
+}
+
struct tag *
tag_ape_load(const char *file)
{
@@ -36,7 +67,6 @@ tag_ape_load(const char *file)
size_t tagLen;
size_t size;
unsigned long flags;
- int i;
char *key;
struct {
@@ -48,26 +78,6 @@ tag_ape_load(const char *file)
unsigned char reserved[8];
} footer;
- const char *apeItems[7] = {
- "title",
- "artist",
- "album",
- "comment",
- "genre",
- "track",
- "year"
- };
-
- int tagItems[7] = {
- TAG_ITEM_TITLE,
- TAG_ITEM_ARTIST,
- TAG_ITEM_ALBUM,
- TAG_ITEM_COMMENT,
- TAG_ITEM_GENRE,
- TAG_ITEM_TRACK,
- TAG_ITEM_DATE,
- };
-
fp = fopen(file, "r");
if (!fp)
return NULL;
@@ -127,17 +137,8 @@ tag_ape_load(const char *file)
if (tagLen < size)
goto fail;
- /* we only care about utf-8 text tags */
- if (!(flags & (0x3 << 1))) {
- for (i = 0; i < 7; i++) {
- if (g_ascii_strcasecmp(key, apeItems[i]) == 0) {
- if (!ret)
- ret = tag_new();
- tag_add_item_n(ret, tagItems[i],
- p, size);
- }
- }
- }
+ ret = tag_ape_import_item(ret, flags, key, p, size);
+
p += size;
tagLen -= size;
}
diff --git a/src/tag_id3.c b/src/tag_id3.c
index a33ebc00b..4f1a248b3 100644
--- a/src/tag_id3.c
+++ b/src/tag_id3.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_id3.h"
#include "tag.h"
#include "riff.h"
@@ -34,25 +35,31 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "id3"
-# define isId3v1(tag) (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1)
# ifndef ID3_FRAME_COMPOSER
# define ID3_FRAME_COMPOSER "TCOM"
# endif
-# ifndef ID3_FRAME_PERFORMER
-# define ID3_FRAME_PERFORMER "TOPE"
-# endif
# ifndef ID3_FRAME_DISC
# define ID3_FRAME_DISC "TPOS"
# endif
+#ifndef ID3_FRAME_ARTIST_SORT
+#define ID3_FRAME_ARTIST_SORT "TSOP"
+#endif
+
#ifndef ID3_FRAME_ALBUM_ARTIST_SORT
-#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2"
+#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
#endif
#ifndef ID3_FRAME_ALBUM_ARTIST
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif
+static inline bool
+tag_is_id3v1(struct id3_tag *tag)
+{
+ return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0;
+}
+
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
@@ -72,14 +79,13 @@ tag_id3_getstring(const struct id3_frame *frame, unsigned i)
/* This will try to convert a string to utf-8,
*/
-static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4, int type)
+static id3_utf8_t *
+import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
{
id3_utf8_t *utf8, *utf8_stripped;
id3_latin1_t *isostr;
const char *encoding;
- if (type == TAG_ITEM_GENRE)
- ucs4 = id3_genre_name(ucs4);
/* use encoding field here? */
if (is_id3v1 &&
(encoding = config_get_string(CONF_ID3V1_ENCODING, NULL)) != NULL) {
@@ -112,8 +118,16 @@ static id3_utf8_t * processID3FieldString (int is_id3v1, const id3_ucs4_t *ucs4,
return utf8_stripped;
}
+/**
+ * Import a "Text information frame" (ID3v2.4.0 section 4.2). It
+ * contains 2 fields:
+ *
+ * - encoding
+ * - string list
+ */
static void
-getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag)
+tag_id3_import_text(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
{
struct id3_frame const *frame;
id3_ucs4_t const *ucs4;
@@ -122,108 +136,77 @@ getID3Info(struct id3_tag *tag, const char *id, int type, struct tag *mpdTag)
unsigned int nstrings, i;
frame = id3_tag_findframe(tag, id, 0);
- /* Check frame */
- if (!frame)
- {
+ if (frame == NULL || frame->nfields != 2)
return;
- }
- /* Check fields in frame */
- if(frame->nfields == 0)
- {
- g_debug("Frame has no fields");
+
+ /* check the encoding field */
+
+ field = id3_frame_field(frame, 0);
+ if (field == NULL || field->type != ID3_FIELD_TYPE_TEXTENCODING)
return;
- }
- /* Starting with T is a stringlist */
- if (id[0] == 'T')
- {
- /* This one contains 2 fields:
- * 1st: Text encoding
- * 2: Stringlist
- * Shamefully this isn't the RL case.
- * But I am going to enforce it anyway.
- */
- if(frame->nfields != 2)
- {
- g_debug("Invalid number '%i' of fields for TXX frame",
- frame->nfields);
- return;
- }
- field = &frame->fields[0];
- /**
- * First field is encoding field.
- * This is ignored by mpd.
- */
- if(field->type != ID3_FIELD_TYPE_TEXTENCODING)
- {
- g_debug("Expected encoding, found: %i",
- field->type);
- }
- /* Process remaining fields, should be only one */
- field = &frame->fields[1];
- /* Encoding field */
- if(field->type == ID3_FIELD_TYPE_STRINGLIST) {
- /* Get the number of strings available */
- nstrings = id3_field_getnstrings(field);
- for (i = 0; i < nstrings; i++) {
- ucs4 = id3_field_getstrings(field,i);
- if(!ucs4)
- continue;
- utf8 = processID3FieldString(isId3v1(tag),ucs4, type);
- if(!utf8)
- continue;
-
- tag_add_item(mpdTag, type, (char *)utf8);
- g_free(utf8);
- }
- }
- else {
- g_warning("Field type not processed: %i",
- (int)id3_field_gettextencoding(field));
- }
- }
- /* A comment frame */
- else if(!strcmp(ID3_FRAME_COMMENT, id))
- {
- /* A comment frame is different... */
- /* 1st: encoding
- * 2nd: Language
- * 3rd: String
- * 4th: FullString.
- * The 'value' we want is in the 4th field
- */
- if(frame->nfields == 4)
- {
- /* for now I only read the 4th field, with the fullstring */
- field = &frame->fields[3];
- if(field->type == ID3_FIELD_TYPE_STRINGFULL)
- {
- ucs4 = id3_field_getfullstring(field);
- if(ucs4)
- {
- utf8 = processID3FieldString(isId3v1(tag),ucs4, type);
- if(utf8)
- {
- tag_add_item(mpdTag, type, (char *)utf8);
- g_free(utf8);
- }
- }
- }
- else
- {
- g_debug("4th field in comment frame differs from expected, got '%i': ignoring",
- field->type);
- }
- }
- else
- {
- g_debug("Invalid 'comments' tag, got '%i' fields instead of 4",
- frame->nfields);
- }
+ /* process the value(s) */
+
+ field = id3_frame_field(frame, 1);
+ if (field == NULL || field->type != ID3_FIELD_TYPE_STRINGLIST)
+ return;
+
+ /* Get the number of strings available */
+ nstrings = id3_field_getnstrings(field);
+ for (i = 0; i < nstrings; i++) {
+ ucs4 = id3_field_getstrings(field, i);
+ if (ucs4 == NULL)
+ continue;
+
+ if (type == TAG_GENRE)
+ ucs4 = id3_genre_name(ucs4);
+
+ utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ if (utf8 == NULL)
+ continue;
+
+ tag_add_item(dest, type, (char *)utf8);
+ g_free(utf8);
}
- /* Unsupported */
- else
- g_debug("Unsupported tag type requrested");
+}
+
+/**
+ * Import a "Comment frame" (ID3v2.4.0 section 4.10). It
+ * contains 4 fields:
+ *
+ * - encoding
+ * - language
+ * - string
+ * - full string (we use this one)
+ */
+static void
+tag_id3_import_comment(struct tag *dest, struct id3_tag *tag, const char *id,
+ enum tag_type type)
+{
+ struct id3_frame const *frame;
+ id3_ucs4_t const *ucs4;
+ id3_utf8_t *utf8;
+ union id3_field const *field;
+
+ frame = id3_tag_findframe(tag, id, 0);
+ if (frame == NULL || frame->nfields != 4)
+ return;
+
+ /* for now I only read the 4th field, with the fullstring */
+ field = id3_frame_field(frame, 3);
+ if (field == NULL)
+ return;
+
+ ucs4 = id3_field_getfullstring(field);
+ if (ucs4 == NULL)
+ return;
+
+ utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
+ if (utf8 == NULL)
+ return;
+
+ tag_add_item(dest, type, (char *)utf8);
+ g_free(utf8);
}
/**
@@ -237,6 +220,7 @@ tag_id3_parse_txxx_name(const char *name)
enum tag_type type;
const char *name;
} musicbrainz_txxx[] = {
+ { TAG_ALBUM_ARTIST_SORT, "ALBUMARTISTSORT" },
{ TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id" },
{ TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id" },
{ TAG_MUSICBRAINZ_ALBUMARTISTID,
@@ -328,20 +312,23 @@ struct tag *tag_id3_import(struct id3_tag * tag)
{
struct tag *ret = tag_new();
- getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_ALBUM_ARTIST,
- TAG_ITEM_ALBUM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
- TAG_ITEM_ALBUM_ARTIST, ret);
- getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret);
- getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret);
- getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret);
- getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret);
- getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret);
- getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret);
- getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret);
- getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret);
- getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST, TAG_ARTIST);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ARTIST_SORT,
+ TAG_ARTIST_SORT);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM_ARTIST_SORT,
+ TAG_ALBUM_ARTIST_SORT);
+ tag_id3_import_text(ret, tag, ID3_FRAME_TITLE, TAG_TITLE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_ALBUM, TAG_ALBUM);
+ tag_id3_import_text(ret, tag, ID3_FRAME_TRACK, TAG_TRACK);
+ tag_id3_import_text(ret, tag, ID3_FRAME_YEAR, TAG_DATE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_GENRE, TAG_GENRE);
+ tag_id3_import_text(ret, tag, ID3_FRAME_COMPOSER, TAG_COMPOSER);
+ tag_id3_import_text(ret, tag, "TPE3", TAG_PERFORMER);
+ tag_id3_import_text(ret, tag, "TPE4", TAG_PERFORMER);
+ tag_id3_import_comment(ret, tag, ID3_FRAME_COMMENT, TAG_COMMENT);
+ tag_id3_import_text(ret, tag, ID3_FRAME_DISC, TAG_DISC);
tag_id3_import_musicbrainz(ret, tag);
tag_id3_import_ufid(ret, tag);
@@ -354,69 +341,72 @@ struct tag *tag_id3_import(struct id3_tag * tag)
return ret;
}
-static int fillBuffer(void *buf, size_t size, FILE * stream,
- long offset, int whence)
+static int
+fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence)
{
if (fseek(stream, offset, whence) != 0) return 0;
return fread(buf, 1, size, stream);
}
-static int getId3v2FooterSize(FILE * stream, long offset, int whence)
+static int
+get_id3v2_footer_size(FILE *stream, long offset, int whence)
{
id3_byte_t buf[ID3_TAG_QUERYSIZE];
int bufsize;
- bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
+ bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
if (bufsize <= 0) return 0;
return id3_tag_query(buf, bufsize);
}
-static struct id3_tag *getId3Tag(FILE * stream, long offset, int whence)
+static struct id3_tag *
+tag_id3_read(FILE *stream, long offset, int whence)
{
struct id3_tag *tag;
- id3_byte_t queryBuf[ID3_TAG_QUERYSIZE];
- id3_byte_t *tagBuf;
- int tagSize;
- int queryBufSize;
- int tagBufSize;
+ id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
+ id3_byte_t *tag_buffer;
+ int tag_size;
+ int query_buffer_size;
+ int tag_buffer_size;
/* It's ok if we get less than we asked for */
- queryBufSize = fillBuffer(queryBuf, ID3_TAG_QUERYSIZE,
- stream, offset, whence);
- if (queryBufSize <= 0) return NULL;
+ query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
+ stream, offset, whence);
+ if (query_buffer_size <= 0) return NULL;
/* Look for a tag header */
- tagSize = id3_tag_query(queryBuf, queryBufSize);
- if (tagSize <= 0) return NULL;
+ tag_size = id3_tag_query(query_buffer, query_buffer_size);
+ if (tag_size <= 0) return NULL;
/* Found a tag. Allocate a buffer and read it in. */
- tagBuf = g_malloc(tagSize);
- if (!tagBuf) return NULL;
+ tag_buffer = g_malloc(tag_size);
+ if (!tag_buffer) return NULL;
- tagBufSize = fillBuffer(tagBuf, tagSize, stream, offset, whence);
- if (tagBufSize < tagSize) {
- g_free(tagBuf);
+ tag_buffer_size = fill_buffer(tag_buffer, tag_size, stream, offset, whence);
+ if (tag_buffer_size < tag_size) {
+ g_free(tag_buffer);
return NULL;
}
- tag = id3_tag_parse(tagBuf, tagBufSize);
+ tag = id3_tag_parse(tag_buffer, tag_buffer_size);
- g_free(tagBuf);
+ g_free(tag_buffer);
return tag;
}
-static struct id3_tag *findId3TagFromBeginning(FILE * stream)
+static struct id3_tag *
+tag_id3_find_from_beginning(FILE *stream)
{
struct id3_tag *tag;
struct id3_tag *seektag;
struct id3_frame *frame;
int seek;
- tag = getId3Tag(stream, 0, SEEK_SET);
+ tag = tag_id3_read(stream, 0, SEEK_SET);
if (!tag) {
return NULL;
- } else if (isId3v1(tag)) {
+ } else if (tag_is_id3v1(tag)) {
/* id3v1 tags don't belong here */
id3_tag_delete(tag);
return NULL;
@@ -430,8 +420,8 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream)
break;
/* Get the tag specified by the SEEK frame */
- seektag = getId3Tag(stream, seek, SEEK_CUR);
- if (!seektag || isId3v1(seektag))
+ seektag = tag_id3_read(stream, seek, SEEK_CUR);
+ if (!seektag || tag_is_id3v1(seektag))
break;
/* Replace the old tag with the new one */
@@ -442,22 +432,23 @@ static struct id3_tag *findId3TagFromBeginning(FILE * stream)
return tag;
}
-static struct id3_tag *findId3TagFromEnd(FILE * stream)
+static struct id3_tag *
+tag_id3_find_from_end(FILE *stream)
{
struct id3_tag *tag;
struct id3_tag *v1tag;
int tagsize;
/* Get an id3v1 tag from the end of file for later use */
- v1tag = getId3Tag(stream, -128, SEEK_END);
+ v1tag = tag_id3_read(stream, -128, SEEK_END);
/* Get the id3v2 tag size from the footer (located before v1tag) */
- tagsize = getId3v2FooterSize(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
+ tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
if (tagsize >= 0)
return v1tag;
/* Get the tag which the footer belongs to */
- tag = getId3Tag(stream, tagsize, SEEK_CUR);
+ tag = tag_id3_read(stream, tagsize, SEEK_CUR);
if (!tag)
return v1tag;
@@ -511,11 +502,11 @@ struct tag *tag_id3_load(const char *file)
return NULL;
}
- tag = findId3TagFromBeginning(stream);
+ tag = tag_id3_find_from_beginning(stream);
if (tag == NULL)
tag = tag_id3_riff_aiff_load(stream);
if (!tag)
- tag = findId3TagFromEnd(stream);
+ tag = tag_id3_find_from_end(stream);
fclose(stream);
diff --git a/src/tag_id3.h b/src/tag_id3.h
index 4f51a70b8..b32a834b5 100644
--- a/src/tag_id3.h
+++ b/src/tag_id3.h
@@ -20,7 +20,7 @@
#ifndef MPD_TAG_ID3_H
#define MPD_TAG_ID3_H
-#include "config.h"
+#include "check.h"
struct tag;
diff --git a/src/tag_pool.c b/src/tag_pool.c
index 6aef12941..25629ffb7 100644
--- a/src/tag_pool.c
+++ b/src/tag_pool.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_pool.h"
#include <assert.h>
diff --git a/src/tag_print.c b/src/tag_print.c
index dddbbbe67..d3b84568e 100644
--- a/src/tag_print.c
+++ b/src/tag_print.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_print.h"
#include "tag.h"
#include "tag_internal.h"
diff --git a/src/tag_save.c b/src/tag_save.c
index fac948b9f..bd0ef7b76 100644
--- a/src/tag_save.c
+++ b/src/tag_save.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "tag_save.h"
#include "tag.h"
#include "tag_internal.h"
diff --git a/src/text_file.c b/src/text_file.c
new file mode 100644
index 000000000..776e57023
--- /dev/null
+++ b/src/text_file.c
@@ -0,0 +1,63 @@
+/*
+ * 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 "text_file.h"
+
+#include <assert.h>
+#include <string.h>
+
+char *
+read_text_line(FILE *file, GString *buffer)
+{
+ enum {
+ max_length = 512 * 1024,
+ step = 1024,
+ };
+
+ gsize length = 0, i;
+ char *p;
+
+ assert(file != NULL);
+ assert(buffer != NULL);
+
+ if (buffer->allocated_len < step)
+ g_string_set_size(buffer, step);
+
+ while (buffer->len < max_length) {
+ p = fgets(buffer->str + length,
+ buffer->allocated_len - length, file);
+ if (p == NULL) {
+ if (length == 0 || ferror(file))
+ return NULL;
+ break;
+ }
+
+ i = strlen(buffer->str + length);
+ length += i;
+ if (i < step - 1 || buffer->str[length - 1] == '\n')
+ break;
+
+ g_string_set_size(buffer, length + step);
+ }
+
+ g_string_set_size(buffer, length);
+ g_strchomp(buffer->str);
+ return buffer->str;
+}
diff --git a/src/text_file.h b/src/text_file.h
new file mode 100644
index 000000000..bc5c92870
--- /dev/null
+++ b/src/text_file.h
@@ -0,0 +1,39 @@
+/*
+ * 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_TEXT_FILE_H
+#define MPD_TEXT_FILE_H
+
+#include <glib.h>
+
+#include <stdio.h>
+
+/**
+ * Reads a line from the input file, and strips trailing space. There
+ * is a reasonable maximum line length, only to prevent denial of
+ * service.
+ *
+ * @param file the source file, opened in text mode
+ * @param buffer an allocator for the buffer
+ * @return a pointer to the line, or NULL on end-of-file or error
+ */
+char *
+read_text_line(FILE *file, GString *buffer);
+
+#endif
diff --git a/src/text_input_stream.c b/src/text_input_stream.c
new file mode 100644
index 000000000..fd402590e
--- /dev/null
+++ b/src/text_input_stream.c
@@ -0,0 +1,89 @@
+/*
+ * 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 "text_input_stream.h"
+#include "input_stream.h"
+#include "fifo_buffer.h"
+
+#include <glib.h>
+
+#include <string.h>
+
+struct text_input_stream {
+ struct input_stream *is;
+
+ struct fifo_buffer *buffer;
+
+ char *line;
+};
+
+struct text_input_stream *
+text_input_stream_new(struct input_stream *is)
+{
+ struct text_input_stream *tis = g_new(struct text_input_stream, 1);
+
+ tis->is = is;
+ tis->buffer = fifo_buffer_new(4096);
+ tis->line = NULL;
+
+ return tis;
+}
+
+void
+text_input_stream_free(struct text_input_stream *tis)
+{
+ fifo_buffer_free(tis->buffer);
+ g_free(tis->line);
+ g_free(tis);
+}
+
+const char *
+text_input_stream_read(struct text_input_stream *tis)
+{
+ void *dest;
+ const char *src, *p;
+ size_t length, nbytes;
+
+ g_free(tis->line);
+ tis->line = NULL;
+
+ do {
+ dest = fifo_buffer_write(tis->buffer, &length);
+ if (dest != NULL) {
+ nbytes = input_stream_read(tis->is, dest, length);
+ if (nbytes > 0)
+ fifo_buffer_append(tis->buffer, nbytes);
+ }
+
+ src = fifo_buffer_read(tis->buffer, &length);
+ if (src == NULL)
+ return NULL;
+
+ p = memchr(src, '\n', length);
+ } while (p == NULL);
+
+ length = p - src + 1;
+ while (p > src && g_ascii_isspace(p[-1]))
+ --p;
+
+ tis->line = g_strndup(src, p - src);
+ fifo_buffer_consume(tis->buffer, length);
+ return tis->line;
+}
diff --git a/src/text_input_stream.h b/src/text_input_stream.h
new file mode 100644
index 000000000..2b93ae180
--- /dev/null
+++ b/src/text_input_stream.h
@@ -0,0 +1,52 @@
+/*
+ * 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_TEXT_INPUT_STREAM_H
+#define MPD_TEXT_INPUT_STREAM_H
+
+struct input_stream;
+struct text_input_stream;
+
+/**
+ * Wraps an existing #input_stream object into a #text_input_stream,
+ * to read its contents as text lines.
+ *
+ * @param is an open #input_stream object
+ * @return the new #text_input_stream object
+ */
+struct text_input_stream *
+text_input_stream_new(struct input_stream *is);
+
+/**
+ * Frees the #text_input_stream object. Does not close or free the
+ * underlying #input_stream.
+ */
+void
+text_input_stream_free(struct text_input_stream *tis);
+
+/**
+ * Reads the next line from the stream.
+ *
+ * @return a line (newline character stripped), or NULL on end of file
+ * or error
+ */
+const char *
+text_input_stream_read(struct text_input_stream *tis);
+
+#endif
diff --git a/src/timer.c b/src/timer.c
index d9a143bcc..39347bd57 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "timer.h"
#include "audio_format.h"
diff --git a/src/tokenizer.c b/src/tokenizer.c
new file mode 100644
index 000000000..52f847671
--- /dev/null
+++ b/src/tokenizer.c
@@ -0,0 +1,222 @@
+/*
+ * 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 "tokenizer.h"
+
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+G_GNUC_CONST
+static GQuark
+tokenizer_quark(void)
+{
+ return g_quark_from_static_string("tokenizer");
+}
+
+static inline bool
+valid_word_first_char(char ch)
+{
+ return g_ascii_isalpha(ch);
+}
+
+static inline bool
+valid_word_char(char ch)
+{
+ return g_ascii_isalnum(ch) || ch == '_';
+}
+
+char *
+tokenizer_next_word(char **input_p, GError **error_r)
+{
+ char *word, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = input = *input_p;
+
+ if (*input == 0)
+ return NULL;
+
+ /* check the first character */
+
+ if (!valid_word_first_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Letter expected");
+ return NULL;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = g_strchug(input + 1);
+ break;
+ }
+
+ if (!valid_word_char(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid word character");
+ return NULL;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ *input_p = input;
+ return word;
+}
+
+static inline bool
+valid_unquoted_char(char ch)
+{
+ return (unsigned char)ch > 0x20 && ch != '"' && ch != '\'';
+}
+
+char *
+tokenizer_next_unquoted(char **input_p, GError **error_r)
+{
+ char *word, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = input = *input_p;
+
+ if (*input == 0)
+ return NULL;
+
+ /* check the first character */
+
+ if (!valid_unquoted_char(*input)) {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return NULL;
+ }
+
+ /* now iterate over the other characters until we find a
+ whitespace or end-of-string */
+
+ while (*++input != 0) {
+ if (g_ascii_isspace(*input)) {
+ /* a whitespace: the word ends here */
+ *input = 0;
+ /* skip all following spaces, too */
+ input = g_strchug(input + 1);
+ break;
+ }
+
+ if (!valid_unquoted_char(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Invalid unquoted character");
+ return NULL;
+ }
+ }
+
+ /* end of string: the string is already null-terminated
+ here */
+
+ *input_p = input;
+ return word;
+}
+
+char *
+tokenizer_next_string(char **input_p, GError **error_r)
+{
+ char *word, *dest, *input;
+
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ word = dest = input = *input_p;
+
+ if (*input == 0)
+ /* end of line */
+ return NULL;
+
+ /* check for the opening " */
+
+ if (*input != '"') {
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "'\"' expected");
+ return NULL;
+ }
+
+ ++input;
+
+ /* copy all characters */
+
+ while (*input != '"') {
+ if (*input == '\\')
+ /* the backslash escapes the following
+ character */
+ ++input;
+
+ if (*input == 0) {
+ /* return input-1 so the caller can see the
+ difference between "end of line" and
+ "error" */
+ *input_p = input - 1;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Missing closing '\"'");
+ return NULL;
+ }
+
+ /* copy one character */
+ *dest++ = *input++;
+ }
+
+ /* the following character must be a whitespace (or end of
+ line) */
+
+ ++input;
+ if (*input != 0 && !g_ascii_isspace(*input)) {
+ *input_p = input;
+ g_set_error(error_r, tokenizer_quark(), 0,
+ "Space expected after closing '\"'");
+ return NULL;
+ }
+
+ /* finish the string and return it */
+
+ *dest = 0;
+ *input_p = g_strchug(input);
+ return word;
+}
+
+char *
+tokenizer_next_param(char **input_p, GError **error_r)
+{
+ assert(input_p != NULL);
+ assert(*input_p != NULL);
+
+ if (**input_p == '"')
+ return tokenizer_next_string(input_p, error_r);
+ else
+ return tokenizer_next_unquoted(input_p, error_r);
+}
diff --git a/src/tokenizer.h b/src/tokenizer.h
new file mode 100644
index 000000000..ce4c37ccd
--- /dev/null
+++ b/src/tokenizer.h
@@ -0,0 +1,83 @@
+/*
+ * 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_TOKENIZER_H
+#define MPD_TOKENIZER_H
+
+#include <glib.h>
+
+/**
+ * Reads the next word from the input string. This function modifies
+ * the input string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or NULL on error or
+ * end of line
+ */
+char *
+tokenizer_next_word(char **input_p, GError **error_r);
+
+/**
+ * Reads the next unquoted word from the input string. This function
+ * modifies the input string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated word, or NULL on error or
+ * end of line
+ */
+char *
+tokenizer_next_unquoted(char **input_p, GError **error_r);
+
+/**
+ * Reads the next quoted string from the input string. A backslash
+ * escapes the following character. This function modifies the input
+ * string.
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_string(char **input_p, GError **error_r);
+
+/**
+ * Reads the next unquoted word or quoted string from the input. This
+ * is a wrapper for tokenizer_next_unquoted() and
+ * tokenizer_next_string().
+ *
+ * @param input_p the input string; this function returns a pointer to
+ * the first non-whitespace character of the following token
+ * @param error_r if this function returns NULL and **input_p!=0, it
+ * optionally provides a GError object in this argument
+ * @return a pointer to the null-terminated string, or NULL on error
+ * or end of line
+ */
+char *
+tokenizer_next_param(char **input_p, GError **error_r);
+
+#endif
diff --git a/src/update.c b/src/update.c
index d5c9779c8..ee946f357 100644
--- a/src/update.c
+++ b/src/update.c
@@ -17,45 +17,21 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
+#include "update_internal.h"
#include "update.h"
#include "database.h"
-#include "directory.h"
-#include "song.h"
-#include "uri.h"
#include "mapper.h"
-#include "path.h"
-#include "decoder_list.h"
-#include "archive_list.h"
#include "playlist.h"
#include "event_pipe.h"
-#include "notify.h"
#include "update.h"
#include "idle.h"
-#include "conf.h"
#include "stats.h"
#include "main.h"
-#include "config.h"
-
-#ifdef ENABLE_SQLITE
-#include "sticker.h"
-#include "song_sticker.h"
-#endif
#include <glib.h>
#include <assert.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "decoder_plugin.h"
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "update"
static enum update_progress {
UPDATE_PROGRESS_IDLE = 0,
@@ -65,32 +41,14 @@ static enum update_progress {
static bool modified;
-/* make this dynamic?, or maybe this is big enough... */
-static char *update_paths[32];
-static size_t update_paths_nr;
-
static GThread *update_thr;
static const unsigned update_task_id_max = 1 << 15;
static unsigned update_task_id;
-static struct song *delete;
-
-/** used by the main thread to notify the update thread */
-static struct notify update_notify;
-
-#ifndef WIN32
-
-enum {
- DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
-};
-
-static bool follow_inside_symlinks;
-static bool follow_outside_symlinks;
-
-#endif
+/* XXX this flag is passed to update_task() */
+static bool discard;
unsigned
isUpdatingDB(void)
@@ -98,692 +56,11 @@ isUpdatingDB(void)
return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0;
}
-static void
-directory_set_stat(struct directory *dir, const struct stat *st)
-{
- dir->inode = st->st_ino;
- dir->device = st->st_dev;
- dir->stat = 1;
-}
-
-static void
-delete_song(struct directory *dir, struct song *del)
-{
- /* first, prevent traversers in main task from getting this */
- songvec_delete(&dir->songs, del);
-
- /* now take it out of the playlist (in the main_task) */
- assert(!delete);
- delete = del;
- event_pipe_emit(PIPE_EVENT_DELETE);
-
- do {
- notify_wait(&update_notify);
- } while (delete != NULL);
-
- /* finally, all possible references gone, free it */
- song_free(del);
-}
-
-static int
-delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
-{
- struct directory *directory = data;
- assert(song->parent == directory);
- delete_song(directory, song);
- return 0;
-}
-
-static void
-delete_directory(struct directory *directory);
-
-/**
- * Recursively remove all sub directories and songs from a directory,
- * leaving an empty directory.
- */
-static void
-clear_directory(struct directory *directory)
-{
- int i;
-
- for (i = directory->children.nr; --i >= 0;)
- delete_directory(directory->children.base[i]);
-
- assert(directory->children.nr == 0);
-
- songvec_for_each(&directory->songs, delete_each_song, directory);
-}
-
-/**
- * Recursively free a directory and all its contents.
- */
-static void
-delete_directory(struct directory *directory)
-{
- assert(directory->parent != NULL);
-
- clear_directory(directory);
-
- dirvec_delete(&directory->parent->children, directory);
- directory_free(directory);
-}
-
-static void
-delete_name_in(struct directory *parent, const char *name)
-{
- struct directory *directory = directory_get_child(parent, name);
- struct song *song = songvec_find(&parent->songs, name);
-
- if (directory != NULL) {
- delete_directory(directory);
- modified = true;
- }
-
- if (song != NULL) {
- delete_song(parent, song);
- modified = true;
- }
-}
-
-/* passed to songvec_for_each */
-static int
-delete_song_if_removed(struct song *song, void *_data)
-{
- struct directory *dir = _data;
- char *path;
- struct stat st;
-
- if ((path = map_song_fs(song)) == NULL ||
- stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
- delete_song(dir, song);
- modified = true;
- }
-
- g_free(path);
- return 0;
-}
-
-static bool
-directory_exists(const struct directory *directory)
-{
- char *path_fs;
- GFileTest test;
- bool exists;
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- /* invalid path: cannot exist */
- return false;
-
- test = directory->device == DEVICE_INARCHIVE ||
- directory->device == DEVICE_CONTAINER
- ? G_FILE_TEST_IS_REGULAR
- : G_FILE_TEST_IS_DIR;
-
- exists = g_file_test(path_fs, test);
- g_free(path_fs);
-
- return exists;
-}
-
-static void
-removeDeletedFromDirectory(struct directory *directory)
-{
- int i;
- struct dirvec *dv = &directory->children;
-
- for (i = dv->nr; --i >= 0; ) {
- if (directory_exists(dv->base[i]))
- continue;
-
- g_debug("removing directory: %s", dv->base[i]->path);
- delete_directory(dv->base[i]);
- modified = true;
- }
-
- songvec_for_each(&directory->songs, delete_song_if_removed, directory);
-}
-
-static int
-stat_directory(const struct directory *directory, struct stat *st)
-{
- char *path_fs;
- int ret;
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- return -1;
- ret = stat(path_fs, st);
- g_free(path_fs);
- return ret;
-}
-
-static int
-stat_directory_child(const struct directory *parent, const char *name,
- struct stat *st)
-{
- char *path_fs;
- int ret;
-
- path_fs = map_directory_child_fs(parent, name);
- if (path_fs == NULL)
- return -1;
-
- ret = stat(path_fs, st);
- g_free(path_fs);
- return ret;
-}
-
-static int
-statDirectory(struct directory *dir)
-{
- struct stat st;
-
- if (stat_directory(dir, &st) < 0)
- return -1;
-
- directory_set_stat(dir, &st);
-
- return 0;
-}
-
-static int
-inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
-{
- while (parent) {
- if (!parent->stat && statDirectory(parent) < 0)
- return -1;
- if (parent->inode == inode && parent->device == device) {
- g_debug("recursive directory found");
- return 1;
- }
- parent = parent->parent;
- }
-
- return 0;
-}
-
-static struct directory *
-make_subdir(struct directory *parent, const char *name)
-{
- struct directory *directory;
-
- directory = directory_get_child(parent, name);
- if (directory == NULL) {
- char *path;
-
- if (directory_is_root(parent))
- path = NULL;
- else
- name = path = g_strconcat(directory_get_path(parent),
- "/", name, NULL);
-
- directory = directory_new_child(parent, name);
- g_free(path);
- }
-
- return directory;
-}
-
-#ifdef ENABLE_ARCHIVE
-static void
-update_archive_tree(struct directory *directory, char *name)
-{
- struct directory *subdir;
- struct song *song;
- char *tmp;
-
- tmp = strchr(name, '/');
- if (tmp) {
- *tmp = 0;
- //add dir is not there already
- if ((subdir = dirvec_find(&directory->children, name)) == NULL) {
- //create new directory
- subdir = make_subdir(directory, name);
- subdir->device = DEVICE_INARCHIVE;
- }
- //create directories first
- update_archive_tree(subdir, tmp+1);
- } else {
- if (strlen(name) == 0) {
- g_warning("archive returned directory only");
- return;
- }
- //add file
- song = songvec_find(&directory->songs, name);
- if (song == NULL) {
- song = song_file_load(name, directory);
- if (song != NULL) {
- songvec_add(&directory->songs, song);
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- }
- }
- }
-}
-
-/**
- * Updates the file listing from an archive file.
- *
- * @param parent the parent directory the archive file resides in
- * @param name the UTF-8 encoded base name of the archive file
- * @param st stat() information on the archive file
- * @param plugin the archive plugin which fits this archive type
- */
-static void
-update_archive_file(struct directory *parent, const char *name,
- const struct stat *st,
- const struct archive_plugin *plugin)
-{
- char *path_fs;
- struct archive_file *file;
- struct directory *directory;
- char *filepath;
-
- directory = dirvec_find(&parent->children, name);
- if (directory != NULL && directory->mtime == st->st_mtime)
- /* MPD has already scanned the archive, and it hasn't
- changed since - don't consider updating it */
- return;
-
- path_fs = map_directory_child_fs(parent, name);
-
- /* open archive */
- file = plugin->open(path_fs);
- if (file == NULL) {
- g_warning("unable to open archive %s", path_fs);
- g_free(path_fs);
- return;
- }
-
- g_debug("archive %s opened", path_fs);
- g_free(path_fs);
-
- if (directory == NULL) {
- g_debug("creating archive directory: %s", name);
- directory = make_subdir(parent, name);
- /* mark this directory as archive (we use device for
- this) */
- directory->device = DEVICE_INARCHIVE;
- }
-
- directory->mtime = st->st_mtime;
-
- plugin->scan_reset(file);
-
- while ((filepath = plugin->scan_next(file)) != NULL) {
- /* split name into directory and file */
- g_debug("adding archive file: %s", filepath);
- update_archive_tree(directory, filepath);
- }
-
- plugin->close(file);
-}
-#endif
-
-static bool
-update_container_file( struct directory* directory,
- const char* name,
- const struct stat* st,
- const struct decoder_plugin* plugin)
-{
- char* vtrack = NULL;
- unsigned int tnum = 0;
- char* pathname = map_directory_child_fs(directory, name);
- struct directory* contdir = dirvec_find(&directory->children, name);
-
- // directory exists already
- if (contdir != NULL)
- {
- // modification time not eq. file mod. time
- if (contdir->mtime != st->st_mtime)
- {
- g_message("removing container file: %s", pathname);
-
- delete_directory(contdir);
- contdir = NULL;
-
- modified = true;
- }
- else {
- g_free(pathname);
- return true;
- }
- }
-
- contdir = make_subdir(directory, name);
- contdir->mtime = st->st_mtime;
- contdir->device = DEVICE_CONTAINER;
-
- while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
- {
- struct song* song = song_file_new(vtrack, contdir);
- char *child_path_fs;
-
- // shouldn't be necessary but it's there..
- song->mtime = st->st_mtime;
-
- child_path_fs = map_directory_child_fs(contdir, vtrack);
- g_free(vtrack);
-
- song->tag = plugin->tag_dup(child_path_fs);
- g_free(child_path_fs);
-
- songvec_add(&contdir->songs, song);
-
- modified = true;
- }
-
- g_free(pathname);
-
- if (tnum == 1)
- {
- delete_directory(contdir);
- return false;
- }
- else
- return true;
-}
-
-static void
-update_regular_file(struct directory *directory,
- const char *name, const struct stat *st)
-{
- const char *suffix = uri_get_suffix(name);
- const struct decoder_plugin* plugin;
-#ifdef ENABLE_ARCHIVE
- const struct archive_plugin *archive;
-#endif
- if (suffix == NULL)
- return;
-
- if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
- {
- struct song* song = songvec_find(&directory->songs, name);
-
- if (!(song != NULL && st->st_mtime == song->mtime) &&
- plugin->container_scan != NULL)
- {
- if (update_container_file(directory, name, st, plugin))
- {
- if (song != NULL)
- delete_song(directory, song);
-
- return;
- }
- }
-
- if (song == NULL) {
- song = song_file_load(name, directory);
- if (song == NULL)
- return;
-
- songvec_add(&directory->songs, song);
- modified = true;
- g_message("added %s/%s",
- directory_get_path(directory), name);
- } else if (st->st_mtime != song->mtime) {
- g_message("updating %s/%s",
- directory_get_path(directory), name);
- if (!song_file_update(song))
- delete_song(directory, song);
- modified = true;
- }
-#ifdef ENABLE_ARCHIVE
- } else if ((archive = archive_plugin_from_suffix(suffix))) {
- update_archive_file(directory, name, st, archive);
-#endif
- }
-}
-
-static bool
-updateDirectory(struct directory *directory, const struct stat *st);
-
-static void
-updateInDirectory(struct directory *directory,
- const char *name, const struct stat *st)
-{
- assert(strchr(name, '/') == NULL);
-
- if (S_ISREG(st->st_mode)) {
- update_regular_file(directory, name, st);
- } else if (S_ISDIR(st->st_mode)) {
- struct directory *subdir;
- bool ret;
-
- if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
- return;
-
- subdir = make_subdir(directory, name);
- assert(directory == subdir->parent);
-
- ret = updateDirectory(subdir, st);
- if (!ret)
- delete_directory(subdir);
- } else {
- g_debug("update: %s is not a directory, archive or music", name);
- }
-}
-
-/* we don't look at "." / ".." nor files with newlines in their name */
-static bool skip_path(const char *path)
-{
- return (path[0] == '.' && path[1] == 0) ||
- (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
- strchr(path, '\n') != NULL;
-}
-
-static bool
-skip_symlink(const struct directory *directory, const char *utf8_name)
-{
-#ifndef WIN32
- char buffer[MPD_PATH_MAX];
- char *path_fs;
- const char *p;
- ssize_t ret;
-
- path_fs = map_directory_child_fs(directory, utf8_name);
- if (path_fs == NULL)
- return true;
-
- ret = readlink(path_fs, buffer, sizeof(buffer));
- g_free(path_fs);
- if (ret < 0)
- /* don't skip if this is not a symlink */
- return errno != EINVAL;
-
- if (!follow_inside_symlinks && !follow_outside_symlinks) {
- /* ignore all symlinks */
- return true;
- } else if (follow_inside_symlinks && follow_outside_symlinks) {
- /* consider all symlinks */
- return false;
- }
-
- if (buffer[0] == '/')
- return !follow_outside_symlinks;
-
- p = buffer;
- while (*p == '.') {
- if (p[1] == '.' && p[2] == '/') {
- /* "../" moves to parent directory */
- directory = directory->parent;
- if (directory == NULL) {
- /* we have moved outside the music
- directory - skip this symlink
- if such symlinks are not allowed */
- return !follow_outside_symlinks;
- }
- p += 3;
- } else if (p[1] == '/')
- /* eliminate "./" */
- p += 2;
- else
- break;
- }
-
- /* we are still in the music directory, so this symlink points
- to a song which is already in the database - skip according
- to the follow_inside_symlinks param*/
- return !follow_inside_symlinks;
-#else
- /* no symlink checking on WIN32 */
-
- (void)directory;
- (void)utf8_name;
-
- return false;
-#endif
-}
-
-static bool
-updateDirectory(struct directory *directory, const struct stat *st)
-{
- DIR *dir;
- struct dirent *ent;
- char *path_fs;
-
- assert(S_ISDIR(st->st_mode));
-
- directory_set_stat(directory, st);
-
- path_fs = map_directory_fs(directory);
- if (path_fs == NULL)
- return false;
-
- dir = opendir(path_fs);
- if (!dir) {
- g_warning("Failed to open directory %s: %s",
- path_fs, g_strerror(errno));
- g_free(path_fs);
- return false;
- }
-
- g_free(path_fs);
-
- removeDeletedFromDirectory(directory);
-
- while ((ent = readdir(dir))) {
- char *utf8;
- struct stat st2;
-
- if (skip_path(ent->d_name))
- continue;
-
- utf8 = fs_charset_to_utf8(ent->d_name);
- if (utf8 == NULL)
- continue;
-
- if (skip_symlink(directory, utf8)) {
- delete_name_in(directory, utf8);
- g_free(utf8);
- continue;
- }
-
- if (stat_directory_child(directory, utf8, &st2) == 0)
- updateInDirectory(directory, utf8, &st2);
- else
- delete_name_in(directory, utf8);
-
- g_free(utf8);
- }
-
- closedir(dir);
-
- directory->mtime = st->st_mtime;
-
- return true;
-}
-
-static struct directory *
-directory_make_child_checked(struct directory *parent, const char *path)
-{
- struct directory *directory;
- char *base;
- struct stat st;
- struct song *conflicting;
-
- directory = directory_get_child(parent, path);
- if (directory != NULL)
- return directory;
-
- base = g_path_get_basename(path);
-
- if (stat_directory_child(parent, base, &st) < 0 ||
- inodeFoundInParent(parent, st.st_ino, st.st_dev)) {
- g_free(base);
- return NULL;
- }
-
- /* if we're adding directory paths, make sure to delete filenames
- with potentially the same name */
- conflicting = songvec_find(&parent->songs, base);
- if (conflicting)
- delete_song(parent, conflicting);
-
- g_free(base);
-
- directory = directory_new_child(parent, path);
- directory_set_stat(directory, &st);
- return directory;
-}
-
-static struct directory *
-addParentPathToDB(const char *utf8path)
-{
- struct directory *directory = db_get_root();
- char *duplicated = g_strdup(utf8path);
- char *slash = duplicated;
-
- while ((slash = strchr(slash, '/')) != NULL) {
- *slash = 0;
-
- directory = directory_make_child_checked(directory,
- duplicated);
- if (directory == NULL || slash == NULL)
- break;
-
- *slash++ = '/';
- }
-
- g_free(duplicated);
- return directory;
-}
-
-static void
-updatePath(const char *path)
-{
- struct directory *parent;
- char *name;
- struct stat st;
-
- parent = addParentPathToDB(path);
- if (parent == NULL)
- return;
-
- name = g_path_get_basename(path);
-
- if (stat_directory_child(parent, name, &st) == 0)
- updateInDirectory(parent, name, &st);
- else
- delete_name_in(parent, name);
-
- g_free(name);
-}
-
static void * update_task(void *_path)
{
- if (_path != NULL && !isRootDirectory(_path)) {
- updatePath((char *)_path);
- } else {
- struct directory *directory = db_get_root();
- struct stat st;
-
- if (stat_directory(directory, &st) == 0)
- updateDirectory(directory, &st);
- }
+ const char *path = _path;
+ modified = update_walk(path, discard);
g_free(_path);
if (modified || !db_exists())
@@ -794,7 +71,8 @@ static void * update_task(void *_path)
return NULL;
}
-static void spawn_update_task(char *path)
+static void
+spawn_update_task(const char *path)
{
GError *e = NULL;
@@ -802,15 +80,18 @@ static void spawn_update_task(char *path)
progress = UPDATE_PROGRESS_RUNNING;
modified = false;
- if (!(update_thr = g_thread_create(update_task, path, TRUE, &e)))
+
+ update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
+ if (update_thr == NULL)
g_error("Failed to spawn update task: %s", e->message);
+
if (++update_task_id > update_task_id_max)
update_task_id = 1;
g_debug("spawned thread for update job id %i", update_task_id);
}
unsigned
-directory_update_init(char *path)
+update_enqueue(const char *path, bool _discard)
{
assert(g_thread_self() == main_task);
@@ -818,48 +99,20 @@ directory_update_init(char *path)
return 0;
if (progress != UPDATE_PROGRESS_IDLE) {
- unsigned next_task_id;
-
- if (update_paths_nr == G_N_ELEMENTS(update_paths)) {
- g_free(path);
+ unsigned next_task_id =
+ update_queue_push(path, discard, update_task_id);
+ if (next_task_id == 0)
return 0;
- }
-
- assert(update_paths_nr < G_N_ELEMENTS(update_paths));
- update_paths[update_paths_nr++] = path;
- next_task_id = update_task_id + update_paths_nr;
return next_task_id > update_task_id_max ? 1 : next_task_id;
}
- spawn_update_task(path);
- return update_task_id;
-}
-
-/**
- * Safely delete a song from the database. This must be done in the
- * main task, to be sure that there is no pointer left to it.
- */
-static void song_delete_event(void)
-{
- char *uri;
-
- assert(progress == UPDATE_PROGRESS_RUNNING);
- assert(delete != NULL);
- uri = song_get_uri(delete);
- g_debug("removing: %s", uri);
- g_free(uri);
-
-#ifdef ENABLE_SQLITE
- /* if the song has a sticker, delete it */
- if (sticker_enabled())
- sticker_song_delete(delete);
-#endif
+ discard = _discard;
+ spawn_update_task(path);
- deleteASongFromPlaylist(&g_playlist, delete);
- delete = NULL;
+ idle_add(IDLE_UPDATE);
- notify_signal(&update_notify);
+ return update_task_id;
}
/**
@@ -867,22 +120,25 @@ static void song_delete_event(void)
*/
static void update_finished_event(void)
{
+ char *path;
+
assert(progress == UPDATE_PROGRESS_DONE);
g_thread_join(update_thr);
+ idle_add(IDLE_UPDATE);
+
if (modified) {
/* send "idle" events */
- playlistVersionChange(&g_playlist);
+ playlist_increment_version_all(&g_playlist);
idle_add(IDLE_DATABASE);
}
- if (update_paths_nr) {
+ path = update_queue_shift(&discard);
+ if (path != NULL) {
/* schedule the next path */
- char *path = update_paths[0];
- memmove(&update_paths[0], &update_paths[1],
- --update_paths_nr * sizeof(char *));
spawn_update_task(path);
+ g_free(path);
} else {
progress = UPDATE_PROGRESS_IDLE;
@@ -892,23 +148,14 @@ static void update_finished_event(void)
void update_global_init(void)
{
-#ifndef WIN32
- follow_inside_symlinks =
- config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
- DEFAULT_FOLLOW_INSIDE_SYMLINKS);
-
- follow_outside_symlinks =
- config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
- DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
-#endif
-
- notify_init(&update_notify);
-
- event_pipe_register(PIPE_EVENT_DELETE, song_delete_event);
event_pipe_register(PIPE_EVENT_UPDATE, update_finished_event);
+
+ update_remove_global_init();
+ update_walk_global_init();
}
void update_global_finish(void)
{
- notify_deinit(&update_notify);
+ update_walk_global_finish();
+ update_remove_global_finish();
}
diff --git a/src/update.h b/src/update.h
index 3b7a5a332..15ff4bad1 100644
--- a/src/update.h
+++ b/src/update.h
@@ -20,6 +20,8 @@
#ifndef MPD_UPDATE_H
#define MPD_UPDATE_H
+#include <stdbool.h>
+
void update_global_init(void);
void update_global_finish(void);
@@ -27,12 +29,14 @@ void update_global_finish(void);
unsigned
isUpdatingDB(void);
-/*
- * returns the positive update job ID on success,
- * returns 0 if busy
- * @path will be freed by this function and should not be reused
+/**
+ * Add this path to the database update queue.
+ *
+ * @param path a path to update; if NULL or an empty string,
+ * the whole music directory is updated
+ * @return the job id, or 0 on error
*/
unsigned
-directory_update_init(char *path);
+update_enqueue(const char *path, bool discard);
#endif
diff --git a/src/update_internal.h b/src/update_internal.h
new file mode 100644
index 000000000..0655c727d
--- /dev/null
+++ b/src/update_internal.h
@@ -0,0 +1,64 @@
+/*
+ * 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_UPDATE_INTERNAL_H
+#define MPD_UPDATE_INTERNAL_H
+
+#include <stdbool.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "update"
+
+struct stat;
+struct song;
+struct directory;
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base);
+
+char *
+update_queue_shift(bool *discard_r);
+
+void
+update_walk_global_init(void);
+
+void
+update_walk_global_finish(void);
+
+/**
+ * Returns true if the database was modified.
+ */
+bool
+update_walk(const char *path, bool discard);
+
+void
+update_remove_global_init(void);
+
+void
+update_remove_global_finish(void);
+
+/**
+ * Sends a signal to the main thread which will in turn remove the
+ * song: from the sticker database and from the playlist. This
+ * serialized access is implemented to avoid excessive locking.
+ */
+void
+update_remove_song(const struct song *song);
+
+#endif
diff --git a/src/update_queue.c b/src/update_queue.c
new file mode 100644
index 000000000..be48f3043
--- /dev/null
+++ b/src/update_queue.c
@@ -0,0 +1,66 @@
+/*
+ * 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 "update_internal.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+/* make this dynamic?, or maybe this is big enough... */
+static struct {
+ char *path;
+ bool discard;
+} update_queue[32];
+
+static size_t update_queue_length;
+
+unsigned
+update_queue_push(const char *path, bool discard, unsigned base)
+{
+ assert(update_queue_length <= G_N_ELEMENTS(update_queue));
+
+ if (update_queue_length == G_N_ELEMENTS(update_queue))
+ return 0;
+
+ update_queue[update_queue_length].path = g_strdup(path);
+ update_queue[update_queue_length].discard = discard;
+
+ ++update_queue_length;
+
+ return base + update_queue_length;
+}
+
+char *
+update_queue_shift(bool *discard_r)
+{
+ char *path;
+
+ if (update_queue_length == 0)
+ return NULL;
+
+ path = update_queue[0].path;
+ *discard_r = update_queue[0].discard;
+
+ memmove(&update_queue[0], &update_queue[1],
+ --update_queue_length * sizeof(update_queue[0]));
+ return path;
+}
diff --git a/src/update_remove.c b/src/update_remove.c
new file mode 100644
index 000000000..a60f0cb98
--- /dev/null
+++ b/src/update_remove.c
@@ -0,0 +1,94 @@
+/*
+ * 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 "update_internal.h"
+#include "notify.h"
+#include "event_pipe.h"
+#include "song.h"
+#include "playlist.h"
+
+#ifdef ENABLE_SQLITE
+#include "sticker.h"
+#include "song_sticker.h"
+#endif
+
+#include <glib.h>
+
+#include <assert.h>
+
+static const struct song *removed_song;
+
+static struct notify remove_notify;
+
+/**
+ * Safely remove a song from the database. This must be done in the
+ * main task, to be sure that there is no pointer left to it.
+ */
+static void
+song_remove_event(void)
+{
+ char *uri;
+
+ assert(removed_song != NULL);
+
+ uri = song_get_uri(removed_song);
+ g_debug("removing: %s", uri);
+ g_free(uri);
+
+#ifdef ENABLE_SQLITE
+ /* if the song has a sticker, remove it */
+ if (sticker_enabled())
+ sticker_song_delete(removed_song);
+#endif
+
+ playlist_delete_song(&g_playlist, removed_song);
+ removed_song = NULL;
+
+ notify_signal(&remove_notify);
+}
+
+void
+update_remove_global_init(void)
+{
+ notify_init(&remove_notify);
+
+ event_pipe_register(PIPE_EVENT_DELETE, song_remove_event);
+}
+
+void
+update_remove_global_finish(void)
+{
+ notify_deinit(&remove_notify);
+}
+
+void
+update_remove_song(const struct song *song)
+{
+ assert(removed_song == NULL);
+
+ removed_song = song;
+
+ event_pipe_emit(PIPE_EVENT_DELETE);
+
+ do {
+ notify_wait(&remove_notify);
+ } while (removed_song != NULL);
+
+}
diff --git a/src/update_walk.c b/src/update_walk.c
new file mode 100644
index 000000000..31b60bd67
--- /dev/null
+++ b/src/update_walk.c
@@ -0,0 +1,833 @@
+/*
+ * 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 "update_internal.h"
+#include "database.h"
+#include "exclude.h"
+#include "directory.h"
+#include "song.h"
+#include "uri.h"
+#include "mapper.h"
+#include "path.h"
+#include "decoder_list.h"
+#include "decoder_plugin.h"
+#include "conf.h"
+
+#ifdef ENABLE_ARCHIVE
+#include "archive_list.h"
+#include "archive_plugin.h"
+#endif
+
+#include <glib.h>
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+static bool walk_discard;
+static bool modified;
+
+#ifndef WIN32
+
+enum {
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
+};
+
+static bool follow_inside_symlinks;
+static bool follow_outside_symlinks;
+
+#endif
+
+void
+update_walk_global_init(void)
+{
+#ifndef WIN32
+ follow_inside_symlinks =
+ config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_INSIDE_SYMLINKS);
+
+ follow_outside_symlinks =
+ config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
+ DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
+#endif
+}
+
+void
+update_walk_global_finish(void)
+{
+}
+
+static void
+directory_set_stat(struct directory *dir, const struct stat *st)
+{
+ dir->inode = st->st_ino;
+ dir->device = st->st_dev;
+ dir->stat = 1;
+}
+
+static void
+delete_song(struct directory *dir, struct song *del)
+{
+ /* first, prevent traversers in main task from getting this */
+ songvec_delete(&dir->songs, del);
+
+ /* now take it out of the playlist (in the main_task) */
+ update_remove_song(del);
+
+ /* finally, all possible references gone, free it */
+ song_free(del);
+}
+
+static int
+delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
+{
+ struct directory *directory = data;
+ assert(song->parent == directory);
+ delete_song(directory, song);
+ return 0;
+}
+
+static void
+delete_directory(struct directory *directory);
+
+/**
+ * Recursively remove all sub directories and songs from a directory,
+ * leaving an empty directory.
+ */
+static void
+clear_directory(struct directory *directory)
+{
+ int i;
+
+ for (i = directory->children.nr; --i >= 0;)
+ delete_directory(directory->children.base[i]);
+
+ assert(directory->children.nr == 0);
+
+ songvec_for_each(&directory->songs, delete_each_song, directory);
+}
+
+/**
+ * Recursively free a directory and all its contents.
+ */
+static void
+delete_directory(struct directory *directory)
+{
+ assert(directory->parent != NULL);
+
+ clear_directory(directory);
+
+ dirvec_delete(&directory->parent->children, directory);
+ directory_free(directory);
+}
+
+static void
+delete_name_in(struct directory *parent, const char *name)
+{
+ struct directory *directory = directory_get_child(parent, name);
+ struct song *song = songvec_find(&parent->songs, name);
+
+ if (directory != NULL) {
+ delete_directory(directory);
+ modified = true;
+ }
+
+ if (song != NULL) {
+ delete_song(parent, song);
+ modified = true;
+ }
+}
+
+/* passed to songvec_for_each */
+static int
+delete_song_if_excluded(struct song *song, void *_data)
+{
+ GSList *exclude_list = _data;
+ char *name_fs;
+
+ assert(song->parent != NULL);
+
+ name_fs = utf8_to_fs_charset(song->uri);
+ if (exclude_list_check(exclude_list, name_fs)) {
+ delete_song(song->parent, song);
+ modified = true;
+ }
+
+ g_free(name_fs);
+ return 0;
+}
+
+static void
+remove_excluded_from_directory(struct directory *directory,
+ GSList *exclude_list)
+{
+ int i;
+ struct dirvec *dv = &directory->children;
+
+ for (i = dv->nr; --i >= 0; ) {
+ struct directory *child = dv->base[i];
+ char *name_fs = utf8_to_fs_charset(directory_get_name(child));
+
+ if (exclude_list_check(exclude_list, name_fs)) {
+ delete_directory(child);
+ modified = true;
+ }
+
+ g_free(name_fs);
+ }
+
+ songvec_for_each(&directory->songs,
+ delete_song_if_excluded, exclude_list);
+}
+
+/* passed to songvec_for_each */
+static int
+delete_song_if_removed(struct song *song, void *_data)
+{
+ struct directory *dir = _data;
+ char *path;
+ struct stat st;
+
+ if ((path = map_song_fs(song)) == NULL ||
+ stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
+ delete_song(dir, song);
+ modified = true;
+ }
+
+ g_free(path);
+ return 0;
+}
+
+static bool
+directory_exists(const struct directory *directory)
+{
+ char *path_fs;
+ GFileTest test;
+ bool exists;
+
+ path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ /* invalid path: cannot exist */
+ return false;
+
+ test = directory->device == DEVICE_INARCHIVE ||
+ directory->device == DEVICE_CONTAINER
+ ? G_FILE_TEST_IS_REGULAR
+ : G_FILE_TEST_IS_DIR;
+
+ exists = g_file_test(path_fs, test);
+ g_free(path_fs);
+
+ return exists;
+}
+
+static void
+removeDeletedFromDirectory(struct directory *directory)
+{
+ int i;
+ struct dirvec *dv = &directory->children;
+
+ for (i = dv->nr; --i >= 0; ) {
+ if (directory_exists(dv->base[i]))
+ continue;
+
+ g_debug("removing directory: %s", dv->base[i]->path);
+ delete_directory(dv->base[i]);
+ modified = true;
+ }
+
+ songvec_for_each(&directory->songs, delete_song_if_removed, directory);
+}
+
+static int
+stat_directory(const struct directory *directory, struct stat *st)
+{
+ char *path_fs;
+ int ret;
+
+ path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ return -1;
+ ret = stat(path_fs, st);
+ g_free(path_fs);
+ return ret;
+}
+
+static int
+stat_directory_child(const struct directory *parent, const char *name,
+ struct stat *st)
+{
+ char *path_fs;
+ int ret;
+
+ path_fs = map_directory_child_fs(parent, name);
+ if (path_fs == NULL)
+ return -1;
+
+ ret = stat(path_fs, st);
+ g_free(path_fs);
+ return ret;
+}
+
+static int
+statDirectory(struct directory *dir)
+{
+ struct stat st;
+
+ if (stat_directory(dir, &st) < 0)
+ return -1;
+
+ directory_set_stat(dir, &st);
+
+ return 0;
+}
+
+static int
+inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
+{
+ while (parent) {
+ if (!parent->stat && statDirectory(parent) < 0)
+ return -1;
+ if (parent->inode == inode && parent->device == device) {
+ g_debug("recursive directory found");
+ return 1;
+ }
+ parent = parent->parent;
+ }
+
+ return 0;
+}
+
+static struct directory *
+make_subdir(struct directory *parent, const char *name)
+{
+ struct directory *directory;
+
+ directory = directory_get_child(parent, name);
+ if (directory == NULL) {
+ char *path;
+
+ if (directory_is_root(parent))
+ path = NULL;
+ else
+ name = path = g_strconcat(directory_get_path(parent),
+ "/", name, NULL);
+
+ directory = directory_new_child(parent, name);
+ g_free(path);
+ }
+
+ return directory;
+}
+
+#ifdef ENABLE_ARCHIVE
+static void
+update_archive_tree(struct directory *directory, char *name)
+{
+ struct directory *subdir;
+ struct song *song;
+ char *tmp;
+
+ tmp = strchr(name, '/');
+ if (tmp) {
+ *tmp = 0;
+ //add dir is not there already
+ if ((subdir = dirvec_find(&directory->children, name)) == NULL) {
+ //create new directory
+ subdir = make_subdir(directory, name);
+ subdir->device = DEVICE_INARCHIVE;
+ }
+ //create directories first
+ update_archive_tree(subdir, tmp+1);
+ } else {
+ if (strlen(name) == 0) {
+ g_warning("archive returned directory only");
+ return;
+ }
+ //add file
+ song = songvec_find(&directory->songs, name);
+ if (song == NULL) {
+ song = song_file_load(name, directory);
+ if (song != NULL) {
+ songvec_add(&directory->songs, song);
+ modified = true;
+ g_message("added %s/%s",
+ directory_get_path(directory), name);
+ }
+ }
+ }
+}
+
+/**
+ * Updates the file listing from an archive file.
+ *
+ * @param parent the parent directory the archive file resides in
+ * @param name the UTF-8 encoded base name of the archive file
+ * @param st stat() information on the archive file
+ * @param plugin the archive plugin which fits this archive type
+ */
+static void
+update_archive_file(struct directory *parent, const char *name,
+ const struct stat *st,
+ const struct archive_plugin *plugin)
+{
+ char *path_fs;
+ struct archive_file *file;
+ struct directory *directory;
+ char *filepath;
+
+ directory = dirvec_find(&parent->children, name);
+ if (directory != NULL && directory->mtime == st->st_mtime &&
+ !walk_discard)
+ /* MPD has already scanned the archive, and it hasn't
+ changed since - don't consider updating it */
+ return;
+
+ path_fs = map_directory_child_fs(parent, name);
+
+ /* open archive */
+ file = plugin->open(path_fs);
+ if (file == NULL) {
+ g_warning("unable to open archive %s", path_fs);
+ g_free(path_fs);
+ return;
+ }
+
+ g_debug("archive %s opened", path_fs);
+ g_free(path_fs);
+
+ if (directory == NULL) {
+ g_debug("creating archive directory: %s", name);
+ directory = make_subdir(parent, name);
+ /* mark this directory as archive (we use device for
+ this) */
+ directory->device = DEVICE_INARCHIVE;
+ }
+
+ directory->mtime = st->st_mtime;
+
+ plugin->scan_reset(file);
+
+ while ((filepath = plugin->scan_next(file)) != NULL) {
+ /* split name into directory and file */
+ g_debug("adding archive file: %s", filepath);
+ update_archive_tree(directory, filepath);
+ }
+
+ plugin->close(file);
+}
+#endif
+
+static bool
+update_container_file( struct directory* directory,
+ const char* name,
+ const struct stat* st,
+ const struct decoder_plugin* plugin)
+{
+ char* vtrack = NULL;
+ unsigned int tnum = 0;
+ char* pathname = map_directory_child_fs(directory, name);
+ struct directory* contdir = dirvec_find(&directory->children, name);
+
+ // directory exists already
+ if (contdir != NULL)
+ {
+ // modification time not eq. file mod. time
+ if (contdir->mtime != st->st_mtime || walk_discard)
+ {
+ g_message("removing container file: %s", pathname);
+
+ delete_directory(contdir);
+ contdir = NULL;
+
+ modified = true;
+ }
+ else {
+ g_free(pathname);
+ return true;
+ }
+ }
+
+ contdir = make_subdir(directory, name);
+ contdir->mtime = st->st_mtime;
+ contdir->device = DEVICE_CONTAINER;
+
+ while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
+ {
+ struct song* song = song_file_new(vtrack, contdir);
+ char *child_path_fs;
+
+ // shouldn't be necessary but it's there..
+ song->mtime = st->st_mtime;
+
+ child_path_fs = map_directory_child_fs(contdir, vtrack);
+
+ song->tag = plugin->tag_dup(child_path_fs);
+ g_free(child_path_fs);
+
+ songvec_add(&contdir->songs, song);
+
+ modified = true;
+
+ g_message("added %s/%s",
+ directory_get_path(directory), vtrack);
+ g_free(vtrack);
+ }
+
+ g_free(pathname);
+
+ if (tnum == 1)
+ {
+ delete_directory(contdir);
+ return false;
+ }
+ else
+ return true;
+}
+
+static void
+update_regular_file(struct directory *directory,
+ const char *name, const struct stat *st)
+{
+ const char *suffix = uri_get_suffix(name);
+ const struct decoder_plugin* plugin;
+#ifdef ENABLE_ARCHIVE
+ const struct archive_plugin *archive;
+#endif
+ if (suffix == NULL)
+ return;
+
+ if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
+ {
+ struct song* song = songvec_find(&directory->songs, name);
+
+ if (!(song != NULL && st->st_mtime == song->mtime &&
+ !walk_discard) &&
+ plugin->container_scan != NULL)
+ {
+ if (update_container_file(directory, name, st, plugin))
+ {
+ if (song != NULL)
+ delete_song(directory, song);
+
+ return;
+ }
+ }
+
+ if (song == NULL) {
+ song = song_file_load(name, directory);
+ if (song == NULL) {
+ g_debug("ignoring unrecognized file %s/%s",
+ directory_get_path(directory), name);
+ return;
+ }
+
+ songvec_add(&directory->songs, song);
+ modified = true;
+ g_message("added %s/%s",
+ directory_get_path(directory), name);
+ } else if (st->st_mtime != song->mtime || walk_discard) {
+ g_message("updating %s/%s",
+ directory_get_path(directory), name);
+ if (!song_file_update(song)) {
+ g_debug("deleting unrecognized file %s/%s",
+ directory_get_path(directory), name);
+ delete_song(directory, song);
+ }
+
+ modified = true;
+ }
+#ifdef ENABLE_ARCHIVE
+ } else if ((archive = archive_plugin_from_suffix(suffix))) {
+ update_archive_file(directory, name, st, archive);
+#endif
+ }
+}
+
+static bool
+updateDirectory(struct directory *directory, const struct stat *st);
+
+static void
+updateInDirectory(struct directory *directory,
+ const char *name, const struct stat *st)
+{
+ assert(strchr(name, '/') == NULL);
+
+ if (S_ISREG(st->st_mode)) {
+ update_regular_file(directory, name, st);
+ } else if (S_ISDIR(st->st_mode)) {
+ struct directory *subdir;
+ bool ret;
+
+ if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
+ return;
+
+ subdir = make_subdir(directory, name);
+ assert(directory == subdir->parent);
+
+ ret = updateDirectory(subdir, st);
+ if (!ret)
+ delete_directory(subdir);
+ } else {
+ g_debug("update: %s is not a directory, archive or music", name);
+ }
+}
+
+/* we don't look at "." / ".." nor files with newlines in their name */
+static bool skip_path(const char *path)
+{
+ return (path[0] == '.' && path[1] == 0) ||
+ (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
+ strchr(path, '\n') != NULL;
+}
+
+static bool
+skip_symlink(const struct directory *directory, const char *utf8_name)
+{
+#ifndef WIN32
+ char buffer[MPD_PATH_MAX];
+ char *path_fs;
+ const char *p;
+ ssize_t ret;
+
+ path_fs = map_directory_child_fs(directory, utf8_name);
+ if (path_fs == NULL)
+ return true;
+
+ ret = readlink(path_fs, buffer, sizeof(buffer));
+ g_free(path_fs);
+ if (ret < 0)
+ /* don't skip if this is not a symlink */
+ return errno != EINVAL;
+
+ if (!follow_inside_symlinks && !follow_outside_symlinks) {
+ /* ignore all symlinks */
+ return true;
+ } else if (follow_inside_symlinks && follow_outside_symlinks) {
+ /* consider all symlinks */
+ return false;
+ }
+
+ if (buffer[0] == '/')
+ return !follow_outside_symlinks;
+
+ p = buffer;
+ while (*p == '.') {
+ if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) {
+ /* "../" moves to parent directory */
+ directory = directory->parent;
+ if (directory == NULL) {
+ /* we have moved outside the music
+ directory - skip this symlink
+ if such symlinks are not allowed */
+ return !follow_outside_symlinks;
+ }
+ p += 3;
+ } else if (G_IS_DIR_SEPARATOR(p[1]))
+ /* eliminate "./" */
+ p += 2;
+ else
+ break;
+ }
+
+ /* we are still in the music directory, so this symlink points
+ to a song which is already in the database - skip according
+ to the follow_inside_symlinks param*/
+ return !follow_inside_symlinks;
+#else
+ /* no symlink checking on WIN32 */
+
+ (void)directory;
+ (void)utf8_name;
+
+ return false;
+#endif
+}
+
+static bool
+updateDirectory(struct directory *directory, const struct stat *st)
+{
+ DIR *dir;
+ struct dirent *ent;
+ char *path_fs, *exclude_path_fs;
+ GSList *exclude_list;
+
+ assert(S_ISDIR(st->st_mode));
+
+ directory_set_stat(directory, st);
+
+ path_fs = map_directory_fs(directory);
+ if (path_fs == NULL)
+ return false;
+
+ dir = opendir(path_fs);
+ if (!dir) {
+ g_warning("Failed to open directory %s: %s",
+ path_fs, g_strerror(errno));
+ g_free(path_fs);
+ return false;
+ }
+
+ exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL);
+ exclude_list = exclude_list_load(exclude_path_fs);
+ g_free(exclude_path_fs);
+
+ g_free(path_fs);
+
+ if (exclude_list != NULL)
+ remove_excluded_from_directory(directory, exclude_list);
+
+ removeDeletedFromDirectory(directory);
+
+ while ((ent = readdir(dir))) {
+ char *utf8;
+ struct stat st2;
+
+ if (skip_path(ent->d_name) ||
+ exclude_list_check(exclude_list, ent->d_name))
+ continue;
+
+ utf8 = fs_charset_to_utf8(ent->d_name);
+ if (utf8 == NULL)
+ continue;
+
+ if (skip_symlink(directory, utf8)) {
+ delete_name_in(directory, utf8);
+ g_free(utf8);
+ continue;
+ }
+
+ if (stat_directory_child(directory, utf8, &st2) == 0)
+ updateInDirectory(directory, utf8, &st2);
+ else
+ delete_name_in(directory, utf8);
+
+ g_free(utf8);
+ }
+
+ exclude_list_free(exclude_list);
+
+ closedir(dir);
+
+ directory->mtime = st->st_mtime;
+
+ return true;
+}
+
+static struct directory *
+directory_make_child_checked(struct directory *parent, const char *path)
+{
+ struct directory *directory;
+ char *base;
+ struct stat st;
+ struct song *conflicting;
+
+ directory = directory_get_child(parent, path);
+ if (directory != NULL)
+ return directory;
+
+ base = g_path_get_basename(path);
+
+ if (stat_directory_child(parent, base, &st) < 0 ||
+ inodeFoundInParent(parent, st.st_ino, st.st_dev)) {
+ g_free(base);
+ return NULL;
+ }
+
+ /* if we're adding directory paths, make sure to delete filenames
+ with potentially the same name */
+ conflicting = songvec_find(&parent->songs, base);
+ if (conflicting)
+ delete_song(parent, conflicting);
+
+ g_free(base);
+
+ directory = directory_new_child(parent, path);
+ directory_set_stat(directory, &st);
+ return directory;
+}
+
+static struct directory *
+addParentPathToDB(const char *utf8path)
+{
+ struct directory *directory = db_get_root();
+ char *duplicated = g_strdup(utf8path);
+ char *slash = duplicated;
+
+ while ((slash = strchr(slash, '/')) != NULL) {
+ *slash = 0;
+
+ directory = directory_make_child_checked(directory,
+ duplicated);
+ if (directory == NULL || slash == NULL)
+ break;
+
+ *slash++ = '/';
+ }
+
+ g_free(duplicated);
+ return directory;
+}
+
+static void
+updatePath(const char *path)
+{
+ struct directory *parent;
+ char *name;
+ struct stat st;
+
+ parent = addParentPathToDB(path);
+ if (parent == NULL)
+ return;
+
+ name = g_path_get_basename(path);
+
+ if (stat_directory_child(parent, name, &st) == 0)
+ updateInDirectory(parent, name, &st);
+ else
+ delete_name_in(parent, name);
+
+ g_free(name);
+}
+
+bool
+update_walk(const char *path, bool discard)
+{
+ walk_discard = discard;
+ modified = false;
+
+ if (path != NULL && !isRootDirectory(path)) {
+ updatePath(path);
+ } else {
+ struct directory *directory = db_get_root();
+ struct stat st;
+
+ if (stat_directory(directory, &st) == 0)
+ updateDirectory(directory, &st);
+ }
+
+ return modified;
+}
diff --git a/src/uri.c b/src/uri.c
index fb3f708b2..5c33d3bae 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "uri.h"
#include <glib.h>
diff --git a/src/uri.h b/src/uri.h
index 1a12cc557..46d36308b 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -20,14 +20,18 @@
#ifndef MPD_URI_H
#define MPD_URI_H
+#include <glib.h>
+
#include <stdbool.h>
/**
* Checks whether the specified URI has a schema in the form
* "scheme://".
*/
+G_GNUC_PURE
bool uri_has_scheme(const char *uri);
+G_GNUC_PURE
const char *
uri_get_suffix(const char *uri);
@@ -37,6 +41,7 @@ uri_get_suffix(const char *uri);
* NULL if nothing needs to be removed, or if the URI is not
* recognized.
*/
+G_GNUC_MALLOC
char *
uri_remove_auth(const char *uri);
diff --git a/src/utils.c b/src/utils.c
index fc27b13c9..0e9584d68 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -17,9 +17,9 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "utils.h"
#include "conf.h"
-#include "config.h"
#include <glib.h>
@@ -44,7 +44,7 @@
char *parsePath(char *path)
{
#ifndef WIN32
- if (path[0] != '/' && path[0] != '~') {
+ if (!g_path_is_absolute(path) && path[0] != '~') {
g_warning("\"%s\" is not an absolute path", path);
return NULL;
} else if (path[0] == '~') {
@@ -102,43 +102,15 @@ char *parsePath(char *path)
#endif
}
-int set_nonblocking(int fd)
+bool
+string_array_contains(const char *const* haystack, const char *needle)
{
-#ifdef WIN32
- u_long val = 1;
- int retval;
- int lasterr = 0;
- retval = ioctlsocket(fd, FIONBIO, &val);
- if(retval == SOCKET_ERROR)
- g_error("Error: ioctlsocket could not set FIONBIO;"
- " Error %d on socket %d", lasterr = WSAGetLastError(), fd);
- if(lasterr == 10038)
- g_debug("Code-up error! Attempt to set non-blocking I/O on "
- "something that is not a Winsock2 socket. This can't "
- "be done on Windows!\n");
- return retval;
-#else
- int ret, flags;
-
- assert(fd >= 0);
-
- while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ;
- if (flags < 0)
- return flags;
-
- flags |= O_NONBLOCK;
- while ((ret = fcntl(fd, F_SETFL, flags)) < 0 && errno == EINTR) ;
- return ret;
-#endif
-}
+ assert(haystack != NULL);
+ assert(needle != NULL);
-int stringFoundInStringArray(const char *const*array, const char *suffix)
-{
- while (array && *array) {
- if (g_ascii_strcasecmp(*array, suffix) == 0)
- return 1;
- array++;
- }
+ for (; *haystack != NULL; ++haystack)
+ if (g_ascii_strcasecmp(*haystack, needle) == 0)
+ return true;
- return 0;
+ return false;
}
diff --git a/src/utils.h b/src/utils.h
index d114003be..9d891be4a 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -20,6 +20,8 @@
#ifndef MPD_UTILS_H
#define MPD_UTILS_H
+#include <stdbool.h>
+
#ifndef assert_static
/* Compile time assertion developed by Ralf Holly */
/* http://pera-software.com/articles/compile-time-assertions.pdf */
@@ -31,8 +33,15 @@
char *parsePath(char *path);
-int set_nonblocking(int fd);
-
-int stringFoundInStringArray(const char *const*array, const char *suffix);
+/**
+ * Checks whether a string array contains the specified string.
+ *
+ * @param haystack a NULL terminated list of strings
+ * @param needle the string to search for; the comparison is
+ * case-insensitive for ASCII characters
+ * @return true if found
+ */
+bool
+string_array_contains(const char *const* haystack, const char *needle);
#endif
diff --git a/src/volume.c b/src/volume.c
index e7fa20a62..8a74e10ae 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -17,15 +17,17 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "volume.h"
#include "conf.h"
#include "player_control.h"
#include "idle.h"
#include "pcm_volume.h"
-#include "config.h"
#include "output_all.h"
#include "mixer_control.h"
#include "mixer_all.h"
+#include "mixer_type.h"
+#include "event_pipe.h"
#include <glib.h>
@@ -39,132 +41,39 @@
#define SW_VOLUME_STATE "sw_volume: "
-static enum {
- VOLUME_MIXER_TYPE_SOFTWARE,
- VOLUME_MIXER_TYPE_HARDWARE,
- VOLUME_MIXER_TYPE_DISABLED,
-} volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE;
-
-static int volume_software_set = 100;
+static unsigned volume_software_set = 100;
/** the cached hardware mixer value; invalid if negative */
static int last_hardware_volume = -1;
/** the age of #last_hardware_volume */
static GTimer *hardware_volume_timer;
-void volume_finish(void)
-{
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- g_timer_destroy(hardware_volume_timer);
-}
-
/**
- * Finds the first audio_output configuration section with the
- * specified type.
- */
-static struct config_param *
-find_output_config(const char *type)
-{
- struct config_param *param = NULL;
-
- while ((param = config_get_next_param(CONF_AUDIO_OUTPUT,
- param)) != NULL) {
- const char *param_type =
- config_get_block_string(param, "type", NULL);
- if (param_type != NULL && strcmp(param_type, type) == 0)
- return param;
- }
-
- return NULL;
-}
-
-/**
- * Copy a (top-level) legacy mixer configuration parameter to the
- * audio_output section.
+ * Handler for #PIPE_EVENT_MIXER.
*/
static void
-mixer_copy_legacy_param(const char *type, const char *name)
+mixer_event_callback(void)
{
- const struct config_param *param;
- struct config_param *output;
- const struct block_param *bp;
-
- /* see if the deprecated configuration exists */
-
- param = config_get_param(name);
- if (param == NULL)
- return;
-
- g_warning("deprecated option '%s' found, moving to '%s' audio output",
- name, type);
-
- /* determine the configuration section */
-
- output = find_output_config(type);
- if (output == NULL) {
- /* if there is no output configuration at all, create
- a new and empty configuration section for the
- legacy mixer */
-
- if (config_get_next_param(CONF_AUDIO_OUTPUT, NULL) != NULL)
- /* there is an audio_output configuration, but
- it does not match the mixer_type setting */
- g_error("no '%s' audio output found", type);
-
- output = config_new_param(NULL, param->line);
- config_add_block_param(output, "type", type, param->line);
- config_add_block_param(output, "name", type, param->line);
- config_add_param(CONF_AUDIO_OUTPUT, output);
- }
-
- bp = config_get_block_param(output, name);
- if (bp != NULL)
- g_error("the '%s' audio output already has a '%s' setting",
- type, name);
-
- /* duplicate the parameter in the configuration section */
+ /* flush the hardware volume cache */
+ last_hardware_volume = -1;
- config_add_block_param(output, name, param->value, param->line);
+ /* notify clients */
+ idle_add(IDLE_MIXER);
}
-static void
-mixer_reconfigure(const char *type)
+void volume_finish(void)
{
- mixer_copy_legacy_param(type, CONF_MIXER_DEVICE);
- mixer_copy_legacy_param(type, CONF_MIXER_CONTROL);
+ g_timer_destroy(hardware_volume_timer);
}
void volume_init(void)
{
- const struct config_param *param = config_get_param(CONF_MIXER_TYPE);
- //hw mixing is by default
- if (param) {
- if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
- volume_mixer_type = VOLUME_MIXER_TYPE_SOFTWARE;
- mixer_disable_all();
- } else if (strcmp(param->value, VOLUME_MIXER_DISABLED) == 0) {
- volume_mixer_type = VOLUME_MIXER_TYPE_DISABLED;
- mixer_disable_all();
- } else if (strcmp(param->value, VOLUME_MIXER_HARDWARE) == 0) {
- //nothing to do
- } else {
- //fallback to old config behaviour
- if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) {
- mixer_reconfigure(param->value);
- } else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) {
- mixer_reconfigure(param->value);
- } else {
- g_error("unknown mixer type %s at line %i\n",
- param->value, param->line);
- }
- }
- }
+ hardware_volume_timer = g_timer_new();
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- hardware_volume_timer = g_timer_new();
+ event_pipe_register(PIPE_EVENT_MIXER, mixer_event_callback);
}
-static int hardware_volume_get(void)
+int volume_level_get(void)
{
assert(hardware_volume_timer != NULL);
@@ -178,101 +87,60 @@ static int hardware_volume_get(void)
return last_hardware_volume;
}
-static int software_volume_get(void)
+static bool software_volume_change(unsigned volume)
{
- return volume_software_set;
-}
-
-int volume_level_get(void)
-{
- switch (volume_mixer_type) {
- case VOLUME_MIXER_TYPE_SOFTWARE:
- return software_volume_get();
- case VOLUME_MIXER_TYPE_HARDWARE:
- return hardware_volume_get();
- case VOLUME_MIXER_TYPE_DISABLED:
- return -1;
- }
-
- /* unreachable */
- assert(false);
- return -1;
-}
-
-static bool software_volume_change(int change, bool rel)
-{
- int new = change;
-
- if (rel)
- new += volume_software_set;
+ assert(volume <= 100);
- if (new > 100)
- new = 100;
- else if (new < 0)
- new = 0;
-
- volume_software_set = new;
-
- /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */
- if (new >= 100)
- new = PCM_VOLUME_1;
- else if (new <= 0)
- new = 0;
- else
- new = pcm_float_to_volume((exp(new / 25.0) - 1) /
- (54.5981500331F - 1));
-
- setPlayerSoftwareVolume(new);
+ volume_software_set = volume;
+ mixer_all_set_software_volume(volume);
return true;
}
-static bool hardware_volume_change(int change, bool rel)
+static bool hardware_volume_change(unsigned volume)
{
/* reset the cache */
last_hardware_volume = -1;
- return mixer_all_set_volume(change, rel);
+ return mixer_all_set_volume(volume);
}
-bool volume_level_change(int change, bool rel)
+bool volume_level_change(unsigned volume)
{
+ assert(volume <= 100);
+
+ volume_software_set = volume;
+
idle_add(IDLE_MIXER);
- switch (volume_mixer_type) {
- case VOLUME_MIXER_TYPE_HARDWARE:
- return hardware_volume_change(change, rel);
- case VOLUME_MIXER_TYPE_SOFTWARE:
- return software_volume_change(change, rel);
- default:
- return true;
- }
+ return hardware_volume_change(volume);
}
-void read_sw_volume_state(FILE *fp)
+bool
+read_sw_volume_state(const char *line)
{
- char buf[sizeof(SW_VOLUME_STATE) + sizeof("100") - 1];
char *end = NULL;
long int sv;
- if (volume_mixer_type != VOLUME_MIXER_TYPE_SOFTWARE)
- return;
- while (fgets(buf, sizeof(buf), fp)) {
- if (!g_str_has_prefix(buf, SW_VOLUME_STATE))
- continue;
+ if (!g_str_has_prefix(line, SW_VOLUME_STATE))
+ return false;
- g_strchomp(buf);
- sv = strtol(buf + strlen(SW_VOLUME_STATE), &end, 10);
- if (G_LIKELY(!*end))
- software_volume_change(sv, 0);
- else
- g_warning("Can't parse software volume: %s\n", buf);
- return;
- }
+ line += sizeof(SW_VOLUME_STATE) - 1;
+ sv = strtol(line, &end, 10);
+ if (*end == 0 && sv >= 0 && sv <= 100)
+ software_volume_change(sv);
+ else
+ g_warning("Can't parse software volume: %s\n", line);
+ return true;
}
void save_sw_volume_state(FILE *fp)
{
- if (volume_mixer_type == VOLUME_MIXER_TYPE_SOFTWARE)
- fprintf(fp, SW_VOLUME_STATE "%d\n", volume_software_set);
+ fprintf(fp, SW_VOLUME_STATE "%u\n", volume_software_set);
+}
+
+unsigned
+sw_volume_state_get_hash(void)
+{
+ return volume_software_set;
}
diff --git a/src/volume.h b/src/volume.h
index 99d31da4e..66e7c43f0 100644
--- a/src/volume.h
+++ b/src/volume.h
@@ -23,22 +23,26 @@
#include <stdbool.h>
#include <stdio.h>
-#define VOLUME_MIXER_OSS "oss"
-#define VOLUME_MIXER_ALSA "alsa"
-#define VOLUME_MIXER_SOFTWARE "software"
-#define VOLUME_MIXER_HARDWARE "hardware"
-#define VOLUME_MIXER_DISABLED "disabled"
-
void volume_init(void);
void volume_finish(void);
int volume_level_get(void);
-bool volume_level_change(int change, bool rel);
+bool volume_level_change(unsigned volume);
-void read_sw_volume_state(FILE *fp);
+bool
+read_sw_volume_state(const char *line);
void save_sw_volume_state(FILE *fp);
+/**
+ * Generates a hash number for the current state of the software
+ * volume control. This is used by timer_save_state_file() to
+ * determine whether the state has changed and the state file should
+ * be saved.
+ */
+unsigned
+sw_volume_state_get_hash(void);
+
#endif
diff --git a/src/zeroconf-avahi.c b/src/zeroconf-avahi.c
index 648f36e03..63ad0f65b 100644
--- a/src/zeroconf-avahi.c
+++ b/src/zeroconf-avahi.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "zeroconf-internal.h"
#include "listen.h"
diff --git a/src/zeroconf-bonjour.c b/src/zeroconf-bonjour.c
index 4e06319e7..41734d3bc 100644
--- a/src/zeroconf-bonjour.c
+++ b/src/zeroconf-bonjour.c
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "zeroconf-internal.h"
#include "listen.h"
diff --git a/src/zeroconf.c b/src/zeroconf.c
index 42e995c45..9a386d53c 100644
--- a/src/zeroconf.c
+++ b/src/zeroconf.c
@@ -17,10 +17,10 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "zeroconf.h"
#include "zeroconf-internal.h"
#include "conf.h"
-#include "config.h"
#include <glib.h>
diff --git a/src/zeroconf.h b/src/zeroconf.h
index 6a5934ed5..0aafbdef2 100644
--- a/src/zeroconf.h
+++ b/src/zeroconf.h
@@ -20,7 +20,7 @@
#ifndef MPD_ZEROCONF_H
#define MPD_ZEROCONF_H
-#include "config.h"
+#include "check.h"
#ifdef HAVE_ZEROCONF