aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--INSTALL3
-rw-r--r--Makefile.am68
-rw-r--r--NEWS25
-rwxr-xr-xautogen.sh4
-rw-r--r--configure.ac109
-rw-r--r--doc/mpd.14
-rw-r--r--doc/mpd.conf.56
-rw-r--r--doc/mpdconf.example31
-rw-r--r--doc/protocol.xml38
-rw-r--r--doc/user.xml69
-rw-r--r--src/cmdline.c14
-rw-r--r--src/cmdline.h10
-rw-r--r--src/command.c56
-rw-r--r--src/conf.c270
-rw-r--r--src/conf.h52
-rw-r--r--src/dbUtils.c2
-rw-r--r--src/decoder/ffmpeg_plugin.c5
-rw-r--r--src/decoder/flac_plugin.c14
-rw-r--r--src/decoder/modplug_plugin.c2
-rw-r--r--src/decoder/sndfile_decoder_plugin.c246
-rw-r--r--src/decoder_api.c3
-rw-r--r--src/decoder_list.c4
-rw-r--r--src/directory_save.c5
-rw-r--r--src/encoder/twolame_encoder.c299
-rw-r--r--src/encoder_list.c4
-rw-r--r--src/filter/chain_filter_plugin.c177
-rw-r--r--src/filter/chain_filter_plugin.h48
-rw-r--r--src/filter/convert_filter_plugin.c154
-rw-r--r--src/filter/convert_filter_plugin.h36
-rw-r--r--src/filter/null_filter_plugin.c93
-rw-r--r--src/filter/volume_filter_plugin.c167
-rw-r--r--src/filter/volume_filter_plugin.h31
-rw-r--r--src/filter_internal.h38
-rw-r--r--src/filter_plugin.c106
-rw-r--r--src/filter_plugin.h145
-rw-r--r--src/filter_registry.c41
-rw-r--r--src/filter_registry.h37
-rw-r--r--src/idle.c1
-rw-r--r--src/idle.h3
-rw-r--r--src/main.c84
-rw-r--r--src/mapper.c44
-rw-r--r--src/mapper.h2
-rw-r--r--src/mixer/software_mixer_plugin.c124
-rw-r--r--src/mixer/software_mixer_plugin.h33
-rw-r--r--src/mixer_all.c79
-rw-r--r--src/mixer_all.h21
-rw-r--r--src/mixer_control.c13
-rw-r--r--src/mixer_control.h3
-rw-r--r--src/mixer_list.h1
-rw-r--r--src/mixer_type.c38
-rw-r--r--src/mixer_type.h47
-rw-r--r--src/output_control.c18
-rw-r--r--src/output_init.c116
-rw-r--r--src/output_internal.h22
-rw-r--r--src/output_state.c47
-rw-r--r--src/output_state.h5
-rw-r--r--src/output_thread.c202
-rw-r--r--src/player_control.c11
-rw-r--r--src/player_control.h3
-rw-r--r--src/player_thread.c20
-rw-r--r--src/playlist.c2
-rw-r--r--src/playlist.h35
-rw-r--r--src/playlist_control.c2
-rw-r--r--src/playlist_edit.c105
-rw-r--r--src/playlist_global.c16
-rw-r--r--src/playlist_save.c4
-rw-r--r--src/playlist_state.c45
-rw-r--r--src/playlist_state.h5
-rw-r--r--src/replay_gain.c47
-rw-r--r--src/song_print.c21
-rw-r--r--src/song_save.c41
-rw-r--r--src/song_save.h16
-rw-r--r--src/state_file.c45
-rw-r--r--src/sticker.c47
-rw-r--r--src/sticker.h8
-rw-r--r--src/tag.c2
-rw-r--r--src/tag.h2
-rw-r--r--src/tag_id3.c17
-rw-r--r--src/update.c7
-rw-r--r--src/volume.c212
-rw-r--r--src/volume.h11
-rw-r--r--test/read_mixer.c18
-rw-r--r--test/run_filter.c168
-rw-r--r--test/run_output.c23
85 files changed, 3351 insertions, 902 deletions
diff --git a/.gitignore b/.gitignore
index 1a58409d8..e55d94c54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,7 @@ doc/api
test/software_volume
test/run_decoder
test/read_tags
+test/run_filter
test/run_encoder
test/run_output
test/read_conf
diff --git a/INSTALL b/INSTALL
index 3a1671a82..22a06fa62 100644
--- a/INSTALL
+++ b/INSTALL
@@ -104,6 +104,9 @@ For MIDI support (DO NOT USE - use libwildmidi instead)
libwildmidi - http://wildmidi.sourceforge.net/
For MIDI support.
+libsndfile - http://www.mega-nerd.com/libsndfile/
+WAVE, AIFF, and many others.
+
Optional Miscellaneous Dependencies
-----------------------------------
diff --git a/Makefile.am b/Makefile.am
index aadeb2508..5c53ca5c1 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,5 +1,5 @@
ACLOCAL_AMFLAGS = -I m4
-AUTOMAKE_OPTIONS = foreign 1.9 dist-bzip2
+AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2
AM_CPPFLAGS = -I$(srcdir)/src $(GLIB_CFLAGS)
@@ -42,6 +42,12 @@ mpd_headers = \
src/output_state.h \
src/output_print.h \
src/output_command.h \
+ src/filter_internal.h \
+ src/filter_plugin.h \
+ src/filter_registry.h \
+ src/filter/chain_filter_plugin.h \
+ src/filter/convert_filter_plugin.h \
+ src/filter/volume_filter_plugin.h \
src/buffer2array.h \
src/command.h \
src/idle.h \
@@ -90,6 +96,8 @@ mpd_headers = \
src/mixer_list.h \
src/event_pipe.h \
src/mixer_plugin.h \
+ src/mixer_type.h \
+ src/mixer/software_mixer_plugin.h \
src/daemon.h \
src/normalize.h \
src/compress.h \
@@ -168,6 +176,7 @@ src_mpd_SOURCES = \
$(ENCODER_SRC) \
$(OUTPUT_API_SRC) $(OUTPUT_SRC) \
$(MIXER_API_SRC) $(MIXER_SRC) \
+ $(FILTER_SRC) \
src/notify.c \
src/audio.c \
src/audio_parser.c \
@@ -188,6 +197,8 @@ src_mpd_SOURCES = \
src/database.c \
src/dirvec.c \
src/fifo_buffer.c \
+ src/filter_plugin.c \
+ src/filter_registry.c \
src/update.c \
src/client.c \
src/listen.c \
@@ -314,12 +325,14 @@ endif
DECODER_CFLAGS = \
$(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
+ $(SNDFILE_CFLAGS) \
$(AUDIOFILE_CFLAGS) \
$(LIBMIKMOD_CFLAGS) \
$(MODPLUG_CFLAGS) \
$(SIDPLAY_CFLAGS) \
$(FLUIDSYNTH_CFLAGS) \
$(WILDMIDI_CFLAGS) \
+ $(WAVPACK_CFLAGS) \
$(MAD_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(CUE_CFLAGS)
@@ -327,11 +340,13 @@ DECODER_CFLAGS = \
DECODER_LIBS = \
$(VORBIS_LIBS) $(TREMOR_LIBS) \
$(FLAC_LIBS) \
+ $(SNDFILE_LIBS) \
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
$(MODPLUG_LIBS) \
$(SIDPLAY_LIBS) \
$(FLUIDSYNTH_LIBS) \
$(WILDMIDI_LIBS) \
+ $(WAVPACK_LIBS) \
$(MAD_LIBS) \
$(MP4FF_LIBS) \
$(FFMPEG_LIBS) \
@@ -409,14 +424,20 @@ if HAVE_FFMPEG
DECODER_SRC += src/decoder/ffmpeg_plugin.c
endif
+if ENABLE_SNDFILE
+DECODER_SRC += src/decoder/sndfile_decoder_plugin.c
+endif
+
# encoder plugins
ENCODER_CFLAGS = \
$(LAME_CFLAGS) \
+ $(TWOLAME_CFLAGS) \
$(VORBISENC_CFLAGS)
ENCODER_LIBS = \
$(LAME_LIBS) \
+ $(TWOLAME_LIBS) \
$(VORBISENC_LIBS)
ENCODER_SRC =
@@ -431,6 +452,10 @@ endif
if ENABLE_LAME_ENCODER
ENCODER_SRC += src/encoder/lame_encoder.c
endif
+
+if ENABLE_TWOLAME_ENCODER
+ENCODER_SRC += src/encoder/twolame_encoder.c
+endif
endif
@@ -508,10 +533,12 @@ OUTPUT_SRC = \
MIXER_API_SRC = \
src/mixer_control.c \
+ src/mixer_type.c \
src/mixer_all.c \
src/mixer_api.c
-MIXER_SRC =
+MIXER_SRC = \
+ src/mixer/software_mixer_plugin.c
if HAVE_ALSA
OUTPUT_SRC += src/output/alsa_plugin.c
@@ -569,6 +596,17 @@ endif
#
+# Filter plugins
+#
+
+FILTER_SRC = \
+ src/filter/null_filter_plugin.c \
+ src/filter/chain_filter_plugin.c \
+ src/filter/convert_filter_plugin.c \
+ src/filter/volume_filter_plugin.c
+
+
+#
# Sparse code analysis
#
# sparse is a semantic parser
@@ -599,6 +637,7 @@ noinst_PROGRAMS = \
test/run_input \
test/run_decoder \
test/read_tags \
+ test/run_filter \
test/run_output \
test/read_mixer \
test/software_volume
@@ -661,6 +700,24 @@ test_read_tags_SOURCES = test/read_tags.c \
$(TAG_SRC) \
$(DECODER_SRC)
+test_run_filter_CPPFLAGS = $(AM_CPPFLAGS)
+test_run_filter_LDADD = $(MPD_LIBS) \
+ $(SAMPLERATE_LIBS) \
+ $(GLIB_LIBS)
+test_run_filter_SOURCES = test/run_filter.c \
+ src/filter_plugin.c \
+ src/filter_registry.c \
+ src/conf.c src/buffer2array.c src/utils.c \
+ src/pcm_volume.c src/pcm_convert.c \
+ src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \
+ src/pcm_resample.c src/pcm_resample_fallback.c \
+ src/audio_parser.c \
+ $(FILTER_SRC)
+
+if HAVE_LIBSAMPLERATE
+test_run_filter_SOURCES += src/pcm_resample_libsamplerate.c
+endif
+
if ENABLE_ENCODER
noinst_PROGRAMS += test/run_encoder
test_run_encoder_SOURCES = test/run_encoder.c \
@@ -699,7 +756,12 @@ test_run_output_SOURCES = test/run_output.c \
$(ENCODER_SRC) \
src/mixer_api.c \
src/mixer_control.c \
+ src/mixer_type.c \
$(MIXER_SRC) \
+ src/filter_plugin.c src/filter/chain_filter_plugin.c \
+ src/filter/convert_filter_plugin.c \
+ src/filter/volume_filter_plugin.c \
+ src/pcm_volume.c \
$(OUTPUT_SRC)
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \
@@ -710,6 +772,8 @@ test_read_mixer_LDADD = $(MPD_LIBS) \
test_read_mixer_SOURCES = test/read_mixer.c \
src/conf.c src/buffer2array.c src/utils.c src/log.c \
src/mixer_control.c src/mixer_api.c \
+ src/filter_plugin.c \
+ src/filter/volume_filter_plugin.c \
$(MIXER_SRC)
endif
diff --git a/NEWS b/NEWS
index bf9401d6c..dd6b15395 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,28 @@
+ver 0.16 (20??/??/??)
+* protocol:
+ - send song modification time to client
+ - added "update" idle event
+ - removed the deprecated "volume" command
+* tags:
+ - added tags "ArtistSort", "AlbumArtistSort"
+ - id3: revised "performer" tag support
+* decoders:
+ - ffmpeg: support multiple tags
+ - sndfile: new decoder plugin based on libsndfile
+ - flac: load external cue sheet when no internal one
+* encoders:
+ - twolame: new encoder plugin based on libtwolame
+* mixers:
+ - removed support for legacy mixer configuration
+ - reimplemented software volume as mixer+filter plugin
+ - per-device software/hardware mixer setting
+* commands:
+ - added new "status" line with more precise "elapsed time"
+* log unused/unknown block parameters
+* save state when stopped
+* renamed option "--stdout" to "--stderr"
+
+
ver 0.15.1 (2009/07/15)
* decoders:
- flac: fix assertion failure in tag_free() call
diff --git a/autogen.sh b/autogen.sh
index 51fe243d7..be192aa59 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -16,13 +16,13 @@ if test -n "$AM_FORCE_VERSION"
then
AM_VERSIONS="$AM_FORCE_VERSION"
else
- AM_VERSIONS='1.9 1.10'
+ AM_VERSIONS='1.10'
fi
if test -n "$AC_FORCE_VERSION"
then
AC_VERSIONS="$AC_FORCE_VERSION"
else
- AC_VERSIONS='2.58 2.59 2.60 2.61'
+ AC_VERSIONS='2.60 2.61'
fi
versioned_bins ()
diff --git a/configure.ac b/configure.ac
index 3f631d238..e828f4201 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,11 +1,11 @@
AC_PREREQ(2.60)
-AC_INIT(mpd, 0.15.1, musicpd-dev-team@lists.sourceforge.net)
+AC_INIT(mpd, 0.16~git, musicpd-dev-team@lists.sourceforge.net)
AC_CONFIG_SRCDIR([src/main.c])
-AM_INIT_AUTOMAKE([foreign 1.9 dist-bzip2])
+AM_INIT_AUTOMAKE([foreign 1.10 dist-bzip2])
AM_CONFIG_HEADER(config.h)
AC_CONFIG_MACRO_DIR([m4])
-AC_DEFINE(PROTOCOL_VERSION, "0.15.0", [The mpd protocol version])
+AC_DEFINE(PROTOCOL_VERSION, "0.16.0", [The MPD protocol version])
dnl
@@ -389,13 +389,18 @@ dnl
dnl decoder plugins
dnl
-
-
AC_ARG_ENABLE(audiofile,
- AS_HELP_STRING([--disable-audiofile],
- [disable audiofile support, disables wave support (default: enable)]),,
+ AS_HELP_STRING([--enable-audiofile],
+ [enable audiofile support, disables wave support]),,
enable_audiofile=yes)
+MPD_AUTO_PKG(audiofile, AUDIOFILE, [audiofile >= 0.1.7],
+ [audiofile decoder plugin], [libaudiofile not found])
+AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes)
+if test x$enable_audiofile = xyes; then
+ AC_DEFINE(HAVE_AUDIOFILE, 1, [Define for audiofile support])
+fi
+
AC_ARG_ENABLE(ffmpeg,
AS_HELP_STRING([--enable-ffmpeg],
[enable FFMPEG support]),,
@@ -470,6 +475,27 @@ AC_ARG_ENABLE(vorbis,
[disable Ogg Vorbis support (default: enable)]),,
enable_vorbis=yes)
+AC_ARG_ENABLE(sndfile,
+ AS_HELP_STRING([--enable-sndfile],
+ [enable sndfile support]),,
+ enable_sndfile=auto)
+
+if test x$enable_sndfile = xauto && test x$enable_modplug = xyes; then
+ dnl If modplug is enabled, enable sndfile only if explicitly
+ dnl requested - modplug's modplug/sndfile.h is known to
+ dnl conflict with libsndfile's sndfile.h.
+ AC_MSG_NOTICE([disabling libsndfile auto-detection, because the modplug decoder is enabled])
+ enable_sndfile=no
+fi
+
+MPD_AUTO_PKG(sndfile, SNDFILE, [sndfile],
+ [libsndfile decoder plugin], [libsndfile not found])
+AM_CONDITIONAL(ENABLE_SNDFILE, test x$enable_sndfile = xyes)
+if test x$enable_sndfile = xyes; then
+ AC_DEFINE(ENABLE_SNDFILE, 1, [Define to enable the sndfile decoder plugin])
+fi
+
+
dnl ###
dnl Ogg Tremor
dnl ###
@@ -540,9 +566,16 @@ AC_ARG_ENABLE(wildmidi,
enable_wildmidi=no)
AC_ARG_ENABLE(wavpack,
- AS_HELP_STRING([--disable-wavpack],
- [disable WavPack support (default: enable)]),,
- enable_wavpack=yes)
+ AS_HELP_STRING([--enable-wavpack],
+ [enable WavPack support]),,
+ enable_wavpack=auto)
+
+MPD_AUTO_PKG(wavpack, WAVPACK, [wavpack],
+ [WavPack decoder plugin], [libwavpack not found])
+AM_CONDITIONAL(HAVE_WAVPACK, test x$enable_wavpack = xyes)
+if test x$enable_wavpack = xyes; then
+ AC_DEFINE([HAVE_WAVPACK], 1, [Define to enable WavPack support])
+fi
dnl
@@ -585,6 +618,11 @@ AC_ARG_ENABLE(lame-encoder,
[enable the LAME mp3 encoder]),,
enable_lame_encoder=auto)
+AC_ARG_ENABLE(twolame-encoder,
+ AS_HELP_STRING([--enable-twolame-encoder],
+ [enable the TwoLAME mp2 encoder]),,
+ enable_twolame_encoder=auto)
+
dnl
dnl audio output plugins
@@ -806,18 +844,6 @@ fi
AM_CONDITIONAL(HAVE_MPCDEC, test x$enable_mpc = xyes)
-if test x$enable_wavpack = xyes; then
- PKG_CHECK_MODULES([WAVPACK], [wavpack],
- [enable_wavpack=yes;
- AC_DEFINE([HAVE_WAVPACK], 1,
- [Define to enable WavPack support])]
- MPD_LIBS="$MPD_LIBS $WAVPACK_LIBS"
- MPD_CFLAGS="$MPD_CFLAGS $WAVPACK_CFLAGS",
- enable_wavpack=no)
-fi
-
-AM_CONDITIONAL(HAVE_WAVPACK, test x$enable_wavpack = xyes)
-
AM_PATH_FAAD()
AM_CONDITIONAL(HAVE_FAAD, test x$enable_aac = xyes)
@@ -906,14 +932,6 @@ AM_CONDITIONAL(HAVE_FLAC_COMMON,
AM_CONDITIONAL(HAVE_OGG_COMMON,
test x$enable_vorbis = xyes || test x$enable_oggflac = xyes || test x$enable_flac = xyes)
-if test x$enable_audiofile = xyes; then
- PKG_CHECK_MODULES(AUDIOFILE, [audiofile >= 0.1.7],
- AC_DEFINE(HAVE_AUDIOFILE, 1, [Define for audiofile support]),
- enable_audiofile=no)
-fi
-
-AM_CONDITIONAL(HAVE_AUDIOFILE, test x$enable_audiofile = xyes)
-
MPD_AUTO_PKG(ffmpeg, FFMPEG, [libavformat libavcodec libavutil],
[ffmpeg decoder library], [libavformat+libavcodec+libavutil not found])
@@ -987,6 +1005,7 @@ else
# don't bother to check for encoder plugins
enable_vorbis_encoder=no
enable_lame_encoder=no
+ enable_twolame_encoder=no
fi
MPD_AUTO_PKG(vorbis_encoder, VORBISENC, [vorbisenc],
@@ -1001,8 +1020,12 @@ fi
AC_SUBST(LAME_CFLAGS)
AC_SUBST(LAME_LIBS)
+MPD_AUTO_PKG(twolame_encoder, TWOLAME, [twolame],
+ [TwoLAME encoder], [libtwolame not found])
+
if test x$enable_vorbis_encoder != xno ||
- test x$enable_lame_encoder != xno; then
+ test x$enable_lame_encoder != xno ||
+ test x$enable_twolame_encoder != xno; then
# at least one encoder plugin is enabled
enable_encoder=yes
else
@@ -1061,6 +1084,12 @@ if test x$enable_lame_encoder = xyes; then
[Define to enable the lame encoder plugin])
fi
+AM_CONDITIONAL(ENABLE_TWOLAME_ENCODER, test x$enable_twolame_encoder = xyes)
+if test x$enable_twolame_encoder = xyes; then
+ AC_DEFINE(ENABLE_TWOLAME_ENCODER, 1,
+ [Define to enable the TwoLAME encoder plugin])
+fi
+
dnl
dnl Documentation
@@ -1290,6 +1319,13 @@ if
else
echo " Ogg Vorbis encoder ............disabled"
fi
+
+ if test x$enable_twolame_encoder = xyes; then
+ echo " TwoLAME mp3 encoder ...........enabled"
+ else
+ echo " TwoLAME mp3 encoder ...........disabled"
+ fi
+
echo ""
fi
@@ -1378,6 +1414,12 @@ else
echo " Ogg Vorbis support ............disabled"
fi
+if test x$enable_sndfile = xyes; then
+ echo " libsndfile ....................enabled"
+else
+ echo " libsndfile ....................disabled"
+fi
+
if test x$enable_audiofile = xyes; then
echo " Wave file support .............enabled"
else
@@ -1490,6 +1532,11 @@ echo ""
echo "##########################################"
echo ""
+if test x$enable_sndfile = xyes && test x$enable_modplug = xyes; then
+ AC_MSG_WARN([compilation may fail, because libmodplug conflicts with libsndfile])
+ AC_MSG_WARN([libmodplug ships modplug/sndfile.h, which hides libsndfile's sndfile.h])
+fi
+
echo "Generating needed files for compilation"
echo ""
diff --git a/doc/mpd.1 b/doc/mpd.1
index f2fd2286f..22c0e7510 100644
--- a/doc/mpd.1
+++ b/doc/mpd.1
@@ -34,8 +34,8 @@ Do not create database, even if it doesn't exist.
.BI --no-daemon
Don't detach from console.
.TP
-.BI --stdout
-Print messages to stdout and stderr.
+.BI --stderr
+Print messages stderr.
.TP
.BI --verbose
Verbose logging.
diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5
index 776fdbefd..61ee87955 100644
--- a/doc/mpd.conf.5
+++ b/doc/mpd.conf.5
@@ -285,6 +285,12 @@ whatever audio format is passed to the audio output.
.B device <dev>
This specifies the device to use for audio output. The default is "default".
.TP
+.B mixer_type <hardware, software or none>
+Specifies which mixer should be used for this audio output: the
+hardware mixer (available for ALSA, OSS and PulseAudio), the software
+mixer or no mixer ("none"). By default, the hardware mixer is used
+for devices which support it, and none for the others.
+.TP
.B mixer_device <mixer dev>
This specifies which mixer to use. The default is "default". To use
the second sound card in a system, use "hw:1".
diff --git a/doc/mpdconf.example b/doc/mpdconf.example
index 919326236..7574ffc87 100644
--- a/doc/mpdconf.example
+++ b/doc/mpdconf.example
@@ -179,6 +179,7 @@ input {
# name "My ALSA Device"
# device "hw:0,0" # optional
# format "44100:16:2" # optional
+# mixer_type "hardware" # optional
# mixer_device "default" # optional
# mixer_control "PCM" # optional
# mixer_index "0" # optional
@@ -191,6 +192,7 @@ input {
# name "My OSS Device"
# device "/dev/dsp" # optional
# format "44100:16:2" # optional
+# mixer_type "hardware" # optional
# mixer_device "/dev/mixer" # optional
# mixer_control "PCM" # optional
#}
@@ -214,6 +216,7 @@ input {
# genre "jazz" # optional
# public "no" # optional
# timeout "2" # optional
+# mixer_type "software" # optional
#}
#
# An example of a httpd output (built-in HTTP streaming server):
@@ -255,6 +258,7 @@ input {
#audio_output {
# type "null"
# name "My Null Output"
+# mixer_type "none" # optional
#}
#
# This setting will change all decoded audio to be converted to the specified
@@ -273,33 +277,6 @@ input {
###############################################################################
-# Volume control mixer ########################################################
-#
-# These are the global volume control settings. By default, this setting will
-# be detected to the available audio output device, with preference going to
-# hardware mixing. Hardware and software mixers for individual audio_output
-# sections cannot yet be mixed.
-#
-# An example for controlling an ALSA, OSS or Pulseaudio mixer; If this
-# setting is used other sound applications will be affected by the volume
-# being controlled by MPD.
-#
-#mixer_type "hardware"
-#
-# An example for controlling all mixers through software. This will control
-# all controls, even if the mixer is not supported by the device and will not
-# affect any other sound producing applications.
-#
-#mixer_type "software"
-#
-# This example will not allow MPD to touch the mixer at all and will disable
-# all volume controls.
-#
-#mixer_type "disabled"
-#
-###############################################################################
-
-
# Normalization automatic volume adjustments ##################################
#
# This setting specifies the type of ReplayGain to use. This setting can have
diff --git a/doc/protocol.xml b/doc/protocol.xml
index 792227a1a..7c93df50f 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -139,6 +139,15 @@
</listitem>
<listitem>
<para>
+ <returnvalue>update</returnvalue>: a database update
+ has started or finished. If the database was
+ modified during the update, the
+ <returnvalue>database</returnvalue> event is also
+ emitted.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
<returnvalue>stored_playlist</returnvalue>: a stored
playlist has been modified, renamed, created or
deleted
@@ -279,6 +288,16 @@
</listitem>
<listitem>
<para>
+ <varname>elapsed</varname>:
+ <footnote id="since_0_16"><simpara>Since MPD 0.16</simpara></footnote>
+ <returnvalue>
+ Total time elapsed within the current song, but
+ with higher resolution.
+ </returnvalue>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
<varname>bitrate</varname>:
<returnvalue>instantaneous bitrate in
kbps</returnvalue>
@@ -453,25 +472,6 @@
</para>
</listitem>
</varlistentry>
- <varlistentry id="command_volume">
- <term>
- <cmdsynopsis>
- <command>volume</command>
- <arg choice="req"><replaceable>CHANGE</replaceable></arg>
- </cmdsynopsis>
- </term>
- <listitem>
- <para>
- Changes volume by amount <varname>CHANGE</varname>.
- </para>
- <note>
- <para>
- <command>volume</command> is deprecated, use
- <command>setvol</command> instead.
- </para>
- </note>
- </listitem>
- </varlistentry>
</variablelist>
</section>
diff --git a/doc/user.xml b/doc/user.xml
index 6c3f5edeb..6e039ebde 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -319,13 +319,72 @@ cd mpd-version</programlisting>
</row>
<row>
<entry>
- <varname>mixer_enabled</varname>
- <parameter>yes|no</parameter>
+ <varname>mixer_type</varname>
+ <parameter>hardware|software|none</parameter>
+ </entry>
+ <entry>
+ Specifies which mixer should be used for this audio
+ output: the hardware mixer (available for ALSA, OSS
+ and PulseAudio), the software mixer or no mixer
+ ("none"). By default, the hardware mixer is used for
+ devices which support it, and none for the others.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </informaltable>
+ </section>
+
+ <section>
+ <title>Configuring filters</title>
+
+ <para>
+ Filters are plugins which modify an audio stream.
+ </para>
+
+ <para>
+ To configure a filter, add a <varname>filter</varname> block
+ to <filename>mpd.conf</filename>:
+ </para>
+
+ <programlisting>filter {
+ plugin "volume"
+ name "software volume"
+}
+ </programlisting>
+
+ <para>
+ The following table lists the <varname>filter</varname>
+ options valid for all plugins:
+ </para>
+
+ <informaltable>
+ <tgroup cols="2">
+ <thead>
+ <row>
+ <entry>
+ Name
+ </entry>
+ <entry>
+ Description
+ </entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry>
+ <varname>plugin</varname>
+ </entry>
+ <entry>
+ The name of the plugin.
+ </entry>
+ </row>
+ <row>
+ <entry>
+ <varname>name</varname>
</entry>
<entry>
- Specifies whether the hardware mixer of this audio
- output should be used. By default, all hardware
- mixers are enabled if available.
+ The name of the filter.
</entry>
</row>
</tbody>
diff --git a/src/cmdline.c b/src/cmdline.c
index e0274ef36..cb796ebab 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -77,7 +77,7 @@ static const char *summary =
"Music Player Daemon - a daemon for playing music.";
#endif
-void parseOptions(int argc, char **argv, Options *options)
+void parse_cmdline(int argc, char **argv, struct options *options)
{
GError *error = NULL;
GOptionContext *context;
@@ -96,7 +96,9 @@ void parseOptions(int argc, char **argv, Options *options)
"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->stderr,
+ NULL, NULL },
+ { "stderr", 0, 0, G_OPTION_ARG_NONE, &options->stderr,
"print messages to stderr", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
"verbose logging", NULL },
@@ -107,9 +109,9 @@ void parseOptions(int argc, char **argv, Options *options)
options->kill = false;
options->daemon = true;
- options->stdOutput = false;
+ options->stderr = false;
options->verbose = false;
- options->createDB = 0;
+ options->create_db = 0;
context = g_option_context_new("[path/to/mpd.conf]");
g_option_context_add_main_entries(context, entries, NULL);
@@ -137,9 +139,9 @@ void parseOptions(int argc, char **argv, Options *options)
g_error("Cannot use both --create-db and --no-create-db\n");
if (option_no_create_db)
- options->createDB = -1;
+ options->create_db = -1;
else if (option_create_db)
- options->createDB = 1;
+ options->create_db = 1;
options->daemon = !option_no_daemon;
diff --git a/src/cmdline.h b/src/cmdline.h
index 673701055..fef1712d6 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -22,14 +22,14 @@
#include <glib.h>
-typedef struct _Options {
+struct options {
gboolean kill;
gboolean daemon;
- gboolean stdOutput;
+ gboolean stderr;
gboolean verbose;
- int createDB;
-} Options;
+ int create_db;
+};
-void parseOptions(int argc, char **argv, Options *options);
+void parse_cmdline(int argc, char **argv, struct options *options);
#endif
diff --git a/src/command.c b/src/command.c
index d30b63594..58b67d8ce 100644
--- a/src/command.c
+++ b/src/command.c
@@ -58,7 +58,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"
@@ -470,7 +469,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"
@@ -501,9 +500,11 @@ handle_status(struct client *client,
const struct audio_format *af = player_get_audio_format();
client_printf(client,
COMMAND_STATUS_TIME ": %i:%i\n"
+ "elapsed: %1.3f\n"
COMMAND_STATUS_BITRATE ": %li\n"
COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
getPlayerElapsedTime(), getPlayerTotalTime(),
+ pc.elapsed_time,
getPlayerBitRate(),
af->sample_rate, af->bits, af->channels);
}
@@ -569,7 +570,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 +606,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 +616,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;
}
}
@@ -637,7 +638,7 @@ handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
if (!check_int(client, &song, argv[1], need_positive))
return COMMAND_RETURN_ERROR;
- result = deleteFromPlaylist(&g_playlist, song);
+ result = playlist_delete(&g_playlist, song);
return print_playlist_result(client, result);
}
@@ -650,7 +651,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 +672,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 +680,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;
}
@@ -1052,25 +1053,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 +1061,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");
@@ -1241,7 +1228,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 +1242,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 +1256,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 +1270,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);
}
@@ -1738,7 +1725,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]);
diff --git a/src/conf.c b/src/conf.c
index cce7dbf27..777cbe6ed 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -39,34 +39,91 @@
#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_ERROR_FILE, false, false },
+ { .name = CONF_PID_FILE, false, false },
+ { .name = CONF_STATE_FILE, false, false },
+ { .name = CONF_USER, 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 = "filter", true, true },
+};
-static int get_bool(const char *value)
+static bool
+string_array_contains(const char *const* array, const char *value)
+{
+ for (const char *const* x = array; *x; x++)
+ if (g_ascii_strcasecmp(*x, value) == 0)
+ return true;
+
+ return false;
+}
+
+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,6 +140,7 @@ config_new_param(const char *value, int line)
ret->num_block_params = 0;
ret->block_params = NULL;
+ ret->used = false;
return ret;
}
@@ -106,41 +164,10 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
}
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)
-{
- struct config_entry *entry = data;
-
- g_slist_foreach(entry->params, config_param_free, NULL);
- g_slist_free(entry->params);
-
- g_free(entry);
-}
-
-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,74 +175,47 @@ 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, 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
@@ -224,6 +224,12 @@ config_add_block_param(struct config_param * param, const char *name,
{
struct block_param *bp;
+ bp = config_get_block_param(param, name);
+ if (bp != NULL)
+ g_error("\"%s\" first defined on line %i, and "
+ "redefined on line %i\n", name,
+ bp->line, line);
+
param->num_block_params++;
param->block_params = g_realloc(param->block_params,
@@ -235,6 +241,7 @@ 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;
}
static struct config_param *
@@ -335,15 +342,14 @@ void config_read_file(const char *file)
g_error("unrecognized parameter in config file at "
"line %i: %s\n", count, string);
- if (!(entry->mask & CONF_REPEATABLE_MASK) &&
- entry->params != NULL) {
+ if (entry->params != NULL && !entry->repeatable) {
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->mask & CONF_BLOCK_MASK) {
+ if (entry->block) {
if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
g_error("improperly formatted config file at "
"line %i: %s\n", count, string);
@@ -357,15 +363,6 @@ void config_read_file(const char *file)
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);
-}
-
struct config_param *
config_get_next_param(const char *name, const struct config_param * last)
{
@@ -391,7 +388,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 +444,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 +513,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..669542167 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -44,10 +44,9 @@
#define CONF_AUDIO_OUTPUT "audio_output"
#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"
@@ -69,9 +68,6 @@
#define CONF_INPUT "input"
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
-#define CONF_BOOL_UNSET -1
-#define CONF_BOOL_INVALID -2
-
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
@@ -79,6 +75,12 @@ 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 +89,50 @@ 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.
+ */
+G_GNUC_CONST
+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);
+
+void config_read_file(const char *file);
/* don't free the returned value
set _last_ to NULL to get first entry */
+G_GNUC_CONST
struct config_param *
config_get_next_param(const char *name, const struct config_param *last);
+G_GNUC_CONST
static inline struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
}
+G_GNUC_CONST
const char *
config_get_string(const char *name, const char *default_value);
@@ -120,21 +141,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.
*/
+G_GNUC_CONST
const char *
config_get_path(const char *name);
+G_GNUC_CONST
unsigned
config_get_positive(const char *name, unsigned default_value);
+G_GNUC_CONST
struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
+G_GNUC_CONST
bool config_get_bool(const char *name, bool default_value);
+G_GNUC_CONST
const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
+G_GNUC_CONST
static inline char *
config_dup_block_string(const struct config_param *param, const char *name,
const char *default_value)
@@ -142,14 +169,17 @@ 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_CONST
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value);
+G_GNUC_CONST
bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
+G_GNUC_CONST
struct config_param *
config_new_param(const char *value, int line);
diff --git a/src/dbUtils.c b/src/dbUtils.c
index f89148c1b..67eb89ebe 100644
--- a/src/dbUtils.c
+++ b/src/dbUtils.c
@@ -168,7 +168,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 {
diff --git a/src/decoder/ffmpeg_plugin.c b/src/decoder/ffmpeg_plugin.c
index abccdf977..03c46a732 100644
--- a/src/decoder/ffmpeg_plugin.c
+++ b/src/decoder/ffmpeg_plugin.c
@@ -342,8 +342,9 @@ static void
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);
}
#endif
diff --git a/src/decoder/flac_plugin.c b/src/decoder/flac_plugin.c
index 1d7a9f868..89a812f52 100644
--- a/src/decoder/flac_plugin.c
+++ b/src/decoder/flac_plugin.c
@@ -300,6 +300,8 @@ flac_cue_tag_load(const char *file)
FLAC__uint64 track_time = 0;
#ifdef HAVE_CUE /* libcue */
FLAC__StreamMetadata* vc = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
+ char* cs_filename;
+ FILE* cs_file;
#endif /* libcue */
FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
FLAC__StreamMetadata* cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);
@@ -328,6 +330,18 @@ 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)
diff --git a/src/decoder/modplug_plugin.c b/src/decoder/modplug_plugin.c
index f636f2fa6..31f0a47c2 100644
--- a/src/decoder/modplug_plugin.c
+++ b/src/decoder/modplug_plugin.c
@@ -186,7 +186,7 @@ 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)
diff --git a/src/decoder/sndfile_decoder_plugin.c b/src/decoder/sndfile_decoder_plugin.c
new file mode 100644
index 000000000..0c5d2f063
--- /dev/null
+++ b/src/decoder/sndfile_decoder_plugin.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "decoder_api.h"
+
+#include <sndfile.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "sndfile"
+
+static sf_count_t
+sndfile_vio_get_filelen(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->size;
+}
+
+static sf_count_t
+sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
+{
+ struct input_stream *is = user_data;
+ bool success;
+
+ success = input_stream_seek(is, offset, whence);
+ if (!success)
+ return -1;
+
+ return is->offset;
+}
+
+static sf_count_t
+sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
+{
+ struct input_stream *is = user_data;
+ size_t nbytes;
+
+ nbytes = input_stream_read(is, ptr, count);
+ if (nbytes == 0 && is->error != 0)
+ return -1;
+
+ return nbytes;
+}
+
+static sf_count_t
+sndfile_vio_write(G_GNUC_UNUSED const void *ptr,
+ G_GNUC_UNUSED sf_count_t count,
+ G_GNUC_UNUSED void *user_data)
+{
+ /* no writing! */
+ return -1;
+}
+
+static sf_count_t
+sndfile_vio_tell(void *user_data)
+{
+ const struct input_stream *is = user_data;
+
+ return is->offset;
+}
+
+/**
+ * This SF_VIRTUAL_IO implementation wraps MPD's #input_stream to a
+ * libsndfile stream.
+ */
+static SF_VIRTUAL_IO vio = {
+ .get_filelen = sndfile_vio_get_filelen,
+ .seek = sndfile_vio_seek,
+ .read = sndfile_vio_read,
+ .write = sndfile_vio_write,
+ .tell = sndfile_vio_tell,
+};
+
+/**
+ * Converts a frame number to a timestamp (in seconds).
+ */
+static float
+frame_to_time(sf_count_t frame, const struct audio_format *audio_format)
+{
+ return (float)frame / (float)audio_format->sample_rate;
+}
+
+/**
+ * Converts a timestamp (in seconds) to a frame number.
+ */
+static sf_count_t
+time_to_frame(float t, const struct audio_format *audio_format)
+{
+ return (sf_count_t)(t * audio_format->sample_rate);
+}
+
+static void
+sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ struct audio_format audio_format;
+ size_t frame_size;
+ sf_count_t read_frames, num_frames, position = 0;
+ int buffer[4096];
+ enum decoder_command cmd;
+
+ info.format = 0;
+
+ sf = sf_open_virtual(&vio, SFM_READ, &info, is);
+ if (sf == NULL) {
+ g_warning("sf_open_virtual() failed");
+ return;
+ }
+
+ audio_format.sample_rate = info.samplerate;
+ /* for now, always read 32 bit samples. Later, we could lower
+ MPD's CPU usage by reading 16 bit samples with
+ sf_readf_short() on low-quality source files. */
+ audio_format.bits = 32;
+ audio_format.channels = info.channels;
+
+ if (!audio_format_valid(&audio_format)) {
+ g_warning("invalid audio format");
+ return;
+ }
+
+ decoder_initialized(decoder, &audio_format, info.seekable,
+ frame_to_time(info.frames, &audio_format));
+
+ frame_size = audio_format_frame_size(&audio_format);
+ read_frames = sizeof(buffer) / frame_size;
+
+ do {
+ num_frames = sf_readf_int(sf, buffer, read_frames);
+ if (num_frames <= 0)
+ break;
+
+ cmd = decoder_data(decoder, is,
+ buffer, num_frames * frame_size,
+ frame_to_time(position, &audio_format),
+ 0, NULL);
+ if (cmd == DECODE_COMMAND_SEEK) {
+ sf_count_t c =
+ time_to_frame(decoder_seek_where(decoder),
+ &audio_format);
+ c = sf_seek(sf, c, SEEK_SET);
+ if (c < 0)
+ decoder_seek_error(decoder);
+ else
+ decoder_command_finished(decoder);
+ cmd = DECODE_COMMAND_NONE;
+ }
+ } while (cmd == DECODE_COMMAND_NONE);
+
+ sf_close(sf);
+}
+
+static struct tag *
+sndfile_tag_dup(const char *path_fs)
+{
+ SNDFILE *sf;
+ SF_INFO info;
+ struct tag *tag;
+ const char *p;
+
+ info.format = 0;
+
+ sf = sf_open(path_fs, SFM_READ, &info);
+ if (sf == NULL)
+ return NULL;
+
+ if (!audio_valid_sample_rate(info.samplerate)) {
+ sf_close(sf);
+ g_warning("Invalid sample rate in %s\n", path_fs);
+ return NULL;
+ }
+
+ tag = tag_new();
+ tag->time = info.frames / info.samplerate;
+
+ p = sf_get_string(sf, SF_STR_TITLE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ITEM_TITLE, p);
+
+ p = sf_get_string(sf, SF_STR_ARTIST);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ITEM_ARTIST, p);
+
+ p = sf_get_string(sf, SF_STR_DATE);
+ if (p != NULL)
+ tag_add_item(tag, TAG_ITEM_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_api.c b/src/decoder_api.c
index 2ece3bb98..408e8005e 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -301,8 +301,7 @@ decoder_data(struct decoder *decoder,
/* apply replay gain or normalization */
- if (replay_gain_info != NULL &&
- replay_gain_mode != REPLAY_GAIN_OFF)
+ if (replay_gain_mode != REPLAY_GAIN_OFF)
replay_gain_apply(replay_gain_info, dest, nbytes,
&dc.out_audio_format);
else if (normalizationEnabled)
diff --git a/src/decoder_list.c b/src/decoder_list.c
index a42585e34..177ac46e4 100644
--- a/src/decoder_list.c
+++ b/src/decoder_list.c
@@ -31,6 +31,7 @@ extern const struct decoder_plugin mad_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;
@@ -56,6 +57,9 @@ static const struct decoder_plugin *const decoder_plugins[] = {
#ifdef HAVE_FLAC
&flac_decoder_plugin,
#endif
+#ifdef ENABLE_SNDFILE
+ &sndfile_decoder_plugin,
+#endif
#ifdef HAVE_AUDIOFILE
&audiofile_decoder_plugin,
#endif
diff --git a/src/directory_save.c b/src/directory_save.c
index 132508447..cb76b225f 100644
--- a/src/directory_save.c
+++ b/src/directory_save.c
@@ -138,7 +138,10 @@ directory_load(FILE *fp, struct directory *directory, GError **error)
if (!success)
return false;
} else if (g_str_has_prefix(buffer, SONG_BEGIN)) {
- readSongInfoIntoList(fp, &directory->songs, directory);
+ success = songvec_load(fp, &directory->songs,
+ directory, error);
+ if (!success)
+ return false;
} else {
g_set_error(error, directory_quark(), 0,
"Malformed line: %s", buffer);
diff --git a/src/encoder/twolame_encoder.c b/src/encoder/twolame_encoder.c
new file mode 100644
index 000000000..5a8a82d81
--- /dev/null
+++ b/src/encoder/twolame_encoder.c
@@ -0,0 +1,299 @@
+/*
+ * 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 "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->bits = 16;
+ 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;
+}
+
+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,
+};
diff --git a/src/encoder_list.c b/src/encoder_list.c
index d563b6bc8..2016d4cba 100644
--- a/src/encoder_list.c
+++ b/src/encoder_list.c
@@ -25,6 +25,7 @@
extern const struct encoder_plugin vorbis_encoder_plugin;
extern const struct encoder_plugin lame_encoder_plugin;
+extern const struct encoder_plugin twolame_encoder_plugin;
static const struct encoder_plugin *encoder_plugins[] = {
#ifdef ENABLE_VORBIS_ENCODER
@@ -33,6 +34,9 @@ static const struct encoder_plugin *encoder_plugins[] = {
#ifdef ENABLE_LAME_ENCODER
&lame_encoder_plugin,
#endif
+#ifdef ENABLE_TWOLAME_ENCODER
+ &twolame_encoder_plugin,
+#endif
NULL
};
diff --git a/src/filter/chain_filter_plugin.c b/src/filter/chain_filter_plugin.c
new file mode 100644
index 000000000..ec8bef5c0
--- /dev/null
+++ b/src/filter/chain_filter_plugin.c
@@ -0,0 +1,177 @@
+/*
+ * 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/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..f4d03ebef
--- /dev/null
+++ b/src/filter/convert_filter_plugin.c
@@ -0,0 +1,154 @@
+/*
+ * 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/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 inline GQuark
+convert_quark(void)
+{
+ return g_quark_from_static_string("pcm_convert");
+}
+
+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);
+ if (dest == NULL) {
+ g_set_error(error_r, convert_quark(), 0,
+ "pcm_convert() has failed");
+ 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));
+
+ 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/null_filter_plugin.c b/src/filter/null_filter_plugin.c
new file mode 100644
index 000000000..689388558
--- /dev/null
+++ b/src/filter/null_filter_plugin.c
@@ -0,0 +1,93 @@
+/*
+ * 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 "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/volume_filter_plugin.c b/src/filter/volume_filter_plugin.c
new file mode 100644
index 000000000..039619917
--- /dev/null
+++ b/src/filter/volume_filter_plugin.c
@@ -0,0 +1,167 @@
+/*
+ * 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/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->bits != 8 && audio_format->bits != 16 &&
+ audio_format->bits != 24) {
+ g_set_error(error_r, volume_quark(), 0,
+ "Unsupported audio format");
+ 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_internal.h b/src/filter_internal.h
new file mode 100644
index 000000000..b086e31b1
--- /dev/null
+++ b/src/filter_internal.h
@@ -0,0 +1,38 @@
+/*
+ * 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
+ *
+ * Internal stuff for the filter core and filter plugins.
+ */
+
+#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..e5c1d5cd8
--- /dev/null
+++ b/src/filter_plugin.c
@@ -0,0 +1,106 @@
+/*
+ * 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_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");
+
+ 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 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..0043246d1
--- /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 and outgoing data
+ * @param error location to store the error occuring, or NULL to
+ * ignore errors.
+ * @return true on success, false on error
+ */
+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..c8887aabf
--- /dev/null
+++ b/src/filter_registry.c
@@ -0,0 +1,41 @@
+/*
+ * 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_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,
+ &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..7eb7f7038
--- /dev/null
+++ b/src/filter_registry.h
@@ -0,0 +1,37 @@
+/*
+ * 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 volume_filter_plugin;
+
+const struct filter_plugin *
+filter_plugin_by_name(const char *name);
+
+#endif
diff --git a/src/idle.c b/src/idle.c
index 11b57376d..c0bb7a908 100644
--- a/src/idle.c
+++ b/src/idle.c
@@ -40,6 +40,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/main.c b/src/main.c
index 5035a4836..3d0aaabc9 100644
--- a/src/main.c
+++ b/src/main.c
@@ -90,13 +90,39 @@ GMainLoop *main_loop;
struct notify main_notify;
+static void
+glue_daemonize_init(const struct options *options)
+{
+ daemonize_init(config_get_string(CONF_USER, 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
* succeeded, and the caller should create the database after the
* process has been daemonized.
*/
static bool
-openDB(const Options *options)
+glue_db_init_and_load(const struct options *options)
{
const char *path = config_get_path(CONF_DB_FILE);
bool ret;
@@ -115,7 +141,7 @@ openDB(const Options *options)
db_init(path);
- if (options->createDB > 0)
+ if (options->create_db > 0)
/* don't attempt to load the old database */
return false;
@@ -124,7 +150,7 @@ openDB(const Options *options)
g_warning("Failed to load database: %s", error->message);
g_error_free(error);
- if (options->createDB < 0)
+ if (options->create_db < 0)
g_error("can't open db file and using "
"\"--no-create-db\" command line option");
@@ -141,6 +167,29 @@ 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
@@ -228,7 +277,7 @@ idle_event_emitted(void)
int main(int argc, char *argv[])
{
- Options options;
+ struct options options;
clock_t start;
bool create_db;
@@ -251,17 +300,13 @@ int main(int argc, char *argv[])
tag_pool_init();
config_global_init();
- parseOptions(argc, argv, &options);
+ parse_cmdline(argc, argv, &options);
- daemonize_init(config_get_string(CONF_USER, NULL),
- config_get_path(CONF_PID_FILE));
-
- 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.stderr);
listen_global_init();
@@ -275,9 +320,9 @@ int main(int argc, char *argv[])
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 +330,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(&options);
-#ifdef ENABLE_SQLITE
- sticker_global_init(config_get_path(CONF_STICKER_FILE));
-#endif
+ glue_sticker_init();
command_init();
initialize_decoder_and_player();
@@ -303,7 +346,7 @@ int main(int argc, char *argv[])
daemonize(options.daemon);
- setup_log_output(options.stdOutput);
+ setup_log_output(options.stderr);
initSigHandlers();
@@ -319,8 +362,9 @@ int main(int argc, char *argv[])
g_error("directory update failed");
}
+ glue_state_file_init();
- state_file_init(config_get_path(CONF_STATE_FILE));
+ config_global_check();
/* run the main loop */
@@ -335,7 +379,7 @@ int main(int argc, char *argv[])
finishZeroconf();
client_manager_deinit();
listen_global_finish();
- finishPlaylist();
+ playlist_global_finish();
start = clock();
db_finish();
diff --git a/src/mapper.c b/src/mapper.c
index aac7c0c48..9a122d068 100644
--- a/src/mapper.c
+++ b/src/mapper.c
@@ -25,16 +25,11 @@
#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 +53,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 +64,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)
diff --git a/src/mapper.h b/src/mapper.h
index 2667f1eee..d63243ff7 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/software_mixer_plugin.c b/src/mixer/software_mixer_plugin.c
new file mode 100644
index 000000000..661334e1b
--- /dev/null
+++ b/src/mixer/software_mixer_plugin.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 "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 const struct config_param *param)
+{
+ 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 bool
+software_mixer_open(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ (void)sm;
+ return true;
+}
+
+static void
+software_mixer_close(struct mixer *data)
+{
+ struct software_mixer *sm = (struct software_mixer *)data;
+
+ (void)sm;
+}
+
+static int
+software_mixer_get_volume(struct mixer *mixer)
+{
+ struct software_mixer *sm = (struct software_mixer *)mixer;
+
+ return sm->volume;
+}
+
+static bool
+software_mixer_set_volume(struct mixer *mixer, unsigned volume)
+{
+ 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,
+ .open = software_mixer_open,
+ .close = software_mixer_close,
+ .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..cd05eec85 100644
--- a/src/mixer_all.c
+++ b/src/mixer_all.c
@@ -22,6 +22,9 @@
#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>
@@ -70,12 +73,13 @@ 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;
assert(i < audio_output_count());
+ assert(volume <= 100);
output = audio_output_get(i);
if (!output->enabled)
@@ -85,31 +89,74 @@ 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;
- }
-
- if (volume > 100)
- volume = 100;
- else if (volume < 0)
- volume = 0;
-
return mixer_set_volume(mixer, volume);
}
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);
+}
+
+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);
+ }
+}
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_control.c b/src/mixer_control.c
index a17885935..927a1276c 100644
--- a/src/mixer_control.c
+++ b/src/mixer_control.c
@@ -28,24 +28,11 @@
#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)
{
struct mixer *mixer;
- //mixers are disabled (by using software volume)
- if (!mixers_enabled) {
- return NULL;
- }
assert(plugin != NULL);
mixer = plugin->init(param);
diff --git a/src/mixer_control.h b/src/mixer_control.h
index 0f73e8f75..b8997a795 100644
--- a/src/mixer_control.h
+++ b/src/mixer_control.h
@@ -31,9 +31,6 @@ 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);
diff --git a/src/mixer_list.h b/src/mixer_list.h
index 7db4a00d8..4e81c57b2 100644
--- a/src/mixer_list.h
+++ b/src/mixer_list.h
@@ -25,6 +25,7 @@
#ifndef MPD_MIXER_LIST_H
#define MPD_MIXER_LIST_H
+extern const struct mixer_plugin software_mixer_plugin;
extern const struct mixer_plugin alsa_mixer;
extern const struct mixer_plugin oss_mixer;
extern const struct mixer_plugin pulse_mixer;
diff --git a/src/mixer_type.c b/src/mixer_type.c
new file mode 100644
index 000000000..6cf007856
--- /dev/null
+++ b/src/mixer_type.c
@@ -0,0 +1,38 @@
+/*
+ * 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_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/output_control.c b/src/output_control.c
index eac9bdfcb..70c6d2b1a 100644
--- a/src/output_control.c
+++ b/src/output_control.c
@@ -23,6 +23,7 @@
#include "output_thread.h"
#include "mixer_control.h"
#include "mixer_plugin.h"
+#include "filter_plugin.h"
#include <assert.h>
#include <stdlib.h>
@@ -82,26 +83,13 @@ 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);
@@ -184,4 +172,6 @@ void audio_output_finish(struct audio_output *ao)
notify_deinit(&ao->notify);
g_mutex_free(ao->mutex);
+
+ filter_free(ao->filter);
}
diff --git a/src/output_init.c b/src/output_init.c
index 04609bb76..08873ac20 100644
--- a/src/output_init.c
+++ b/src/output_init.c
@@ -23,9 +23,17 @@
#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/chain_filter_plugin.h"
#include <glib.h>
+#include <assert.h>
+
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "output"
@@ -56,28 +64,81 @@ 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(const struct config_param *param,
+ const struct mixer_plugin *plugin,
+ struct filter *filter_chain)
+{
+ 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, param);
+
+ case MIXER_TYPE_SOFTWARE:
+ mixer = mixer_new(&software_mixer_plugin, 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)
{
- const char *format;
const struct audio_output_plugin *plugin = NULL;
if (param) {
- const char *type = NULL;
+ const char *p;
- type = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
- if (type == NULL) {
+ p = config_get_block_string(param, AUDIO_OUTPUT_TYPE, NULL);
+ if (p == NULL) {
g_set_error(error, 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);
+ "No such audio output plugin: %s", p);
return false;
}
@@ -89,8 +150,16 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
return false;
}
- format = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
+ p = config_get_block_string(param, AUDIO_OUTPUT_FORMAT,
NULL);
+ ao->config_audio_format = p != NULL;
+ if (p != NULL) {
+ bool success =
+ audio_format_parse(&ao->out_audio_format,
+ p, error);
+ if (!success)
+ return false;
+ }
} else {
g_warning("No \"%s\" defined in config file\n",
CONF_AUDIO_OUTPUT);
@@ -103,7 +172,7 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
plugin->name);
ao->name = "default detected output";
- format = NULL;
+ ao->config_audio_format = false;
}
ao->plugin = plugin;
@@ -111,17 +180,10 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
ao->open = false;
ao->fail_timer = NULL;
- pcm_convert_init(&ao->convert_state);
+ /* set up the filter chain */
- 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;
- }
+ ao->filter = filter_chain_new();
+ assert(ao->filter != NULL);
ao->thread = NULL;
notify_init(&ao->notify);
@@ -135,11 +197,17 @@ audio_output_init(struct audio_output *ao, const struct config_param *param,
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(param, plugin->mixer_plugin,
+ ao->filter);
+
+ /* 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 362d24947..6ca179287 100644
--- a/src/output_internal.h
+++ b/src/output_internal.h
@@ -21,7 +21,6 @@
#define MPD_OUTPUT_INTERNAL_H
#include "audio_format.h"
-#include "pcm_convert.h"
#include "notify.h"
#include <time.h>
@@ -29,6 +28,13 @@
enum audio_output_command {
AO_COMMAND_NONE = 0,
AO_COMMAND_OPEN,
+
+ /**
+ * This command is invoked when the input audio format
+ * changes.
+ */
+ AO_COMMAND_REOPEN,
+
AO_COMMAND_CLOSE,
AO_COMMAND_PAUSE,
AO_COMMAND_CANCEL,
@@ -101,7 +107,19 @@ 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 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.
+ */
+ struct filter *convert_filter;
/**
* The thread handle, or NULL if the output thread isn't
diff --git a/src/output_state.c b/src/output_state.c
index c7e6c8579..5efae3626 100644
--- a/src/output_state.c
+++ b/src/output_state.c
@@ -49,35 +49,34 @@ saveAudioDevicesState(FILE *fp)
}
}
-void
-readAudioDevicesState(FILE *fp)
+bool
+readAudioDevicesState(const char *line)
{
- char buffer[1024];
-
- while (fgets(buffer, sizeof(buffer), fp)) {
- char *c, *name;
- struct audio_output *ao;
+ long value;
+ char *endptr;
+ const char *name;
+ struct audio_output *ao;
- g_strchomp(buffer);
+ if (!g_str_has_prefix(line, AUDIO_DEVICE_STATE))
+ return false;
- if (!g_str_has_prefix(buffer, AUDIO_DEVICE_STATE))
- continue;
+ line += sizeof(AUDIO_DEVICE_STATE) - 1;
- c = strchr(buffer, ':');
- if (!c || !(++c))
- goto errline;
+ value = strtol(line, &endptr, 10);
+ if (*endptr != ':' || (value != 0 && value != 1))
+ return false;
- name = strchr(c, ':');
- if (!name || !(++name))
- goto errline;
+ if (value != 0)
+ /* state is "enabled": no-op */
+ return true;
- ao = audio_output_find(name);
- if (ao != NULL && atoi(c) == 0)
- ao->enabled = false;
-
- continue;
-errline:
- /* nonfatal */
- g_warning("invalid line in state_file: %s\n", buffer);
+ name = endptr + 1;
+ ao = audio_output_find(name);
+ if (ao == NULL) {
+ g_debug("Ignoring device state for '%s'", name);
+ return true;
}
+
+ ao->enabled = false;
+ return true;
}
diff --git a/src/output_state.h b/src/output_state.h
index 8592574ab..b171fed18 100644
--- a/src/output_state.h
+++ b/src/output_state.h
@@ -25,10 +25,11 @@
#ifndef OUTPUT_STATE_H
#define OUTPUT_STATE_H
+#include <stdbool.h>
#include <stdio.h>
-void
-readAudioDevicesState(FILE *fp);
+bool
+readAudioDevicesState(const char *line);
void
saveAudioDevicesState(FILE *fp);
diff --git a/src/output_thread.c b/src/output_thread.c
index d414ba8d5..2592b2456 100644
--- a/src/output_thread.c
+++ b/src/output_thread.c
@@ -23,6 +23,8 @@
#include "chunk.h"
#include "pipe.h"
#include "player_control.h"
+#include "filter_plugin.h"
+#include "filter/convert_filter_plugin.h"
#include <glib.h>
@@ -41,6 +43,71 @@ static void ao_command_finished(struct audio_output *ao)
}
static void
+ao_open(struct audio_output *ao)
+{
+ bool success;
+ GError *error = NULL;
+ const struct audio_format *filter_audio_format;
+
+ assert(!ao->open);
+ assert(ao->fail_timer == NULL);
+ assert(ao->pipe != NULL);
+ assert(ao->chunk == NULL);
+
+ /* 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;
+ }
+
+ if (!ao->config_audio_format)
+ ao->out_audio_format = *filter_audio_format;
+
+ success = ao_plugin_open(ao->plugin, ao->data,
+ &ao->out_audio_format,
+ &error);
+
+ 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);
+
+ g_mutex_lock(ao->mutex);
+ ao->open = true;
+ g_mutex_unlock(ao->mutex);
+
+ 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);
+}
+
+static void
ao_close(struct audio_output *ao)
{
assert(ao->open);
@@ -53,11 +120,69 @@ ao_close(struct audio_output *ao)
g_mutex_unlock(ao->mutex);
ao_plugin_close(ao->plugin, ao->data);
- pcm_convert_deinit(&ao->convert_state);
+ filter_close(ao->filter);
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;
+
+ g_mutex_lock(ao->mutex);
+ ao->chunk = NULL;
+ ao->open = false;
+ g_mutex_unlock(ao->mutex);
+
+ ao_plugin_close(ao->plugin, ao->data);
+
+ ao->fail_timer = g_timer_new();
+ return;
+ }
+
+ convert_filter_set(ao->convert_filter, &ao->out_audio_format);
+}
+
+static void
+ao_reopen(struct audio_output *ao)
+{
+ if (!ao->config_audio_format) {
+ if (ao->open) {
+ const struct music_pipe *mp = ao->pipe;
+ ao_close(ao);
+ 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;
+ }
+
+ 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,6 +190,8 @@ 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);
@@ -75,18 +202,19 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
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_plugin_cancel(ao->plugin, ao->data);
+ ao_close(ao);
+
+ /* 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) {
@@ -179,8 +307,6 @@ static void ao_pause(struct audio_output *ao)
static gpointer audio_output_task(gpointer arg)
{
struct audio_output *ao = arg;
- bool ret;
- GError *error;
while (1) {
switch (ao->command) {
@@ -188,47 +314,12 @@ static gpointer audio_output_task(gpointer arg)
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);
-
- g_mutex_lock(ao->mutex);
- ao->open = true;
- g_mutex_unlock(ao->mutex);
-
- 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();
- }
+ ao_open(ao);
+ ao_command_finished(ao);
+ break;
+ case AO_COMMAND_REOPEN:
+ ao_reopen(ao);
ao_command_finished(ao);
break;
@@ -241,6 +332,7 @@ static gpointer audio_output_task(gpointer arg)
ao_plugin_cancel(ao->plugin, ao->data);
ao_close(ao);
+ filter_close(ao->filter);
ao_command_finished(ao);
break;
diff --git a/src/player_control.c b/src/player_control.c
index ac4b006dd..df80ac4ff 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -40,7 +40,6 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
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)
@@ -253,16 +252,6 @@ void setPlayerCrossFade(float crossFadeInSeconds)
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)
{
return pc.total_play_time;
diff --git a/src/player_control.h b/src/player_control.h
index b1f7481cd..0cc3c73a8 100644
--- a/src/player_control.h
+++ b/src/player_control.h
@@ -81,7 +81,6 @@ struct player_control {
struct song *errored_song;
volatile double seek_where;
float cross_fade_seconds;
- uint16_t software_volume;
double total_play_time;
};
@@ -145,8 +144,6 @@ void setPlayerCrossFade(float crossFadeInSeconds);
float getPlayerCrossFade(void);
-void setPlayerSoftwareVolume(int volume);
-
double getPlayerTotalPlayTime(void);
static inline const struct audio_format *
diff --git a/src/player_thread.c b/src/player_thread.c
index 7fc55d3d1..cffc5d82c 100644
--- a/src/player_thread.c
+++ b/src/player_thread.c
@@ -423,8 +423,6 @@ static bool
play_chunk(struct song *song, struct music_chunk *chunk,
const struct audio_format *format, double sizeToTime)
{
- bool success;
-
assert(music_chunk_check_format(chunk, format));
if (chunk->tag != NULL) {
@@ -455,18 +453,6 @@ play_chunk(struct song *song, struct music_chunk *chunk,
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)) {
@@ -581,8 +567,14 @@ play_next_chunk(struct player *player)
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;
diff --git a/src/playlist.c b/src/playlist.c
index 35c09329a..ef20894e2 100644
--- a/src/playlist.c
+++ b/src/playlist.c
@@ -109,7 +109,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);
}
diff --git a/src/playlist.h b/src/playlist.h
index 57b2450fa..db2e750ab 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 '#'
@@ -94,9 +93,11 @@ struct playlist {
/** 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 +117,7 @@ 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,17 +130,18 @@ 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);
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned song);
+playlist_delete_id(struct playlist *playlist, unsigned song);
void stopPlaylist(struct playlist *playlist);
@@ -159,22 +157,23 @@ void syncPlayerAndPlaylist(struct playlist *playlist);
void previousSongInPlaylist(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);
diff --git a/src/playlist_control.c b/src/playlist_control.c
index 4359611fd..932a45cf2 100644
--- a/src/playlist_control.c
+++ b/src/playlist_control.c
@@ -183,7 +183,7 @@ nextSongInPlaylist(struct playlist *playlist)
/* 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)
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
index b83dc0933..6ab27a93b 100644
--- a/src/playlist_edit.c
+++ b/src/playlist_edit.c
@@ -35,14 +35,14 @@
#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);
@@ -58,7 +58,7 @@ void clearPlaylist(struct playlist *playlist)
playlist->current = -1;
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
}
#ifndef WIN32
@@ -86,41 +86,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 +118,7 @@ addSongToPlaylist(struct playlist *playlist,
queue_length(&playlist->queue));
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -157,8 +128,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
-swapSongsInPlaylist(struct playlist *playlist, unsigned song1, unsigned song2)
+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
+playlist_swap_songs(struct playlist *playlist, unsigned song1, unsigned song2)
{
const struct song *queued;
@@ -188,7 +189,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 +197,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,11 +205,11 @@ 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)
+playlist_delete(struct playlist *playlist, unsigned song)
{
const struct song *queued;
unsigned songOrder;
@@ -256,7 +257,7 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
queue_delete(&playlist->queue, song);
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
/* update the "current" and "queued" variables */
@@ -270,27 +271,28 @@ deleteFromPlaylist(struct playlist *playlist, unsigned song)
}
enum playlist_result
-deleteFromPlaylistById(struct playlist *playlist, unsigned id)
+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 +344,7 @@ moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end,
}
}
- incrPlaylistVersion(playlist);
+ playlist_increment_version(playlist);
playlist_update_queued_song(playlist, queued);
@@ -350,16 +352,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 +402,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..4f8374494 100644
--- a/src/playlist_global.c
+++ b/src/playlist_global.c
@@ -40,7 +40,8 @@ playlist_event(void)
syncPlayerAndPlaylist(&g_playlist);
}
-void initPlaylist(void)
+void
+playlist_global_init(void)
{
playlist_init(&g_playlist);
@@ -48,17 +49,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_save.c b/src/playlist_save.c
index 776d3c385..103a810fb 100644
--- a/src/playlist_save.c
+++ b/src/playlist_save.c
@@ -115,7 +115,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;
@@ -124,7 +124,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..cbf77813e 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -66,9 +66,15 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
playlist->current));
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME,
getPlayerElapsedTime());
- } else
+ } 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,
@@ -109,8 +115,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,21 +124,20 @@ 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)) {
@@ -172,19 +177,19 @@ 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);
- 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)
+ if (state == PLAYER_STATE_STOP /* && config_option */)
+ playlist->current = current;
+ else if (seek_time == 0)
playPlaylist(playlist, current);
else
seekSongInPlaylist(playlist, current, seek_time);
@@ -192,4 +197,6 @@ playlist_state_restore(FILE *fp, struct playlist *playlist)
if (state == PLAYER_STATE_PAUSE)
playerPause();
}
+
+ return true;
}
diff --git a/src/playlist_state.h b/src/playlist_state.h
index 989430264..7ed7e8c8e 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,7 @@ 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);
#endif
diff --git a/src/replay_gain.c b/src/replay_gain.c
index bcb501e54..d21b94e0a 100644
--- a/src/replay_gain.c
+++ b/src/replay_gain.c
@@ -38,6 +38,7 @@ 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)
{
@@ -73,6 +74,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 +136,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/song_print.c b/src/song_print.c
index 64ab9f6b1..2c4da6cb0 100644
--- a/src/song_print.c
+++ b/src/song_print.c
@@ -50,6 +50,27 @@ song_print_info(struct client *client, struct song *song)
{
song_print_url(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_save.c b/src/song_save.c
index 8f4e1614d..3e54ce222 100644
--- a/src/song_save.c
+++ b/src/song_save.c
@@ -34,6 +34,12 @@
#define SONG_KEY "key: "
#define SONG_MTIME "mtime: "
+static GQuark
+song_save_quark(void)
+{
+ return g_quark_from_static_string("song_save");
+}
+
static void
song_save_url(FILE *fp, struct song *song)
{
@@ -70,7 +76,7 @@ void songvec_save(FILE *fp, struct songvec *sv)
}
static void
-insertSongIntoList(struct songvec *sv, struct song *newsong)
+commit_song(struct songvec *sv, struct song *newsong)
{
struct song *existing = songvec_find(sv, newsong->url);
@@ -93,7 +99,7 @@ insertSongIntoList(struct songvec *sv, struct song *newsong)
}
static char *
-matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
+parse_tag_value(char *buffer, enum tag_type *type_r)
{
int i;
@@ -102,7 +108,7 @@ matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
if (0 == strncmp(tag_item_names[i], buffer, len) &&
buffer[len] == ':') {
- *itemType = i;
+ *type_r = i;
return g_strchug(buffer + len + 1);
}
}
@@ -110,12 +116,13 @@ matchesAnMpdTagItemKey(char *buffer, enum tag_type *itemType)
return NULL;
}
-void readSongInfoIntoList(FILE *fp, struct songvec *sv,
- struct directory *parent)
+bool
+songvec_load(FILE *fp, struct songvec *sv, struct directory *parent,
+ GError **error_r)
{
char buffer[MPD_PATH_MAX + 1024];
struct song *song = NULL;
- enum tag_type itemType;
+ enum tag_type type;
const char *value;
while (fgets(buffer, sizeof(buffer), fp) &&
@@ -124,24 +131,26 @@ void readSongInfoIntoList(FILE *fp, struct songvec *sv,
if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) {
if (song)
- insertSongIntoList(sv, song);
+ commit_song(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");
+ g_set_error(error_r, song_save_quark(), 0,
+ "Problems reading song info");
+ return false;
} else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) {
/* we don't need this info anymore */
- } else if ((value = matchesAnMpdTagItemKey(buffer,
- &itemType)) != NULL) {
+ } else if ((value = parse_tag_value(buffer,
+ &type)) != NULL) {
if (!song->tag) {
song->tag = tag_new();
tag_begin_add(song->tag);
}
- tag_add_item(song->tag, itemType, value);
+ tag_add_item(song->tag, type, value);
} else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) {
if (!song->tag) {
song->tag = tag_new();
@@ -151,11 +160,15 @@ void readSongInfoIntoList(FILE *fp, struct songvec *sv,
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)]));
+ } else {
+ g_set_error(error_r, song_save_quark(), 0,
+ "unknown line in db: %s", buffer);
+ return false;
}
- else
- g_error("unknown line in db: %s", buffer);
}
if (song)
- insertSongIntoList(sv, song);
+ commit_song(sv, song);
+
+ return true;
}
diff --git a/src/song_save.h b/src/song_save.h
index 370e42730..36e03584e 100644
--- a/src/song_save.h
+++ b/src/song_save.h
@@ -20,6 +20,9 @@
#ifndef MPD_SONG_SAVE_H
#define MPD_SONG_SAVE_H
+#include <glib.h>
+
+#include <stdbool.h>
#include <stdio.h>
struct songvec;
@@ -27,7 +30,16 @@ struct directory;
void songvec_save(FILE *fp, struct songvec *sv);
-void readSongInfoIntoList(FILE * fp, struct songvec *sv,
- struct directory *parent);
+/**
+ * Loads songs from the input file and add the to the specified
+ * directory.
+ *
+ * @param error_r location to store the error occuring, or NULL to
+ * ignore errors
+ * @return true on success, false on error
+ */
+bool
+songvec_load(FILE *file, struct songvec *sv, struct directory *parent,
+ GError **error_r);
#endif
diff --git a/src/state_file.c b/src/state_file.c
index 9c6475cc8..18548d03c 100644
--- a/src/state_file.c
+++ b/src/state_file.c
@@ -20,6 +20,7 @@
#include "state_file.h"
#include "output_state.h"
#include "playlist.h"
+#include "playlist_state.h"
#include "volume.h"
#include <glib.h>
@@ -30,15 +31,6 @@
#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 */
@@ -47,11 +39,11 @@ static guint save_state_source_id;
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,8 +52,9 @@ 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);
+ saveAudioDevicesState(fp);
+ playlist_state_save(fp, &g_playlist);
while(fclose(fp) && errno == EINTR) /* nothing */;
}
@@ -69,12 +62,13 @@ state_file_write(void)
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,9 +76,15 @@ 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) ||
+ readAudioDevicesState(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 */;
@@ -119,11 +119,14 @@ state_file_init(const char *path)
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/sticker.c b/src/sticker.c
index 0d30fbb70..4cccd9399 100644
--- a/src/sticker.c
+++ b/src/sticker.c
@@ -72,50 +72,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/tag.c b/src/tag.c
index 34205d20d..5d473322e 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -43,8 +43,10 @@ static struct {
const char *tag_item_names[TAG_NUM_OF_ITEM_TYPES] = {
"Artist",
+ [TAG_ARTIST_SORT] = "ArtistSort",
"Album",
"AlbumArtist",
+ [TAG_ALBUM_ARTIST_SORT] = "AlbumArtistSort",
"Title",
"Track",
"Name",
diff --git a/src/tag.h b/src/tag.h
index 4b72dd187..451e13112 100644
--- a/src/tag.h
+++ b/src/tag.h
@@ -32,8 +32,10 @@
*/
enum tag_type {
TAG_ITEM_ARTIST,
+ TAG_ARTIST_SORT,
TAG_ITEM_ALBUM,
TAG_ITEM_ALBUM_ARTIST,
+ TAG_ALBUM_ARTIST_SORT,
TAG_ITEM_TITLE,
TAG_ITEM_TRACK,
TAG_ITEM_NAME,
diff --git a/src/tag_id3.c b/src/tag_id3.c
index ce0386a51..d8e96d1c2 100644
--- a/src/tag_id3.c
+++ b/src/tag_id3.c
@@ -38,15 +38,16 @@
# 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
@@ -237,6 +238,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,
@@ -331,15 +333,18 @@ struct tag *tag_id3_import(struct id3_tag * tag)
getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret);
getID3Info(tag, ID3_FRAME_ALBUM_ARTIST,
TAG_ITEM_ALBUM_ARTIST, ret);
+ getID3Info(tag, ID3_FRAME_ARTIST_SORT,
+ TAG_ARTIST_SORT, ret);
getID3Info(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
- TAG_ITEM_ALBUM_ARTIST, ret);
+ TAG_ALBUM_ARTIST_SORT, 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, "TPE3", TAG_ITEM_PERFORMER, ret);
+ getID3Info(tag, "TPE4", TAG_ITEM_PERFORMER, ret);
getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret);
getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret);
diff --git a/src/update.c b/src/update.c
index 1088f5338..22fed2c7a 100644
--- a/src/update.c
+++ b/src/update.c
@@ -822,6 +822,9 @@ directory_update_init(char *path)
return next_task_id > update_task_id_max ? 1 : next_task_id;
}
spawn_update_task(path);
+
+ idle_add(IDLE_UPDATE);
+
return update_task_id;
}
@@ -846,7 +849,7 @@ static void song_delete_event(void)
sticker_song_delete(delete);
#endif
- deleteASongFromPlaylist(&g_playlist, delete);
+ playlist_delete_song(&g_playlist, delete);
delete = NULL;
notify_signal(&update_notify);
@@ -861,6 +864,8 @@ static void update_finished_event(void)
g_thread_join(update_thr);
+ idle_add(IDLE_UPDATE);
+
if (modified) {
/* send "idle" events */
playlistVersionChange(&g_playlist);
diff --git a/src/volume.c b/src/volume.c
index e7fa20a62..5f4a66837 100644
--- a/src/volume.c
+++ b/src/volume.c
@@ -26,6 +26,7 @@
#include "output_all.h"
#include "mixer_control.h"
#include "mixer_all.h"
+#include "mixer_type.h"
#include <glib.h>
@@ -39,13 +40,7 @@
#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;
@@ -54,117 +49,15 @@ 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.
- */
-static void
-mixer_copy_legacy_param(const char *type, const char *name)
-{
- 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 */
-
- config_add_block_param(output, name, param->value, param->line);
-}
-
-static void
-mixer_reconfigure(const char *type)
-{
- 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);
- }
- }
- }
-
- if (volume_mixer_type == VOLUME_MIXER_TYPE_HARDWARE)
- hardware_volume_timer = g_timer_new();
+ hardware_volume_timer = g_timer_new();
}
-static int hardware_volume_get(void)
+int volume_level_get(void)
{
assert(hardware_volume_timer != NULL);
@@ -178,101 +71,54 @@ 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;
-}
+ assert(volume <= 100);
-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;
-
- 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);
}
diff --git a/src/volume.h b/src/volume.h
index 99d31da4e..0db231ef6 100644
--- a/src/volume.h
+++ b/src/volume.h
@@ -23,21 +23,16 @@
#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);
diff --git a/test/read_mixer.c b/test/read_mixer.c
index be6864ba1..bbfb23687 100644
--- a/test/read_mixer.c
+++ b/test/read_mixer.c
@@ -19,6 +19,8 @@
#include "mixer_control.h"
#include "mixer_list.h"
+#include "filter_registry.h"
+#include "pcm_volume.h"
#include <glib.h>
@@ -26,6 +28,22 @@
#include <string.h>
#include <unistd.h>
+const struct filter_plugin *
+filter_plugin_by_name(G_GNUC_UNUSED const char *name)
+{
+ assert(false);
+ return NULL;
+}
+
+bool
+pcm_volume(G_GNUC_UNUSED void *buffer, G_GNUC_UNUSED int length,
+ G_GNUC_UNUSED const struct audio_format *format,
+ G_GNUC_UNUSED int volume)
+{
+ assert(false);
+ return false;
+}
+
int main(int argc, G_GNUC_UNUSED char **argv)
{
struct mixer *mixer;
diff --git a/test/run_filter.c b/test/run_filter.c
new file mode 100644
index 000000000..0d97207e1
--- /dev/null
+++ b/test/run_filter.c
@@ -0,0 +1,168 @@
+/*
+ * 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 "conf.h"
+#include "audio_parser.h"
+#include "audio_format.h"
+#include "filter_plugin.h"
+#include "pcm_volume.h"
+
+#include <glib.h>
+
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+static const struct config_param *
+find_named_config_block(const char *block, const char *name)
+{
+ const struct config_param *param = NULL;
+
+ while ((param = config_get_next_param(block, param)) != NULL) {
+ const char *current_name =
+ config_get_block_string(param, "name", NULL);
+ if (current_name != NULL && strcmp(current_name, name) == 0)
+ return param;
+ }
+
+ return NULL;
+}
+
+static struct filter *
+load_filter(const char *name)
+{
+ const struct config_param *param;
+ struct filter *filter;
+ GError *error = NULL;
+
+ param = find_named_config_block("filter", name);
+ if (param == NULL) {
+ g_printerr("No such configured filter: %s\n", name);
+ return false;
+ }
+
+ filter = filter_configured_new(param, &error);
+ if (filter == NULL) {
+ g_printerr("Failed to load filter: %s\n", error->message);
+ g_error_free(error);
+ return NULL;
+ }
+
+ return filter;
+}
+
+int main(int argc, char **argv)
+{
+ struct audio_format audio_format = {
+ .sample_rate = 44100,
+ .bits = 16,
+ .channels = 2,
+ };
+ bool success;
+ GError *error = NULL;
+ struct filter *filter;
+ const struct audio_format *out_audio_format;
+ char buffer[4096];
+ size_t frame_size;
+
+ if (argc < 3 || argc > 4) {
+ g_printerr("Usage: run_filter CONFIG NAME [FORMAT] <IN\n");
+ return 1;
+ }
+
+ g_thread_init(NULL);
+
+ /* read configuration file (mpd.conf) */
+
+ config_global_init();
+ config_read_file(argv[1]);
+
+ /* parse the audio format */
+
+ if (argc > 3) {
+ success = audio_format_parse(&audio_format, argv[3], &error);
+ if (!success) {
+ g_printerr("Failed to parse audio format: %s\n",
+ error->message);
+ g_error_free(error);
+ return 1;
+ }
+ }
+
+ /* initialize the filter */
+
+ filter = load_filter(argv[2]);
+ if (filter == NULL)
+ return 1;
+
+ /* open the filter */
+
+ out_audio_format = filter_open(filter, &audio_format, &error);
+ if (out_audio_format == NULL) {
+ g_printerr("Failed to open filter: %s\n", error->message);
+ g_error_free(error);
+ filter_free(filter);
+ return 1;
+ }
+
+
+ g_printerr("audio_format=%u:%u:%u\n", out_audio_format->sample_rate,
+ out_audio_format->bits, out_audio_format->channels);
+
+ frame_size = audio_format_frame_size(&audio_format);
+
+ /* play */
+
+ while (true) {
+ ssize_t nbytes;
+ size_t length;
+ const void *dest;
+
+ nbytes = read(0, buffer, sizeof(buffer));
+ if (nbytes <= 0)
+ break;
+
+ dest = filter_filter(filter, buffer, (size_t)nbytes,
+ &length, &error);
+ if (dest == NULL) {
+ g_printerr("Filter failed: %s\n", error->message);
+ filter_close(filter);
+ filter_free(filter);
+ return 1;
+ }
+
+ nbytes = write(1, dest, length);
+ if (nbytes < 0) {
+ g_printerr("Failed to write: %s\n", strerror(errno));
+ filter_close(filter);
+ filter_free(filter);
+ return 1;
+ }
+ }
+
+ /* cleanup and exit */
+
+ filter_close(filter);
+ filter_free(filter);
+
+ config_global_finish();
+
+ return 0;
+}
diff --git a/test/run_output.c b/test/run_output.c
index 1a171198d..adf6e1dd9 100644
--- a/test/run_output.c
+++ b/test/run_output.c
@@ -22,6 +22,8 @@
#include "output_control.h"
#include "conf.h"
#include "audio_parser.h"
+#include "filter_registry.h"
+#include "pcm_convert.h"
#include <glib.h>
@@ -33,10 +35,31 @@ void pcm_convert_init(G_GNUC_UNUSED struct pcm_convert_state *state)
{
}
+void pcm_convert_deinit(G_GNUC_UNUSED struct pcm_convert_state *state)
+{
+}
+
+const void *
+pcm_convert(G_GNUC_UNUSED struct pcm_convert_state *state,
+ G_GNUC_UNUSED const struct audio_format *src_format,
+ G_GNUC_UNUSED const void *src, G_GNUC_UNUSED size_t src_size,
+ G_GNUC_UNUSED const struct audio_format *dest_format,
+ G_GNUC_UNUSED size_t *dest_size_r)
+{
+ return NULL;
+}
+
void notify_init(G_GNUC_UNUSED struct notify *notify)
{
}
+const struct filter_plugin *
+filter_plugin_by_name(G_GNUC_UNUSED const char *name)
+{
+ assert(false);
+ return NULL;
+}
+
static const struct config_param *
find_named_config_block(const char *block, const char *name)
{