diff options
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 @@ -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 @@ -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 "" @@ -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 = ¶m->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 = ¶m->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. @@ -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", @@ -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) { |